diff --git a/korman/exporter.py b/korman/exporter.py
deleted file mode 100644
index 2ef91ba..0000000
--- a/korman/exporter.py
+++ /dev/null
@@ -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 .
-
-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)
diff --git a/korman/exporter/__init__.py b/korman/exporter/__init__.py
new file mode 100644
index 0000000..8623197
--- /dev/null
+++ b/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 .
+
+import bpy
+import os.path
+from PyHSPlasma import *
+
+from .convert import *
+from .explosions import *
diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py
new file mode 100644
index 0000000..789cce2
--- /dev/null
+++ b/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 .
+
+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)
\ No newline at end of file
diff --git a/korman/exporter/explosions.py b/korman/exporter/explosions.py
new file mode 100644
index 0000000..0198c0a
--- /dev/null
+++ b/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 .
+
+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
diff --git a/korman/exporter/manager.py b/korman/exporter/manager.py
new file mode 100644
index 0000000..c992f67
--- /dev/null
+++ b/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 .
+
+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)
diff --git a/korman/operators/op_export.py b/korman/operators/op_export.py
index 069c0bd..f41d0ba 100644
--- a/korman/operators/op_export.py
+++ b/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:
diff --git a/korman/operators/op_world.py b/korman/operators/op_world.py
index 0b427b1..f8052f6 100644
--- a/korman/operators/op_world.py
+++ b/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:
diff --git a/korman/properties/prop_object.py b/korman/properties/prop_object.py
index 4efcd6c..aa31d86 100644
--- a/korman/properties/prop_object.py
+++ b/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,
diff --git a/korman/properties/prop_world.py b/korman/properties/prop_world.py
index 0400b63..baf4785 100644
--- a/korman/properties/prop_world.py
+++ b/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,
diff --git a/korman/render.py b/korman/render.py
index 7e2c9ee..5cf02a3 100644
--- a/korman/render.py
+++ b/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