diff --git a/korman/__init__.py b/korman/__init__.py
index 01a81a1..940b014 100644
--- a/korman/__init__.py
+++ b/korman/__init__.py
@@ -15,7 +15,8 @@
import bpy
from . import exporter, render
-from . import operators, properties, ui
+from . import properties, ui
+from . import operators
bl_info = {
"name": "Korman",
diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py
index 95ecba6..2b4c714 100644
--- a/korman/exporter/convert.py
+++ b/korman/exporter/convert.py
@@ -148,6 +148,11 @@ class Exporter:
self._export_actor(sceneobject, bl_obj)
export_fn(sceneobject, bl_obj)
+ # And now we puke out the modifiers...
+ for mod in bl_obj.plasma_modifiers.modifiers:
+ print(" Exporting '{}' modifier as '{}'".format(mod.bl_label, mod.display_name))
+ mod.export(self, bl_obj, sceneobject)
+
def _export_empty_blobj(self, so, bo):
# We don't need to do anything here. This function just makes sure we don't error out
# or add a silly special case :(
diff --git a/korman/operators/__init__.py b/korman/operators/__init__.py
index fc9150b..12cf63f 100644
--- a/korman/operators/__init__.py
+++ b/korman/operators/__init__.py
@@ -14,6 +14,7 @@
# along with Korman. If not, see .
from . import op_export as exporter
+from . import op_modifier as modifier
from . import op_world as world
def register():
diff --git a/korman/operators/op_modifier.py b/korman/operators/op_modifier.py
new file mode 100644
index 0000000..0a9526c
--- /dev/null
+++ b/korman/operators/op_modifier.py
@@ -0,0 +1,125 @@
+# 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
+from bpy.props import *
+
+from ..properties import modifiers
+
+def _fetch_modifiers():
+ items = []
+
+ mapping = modifiers.modifier_mapping()
+ for i in mapping.keys():
+ items.append(("", i, ""))
+ items.extend(mapping[i])
+ #yield ("", i, "")
+ #yield mapping[i]
+ return items
+
+class ModifierOperator:
+ @classmethod
+ def poll(cls, context):
+ return context.scene.render.engine == "PLASMA_GAME"
+
+
+class ModifierAddOperator(ModifierOperator, bpy.types.Operator):
+ bl_idname = "object.plasma_modifier_add"
+ bl_label = "Add Modifier"
+ bl_description = "Adds a Plasma Modifier"
+
+ types = EnumProperty(name="Modifier Type",
+ description="The type of modifier we add to the list",
+ items=_fetch_modifiers())
+
+ def execute(self, context):
+ plmods = context.object.plasma_modifiers
+ myType = self.types
+ theMod = getattr(plmods, myType)
+
+ theMod.display_order = plmods.determine_next_id()
+ theMod.created(context.object)
+ return {"FINISHED"}
+
+
+class ModifierRemoveOperator(ModifierOperator, bpy.types.Operator):
+ bl_idname = "object.plasma_modifier_remove"
+ bl_label = "Remove Modifier"
+ bl_description = "Removes this Plasma Modifier"
+
+ active_modifier = IntProperty(name="Modifier Display Order",
+ default=-1,
+ options={"HIDDEN"})
+
+ def execute(self, context):
+ assert self.active_modifier >= 0
+
+ for mod in context.object.plasma_modifiers.modifiers:
+ if mod.display_order == self.active_modifier:
+ mod.display_order = -1
+ mod.destroyed()
+ elif mod.display_order > self.active_modifier:
+ mod.display_order -= 1
+ return {"FINISHED"}
+
+
+class ModifierMoveOperator(ModifierOperator):
+ def swap_modifier_ids(self, mods, s1, s2):
+ done = 0
+ for mod in mods.modifiers:
+ if mod.display_order == s1:
+ mod.display_order = s2
+ done += 1
+ elif mod.display_order == s2:
+ mod.display_order = s1
+ done += 1
+ if done == 2:
+ break
+
+
+class ModifierMoveUpOperator(ModifierMoveOperator, bpy.types.Operator):
+ bl_idname = "object.plasma_modifier_move_up"
+ bl_label = "Move Up"
+ bl_description = "Move the modifier up in the stack"
+
+ active_modifier = IntProperty(name="Modifier Display Order",
+ default=-1,
+ options={"HIDDEN"})
+
+ def execute(self, context):
+ assert self.active_modifier >= 0
+ if self.active_modifier > 0:
+ plmods = context.object.plasma_modifiers
+ self.swap_modifier_ids(plmods, self.active_modifier, self.active_modifier-1)
+ return {"FINISHED"}
+
+
+class ModifierMoveDownOperator(ModifierMoveOperator, bpy.types.Operator):
+ bl_idname = "object.plasma_modifier_move_down"
+ bl_label = "Move Down"
+ bl_description = "Move the modifier down in the stack"
+
+ active_modifier = IntProperty(name="Modifier Display Order",
+ default=-1,
+ options={"HIDDEN"})
+
+ def execute(self, context):
+ assert self.active_modifier >= 0
+
+ plmods = context.object.plasma_modifiers
+ last = max([mod.display_order for mod in plmods.modifiers])
+ if self.active_modifier < last:
+ self.swap_modifier_ids(plmods, self.active_modifier, self.active_modifier+1)
+ return {"FINISHED"}
diff --git a/korman/properties/__init__.py b/korman/properties/__init__.py
index 7d67d15..ec3a52d 100644
--- a/korman/properties/__init__.py
+++ b/korman/properties/__init__.py
@@ -15,12 +15,16 @@
import bpy
+from . import modifiers
from .prop_object import *
from .prop_world import *
def register():
- bpy.types.Object.plasma_net = PointerProperty(type=PlasmaNet)
- bpy.types.Object.plasma_object = PointerProperty(type=PlasmaObject)
- bpy.types.World.plasma_age = PointerProperty(type=PlasmaAge)
- bpy.types.World.plasma_fni = PointerProperty(type=PlasmaFni)
+ bpy.types.Object.plasma_net = bpy.props.PointerProperty(type=PlasmaNet)
+ bpy.types.Object.plasma_object = bpy.props.PointerProperty(type=PlasmaObject)
+ bpy.types.World.plasma_age = bpy.props.PointerProperty(type=PlasmaAge)
+ bpy.types.World.plasma_fni = bpy.props.PointerProperty(type=PlasmaFni)
+
+ # We have our own brand of special insanity in the modifier code, so let's handle that in there
+ modifiers.register()
diff --git a/korman/properties/modifiers/__init__.py b/korman/properties/modifiers/__init__.py
new file mode 100644
index 0000000..2179d9f
--- /dev/null
+++ b/korman/properties/modifiers/__init__.py
@@ -0,0 +1,90 @@
+# 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 inspect
+
+from .base import PlasmaModifierProperties
+from .logic import *
+
+class PlasmaModifiers(bpy.types.PropertyGroup):
+ def determine_next_id(self):
+ """Gets the ID for the next modifier in the UI"""
+ # This is NOT a property, otherwise the modifiers property would access this...
+ # Which acesses the modifiers property... INFINITE RECURSION! :D
+ ids = [mod.display_order for mod in self.modifiers]
+ if ids:
+ return max(ids) + 1
+ else:
+ return 0
+
+ @property
+ def modifiers(self):
+ """Generates all of the enabled modifiers.
+ NOTE: We do not promise to return modifiers in their display_order!
+ """
+ for i in dir(self):
+ attr = getattr(self, i)
+ # Assumes each modifier is a single pointer to PlasmaModifierProperties
+ if isinstance(attr, PlasmaModifierProperties):
+ if attr.enabled:
+ yield attr
+
+
+def _is_plasma_modifier(hClass):
+ if inspect.isclass(hClass):
+ if issubclass(hClass, PlasmaModifierProperties) and hasattr(hClass, "pl_id"):
+ return True
+ return False
+
+def modifier_definitions():
+ """This returns a sequence of all modifiers"""
+ for i in globals().values():
+ if _is_plasma_modifier(i):
+ yield i
+
+def modifier_mapping():
+ """This returns a dict mapping Plasma Modifier categories to names"""
+
+ # FIXME: a more pythonic way to do this???
+ d = {}
+ for i, mod in enumerate(modifier_definitions()):
+ if hasattr(mod, "bl_icon"):
+ icon = mod.bl_icon
+ else:
+ icon = ""
+
+ tup = (mod.pl_id, mod.bl_label, mod.bl_description, icon, i)
+ if mod.bl_category not in d:
+ d[mod.bl_category] = [tup]
+ else:
+ d[mod.bl_category].append(tup)
+ return d
+
+def register():
+ # Okay, so we have N plasma modifer property groups...
+ # Rather than have (dis)organized chaos on the Blender Object, we will collect all of the
+ # property groups of type PlasmaModifierProperties and generate on-the-fly a PlasmaModifier
+ # property group to rule them all. The class attribute 'pl_id' will determine the name of
+ # the property group in PlasmaModifiers.
+ # Also, just to spite us, Blender doesn't seem to handle PropertyGroup inheritance... at all.
+ # So, we're going to have to create our base properties on all of the PropertyGroups.
+ # It's times like these that make me wonder about life...
+ # Enjoy!
+ for i in modifier_definitions():
+ for name, (prop, kwargs) in PlasmaModifierProperties._subprops.items():
+ setattr(i, name, prop(**kwargs))
+ setattr(PlasmaModifiers, i.pl_id, bpy.props.PointerProperty(type=i))
+ bpy.types.Object.plasma_modifiers = bpy.props.PointerProperty(type=PlasmaModifiers)
diff --git a/korman/properties/modifiers/base.py b/korman/properties/modifiers/base.py
new file mode 100644
index 0000000..53a1992
--- /dev/null
+++ b/korman/properties/modifiers/base.py
@@ -0,0 +1,45 @@
+# 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
+from bpy.props import *
+
+class PlasmaModifierProperties(bpy.types.PropertyGroup):
+ def created(self, obj):
+ # This is here just to prevent us from having unnamed modifiers
+ self.display_name = "{}Modifier{}".format(obj.name, self.display_order)
+
+ def destroyed(self):
+ pass
+
+ @property
+ def enabled(self):
+ return self.display_order >= 0
+
+ # Guess what?
+ # You can't register properties on a base class--Blender isn't smart enough to do inheritance,
+ # you see... So, we'll store our definitions in a dict and make those properties on each subclass
+ # at runtime. What joy. Python FTW. See register() in __init__.py
+ _subprops = {
+ "display_name": (StringProperty, {"name": "Name",
+ "description": "Modifier name"}),
+ "display_order": (IntProperty, {"name": "INTERNAL: Display Ordering",
+ "description": "Position in the list of buttons",
+ "default": -1,
+ "options": {"HIDDEN"}}),
+ "show_expanded": (BoolProperty, {"name": "INTERNAL: Actually draw the modifier",
+ "default": True,
+ "options": {"HIDDEN"}})
+ }
diff --git a/korman/properties/modifiers/logic.py b/korman/properties/modifiers/logic.py
new file mode 100644
index 0000000..2facf55
--- /dev/null
+++ b/korman/properties/modifiers/logic.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 .
+
+import bpy
+from PyHSPlasma import *
+
+from .base import PlasmaModifierProperties
+
+class PlasmaSpawnPoint(PlasmaModifierProperties):
+ pl_id = "spawnpoint"
+
+ bl_category = "Logic"
+ bl_label = "Spawn Point"
+ bl_description = "Point at which avatars link into the Age"
+
+ def created(self, obj):
+ self.display_name = obj.name
+
+ def export(self, exporter, bo, so):
+ # Not much to this modifier... It's basically a flag that tells the engine, "hey, this is a
+ # place the avatar can show up." Nice to have a simple one to get started with.
+ spawn = exporter.mgr.add_object(pl=plSpawnModifier, bl=bo, name=self.display_name)
+ so.addModifier(spawn.key)
diff --git a/korman/render.py b/korman/render.py
index ac16ca0..207e0be 100644
--- a/korman/render.py
+++ b/korman/render.py
@@ -51,3 +51,10 @@ def _new_poll(cls, context):
def _swap_poll(cls):
cls._old_poll = cls.poll
cls.poll = _new_poll
+
+# This is where we claim the physics context for our own nefarious purposes...
+# Hmm... Physics panels don't respect the supported engine thing.
+# Metaprogramming to the rescue!
+from bl_ui import properties_physics_common
+_swap_poll(properties_physics_common.PhysicButtonsPanel)
+del properties_physics_common
diff --git a/korman/ui/__init__.py b/korman/ui/__init__.py
index 00688be..1416d1e 100644
--- a/korman/ui/__init__.py
+++ b/korman/ui/__init__.py
@@ -14,4 +14,5 @@
# along with Korman. If not, see .
from .ui_object import *
+from .ui_modifiers import *
from .ui_world import *
diff --git a/korman/ui/modifiers/__init__.py b/korman/ui/modifiers/__init__.py
new file mode 100644
index 0000000..a8b2222
--- /dev/null
+++ b/korman/ui/modifiers/__init__.py
@@ -0,0 +1,16 @@
+# 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 .
+
+from .logic import *
diff --git a/korman/ui/modifiers/logic.py b/korman/ui/modifiers/logic.py
new file mode 100644
index 0000000..08cc998
--- /dev/null
+++ b/korman/ui/modifiers/logic.py
@@ -0,0 +1,18 @@
+# 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 .
+
+def spawnpoint(modifier, layout, context):
+ layout.label(text="The Y axis indicates the direction the avatar is facing.")
+
diff --git a/korman/ui/ui_modifiers.py b/korman/ui/ui_modifiers.py
new file mode 100644
index 0000000..ffb7257
--- /dev/null
+++ b/korman/ui/ui_modifiers.py
@@ -0,0 +1,89 @@
+# 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
+
+from . import modifiers as modifier_draw
+
+class ModifierButtonsPanel:
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+
+ # Let me take this opportunity to rant.
+ # For some STUPID REASON, Blender decides which buttons to show in the C code. This is all well
+ # and good, EXCEPT THEY DO NOT SHOW THE MODIFIERS BUTTON FOR EMPTIES. This totally breaks the
+ # Plasma Modifier workflow. As a shim workaround, we're overtaking the physics panel as our
+ # Plasma Modifier Panel. The Physics, Object, and Constraint Panels' visibility determined by
+ # the same block of a switch statement in Blender 2.71 (buttons_context_path in buttons_context.c)
+ bl_context = "physics"
+
+ @classmethod
+ def poll(cls, context):
+ return context.object and context.scene.render.engine == "PLASMA_GAME"
+
+
+class PlasmaModifiersPanel(ModifierButtonsPanel, bpy.types.Panel):
+ bl_label = "Plasma Modifiers"
+
+ def draw(self, context):
+ layout = self.layout
+ obj = context.object
+
+ # So, I had to read the doggone Blender source code to figure out how to use this because the
+ # "documentation" only gives this helpful information about this interesting feature: "operator_menu_enum"
+ # Bah. For the record: first param is the operator, second is an EnumProperty on that operator.
+ # You define categories by inserting an enum item with an empty key, empty description, and just a name.
+ # Any items following that are members of that category, of course...
+ # ... I hope that my rambling has helped somebody understand more about the undocumented mess
+ # that is Blender Python.
+ layout.operator_menu_enum("object.plasma_modifier_add", "types")
+
+ # First, let's sort the list of modifiers based on their display order
+ # We don't do this sort in the property itself because this is really just a UI hint.
+ modifiers = sorted(obj.plasma_modifiers.modifiers, key=lambda x: x.display_order)
+
+ # Inside the modifier_draw module, we have draw callbables for each modifier
+ # We'll loop through the list of active modifiers and call the drawprocs for the enabled mods
+ for i in modifiers:
+ modLayout = self._draw_modifier_template(i)
+ if i.show_expanded:
+ getattr(modifier_draw, i.pl_id)(i, modLayout, context)
+
+ def _draw_modifier_template(self, modifier):
+ """This draws our lookalike modifier template and returns a UILayout object for each modifier
+ to consume in order to draw its specific properties"""
+ layout = self.layout.box()
+
+ # This is the main title row. It mimics the Blender template_modifier, which (unfortunately)
+ # requires valid Blender Modifier data. It would be nice if the Blender UI code were consistently
+ # C or Python and not a frankenstein mix. I would probably prefer working in C, just because
+ # the compiler saves my neck 99.9% of the time...
+ row = layout.row(align=True)
+ exicon = "TRIA_DOWN" if modifier.show_expanded else "TRIA_RIGHT"
+ row.prop(modifier, "show_expanded", text="", icon=exicon, emboss=False)
+ if hasattr(modifier, "bl_icon"):
+ row.label(icon=modifier.bl_icon)
+ else:
+ row.label(text=modifier.bl_label)
+ row.prop(modifier, "display_name", text="")
+
+ row.operator("object.plasma_modifier_move_up", text="", icon="TRIA_UP").active_modifier = modifier.display_order
+ row.operator("object.plasma_modifier_move_down", text="", icon="TRIA_DOWN").active_modifier = modifier.display_order
+ row.operator("object.plasma_modifier_remove", text="", icon="X").active_modifier = modifier.display_order
+
+ # Now we return the modifier box, which is populated with the modifier specific properties
+ # by whatever insanity is in the modifier module. modifier modifier modifier...
+ # MODDDDDDDDIFIIIIEEEERRRRRRRR!!!
+ return layout
diff --git a/korman/ui/ui_object.py b/korman/ui/ui_object.py
index 489b112..b0f869b 100644
--- a/korman/ui/ui_object.py
+++ b/korman/ui/ui_object.py
@@ -15,17 +15,32 @@
import bpy
-
class ObjectButtonsPanel:
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
- bl_context = "object"
+ bl_context = "physics"
@classmethod
def poll(cls, context):
return context.object and context.scene.render.engine == "PLASMA_GAME"
+class BlenderObjectSearchPanel(ObjectButtonsPanel, bpy.types.Panel):
+ bl_label = ""
+ bl_options = {"HIDE_HEADER"}
+
+ def draw(self, context):
+ # Yes, this is stolen shamelessly from bl_ui
+ layout = self.layout
+ space = context.space_data
+
+ if space.use_pin_id:
+ layout.template_ID(space, "pin_id")
+ else:
+ row = layout.row()
+ row.template_ID(context.scene.objects, "active")
+
+
class PlasmaObjectPanel(ObjectButtonsPanel, bpy.types.Panel):
bl_label = "Plasma Object"
@@ -45,6 +60,7 @@ class PlasmaObjectPanel(ObjectButtonsPanel, bpy.types.Panel):
class PlasmaNetPanel(ObjectButtonsPanel, bpy.types.Panel):
bl_label = "Plasma Synchronization"
+ bl_options = {"DEFAULT_CLOSED"}
def draw_header(self, context):
self.layout.prop(context.object.plasma_net, "manual_sdl", text="")