Browse Source

Basic Modifiers

So this is a fairly massive chunk. I tried to get our Plasma Modifiers to
match the Blender Modifier UI fairly well. In the process, I discovered
that Blender "helpfully" hides the modifier button on Empty objects. Bah.

Significant Changes:
- Hid all of the Blender Physics mess
- The Physics context is now the Plasma Object context
- Moved Plasma Object and Plasma Synchronization to the Context Formerly
  Known as Physics
- Added a Plasma Modifier Panel

Here's how you create Plasma Modifiers:
- Add your PropertyGroup to properties.modifiers
- Make sure you have a pl_id naming your modifier, a category, and a label
- Implement the export() method to actually export something useful (not
  goat porn, please)
- Implement your UI draw function in ui.modifiers.
- Wasn't that easy?
pull/6/head
Adam Johnson 11 years ago
parent
commit
ef9e17af61
  1. 3
      korman/__init__.py
  2. 5
      korman/exporter/convert.py
  3. 1
      korman/operators/__init__.py
  4. 125
      korman/operators/op_modifier.py
  5. 12
      korman/properties/__init__.py
  6. 90
      korman/properties/modifiers/__init__.py
  7. 45
      korman/properties/modifiers/base.py
  8. 35
      korman/properties/modifiers/logic.py
  9. 7
      korman/render.py
  10. 1
      korman/ui/__init__.py
  11. 16
      korman/ui/modifiers/__init__.py
  12. 18
      korman/ui/modifiers/logic.py
  13. 89
      korman/ui/ui_modifiers.py
  14. 20
      korman/ui/ui_object.py

3
korman/__init__.py

@ -15,7 +15,8 @@
import bpy import bpy
from . import exporter, render from . import exporter, render
from . import operators, properties, ui from . import properties, ui
from . import operators
bl_info = { bl_info = {
"name": "Korman", "name": "Korman",

5
korman/exporter/convert.py

@ -148,6 +148,11 @@ class Exporter:
self._export_actor(sceneobject, bl_obj) self._export_actor(sceneobject, bl_obj)
export_fn(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): 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 # We don't need to do anything here. This function just makes sure we don't error out
# or add a silly special case :( # or add a silly special case :(

1
korman/operators/__init__.py

@ -14,6 +14,7 @@
# along with Korman. If not, see <http://www.gnu.org/licenses/>. # along with Korman. If not, see <http://www.gnu.org/licenses/>.
from . import op_export as exporter from . import op_export as exporter
from . import op_modifier as modifier
from . import op_world as world from . import op_world as world
def register(): def register():

125
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 <http://www.gnu.org/licenses/>.
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"}

12
korman/properties/__init__.py

@ -15,12 +15,16 @@
import bpy import bpy
from . import modifiers
from .prop_object import * from .prop_object import *
from .prop_world import * from .prop_world import *
def register(): def register():
bpy.types.Object.plasma_net = PointerProperty(type=PlasmaNet) bpy.types.Object.plasma_net = bpy.props.PointerProperty(type=PlasmaNet)
bpy.types.Object.plasma_object = PointerProperty(type=PlasmaObject) bpy.types.Object.plasma_object = bpy.props.PointerProperty(type=PlasmaObject)
bpy.types.World.plasma_age = PointerProperty(type=PlasmaAge) bpy.types.World.plasma_age = bpy.props.PointerProperty(type=PlasmaAge)
bpy.types.World.plasma_fni = PointerProperty(type=PlasmaFni) 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()

90
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 <http://www.gnu.org/licenses/>.
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)

45
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 <http://www.gnu.org/licenses/>.
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"}})
}

35
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 <http://www.gnu.org/licenses/>.
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)

7
korman/render.py

@ -51,3 +51,10 @@ def _new_poll(cls, context):
def _swap_poll(cls): def _swap_poll(cls):
cls._old_poll = cls.poll cls._old_poll = cls.poll
cls.poll = _new_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

1
korman/ui/__init__.py

@ -14,4 +14,5 @@
# along with Korman. If not, see <http://www.gnu.org/licenses/>. # along with Korman. If not, see <http://www.gnu.org/licenses/>.
from .ui_object import * from .ui_object import *
from .ui_modifiers import *
from .ui_world import * from .ui_world import *

16
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 <http://www.gnu.org/licenses/>.
from .logic import *

18
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 <http://www.gnu.org/licenses/>.
def spawnpoint(modifier, layout, context):
layout.label(text="The Y axis indicates the direction the avatar is facing.")

89
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 <http://www.gnu.org/licenses/>.
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...</rant>
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

20
korman/ui/ui_object.py

@ -15,17 +15,32 @@
import bpy import bpy
class ObjectButtonsPanel: class ObjectButtonsPanel:
bl_space_type = "PROPERTIES" bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW" bl_region_type = "WINDOW"
bl_context = "object" bl_context = "physics"
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return context.object and context.scene.render.engine == "PLASMA_GAME" 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): class PlasmaObjectPanel(ObjectButtonsPanel, bpy.types.Panel):
bl_label = "Plasma Object" bl_label = "Plasma Object"
@ -45,6 +60,7 @@ class PlasmaObjectPanel(ObjectButtonsPanel, bpy.types.Panel):
class PlasmaNetPanel(ObjectButtonsPanel, bpy.types.Panel): class PlasmaNetPanel(ObjectButtonsPanel, bpy.types.Panel):
bl_label = "Plasma Synchronization" bl_label = "Plasma Synchronization"
bl_options = {"DEFAULT_CLOSED"}
def draw_header(self, context): def draw_header(self, context):
self.layout.prop(context.object.plasma_net, "manual_sdl", text="") self.layout.prop(context.object.plasma_net, "manual_sdl", text="")

Loading…
Cancel
Save