Browse Source

Begin separating the exporter.

I want the logic and the helper functions to be separate, doggone it!
pull/6/head
Adam Johnson 10 years ago
parent
commit
59d37ed830
  1. 222
      korman/exporter.py
  2. 21
      korman/exporter/__init__.py
  3. 80
      korman/exporter/convert.py
  4. 35
      korman/exporter/explosions.py
  5. 191
      korman/exporter/manager.py
  6. 2
      korman/operators/op_export.py
  7. 2
      korman/operators/op_world.py
  8. 10
      korman/properties/prop_object.py
  9. 9
      korman/properties/prop_world.py
  10. 2
      korman/render.py

222
korman/exporter.py

@ -1,222 +0,0 @@
# This file is part of Korman.
#
# Korman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Korman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
import os.path
from PyHSPlasma import *
class ExportError(Exception):
def __init__(self, value="Undefined Export Error"):
super(Exception, self).__init__(value)
class Exporter:
def __init__(self, op):
self._op = op # Blender operator
# This stuff doesn't need to be static
self._nodes = {}
self._objects = []
self._pages = {}
def add_object(self, pl, bl=None, loc=None):
"""Automates adding a converted Blender object to our Plasma Resource Manager"""
assert (bl or loc)
if loc:
location = loc
else:
location = self._pages[bl.plasma_object.page]
self.mgr.AddObject(location, pl)
node = self._nodes[location]
if node: # All objects must be in the scene node
if isinstance(pl, plSceneObject):
f = node.addSceneObject
else:
f = node.addPoolObject
f(pl.key)
@property
def age_name(self):
return os.path.splitext(os.path.split(self._op.filepath)[1])[0]
def _create_page(self, name, id, builtin=False):
location = plLocation(self.version)
location.prefix = bpy.context.scene.world.plasma_age.seq_prefix
if builtin:
location.flags |= plLocation.kBuiltIn
location.page = id
self._pages[name] = location
info = plPageInfo()
info.age = self.age_name
info.page = name
info.location = location
self.mgr.AddPage(info)
if not builtin:
self._age_info.addPage((name, id, 0))
if self.version <= pvPots:
node = plSceneNode("%s_District_%s" % (self.age_name, name))
else:
node = plSceneNode("%s_%s" % (self.age_name, name))
self._nodes[location] = node
self.mgr.AddObject(location, node)
else:
self._nodes[location] = None
return location
def get_textures_page(self, obj):
if self.pages["Textures"] is not None:
return self.pages["Textures"]
else:
return self.pages[obj.plasma_object.page]
@property
def version(self):
# I <3 Python
return globals()[self._op.version]
def run(self):
# Step 0: Init export resmgr and stuff
self.mgr = plResManager()
self.mgr.setVer(self.version)
# Step 1: Gather a list of objects that we need to export
# We should do this first so we can sanity check
# and give accurate progress reports
self._collect_objects()
# Step 2: Collect some age information
self._grab_age_info() # World Props -> plAgeInfo
for page in bpy.context.scene.world.plasma_age.pages:
self._create_page(page.name, page.seq_suffix)
self._sanity_check_pages()
self._generate_builtins() # Creates BuiltIn and Textures
# Step 3: Export all the things!
self._export_scene_objects()
# Step 4: FINALLY. Let's write the PRPs and crap.
self.mgr.WriteAge(self._op.filepath, self._age_info)
self._write_fni()
self._write_pages()
def _collect_objects(self):
for obj in bpy.data.objects:
if obj.plasma_object.enabled:
self._objects.append(obj)
def _grab_age_info(self):
age = bpy.context.scene.world.plasma_age
self._age_info = plAgeInfo()
self._age_info.dayLength = age.day_length
self._age_info.lingerTime = 180 # this is fairly standard
self._age_info.name = self.age_name
self._age_info.seqPrefix = age.seq_prefix
self._age_info.startDateTime = age.start_time
self.mgr.AddAge(self._age_info)
def _sanity_check_pages(self):
"""Ensure all objects are in valid pages and create the Default page if used"""
for obj in self._objects:
page = obj.plasma_object.page
if page in self._pages:
# good. keep trying.
continue
elif page == "":
# This object is in the default page... Init that.
for loc in self._pages.values():
if not loc.page:
self._pages[""] = loc
break
else:
# need to create default page
self._pages[""] = self._create_page("Default", 0)
else:
# oh dear...
raise ExportError("Object '%s' in undefined page '%s'" % (obj.name, page))
def _generate_builtins(self):
# Find the highest two available negative suffixes for BuiltIn and Textures
# This should generally always resolve to -2 and -1
suffixes = []; _s = -1
while len(suffixes) != 2:
for location in self._pages.values():
if location.page == _s:
break
else:
suffixes.append(_s)
_s -= 1
# Grunt work...
if self.version <= pvMoul and self._op.save_state:
builtin = self._create_page("BuiltIn", suffixes[1], True)
pfm = plPythonFileMod("VeryVerySpecialPythonFileMod")
pfm.filename = self.age_name
self.mgr.AddObject(builtin, pfm) # add_object has lots of overhead
sdlhook = plSceneObject("AgeSDLHook")
sdlhook.addModifier(pfm.key)
self.mgr.AddObject(builtin, sdlhook)
self._pages["BuiltIn"] = builtin
if self._op.use_texture_page:
textures = self._create_page("Textures", suffixes[0], True)
self._pages["Textures"] = textures
else:
self._pages["Textures"] = None # probably easier than looping to find it
def _export_scene_objects(self):
for bl_obj in self._objects:
# Normally, we'd pass off to the property for export logic, but we need to
# do meshes here, so let's stay local until it's modifier time
so = plSceneObject(bl_obj.name)
self.add_object(pl=so, bl=bl_obj)
# TODO: export mesh
# TODO: export plasma modifiers
def _write_fni(self):
if self.version <= pvMoul:
enc = plEncryptedStream.kEncXtea
else:
enc = plEncryptedStream.kEncAES
fname = os.path.join(os.path.split(self._op.filepath)[0], "%s.fni" % self.age_name)
stream = plEncryptedStream()
stream.open(fname, fmWrite, enc)
# Write out some stuff
fni = bpy.context.scene.world.plasma_fni
stream.writeLine("Graphics.Renderer.Fog.SetClearColor %f %f %f" % tuple(fni.clear_color))
if fni.fog_method != "none":
stream.writeLine("Graphics.Renderer.Fog.SetDefColor %f %f %f" % tuple(fni.fog_color))
if fni.fog_method == "linear":
stream.writeLine("Graphics.Renderer.Fog.SetDefLinear %f %f %f" % (fni.fog_start, fni.fog_end, fni.fog_density))
elif fni.fog_method == "exp2":
stream.writeLine("Graphics.Renderer.Fog.SetDefExp2 %f %f" % (fni.fog_end, fni.fog_density))
stream.writeLine("Graphics.Renderer.Setyon %f" % fni.yon)
stream.close()
def _write_pages(self):
dir = os.path.split(self._op.filepath)[0]
for name, loc in self._pages.items():
page = self.mgr.FindPage(loc) # not cached because it's C++ owned
# I know that plAgeInfo has its own way of doing this, but we'd have
# to do some looping and stuff. This is easier.
if self.version <= pvMoul:
chapter = "_District_"
else:
chapter = "_"
f = os.path.join(dir, "%s%s%s.prp" % (self.age_name, chapter, name))
self.mgr.WritePage(f, page)

21
korman/exporter/__init__.py

@ -0,0 +1,21 @@
# This file is part of Korman.
#
# Korman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Korman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
import os.path
from PyHSPlasma import *
from .convert import *
from .explosions import *

80
korman/exporter/convert.py

@ -0,0 +1,80 @@
# This file is part of Korman.
#
# Korman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Korman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
import os.path
from PyHSPlasma import *
from . import explosions
from . import manager
class Exporter:
# These are objects that we need to export as plSceneObjects
_objects = []
def __init__(self, op):
self._op = op # Blender export operator
@property
def age_name(self):
return os.path.splitext(os.path.split(self._op.filepath)[1])[0]
def run(self):
# Step 0: Init export resmgr and stuff
self.mgr = manager.ExportManager(globals()[self._op.version])
# Step 1: Gather a list of objects that we need to export
# We should do this first so we can sanity check
# and give accurate progress reports
self._collect_objects()
# Step 2: Create the age info and the pages
self._export_age_info()
# Step 2.9: Ensure that all Plasma Objects are in a valid page
# This creates the default page if it is used
self.mgr.sanity_check_object_pages(self.age_name, self._objects)
# Step 3: Export all the things!
self._export_scene_objects()
# Step 4: FINALLY. Let's write the PRPs and crap.
self.mgr.save_age(self._op.filepath)
def _collect_objects(self):
for obj in bpy.data.objects:
if obj.plasma_object.enabled:
self._objects.append(obj)
def _export_age_info(self):
# Make life slightly easier...
age_info = bpy.context.scene.world.plasma_age
age_name = self.age_name
mgr = self.mgr
# Generate the plAgeInfo
mgr.AddAge(age_info.export(self))
# Create all the pages we need
for page in age_info.pages:
mgr.create_page(age_name, page.name, page.seq_suffix)
mgr.create_builtins(age_name, self._op.use_texture_page)
def _export_scene_objects(self):
for bl_obj in self._objects:
# Naive export all Plasma Objects. They will be responsible for calling back into the
# exporter to find/create drawable meshes, materials, etc. Not sure if this design will
# work well, but we're going to go with it for now.
bl_obj.plasma_object.export(self, bl_obj)

35
korman/exporter/explosions.py

@ -0,0 +1,35 @@
# This file is part of Korman.
#
# Korman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Korman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
class ExportError(Exception):
def __init__(self, value="Undefined Export Error"):
super(Exception, self).__init__(value)
class UndefinedPageError(ExportError):
mistakes = {}
def __init__(self):
super(ExportError, self).__init__("You have objects in pages that do not exist!")
def add(self, page, obj):
if page not in self.mistakes:
self.mistakes[page] = [obj,]
else:
self.mistakes[page].append(obj)
def raise_if_error(self):
if self.mistakes:
raise self

191
korman/exporter/manager.py

@ -0,0 +1,191 @@
# This file is part of Korman.
#
# Korman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Korman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
import os.path
from PyHSPlasma import *
from . import explosions
class ExportManager:
"""Friendly resource-managing helper class."""
_nodes = {}
_pages = {}
def __init__(self, version):
self.mgr = plResManager()
self.mgr.setVer(version)
# cheap inheritance
for i in dir(self.mgr):
if not hasattr(self, i):
setattr(self, i, getattr(self.mgr, i))
def AddAge(self, info):
# There's only ever one age being exported, so hook the call and hang onto the plAgeInfo
# We'll save from our reference later. Forget grabbing this from C++
self._age_info = info
self.mgr.AddAge(info)
def add_object(self, pl, name=None, bl=None, loc=None):
"""Automates adding a converted Blender object to our Plasma Resource Manager"""
assert (bl or loc)
if loc:
location = loc
else:
location = self._pages[bl.plasma_object.page]
# pl can be a class or an instance.
# This is one of those "sanity" things to ensure we don't suddenly
# passing around the key of an uninitialized object.
if isinstance(pl, type(object)):
assert name or bl
if name is None:
name = bl.name
pl = pl(name)
self.mgr.AddObject(location, pl)
node = self._nodes[location]
if node: # All objects must be in the scene node
if isinstance(pl, plSceneObject):
node.addSceneObject(pl.key)
else:
# FIXME: determine which types belong here...
# Probably anything that's not a plModifier or a plBitmap...
# Remember that the idea is that Plasma needs to deliver refs to load the age.
# It's harmless to have too many refs here (though the file size will be big, heh)
node.addPoolObject(pl.key)
return pl # we may have created it...
def create_builtins(self, age, textures):
# BuiltIn.prp
# FIXME: Only gen this if the artist wants it
builtin = self.create_page(age, "BuiltIn", -1, True)
pfm = self.add_object(plPythonFileMod, name="VeryVerySpecialPythonFileMod", loc=builtin)
sdl = self.add_object(plSceneObject, name="AgeSDLHook", loc=builtin)
sdl.addModifier(pfm.key)
# Textures.prp
if textures:
self.create_page(age, "Textures", -2, True)
def create_page(self, age, name, id, builtin=False):
location = plLocation(self.mgr.getVer())
location.prefix = bpy.context.scene.world.plasma_age.seq_prefix
if builtin:
location.flags |= plLocation.kBuiltIn
location.page = id
self._pages[name] = location
info = plPageInfo()
info.age = age
info.page = name
info.location = location
self.mgr.AddPage(info)
if not builtin:
self._age_info.addPage((name, id, 0))
if self.getVer() <= pvPots:
node = plSceneNode("{}_District_{}".format(age, name))
else:
node = plSceneNode("{}_{}".format(age, name))
self._nodes[location] = node
self.mgr.AddObject(location, node)
else:
self._nodes[location] = None
return location
def find_key(self, bl_obj, index):
"""Given a blender Object and a pCre index, find an exported plKey"""
location = self._pages[bl_obj.plasma_object.page]
# NOTE: may need to replace with a python side dict for faster lookups
# evaluate when exporter has been fleshed out
for key in self.mgr.getKeys(location, index):
if bl_obj.name == key.name:
return key
return None
def get_textures_page(self, obj):
"""Returns the page that Blender Object obj's textures should be exported to"""
# The point of this is to account for per-page textures...
if "Textures" in self._pages:
return self._pages["Textures"]
else:
return self._pages[obj.plasma_object.page]
def sanity_check_object_pages(self, age, objects):
"""Ensure all objects are in valid pages and create the Default page if used"""
error = explosions.UndefinedPageError()
for obj in objects:
page = obj.plasma_object.page
if page in self._pages:
# good. keep trying.
continue
elif page == "":
# This object is in the default page... Init that.
for loc in self._pages.values():
if not loc.page:
self.mgr._pages[""] = loc
break
else:
# need to create default page
self._pages[""] = self.create_page("Default", 0)
else:
error.add(page, obj.name)
error.raise_if_error()
def save_age(self, path):
relpath, ageFile = os.path.split(path)
ageName = os.path.splitext(ageFile)[0]
self.mgr.WriteAge(path, self._age_info)
self._write_fni(relpath, ageName)
self._write_pages(relpath, ageName)
def _write_fni(self, path, ageName):
if self.mgr.getVer() <= pvMoul:
enc = plEncryptedStream.kEncXtea
else:
enc = plEncryptedStream.kEncAES
fname = os.path.join(path, "{}.fni".format(ageName))
stream = plEncryptedStream()
stream.open(fname, fmWrite, enc)
# Write out some stuff
fni = bpy.context.scene.world.plasma_fni
stream.writeLine("Graphics.Renderer.Fog.SetClearColor %f %f %f" % tuple(fni.clear_color))
if fni.fog_method != "none":
stream.writeLine("Graphics.Renderer.Fog.SetDefColor %f %f %f" % tuple(fni.fog_color))
if fni.fog_method == "linear":
stream.writeLine("Graphics.Renderer.Fog.SetDefLinear %f %f %f" % (fni.fog_start, fni.fog_end, fni.fog_density))
elif fni.fog_method == "exp2":
stream.writeLine("Graphics.Renderer.Fog.SetDefExp2 %f %f" % (fni.fog_end, fni.fog_density))
stream.writeLine("Graphics.Renderer.Setyon %f" % fni.yon)
stream.close()
def _write_pages(self, path, ageName):
for name, loc in self._pages.items():
page = self.mgr.FindPage(loc) # not cached because it's C++ owned
# I know that plAgeInfo has its own way of doing this, but we'd have
# to do some looping and stuff. This is easier.
if self.mgr.getVer() <= pvMoul:
chapter = "_District_"
else:
chapter = "_"
f = os.path.join(path, "{}{}{}.prp".format(ageName, chapter, name))
self.mgr.WritePage(f, page)

2
korman/operators/op_export.py

@ -57,7 +57,7 @@ class ExportOperator(bpy.types.Operator):
def execute(self, context):
# Before we begin, do some basic sanity checking...
if self.filepath == "":
if not self.filepath:
self.error = "No file specified"
return {"CANCELLED"}
else:

2
korman/operators/op_world.py

@ -35,7 +35,7 @@ class PageAddOperator(AgeOperator, bpy.types.Operator):
suffixes = {p.seq_suffix for p in age.pages}
if suffixes:
test = set(range(min(suffixes), max(suffixes)))
missing = test - set(suffixes)
missing = test - suffixes
try:
suffix = missing.pop()
except KeyError:

10
korman/properties/prop_object.py

@ -15,6 +15,7 @@
import bpy
from bpy.props import *
from PyHSPlasma import *
class PlasmaObject(bpy.types.PropertyGroup):
def _enabled(self, context):
@ -38,6 +39,15 @@ class PlasmaObject(bpy.types.PropertyGroup):
o.plasma_object.page = page.name
break
def export(self, exporter, bl_obj):
"""Plasma Object Export"""
# This is where the magic happens...
if self.enabled:
# TODO: Something more useful than a blank object.
exporter.mgr.add_object(plSceneObject, bl=bl_obj)
enabled = BoolProperty(name="Export",
description="Export this as a discrete object",
default=False,

9
korman/properties/prop_world.py

@ -116,6 +116,15 @@ class PlasmaPage(bpy.types.PropertyGroup):
class PlasmaAge(bpy.types.PropertyGroup):
def export(self, exporter):
_age_info = plAgeInfo()
_age_info.dayLength = self.day_length
_age_info.lingerTime = 180 # this is fairly standard
_age_info.name = exporter.age_name
_age_info.seqPrefix = self.seq_prefix
_age_info.startDateTime = self.start_time
return _age_info
day_length = FloatProperty(name="Day Length",
description="Length of a day (in hours) on this age",
default=30.230000,

2
korman/render.py

@ -22,9 +22,11 @@ class PlasmaRenderEngine(bpy.types.RenderEngine):
pass
# Explicitly whitelist compatible Blender panels...
from bl_ui import properties_material
properties_material.MATERIAL_PT_context_material.COMPAT_ENGINES.add("PLASMA_GAME")
properties_material.MATERIAL_PT_options.COMPAT_ENGINES.add("PLASMA_GAME")
properties_material.MATERIAL_PT_preview.COMPAT_ENGINES.add("PLASMA_GAME")
del properties_material

Loading…
Cancel
Save