Browse Source

Merge pull request #156 from Hoikas/nodes

Make node trees reusable.
pull/161/head
Adam Johnson 5 years ago committed by GitHub
parent
commit
465611650d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      korman/exporter/convert.py
  2. 4
      korman/nodes/node_avatar.py
  3. 54
      korman/nodes/node_conditions.py
  4. 50
      korman/nodes/node_core.py
  5. 6
      korman/nodes/node_logic.py
  6. 7
      korman/nodes/node_messages.py
  7. 7
      korman/nodes/node_python.py
  8. 7
      korman/nodes/node_responder.py
  9. 14
      korman/nodes/node_softvolume.py
  10. 19
      korman/properties/modifiers/logic.py
  11. 5
      korman/properties/modifiers/region.py
  12. 15
      korman/ui/modifiers/logic.py

14
korman/exporter/convert.py

@ -38,8 +38,8 @@ class Exporter:
self._op = op # Blender export operator
self._objects = []
self.actors = set()
self.node_trees_exported = set()
self.want_node_trees = {}
self.exported_nodes = {}
def run(self):
log = logger.ExportVerboseLogger if self._op.verbose else logger.ExportProgressLogger
@ -276,13 +276,11 @@ class Exporter:
inc_progress = self.report.progress_increment
self.report.msg("\nChecking Logic Trees...")
need_to_export = [(name, bo, so) for name, (bo, so) in self.want_node_trees.items()
if name not in self.node_trees_exported]
self.report.progress_value = len(self.want_node_trees) - len(need_to_export)
for tree, bo, so in need_to_export:
self.report.msg("NodeTree '{}'", tree, indent=1)
bpy.data.node_groups[tree].export(self, bo, so)
for tree_name, references in self.want_node_trees.items():
self.report.msg("NodeTree '{}'", tree_name, indent=1)
tree = bpy.data.node_groups[tree_name]
for bo, so in references:
tree.export(self, bo, so)
inc_progress()
def _harvest_actors(self):

4
korman/nodes/node_avatar.py

@ -56,10 +56,10 @@ class PlasmaSittingBehaviorNode(PlasmaNodeBase, bpy.types.Node):
layout.prop_menu_enum(self, "approach")
def get_key(self, exporter, so):
return exporter.mgr.find_create_key(plSittingModifier, name=self.key_name, so=so)
return self._find_create_key(plSittingModifier, exporter, so=so)
def export(self, exporter, bo, so):
sitmod = self.get_key(exporter, so).object
sitmod = self._find_create_object(plSittingModifier, exporter, so=so)
for flag in self.approach:
sitmod.miscFlags |= getattr(plSittingModifier, flag)
for i in self.find_outputs("satisfies"):

54
korman/nodes/node_conditions.py

@ -74,9 +74,8 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
if clickable_bo is None:
clickable_bo = parent_bo
name = self.key_name
interface = exporter.mgr.find_create_key(plInterfaceInfoModifier, name=name, so=clickable_so).object
logicmod = exporter.mgr.find_create_key(plLogicModifier, name=name, so=clickable_so)
interface = self._find_create_object(plInterfaceInfoModifier, exporter, bl=clickable_bo, so=clickable_so)
logicmod = self._find_create_key(plLogicModifier, exporter, bl=clickable_bo, so=clickable_so)
interface.addIntfKey(logicmod)
# Matches data seen in Cyan's PRPs...
interface.addIntfKey(logicmod)
@ -105,11 +104,11 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
physical.LOSDBs |= plSimDefs.kLOSDBUIItems
# Picking Detector -- detect when the physical is clicked
detector = exporter.mgr.find_create_key(plPickingDetector, name=name, so=clickable_so).object
detector = self._find_create_object(plPickingDetector, exporter, bl=clickable_bo, so=clickable_so)
detector.addReceiver(logicmod.key)
# Clickable
activator = exporter.mgr.find_create_key(plActivatorConditionalObject, name=name, so=clickable_so).object
activator = self._find_create_object(plActivatorConditionalObject, exporter, bl=clickable_bo, so=clickable_so)
activator.addActivator(detector.key)
logicmod.addCondition(activator.key)
logicmod.setLogicFlag(plLogicModifier.kLocalElement, True)
@ -125,10 +124,14 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
face_target = self.find_input_socket("facing")
face_target.convert_subcondition(exporter, clickable_bo, clickable_so, logicmod)
@property
def export_once(self):
return self.clickable_object is not None
def get_key(self, exporter, parent_so):
# careful... we really make lots of keys...
clickable_bo, clickable_so = self._get_objects(exporter, parent_so)
key = exporter.mgr.find_create_key(plLogicModifier, name=self.key_name, so=clickable_so)
key = self._find_create_key(plLogicModifier, exporter, bl=clickable_bo, so=clickable_so)
return key
def _get_objects(self, exporter, parent_so):
@ -185,7 +188,7 @@ class PlasmaClickableRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.t
region_bo = self.region_object
if region_bo is None:
self.raise_error("invalid Region")
region_so = exporter.mgr.find_create_key(plSceneObject, bl=region_bo).object
region_so = exporter.mgr.find_create_object(plSceneObject, bl=region_bo)
# Try to figure out the appropriate bounds type for the region....
phys_mod = region_bo.plasma_modifiers.collision
@ -202,15 +205,13 @@ class PlasmaClickableRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.t
# one detector for many unrelated logic mods. However, LogicMods and Conditions appear to
# assume they pwn each other... so we need a unique detector. This detector must be attached
# as a modifier to the region's SO however.
name = self.key_name
detector_key = exporter.mgr.find_create_key(plObjectInVolumeDetector, name=name, so=region_so)
detector = detector_key.object
detector = self._find_create_object(plObjectInVolumeDetector, exporter, bl=region_bo, so=region_so)
detector.addReceiver(logicmod.key)
detector.type = plObjectInVolumeDetector.kTypeAny
# Now, the conditional object. At this point, these seem very silly. At least it's not a plModifier.
# All they really do is hold a satisfied boolean...
objinbox_key = exporter.mgr.find_create_key(plObjectInBoxConditionalObject, name=name, so=parent_so)
objinbox_key = self._find_create_key(plObjectInBoxConditionalObject, exporter, bl=region_bo, so=parent_so)
objinbox_key.object.satisfied = True
logicmod.addCondition(objinbox_key)
@ -270,19 +271,18 @@ class PlasmaFacingTargetSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
# First, gather the schtuff from the appropriate blah blah blah
if self.simple_mode:
node = self.node
directional = True
tolerance = 45
name = "{}_SimpleFacing".format(self.node.key_name)
elif self.is_linked:
node = self.links[0].from_node
directional = node.directional
tolerance = node.tolerance
name = node.key_name
else:
# This is a programmer failure, so we need a traceback.
raise RuntimeError("Tried to export an unused PlasmaFacingTargetSocket")
facing_key = exporter.mgr.find_create_key(plFacingConditionalObject, name=name, so=so)
facing_key = node._find_create_key(plFacingConditionalObject, exporter, bl=bo, so=so)
facing = facing_key.object
facing.directional = directional
facing.satisfied = True
@ -395,13 +395,12 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
self.raise_error("Region cannot be empty")
so = exporter.mgr.find_create_object(plSceneObject, bl=bo)
rgn_enter, rgn_exit = None, None
parent_key = parent_so.key
if self.report_enters:
theName = "{}_{}_Enter".format(self.id_data.name, self.name)
rgn_enter = exporter.mgr.find_create_key(plLogicModifier, name=theName, so=so)
rgn_enter = self._find_create_key(plLogicModifier, exporter, suffix="Enter", bl=bo, so=so)
if self.report_exits:
theName = "{}_{}_Exit".format(self.id_data.name, self.name)
rgn_exit = exporter.mgr.find_create_key(plLogicModifier, name=theName, so=so)
rgn_exit = self._find_create_key(plLogicModifier, exporter, suffix="Exit", bl=bo, so=so)
if rgn_enter is None:
return rgn_exit
@ -415,12 +414,12 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
return (rgn_enter, rgn_exit)
def export(self, exporter, bo, parent_so):
# We need to ensure we export to the correct SO
region_bo = self.region_object
if region_bo is None:
self.raise_error("Region cannot be empty")
region_so = exporter.mgr.find_create_object(plSceneObject, bl=region_bo)
interface = exporter.mgr.find_create_object(plInterfaceInfoModifier, name=self.key_name, so=region_so)
interface = self._find_create_object(plInterfaceInfoModifier, exporter, bl=region_bo, so=region_so)
# Region Enters
enter_simple = self.find_input_socket("enter").allow
@ -452,20 +451,15 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
else:
suffix = "Exit"
theName = "{}_{}_{}".format(self.id_data.name, self.name, suffix)
exporter.report.msg("[LogicModifier '{}']", theName, indent=2)
logicKey = exporter.mgr.find_create_key(plLogicModifier, name=theName, so=so)
logicKey = self._find_create_key(plLogicModifier, exporter, suffix=suffix, bl=bo, so=so)
logicmod = logicKey.object
logicmod.setLogicFlag(plLogicModifier.kMultiTrigger, True)
logicmod.notify = self.generate_notify_msg(exporter, so, "satisfies")
# Now, the detector objects
exporter.report.msg("[ObjectInVolumeDetector '{}']", theName, indent=2)
detKey = exporter.mgr.find_create_key(plObjectInVolumeDetector, name=theName, so=so)
det = detKey.object
det = self._find_create_object(plObjectInVolumeDetector, exporter, suffix=suffix, bl=bo, so=so)
exporter.report.msg("[VolumeSensorConditionalObject '{}']", theName, indent=2)
volKey = exporter.mgr.find_create_key(plVolumeSensorConditionalObject, name=theName, so=so)
volKey = self._find_create_key(plVolumeSensorConditionalObject, exporter, suffix=suffix, bl=bo, so=so)
volsens = volKey.object
volsens.type = event
@ -483,6 +477,10 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
logicmod.addCondition(volKey)
return logicKey
@property
def export_once(self):
return True
@classmethod
def _idprop_mapping(cls):
return {"region_object": "region"}

50
korman/nodes/node_core.py

@ -15,7 +15,7 @@
import abc
import bpy
from PyHSPlasma import plMessage, plNotifyMsg
from PyHSPlasma import *
from ..exporter import ExportError
@ -37,6 +37,20 @@ class PlasmaNodeBase:
def get_key(self, exporter, so):
return None
def get_key_name(self, single, suffix=None, bl=None, so=None):
assert bl or so
if single:
name = bl.name if bl is not None else so.key.name
if suffix:
return "{}_{}_{}_{}".format(name, self.id_data.name, self.name, suffix)
else:
return "{}_{}_{}".format(name, self.id_data.name, self.name)
else:
if suffix:
return "{}_{}_{}".format(self.id_data.name, self.name, suffix)
else:
return "{}_{}".format(self.id_data.name, self.name)
def draw_label(self):
if hasattr(self, "pl_label_attr") and self.hide:
return str(getattr(self, self.pl_label_attrib, self.bl_label))
@ -45,6 +59,27 @@ class PlasmaNodeBase:
def export(self, exporter, bo, so):
pass
@property
def export_once(self):
"""This node can only be exported once because it is a targeted plSingleModifier"""
return False
def _find_create_object(self, pClass, exporter, **kwargs):
"""Finds or creates an hsKeyedObject specific to this node."""
assert "name" not in kwargs
kwargs["name"] = self.get_key_name(issubclass(pClass, (plObjInterface, plSingleModifier)),
kwargs.pop("suffix", ""), kwargs.get("bl"),
kwargs.get("so"))
return exporter.mgr.find_create_object(pClass, **kwargs)
def _find_create_key(self, pClass, exporter, **kwargs):
"""Finds or creates a plKey specific to this node."""
assert "name" not in kwargs
kwargs["name"] = self.get_key_name(issubclass(pClass, (plObjInterface, plSingleModifier)),
kwargs.pop("suffix", ""), kwargs.get("bl"),
kwargs.get("so"))
return exporter.mgr.find_create_key(pClass, **kwargs)
def find_input(self, key, idname=None):
for i in self.inputs:
if i.alias == key:
@ -188,10 +223,6 @@ class PlasmaNodeBase:
def harvest_actors(self, bo):
return set()
@property
def key_name(self):
return "{}_{}".format(self.id_data.name, self.name)
def link_input(self, node, out_key, in_key):
"""Links a given Node's output socket to a given input socket on this Node"""
if isinstance(in_key, str):
@ -221,6 +252,9 @@ class PlasmaNodeBase:
"""Returns an absolute path to this Node. Needed because repr() uses an elipsis..."""
return "{}.{}".format(repr(self.id_data), self.path_from_id())
def previously_exported(self, exporter):
return self.name in exporter.exported_nodes[self.id_data.name]
@classmethod
def poll(cls, context):
return (context.bl_idname == "PlasmaNodeTree")
@ -420,9 +454,11 @@ class PlasmaNodeTree(bpy.types.NodeTree):
bl_icon = "NODETREE"
def export(self, exporter, bo, so):
# just pass it off to each node
exported_nodes = exporter.exported_nodes.setdefault(self.name, set())
for node in self.nodes:
node.export(exporter, bo, so)
if not (node.export_once and node.previously_exported(exporter)):
node.export(exporter, bo, so)
exported_nodes.add(node.name)
def find_output(self, idname):
for node in self.nodes:

6
korman/nodes/node_logic.py

@ -82,7 +82,7 @@ class PlasmaExcludeRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.typ
def get_key(self, exporter, parent_so):
if self.region_object is None:
self.raise_error("Region must be set")
return exporter.mgr.find_create_key(plExcludeRegionModifier, bl=self.region_object, name=self.key_name)
return self._find_create_key(plExcludeRegionModifier, exporter, bl=self.region_object)
def harvest_actors(self, bo):
return [i.safepoint.name for i in self.find_input_sockets("safe_points") if i.safepoint is not None]
@ -108,6 +108,10 @@ class PlasmaExcludeRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.typ
physical.memberGroup = plSimDefs.kGroupDetector
physical.collideGroup |= 1 << plSimDefs.kGroupDynamic
@property
def export_once(self):
return True
@classmethod
def _idprop_mapping(cls):
return {"region_object": "region"}

7
korman/nodes/node_messages.py

@ -544,6 +544,8 @@ class PlasmaOneShotMsgNode(idprops.IDPropObjectMixin, PlasmaMessageWithCallbacks
layout.prop(self, "seek")
def export(self, exporter, bo, so):
# Note: we purposefully allow this to proceed because plOneShotMod is a MultiMod, so we
# want all referencing SOs to get a copy of the modifier.
oneshotmod = self.get_key(exporter, so).object
oneshotmod.animName = self.animation
oneshotmod.drivable = self.drivable
@ -553,12 +555,11 @@ class PlasmaOneShotMsgNode(idprops.IDPropObjectMixin, PlasmaMessageWithCallbacks
oneshotmod.seekDuration = 1.0
def get_key(self, exporter, so):
name = self.key_name
if self.pos_object is not None:
pos_so = exporter.mgr.find_create_object(plSceneObject, bl=self.pos_object)
return exporter.mgr.find_create_key(plOneShotMod, name=name, so=pos_so)
return self._find_create_key(plOneShotMod, exporter, so=pos_so)
else:
return exporter.mgr.find_create_key(plOneShotMod, name=name, so=so)
return self._find_create_key(plOneShotMod, exporter, so=so)
def harvest_actors(self, bo):
actors = set()

7
korman/nodes/node_python.py

@ -252,10 +252,15 @@ class PlasmaPythonFileNode(PlasmaVersionedNode, bpy.types.Node):
layout.label(text="Script '{}' is not loaded in Blender".format(self.filename), icon="ERROR")
def get_key(self, exporter, so):
return exporter.mgr.find_create_key(plPythonFileMod, name=self.key_name, so=so)
return self._find_create_key(plPythonFileMod, exporter, so=so)
def export(self, exporter, bo, so):
pfm = self.get_key(exporter, so).object
# No need to continue if the PFM was already generated.
if self.previously_exported(exporter):
return
py_name = Path(self.filename).stem
pfm.filename = py_name

7
korman/nodes/node_responder.py

@ -83,7 +83,7 @@ class PlasmaResponderNode(PlasmaVersionedNode, bpy.types.Node):
layout.prop(self, "no_ff_sounds")
def get_key(self, exporter, so):
return exporter.mgr.find_create_key(plResponderModifier, name=self.key_name, so=so)
return self._find_create_key(plResponderModifier, exporter, so=so)
def export(self, exporter, bo, so):
responder = self.get_key(exporter, so).object
@ -135,6 +135,11 @@ class PlasmaResponderNode(PlasmaVersionedNode, bpy.types.Node):
stateMgr.register_state(stateNode)
stateMgr.convert_states(exporter, so)
@property
def export_once(self):
# What exactly is a reused responder? All the messages are directed, after all...
return True
@property
def latest_version(self):
return 2

14
korman/nodes/node_softvolume.py

@ -130,7 +130,7 @@ class PlasmaSoftVolumeInvertNode(PlasmaNodeBase, bpy.types.Node):
])
def get_key(self, exporter, so):
return exporter.mgr.find_create_key(plSoftVolumeInvert, name=self.key_name, so=so)
return self._find_create_key(plSoftVolumeInvert, exporter, so=so)
def export(self, exporter, bo, so):
parent = self.find_input("input")
@ -147,6 +147,10 @@ class PlasmaSoftVolumeInvertNode(PlasmaNodeBase, bpy.types.Node):
sv.insideStrength = 1.0
sv.outsideStrength = 0.0
@property
def export_once(self):
return True
class PlasmaSoftVolumeLinkNode(PlasmaNodeBase):
input_sockets = OrderedDict([
@ -180,6 +184,10 @@ class PlasmaSoftVolumeLinkNode(PlasmaNodeBase):
sv.insideStrength = 1.0
sv.outsideStrength = 0.0
@property
def export_once(self):
return True
class PlasmaSoftVolumeIntersectNode(PlasmaSoftVolumeLinkNode, bpy.types.Node):
bl_category = "SV"
@ -188,7 +196,7 @@ class PlasmaSoftVolumeIntersectNode(PlasmaSoftVolumeLinkNode, bpy.types.Node):
def get_key(self, exporter, so):
## FIXME: SoftVolumeIntersect should not be listed as an interface
return exporter.mgr.find_create_key(plSoftVolumeIntersect, name=self.key_name, so=so)
return self._find_create_key(plSoftVolumeIntersect, exporter, so=so)
class PlasmaSoftVolumeUnionNode(PlasmaSoftVolumeLinkNode, bpy.types.Node):
@ -198,4 +206,4 @@ class PlasmaSoftVolumeUnionNode(PlasmaSoftVolumeLinkNode, bpy.types.Node):
def get_key(self, exporter, so):
## FIXME: SoftVolumeUnion should not be listed as an interface
return exporter.mgr.find_create_key(plSoftVolumeUnion, name=self.key_name, so=so)
return self._find_create_key(plSoftVolumeUnion, exporter, so=so)

19
korman/properties/modifiers/logic.py

@ -23,7 +23,6 @@ from ...exporter import ExportError
from ... import idprops
class PlasmaVersionedNodeTree(idprops.IDPropMixin, bpy.types.PropertyGroup):
name = StringProperty(name="Name")
version = EnumProperty(name="Version",
description="Plasma versions this node tree exports under",
items=game_versions,
@ -32,8 +31,6 @@ class PlasmaVersionedNodeTree(idprops.IDPropMixin, bpy.types.PropertyGroup):
node_tree = PointerProperty(name="Node Tree",
description="Node Tree to export",
type=bpy.types.NodeTree)
node_name = StringProperty(name="Node Ref",
description="Attach a reference to this node")
@classmethod
def _idprop_mapping(cls):
@ -62,20 +59,8 @@ class PlasmaAdvancedLogic(PlasmaModifierProperties):
if i.node_tree is None:
raise ExportError("'{}': Advanced Logic is missing a node tree for '{}'".format(bo.name, i.name))
# If node_name is defined, then we're only adding a reference. We will make sure that
# the entire node tree is exported once before the post_export step, however.
if i.node_name:
exporter.want_node_trees[i.node_tree.name] = (bo, so)
node = i.node_tree.nodes.get(i.node_name, None)
if node is None:
raise ExportError("Node '{}' does not exist in '{}'".format(i.node_name, i.node_tree.name))
# We are going to assume get_key will do the adding correctly. Single modifiers
# should fetch the appropriate SceneObject before doing anything, so this will
# be a no-op in that case. Multi modifiers should accept any SceneObject, however
node.get_key(exporter, so)
else:
exporter.node_trees_exported.add(i.node_tree.name)
i.node_tree.export(exporter, bo, so)
# Defer node tree export until all trees are harvested.
exporter.want_node_trees.setdefault(i.node_tree.name, set()).add((bo, so))
def harvest_actors(self):
actors = set()

5
korman/properties/modifiers/region.py

@ -284,9 +284,8 @@ class PlasmaSoftVolume(idprops.IDPropMixin, PlasmaModifierProperties):
def _export_sv_nodes(self, exporter, bo, so):
tree = self.get_node_tree()
if tree.name not in exporter.node_trees_exported:
exporter.node_trees_exported.add(tree.name)
tree.export(exporter, bo, so)
# Stash for later
exporter.want_node_trees.setdefault(tree.name, set()).add((bo, so))
def get_node_tree(self):
if self.node_tree is None:

15
korman/ui/modifiers/logic.py

@ -19,25 +19,22 @@ from .. import ui_list
class LogicListUI(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index=0, flt_flag=0):
layout.prop(item, "name", emboss=False, text="", icon="NODETREE")
if item.node_tree:
# Using layout.prop on the pointer prevents clicking on the item O.o
layout.label(item.node_tree.name, icon="NODETREE")
else:
layout.label("[Empty]")
def advanced_logic(modifier, layout, context):
ui_list.draw_modifier_list(layout, "LogicListUI", modifier, "logic_groups",
"active_group_index", name_prefix="Logic",
name_prop="name", rows=2, maxrows=3)
"active_group_index", rows=2, maxrows=3)
# Modify the logic groups
if modifier.logic_groups:
logic = modifier.logic_groups[modifier.active_group_index]
layout.row().prop_menu_enum(logic, "version")
layout.prop(logic, "node_tree", icon="NODETREE")
try:
layout.prop_search(logic, "node_name", logic.node_tree, "nodes", icon="NODE")
except:
row = layout.row()
row.enabled = False
row.prop(logic, "node_name", icon="NODE")
def spawnpoint(modifier, layout, context):
layout.label(text="Avatar faces negative Y.")

Loading…
Cancel
Save