Selaa lähdekoodia

BIG REFACTOR of evaluate_sockets

This commit completely refactors evaluate_sockets and the driver system
Instead of being an utterly impenatreble bog with complicated branching
where I try to solve every problem at once right at the end
we prepare the solution ahead of time
cache as much as we can
do the least work possible when evaluating the sockets
It's much, much simpler. Most of the complexity comes from handling
the 4 different kinds of Mantis Socket <==> Blender Property relationships
which are thoroghly explained in the comment
above base_definitions.gen_property_socket_map()

This is a very big commit, but I tested it thoroughly
Every driver that formerly worked and some which did not are all working
in my stego test graph

This commit also substantially improves performance when executing the tree.
Joseph Brandenburg 2 viikkoa sitten
vanhempi
sitoutus
634d736dba

+ 49 - 21
base_definitions.py

@@ -944,7 +944,7 @@ class MantisNode:
         self.ui_signature = signature
         self.inputs = MantisNodeSocketCollection(node=self, is_input=True)
         self.outputs = MantisNodeSocketCollection(node=self, is_input=False)
-        self.parameters, self.drivers = {}, {}; self.bObject=None
+        self.parameters, self.drivers = {}, []; self.bObject=None
         self.hierarchy_connections, self.connections = [], []
         self.hierarchy_dependencies, self.dependencies = [], []
         self.unmet_hierarchy_dependency_count = float("inf") # we will set this when filling the dependencies lists
@@ -972,7 +972,7 @@ class MantisNode:
     
     def reset_execution(self) -> None:
         """ Reset the node for additional execution without re-building the tree."""
-        self.drivers={}; self.bObject=None
+        self.drivers.clear(); self.bObject=None
         self.executed = False
         self.execution_prepared = False
 
@@ -1016,11 +1016,21 @@ class MantisNode:
             self.parameters[key]=value
 
     # We really only need to do this once at class registration
+    # Therefore the goal is not to have any mutable data here
+    # ======================= READ BELOW =======================
+    # We have the following classes of Mantis Socket ==> Blender Proeprty relationships:
+    # Scalar: 1 Mantis scalar ==> 1 Blender property. 1 Filler.
+    # Vector: 1 Mantis Vector socket ==> Blender vector property. 1 Filler.
+    # Scalar Combo: 1 Mantis scalar ==> many Blender scalars. 1 Filler per property.
+    # Vector Combo: 1 Mantis vector ==> vector of Blender vector property. 1 Filler per property.
+    # In every case, 1 filler per Blender property.
     @classmethod
     def gen_property_socket_map(cls, prop_owner_group=None) -> dict:
         from .mantis_dataclasses import MantisSocketFiller
         from dataclasses import replace
         from bpy.app import version as bpy_version
+        if cls.property_socket_map: # retrieve the cache if possible
+            return cls.property_socket_map
         props_sockets = {}
         for s_template in cls.socket_templates:
             if s_template.min_bpy_version:
@@ -1028,26 +1038,44 @@ class MantisNode:
                     prPurple(f"Skipping socket {s_template.name} because "
                              f"it requires Blender {s_template.min_bpy_version} or higher")
                     continue # We can't set or drive this obviously.
-            if prop_owner_group is not None and \
-                    s_template.prop_owner_group != prop_owner_group:
+            if prop_owner_group is not None and s_template.prop_owner_group != prop_owner_group:
                 continue # for composite objects like bones
-            if not s_template.blender_property:
-                continue
-            filler = MantisSocketFiller(socket_name=s_template.name)
-            if isinstance(s_template.blender_property, str):
-                filler = replace(filler)
-                filler.property_name = s_template.blender_property
-                filler.default_value=s_template.default_value
-                props_sockets[s_template.blender_property]=filler
-            elif isinstance(s_template.blender_property, (tuple, list)):
-                for index, sub_prop in enumerate(s_template.blender_property):
-                    filler = replace(filler)
-                    filler.property_name = sub_prop
-                    filler.value_index=index
-                    filler.default_value = s_template.default_value[index]
-                    filler.combo_property=True
-                    props_sockets[sub_prop]= filler
-        cls.property_socket_map = props_sockets
+            if not s_template.blender_property: # this one has to be handled by the node itself.
+                continue # Or maybe it is UI-only, in any case we don't need to do anything here.
+            # Default to (Scalar <==> Scalar), as tuples for compatibility with other code paths
+            vector_property, combo = False, False # TODO: make socket templates pre-tuple-ize this
+            defaults = ( s_template.default_value, ); properties = ( s_template.blender_property, )
+            if isinstance( s_template.default_value, (list, tuple) ):
+                vector_property = True # this tells us if MANTIS provides a vector
+                # if Blender consumes a vector, then we map the vector to the vector. Easy.
+            if isinstance(s_template.blender_property, (tuple, list)):
+                # But maybe we provide data from one Mantis Socket to many Blender properties...
+                combo=True; properties = s_template.blender_property
+                if vector_property: # we have to break the default value up so we can map
+                                    # mantis data element-wise to a vector of Blender properties 
+                    defaults = s_template.default_value 
+            # Now iteratre through the properties and make 1 filler per.
+            for i, property in enumerate(properties):
+                default_index = i if vector_property and combo else 0 # only needed for Combo Vector
+                default = defaults[default_index] # usually 1:1 except for Combo Scalar
+                key = s_template.name + f"_{default_index}" + (f"_{property}" if combo else "")
+                value_index = default_index if combo and vector_property else None
+                from .utilities import socket_filler_operators as operators
+                property_value_operator = None
+                if s_template.property_value_operator:
+                    property_value_operator = getattr(operators, s_template.property_value_operator)
+                props_sockets[key] = MantisSocketFiller(
+                    socket_name = s_template.name,     # This is a dict because node
+                    property_name = property,          # instances should be able 
+                    value_index = value_index,         # to edit Fillers.
+                    default_value = default,           # The keys are not inherently
+                    combo_property = combo,            # meaningful and are
+                    vector_property = vector_property, # NOT USED DOWNSTREAM!
+                    is_animatable = s_template.is_animatable,
+                    property_group = s_template.prop_owner_group,
+                    property_value_operator = property_value_operator)
+        if prop_owner_group is None:
+            cls.property_socket_map = props_sockets  # We should cache this one!
         return props_sockets
 
     def set_traverse(self, traversal_pairs = [(str, str)]) -> None:

+ 15 - 17
deformer_nodes.py

@@ -268,7 +268,7 @@ class DeformerArmature(MantisDeformerNode):
             self.bObject.append(d)
             d.object = self.bGetParentArmature()
             props_sockets = self.property_socket_map
-            evaluate_sockets(self, d, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":d}, props_sockets)
             #
             if (skin_method := self.evaluate_input("Skinning Method")) == "AUTOMATIC_HEAT":
                 match ob.type:
@@ -406,7 +406,7 @@ class DeformerHook(MantisDeformerNode):
                 self.driver_for_radius(ob, target_node.bGetObject(), vertex, d.strength, bezier=False)
                 
             d.vertex_indices_set(vertices_used)
-            evaluate_sockets(self, d, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":d}, props_sockets)
         finish_drivers(self)
         # todo: this node should be able to take many indices in the future.
         # Also: I have a Geometry Nodes implementation of this I can use... maybe...
@@ -472,14 +472,14 @@ class DeformerMorphTargetDeform(MantisDeformerNode):
         
         # at this point we make the node tree
         from .utilities.geometry_node_graphgen import gen_morph_target_nodes
-        m, props_sockets = gen_morph_target_nodes(
+        d, props_sockets = gen_morph_target_nodes(
                             self.evaluate_input("Name"),
                             xf.bGetObject(),
                             targets,
                             context,
                             use_offset=self.evaluate_input("Use Offset"))
-        self.bObject.append(m)
-        evaluate_sockets(self, m, props_sockets)
+        self.bObject.append(d)
+        evaluate_sockets(self, {"DEFAULT":d}, props_sockets)
         finish_drivers(self)
 
     def gen_shape_key_lattice(self, xf, context):
@@ -508,7 +508,7 @@ class DeformerMorphTargetDeform(MantisDeformerNode):
         xf.has_shape_keys = True
         
         # first make a basis shape key
-        keys, props_sockets, ={}, {}
+        keys = {}
         for i, t in enumerate(targets):
             mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
             if sk_ob is None:
@@ -536,12 +536,12 @@ class DeformerMorphTargetDeform(MantisDeformerNode):
             sk.slider_max = 10
             keys[mt_name]=sk
             from .mantis_dataclasses import MantisSocketFiller
-            socket_value = MantisSocketFiller(
+            filler = MantisSocketFiller(
                 property_name=mt_name,
                 socket_name = "Value."+str(i).zfill(3),
                 default_value= 1.0,
             )
-            props_sockets[mt_name]= socket_value
+            evaluate_sockets( self, {"DEFAULT":sk}, {mt_name:filler})
         for i, t in enumerate(targets):
             mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
             if sk_ob is None: continue
@@ -550,7 +550,6 @@ class DeformerMorphTargetDeform(MantisDeformerNode):
                 sk.relative_key = keys.get(rel)
         
         self.bObject.append(sk.id_data)
-        evaluate_sockets(self, sk.id_data, props_sockets)
         finish_drivers(self)
         prWhite(f"Initializing morph target took {time() -start_time} seconds")
 
@@ -597,7 +596,6 @@ class DeformerMorphTargetDeform(MantisDeformerNode):
 
         # first make a basis shape key
         keys={}
-        props_sockets={}
         for i, t in enumerate(targets):
             mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
             if sk_ob is None:
@@ -630,12 +628,13 @@ class DeformerMorphTargetDeform(MantisDeformerNode):
             sk.slider_max = 10
             keys[mt_name]=sk
             from .mantis_dataclasses import MantisSocketFiller
-            socket_value = MantisSocketFiller(
+            filler = MantisSocketFiller(
+                property_group=mt_name,
                 property_name=mt_name,
                 socket_name = "Value."+str(i).zfill(3),
                 default_value= 1.0,
             )
-            props_sockets[mt_name]= socket_value
+            evaluate_sockets( self, {"DEFAULT":sk}, {mt_name:filler})
         for i, t in enumerate(targets):
             mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
             if sk_ob is None: continue
@@ -644,7 +643,6 @@ class DeformerMorphTargetDeform(MantisDeformerNode):
                 sk.relative_key = keys.get(rel)
         
         self.bObject.append(sk.id_data)
-        evaluate_sockets(self, sk.id_data, props_sockets)
         finish_drivers(self)
         prWhite(f"Initializing morph target took {time() -start_time} seconds")
 
@@ -699,7 +697,7 @@ class DeformerSurfaceDeform(MantisDeformerNode):
             self.bObject.append(d)
             self.get_target_and_subtarget(d, input_name="Target")
             props_sockets = self.property_socket_map
-            evaluate_sockets(self, d, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":d}, props_sockets)
 
     def bModifierApply(self, bContext=None):
         from bpy import ops
@@ -730,7 +728,7 @@ class DeformerMeshDeform(MantisDeformerNode):
             self.bObject.append(d)
             self.get_target_and_subtarget(d, input_name="Object")
             props_sockets = self.property_socket_map
-            evaluate_sockets(self, d, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":d}, props_sockets)
 
     def bModifierApply(self, bContext=None):
         from bpy import ops
@@ -761,7 +759,7 @@ class DeformerLatticeDeform(MantisDeformerNode):
             self.bObject.append(d)
             self.get_target_and_subtarget(d, input_name="Object")
             props_sockets = self.property_socket_map
-            evaluate_sockets(self, d, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":d}, props_sockets)
 
 class DeformerSmoothCorrectiveDeform(MantisDeformerNode):
     '''A node representing a corrective smooth deform modifier'''
@@ -788,4 +786,4 @@ class DeformerSmoothCorrectiveDeform(MantisDeformerNode):
             self.bObject.append(d)
             # self.get_target_and_subtarget(d, input_name="Object")
             props_sockets = self.property_socket_map
-            evaluate_sockets(self, d, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":d}, props_sockets)

+ 2 - 0
execution/schema_solve.py

@@ -87,6 +87,8 @@ class SchemaSolver:
                 mantis_node = SchemaConstOutput(signature=signature, base_tree=self.node.base_tree, parent_schema_node=self.node)
                 self.schema_nodes[signature] = mantis_node
 
+    # Using lambdas here causes a lot of function calls, should be optimized out
+    # since after all this only needs to change when the index updates
     def set_index_strings(self):
         self.index_str = lambda : '.'+str(self.uuid)+'.'+str(self.index).zfill(4)
         self.prev_index_str = lambda : '.'+str(self.uuid)+'.'+str(self.index-1).zfill(4)

+ 31 - 62
link_nodes.py

@@ -189,7 +189,7 @@ class LinkCopyLocation(MantisLinkNode):
             self.bObject.append(c)
             self.set_custom_space()
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 class LinkCopyRotation(MantisLinkNode):
@@ -219,7 +219,7 @@ class LinkCopyRotation(MantisLinkNode):
             self.bObject.append(c)
             self.set_custom_space()
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 class LinkCopyScale(MantisLinkNode):
@@ -240,7 +240,7 @@ class LinkCopyScale(MantisLinkNode):
             self.bObject.append(c)
             self.set_custom_space()
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 
@@ -262,7 +262,7 @@ class LinkCopyTransforms(MantisLinkNode):
             self.bObject.append(c)
             self.set_custom_space()
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 class LinkTransformation(MantisLinkNode):
@@ -298,43 +298,8 @@ class LinkTransformation(MantisLinkNode):
                 c.name = constraint_name
             self.bObject.append(c)
             self.set_custom_space()
-            # TODO: this can be cached I just need a custom
-            #  gen_property_socket_map for this class.
-            props_sockets = self.gen_property_socket_map().copy()
-            # we have to fix the blender-property for scale/rotation
-            # because Blender stores these separately.
-            # I do not care that this code is ugly.
-            # TODO this should be handled by the socket templates
-            from_suffix, to_replace = '', ''
-            if self.evaluate_input("Map From") == 'ROTATION':
-                from_suffix='_rot'
-            elif self.evaluate_input("Map From") == 'SCALE':
-                from_suffix='_scale'
-            if self.evaluate_input("Map To") == 'ROTATION':
-                to_replace='_rot'
-            elif self.evaluate_input("Map To") == 'SCALE':
-                to_replace='_scale'
-            if from_suffix:
-                for axis in ['x', 'y', 'z']:
-                    stub='from_min_'+axis
-                    props_sockets[stub+from_suffix]=props_sockets[stub]
-                    props_sockets[stub+from_suffix].property_name = stub+from_suffix
-                    del props_sockets[stub]
-                    stub='from_max_'+axis
-                    props_sockets[stub+from_suffix]=props_sockets[stub]
-                    props_sockets[stub+from_suffix].property_name = stub+from_suffix
-                    del props_sockets[stub]
-            if to_replace:
-                for axis in ['x', 'y', 'z']:
-                    stub='to_min_'+axis
-                    props_sockets[stub+to_replace]=props_sockets[stub]
-                    props_sockets[stub+to_replace].property_name = stub+to_replace
-                    del props_sockets[stub]
-                    stub='to_max_'+axis
-                    props_sockets[stub+to_replace]=props_sockets[stub]
-                    props_sockets[stub+to_replace].property_name = stub+to_replace
-                    del props_sockets[stub]
-            evaluate_sockets(self, c, props_sockets)
+            props_sockets = self.gen_property_socket_map()
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 class LinkLimitLocation(MantisLinkNode):
@@ -353,7 +318,7 @@ class LinkLimitLocation(MantisLinkNode):
             self.bObject.append(c)
             self.set_custom_space()
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 class LinkLimitRotation(MantisLinkNode):
@@ -367,13 +332,12 @@ class LinkLimitRotation(MantisLinkNode):
             print(wrapGreen("Creating ")+wrapWhite("Limit Rotation")+
                 wrapGreen(" Constraint for bone: ") +
                 wrapOrange(xf.bGetObject().name))
-
             if constraint_name := self.evaluate_input("Name"):
                 c.name = constraint_name
             self.bObject.append(c)
             self.set_custom_space()
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 class LinkLimitScale(MantisLinkNode):
@@ -393,7 +357,7 @@ class LinkLimitScale(MantisLinkNode):
             self.bObject.append(c)
             self.set_custom_space()
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 class LinkLimitDistance(MantisLinkNode):
@@ -413,7 +377,7 @@ class LinkLimitDistance(MantisLinkNode):
             self.bObject.append(c)
             self.set_custom_space()
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 # Tracking
@@ -434,7 +398,7 @@ class LinkStretchTo(MantisLinkNode):
                 c.name = constraint_name
             self.bObject.append(c)
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
 
             if (self.evaluate_input("Original Length") == 0):
                 # this is meant to be set automatically.
@@ -457,7 +421,7 @@ class LinkDampedTrack(MantisLinkNode):
                 c.name = constraint_name
             self.bObject.append(c)
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 class LinkLockedTrack(MantisLinkNode):
@@ -476,7 +440,7 @@ class LinkLockedTrack(MantisLinkNode):
                 c.name = constraint_name
             self.bObject.append(c)
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 class LinkTrackTo(MantisLinkNode):
@@ -495,7 +459,7 @@ class LinkTrackTo(MantisLinkNode):
                 c.name = constraint_name
             self.bObject.append(c)
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 class LinkInheritConstraint(MantisLinkNode):
@@ -515,7 +479,7 @@ class LinkInheritConstraint(MantisLinkNode):
             self.bObject.append(c)
 
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
             c.set_inverse_pending
             self.executed = True
 
@@ -696,7 +660,7 @@ class LinkInverseKinematics(MantisLinkNode):
                 self.cache_key.append(None)
 
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
             c.use_location   = self.evaluate_input("Position") > 0
             c.use_rotation   = self.evaluate_input("Rotation") > 0
         self.executed = True
@@ -774,7 +738,7 @@ class LinkDrivenParameter(MantisLinkNode):
                                           socket_name="Value",
                                           default_value=def_value)
             }
-            evaluate_sockets(self, ob, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":ob}, props_sockets)
 
         self.executed = True
 
@@ -804,6 +768,7 @@ class LinkArmature(MantisLinkNode):
                 wrapGreen(" Constraint for bone: ") +
                 wrapOrange(xf.bGetObject().name))
             c = xf.bGetObject().constraints.new('ARMATURE')
+            b_objects = {"DEFAULT":c}
             if constraint_name := self.evaluate_input("Name"):
                 c.name = constraint_name
             self.bObject.append(c)
@@ -828,14 +793,18 @@ class LinkArmature(MantisLinkNode):
                 default_value = 1.0
                 if self.inputs['Weights'].links:
                     default_value = 1/len(self.inputs['Weights'].links)
+                prop_name = 'weight'
+                # prop_name = f"targets[{i}].weight"
                 filler = MantisSocketFiller(
-                    property_name= (prop_name := "targets[%d].weight" % i),
-                    socket_name="Weights",
-                    default_value=default_value,
+                    property_group = f"target.{str(i).zfill(4)}",
+                    property_name = prop_name,
+                    socket_name = "Weights",
                     array_index=i,
+                    default_value=default_value,
                 )
-                props_sockets[prop_name] = filler
-            evaluate_sockets(self, c, props_sockets)
+                b_objects[f"target.{str(i).zfill(4)}"] = target
+                props_sockets[prop_name+str(i)] = filler
+            evaluate_sockets(self, b_objects, props_sockets)
             for target, value in targets_weights.items():
                 c.targets[target].weight=value
         self.executed = True
@@ -871,7 +840,7 @@ class LinkSplineIK(MantisLinkNode):
                 c.name = constraint_name
             self.bObject.append(c)
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 
@@ -893,7 +862,7 @@ class LinkFloor(MantisLinkNode):
             self.bObject.append(c)
             self.set_custom_space()
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True
 
 class LinkShrinkWrap(MantisLinkNode):
@@ -913,7 +882,7 @@ class LinkShrinkWrap(MantisLinkNode):
             if constraint_name := self.evaluate_input("Name"):
                 c.name = constraint_name
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
             # self.set_custom_space() # this needs to be overridden for me TODO
         self.executed = True
 
@@ -937,5 +906,5 @@ class LinkGeometryAttribute(MantisLinkNode):
             if constraint_name := self.evaluate_input("Name"):
                 c.name = constraint_name
             props_sockets = self.gen_property_socket_map()
-            evaluate_sockets(self, c, props_sockets)
+            evaluate_sockets(self, {"DEFAULT":c}, props_sockets)
         self.executed = True

+ 8 - 4
link_socket_templates.py

@@ -33,7 +33,8 @@ InfluenceTemplate : SockTemplate = SockTemplate(
     default_value=1.0, blender_property='influence')
 EnableTemplate : SockTemplate = SockTemplate(
     name="Enable", is_input=True,  bl_idname='EnableSocket',
-    default_value=True, blender_property='mute')
+    default_value=True, blender_property='mute',
+    property_value_operator='invert_cast_to_bool')
 OffsetTemplate : SockTemplate = SockTemplate(
     name="Offset", bl_idname='BooleanSocket', is_input=True,
     default_value=False, blender_property='use_offset')
@@ -164,7 +165,8 @@ TransformationMinMaxTemplateGenerator = lambda name, bprop  : SockTemplate(
     name=name, is_input=True,
     bl_idname='EnumTransformationAxes' if "Source" in name else "FloatSocket",
     default_value='X' if "Source" in name else 1.0,
-    blender_property=bprop)
+    blender_property= bprop if "Source" in name else ( bprop, bprop+"_rot", bprop+"_scale") ,
+    )
 
 LinkTransformationSockets = [
     InputRelationshipTemplate,
@@ -384,8 +386,10 @@ LinkArmatureSockets=[
                  default_value=False, blender_property='use_bone_envelopes'),
     SockTemplate(name="Use Current Location", bl_idname='BooleanSocket', is_input=True,
                  default_value=False, blender_property='use_current_location'),
-    TargetsArray := replace(TargetTemplate, name='Targets', use_multi_input=True),
-    WeightsArray := replace(InfluenceTemplate, name='Weights', use_multi_input=True),
+    TargetsArray := replace(TargetTemplate, name='Targets', use_multi_input=True,
+                            blender_property=None), # the node sets this up
+    WeightsArray := replace(InfluenceTemplate, name='Weights', use_multi_input=True,
+                            blender_property=None), # the node sets this up
     InfluenceTemplate,
     EnableTemplate,
     OutputRelationshipTemplate,

+ 10 - 8
mantis_dataclasses.py

@@ -1,5 +1,5 @@
 from dataclasses import dataclass, field
-from typing import List, Any, Tuple, Optional
+from typing import List, Any, Tuple, Optional, Callable
 
 @dataclass(slots=True)
 class MantisSocketTemplate():
@@ -15,6 +15,9 @@ class MantisSocketTemplate():
     use_multi_input  : bool = field(default=False)
     ui_only          : bool = field(default=False)
     default_value    : Any = field(default=None)
+    # The data operator is used to transform the Mantis data to a format Blender can use.
+    # For example, type-casting when Blender's C/C++ is more type-sensitve than Python
+    property_value_operator : Optional[Callable] = field (default=None)
     # this is a hint for the props_sockets system for complex situations
     #  where multiple blender objects own the properties, like e.g. bone/posebone
     prop_owner_group : str = field(default="")
@@ -87,19 +90,18 @@ class MantisBBoneTransform():
 @dataclass(slots=True)
 class MantisSocketFiller():
     socket_name: str = field(default="")
+    property_group : str = field(default="")
     property_name : str = field(default="")
     default_value: Any = field(default=None)
     array_index: int = field(default=0) # for multi-input array sockets
     value_index: Optional[int] = field(default=None) # for vector properties
     # A Combo Property is a property that jams a bunch of blender properties into one socket
-    combo_property: bool = field(default=False)
+    combo_property: bool = field(default=False) # i.e. it provides a vector of Blender Properties
+    vector_property : bool = field(default=False)
+    is_animatable : bool = field(default=True) # for catching bad drivers
+    # we pack a function for transforming the data if needed.
+    property_value_operator: Optional[Callable] = field(default=None)
 
-    def __repr__(self):
-        return f"socket_name: {self.socket_name}, " \
-               f"property_name: {self.property_name}, " \
-               f"default_value: {self.default_value}, " \
-               f"array_index: {self.array_index or 0}, " \
-               f"value_index: {self.value_index or None}"
 
 @dataclass(slots=True)
 class RerouteCacheItem:

+ 1 - 1
primitives_nodes.py

@@ -92,5 +92,5 @@ class GeometryLattice(PrimitiveNode):
         if not data:
             data = bpy.data.lattices.new( self.evaluate_input("Name") )
         props_sockets = self.property_socket_map
-        evaluate_sockets(self, data, props_sockets)
+        evaluate_sockets(self, {"DEFAULT":data}, props_sockets)
         self.prepared = True; self.executed = True

+ 7 - 1
utilities/drivers.py

@@ -58,10 +58,16 @@ def CreateDrivers(drivers):
             ob = driver["owner"]
         else: # Pose Bone:
             ob = driver["owner"].id_data
+        path_from_id = driver.get('path_from_id')
+        # TODO: I want to refactor path_from_id check&set out. it should always be provided
+        # I think this is still here because of the 2 or three places where nodes make their
+        # own drivers e.g. Hook deformer for curves
+        if path_from_id is None:
+            path_from_id = driver["owner"].path_from_id(driver["prop"])
         if isinstance(driver["owner"], Key):
             fcurves = ob.driver_add(driver["prop"])
         else:
-            fcurves = ob.driver_add(driver["owner"].path_from_id(driver["prop"]), driver["ind"])
+            fcurves = ob.driver_add(path_from_id, driver["ind"])
         if not isinstance(fcurves, list):
             fcurves = [fcurves] # ususally it is a single fCurve, sometimes it is a list
             # for e.g. array properties if driver_add is given -1 as index

+ 122 - 193
utilities/node_common.py

@@ -369,7 +369,7 @@ def prepare_parameters(mantis_node):
             mantis_node.parameters[s_name] = sock.links[0].from_node.parameters[sock.name]
     # should work, this is ugly.
 
-def check_for_driver(mantis_node, input_name, value_index = None, array_index = 0, combo_property=False):
+def check_for_driver(mantis_node, input_name, value_index = None, array_index = 0):
     prop = mantis_node.evaluate_input(input_name, array_index)
     if (value_index is not None):
         try:
@@ -379,204 +379,133 @@ def check_for_driver(mantis_node, input_name, value_index = None, array_index =
         # TODO an Index or Key error here should have a reasonable error message
     return (prop.__class__.__name__ == 'MantisDriver')
 
-def print_driver_item(mantis_node, b_object, original_prop):
-    if mantis_node.node_type in ['LINK',]:
-        printname  = wrapOrange(b_object.id_data.name)
-    elif mantis_node.node_type in ['XFORM',]:
-        printname  = wrapOrange(mantis_node.bGetObject().name)
-    else:
-        printname = wrapOrange(mantis_node)
-    print("Adding driver %s to %s in %s" % (wrapPurple(original_prop), wrapWhite(mantis_node.signature[-1]), printname))
-
-# HACK This is the most hideous voodoo black magic fucntion in the entire program
-#      but everything depends on it and it works 
-def evaluate_sockets(mantis_node, b_object, props_sockets,):
-    # this is neccesary because some things use dict properties for dynamic properties and setattr doesn't work
-    def safe_setattr(ob, att_name, val):
-        if ob.__class__.__name__ in ["NodesModifier"]:
+# this is neccesary because some things use dict properties for dynamic properties and setattr doesn't work
+def safe_setattr(ob, att_name, val):
+    ob_class_name = ob.__class__.__name__
+    match ob_class_name:
+        case "NodesModifier":
             ob[att_name]=val
-        elif b_object.__class__.__name__ in ["Key"]:
-            if not val: val=0
+        case "Key":
             ob.key_blocks[att_name].value=val
-        elif "]." in att_name:
-            # it is of the form prop[int].prop2
-            prop=att_name.split('[')[0]
-            prop1=att_name.split('.')[1]
-            get_index = int(att_name.split('[')[1][0])
-            setattr(getattr(b_object, prop)[get_index], prop1, val)
-        else:
-            try:
-                setattr(ob, att_name, val)
-            except Exception as e:
-                print ("ob, att_name, val")
-                prRed(ob, att_name, val)
-                raise e
-    for prop, filler_item in props_sockets.items():
-        if isinstance(filler_item, tuple):
-            raise NotImplementedError(mantis_node)
-        sock = filler_item.socket_name
-        default = filler_item.default_value
-        value_index = filler_item.value_index
-        array_index = filler_item.array_index
-        combo_property = filler_item.combo_property
-        driver_key = (sock, value_index, array_index)
-        if (check_for_driver(mantis_node, sock, value_index, array_index, combo_property)):
-            original_prop = prop
-            # TODO: deduplicate this terrible hack
-            if ("." in prop) and not b_object.__class__.__name__ in ["Key"]: # this is a property of a property...
-                sub_props = [b_object]
-                while ("." in prop):
-                    split_prop = prop.split(".")
-                    prop = split_prop[1]
-                    sub_prop = (split_prop[0])
-                    if ("[" in sub_prop):
-                        sub_prop, sub_index = sub_prop.split("[")
-                        sub_prop_index = int(sub_index[0])
-                        sub_props.append(getattr(sub_props[-1], sub_prop)[sub_prop_index])
-                    else:
-                        sub_props.append(getattr(sub_props[-1], sub_prop))
-                safe_setattr(sub_props[-1], prop, default)
-            # this is really stupid
-            else:
-                safe_setattr(b_object, prop, default)
-            print_driver_item( mantis_node, b_object, original_prop)
-            if b_object.__class__.__name__ in ["NodesModifier"]:
-                mantis_node.drivers[driver_key] = "[\""+original_prop+"\"]" # lol. It is a dict element not a "true" property
-            elif b_object.__class__.__name__ in ["Key"]:
-                mantis_node.drivers[driver_key] = "key_blocks[\""+original_prop+"\"].value"
-            else:
-                mantis_node.drivers[driver_key] = original_prop
-        else: # here we can do error checking for the socket if needed
-            if (value_index is not None):
-                safe_setattr(b_object, prop, mantis_node.evaluate_input(sock)[value_index])
-            else:                    # 'mute' is better than 'enabled'
-                # UGLY HACK          # because it is available in older
-                if (prop == 'mute'): # Blenders.
-                    safe_setattr(b_object, prop, not bool(mantis_node.evaluate_input(sock)))
-                elif (prop == 'hide'): # this will not cast it for me, annoying.
-                    safe_setattr(b_object, prop, bool(mantis_node.evaluate_input(sock)))
-                else:
-                    try:
-                    # just wanna see if we are dealing with some collection
-                    # check hasattr in case it is one of those ["such-and-such"] props, and ignore those
-                        if hasattr(b_object, prop) and (not isinstance(getattr(b_object, prop), str)) and hasattr(getattr(b_object, prop), "__getitem__"):
-                            # prGreen("Doing the thing")
-                            for val_index, value in enumerate(mantis_node.evaluate_input(sock)):
-                                # assume this will work, both because val should have the right number of elements, and because this should be the right data type.
-                                from .drivers import MantisDriver
-                                if isinstance(value, MantisDriver):
-                                    getattr(b_object,prop)[val_index] = default[val_index]
-                                    print("Adding driver %s to %s in %s" % (wrapPurple(prop), wrapWhite(mantis_node.signature[-1]), mantis_node))
-                                    try:
-                                        mantis_node.drivers[sock].append((prop, val_index))
-                                    except:
-                                        mantis_node.drivers[sock] = [(prop, val_index)]
-                                else:
-                                    getattr(b_object,prop)[val_index] =  value
-                        else:
-                            safe_setattr(b_object, prop, mantis_node.evaluate_input(sock))
-                    except Exception as e:
-                        prRed(b_object, mantis_node, prop, sock, mantis_node.evaluate_input(sock))
-                        raise e
+        case _:
+            setattr(ob, att_name, val)
 
-def finish_driver(mantis_node, b_object, driver_item, prop):
-    try:
-        driver_sock = driver_item[0]; index = driver_item[1]; array_index =driver_item[2]
-    except Exception as e:
-        print (wrapGreen(mantis_node), wrapOrange(b_object), wrapRed(driver_item), wrapWhite(prop),)
-        raise e
-    driver_trace = trace_single_line(mantis_node, driver_sock, array_index)
-    driver_provider, driver_socket = driver_trace[0][-1], driver_trace[1]
-    
-    combo_property = False
-    # try to detect combo properties
-    if mantis_node.socket_templates:
-        for st in mantis_node.socket_templates:
-            if st.name == driver_sock:
-                if isinstance(st.blender_property, tuple):
-                    combo_property = True
-                break
+# do: make this function end in a single setattr
+def evaluate_sockets(mantis_node, b_objects, props_sockets,):
+    # TODO: I think it is wasteful to run this once per b_object for e.g. Link nodes.
+    #       Should be able to get the results once and pass 'em along. Probably only saves
+    #       a handful of fn calls in a big graph. Not worth doing a big refactor right now
+    # Because Mantis Nodes are sometimes a composition of different Blender objects,
+    # We have to set properties on different Blender Objects and the easiest way
+    # is to send the object in directly instead of trying to find e.g. nested properties
+    for property_group, b_object in b_objects.items():
+        ob_class_name = b_object.__class__.__name__
+        for filler_item in props_sockets.values():
+            filler_property_group = filler_item.property_group or "DEFAULT"
+            prop = filler_item.property_name
+            if filler_property_group != property_group: continue
+            if b_object is None: continue
 
-    if index is not None:
-        try:
-            driver = driver_provider.parameters[driver_socket.name][index].copy()
-        except KeyError as e: # try single driver, it should still work fine.
-            if combo_property:
-                driver = driver_provider.parameters[driver_socket.name].copy()
-            else:
-                raise e
-        # this is harmless and necessary for the weird ones where the property is a vector too
-        if not combo_property:
-            driver["ind"] = index
-    else:
-        driver = driver_provider.parameters[driver_socket.name].copy()
-    if driver:
-        socket_template = None
-        if mantis_node.socket_templates:
-            for socket_template in mantis_node.socket_templates:
-                if socket_template.name == driver_sock: break
-        if socket_template:
-            if socket_template.is_animatable == False:
-                raise RuntimeError(f"Driver is not allowed for {driver_sock} in node {mantis_node}")
-        if ("." in prop) and mantis_node.__class__.__name__ != "DeformerMorphTargetDeform": # this is a property of a property...
-            sub_props = [b_object]
-            while ("." in prop):
-                split_prop = prop.split(".")
-                prop = split_prop[1]
-                sub_prop = (split_prop[0])
-                if ("[" in sub_prop):
-                    sub_prop, index = sub_prop.split("[")
-                    index = int(index[0])
-                    sub_props.append(getattr(sub_props[-1], sub_prop)[index])
+            input_name = filler_item.socket_name
+            default = ( filler_item.default_value, )
+            value_index = filler_item.value_index
+            array_index = filler_item.array_index
+            combo = filler_item.combo_property
+            vector = filler_item.vector_property
+
+            # Check for a driver -- this is inlined from check_for_driver()
+            evaluated_value = mantis_node.evaluate_input(input_name, array_index)
+            driver = evaluated_value
+            drivers_exist = (evaluated_value.__class__.__name__ == 'MantisDriver', )
+            single_driver_input = drivers_exist[0]
+
+            if combo and vector: # we map an element of the socket's vector to a single scalar property
+                evaluated_value = evaluated_value[value_index]
+                drivers_exist = (evaluated_value.__class__.__name__ == 'MantisDriver', )
+            if vector and not combo: # then we map a vector to a vector and have to check for drivers per-element
+                default = filler_item.default_value
+                # the evaluated value could either be a vector of scalar values (or scalar drivers) OR one driver
+                if single_driver_input: # we have alredy checked for single-driver, so this is a vector otherwise
+                    drivers_exist = tuple( True for _unused in range(len(default)) )
                 else:
-                    sub_props.append(getattr(sub_props[-1], sub_prop))
-            driver["owner"] = sub_props[-1]
-        elif mantis_node.node_type in ['XFORM',] and mantis_node.__class__.__name__ in ['xFormBone']:
-            # TODO: I really shouldn't have to hardcode this. Look into better solutions.
-            # NOTE: VERSIONING in Blender 5.0 we have to use pose bone for this, too.
-            # default to pose bone, it's usually what we want
-            bone_col = mantis_node.bGetParentArmature().pose.bones
-            from ..xForm_socket_templates import attributes_shared_by_bone_and_pose_bone
-            if socket_template.prop_owner_group == 'bone':
-                bone_col = mantis_node.bGetParentArmature().data.bones
-            # this is a little bit clumsy. TODO clean up here
-            if prop in attributes_shared_by_bone_and_pose_bone:
-                # we want to use the pose bone for this...only if it has a driver
-                bone_col = mantis_node.bGetParentArmature().pose.bones
-            driver["owner"] = bone_col[b_object] # we use "unsafe" brackets instead of get() because we want to see any errors that occur
-        # HACK having special cases here is indicitave of a deeper problem that should be refactored
-        elif mantis_node.__class__.__name__ in ['xFormCurvePin'] and \
-                      prop in ['offset_factor', 'forward_axis', 'up_axis']:
-                driver["owner"] = b_object.constraints['Curve Pin']
-        else:
-            driver["owner"] = b_object
-        driver["prop"] = prop
-        return driver
-    else:
-        prOrange("Provider", driver_provider)
-        prGreen("socket", driver_socket)
-        print (index)
-        prPurple(driver_provider.parameters[driver_socket.name])
-        prRed("Failed to create driver for %s" % prop)
-        return None
+                    drivers_exist = tuple( val.__class__.__name__ == 'MantisDriver' for val in evaluated_value )
+            assign_this_value = evaluated_value # Usually we just assign the value right away
+            if single_driver_input and vector: # in this pernicious case, we need to lift
+                assign_this_value = list(default) # the evaluated value from scalar to vector
+            # We had to pack everything into this weird iterable format so we can handle
+            # all 4 cases, (scalar, vector, scalar combo, vector combo) in one place
+            # this isn't perfectly optimal but I expect it to cost a few milliseconds at most
+            # and while it is kind of confusing it is nice to keep all this in one code path
+            if any(drivers_exist):
+                if vector and not combo and isinstance(assign_this_value, tuple):
+                    assign_this_value = list(assign_this_value) # because we have to mutate it
+                for i, (default_value, driver_exists) in enumerate(zip(default, drivers_exist)):
+                    if driver_exists: # we need to pack data for the driver
+                        if filler_item.is_animatable == False:
+                            raise RuntimeError(f"Driver is not allowed for input \"{input_name}\" in node \"{mantis_node}\".")
+                        # NOTE at this point we can inject the driver's default resulting value
+                        #      and we have to do so in Curve Pin where it has a special case
+                        #      In the future I may implement this HERE in general
+                        printname = wrapOrange(mantis_node)
+
+                        if vector and not combo: assign_this_value[i] = default_value
+                        else: assign_this_value = default_value
+
+                        if mantis_node.node_type == 'LINK': printname  = wrapOrange(b_object.id_data.name)
+                        elif mantis_node.node_type == 'XFORM': printname  = wrapOrange(mantis_node.bGetObject().name)
+                        print(f"Adding driver to property: {wrapPurple(prop)} in {printname} in node: {wrapWhite(mantis_node.ui_signature[1:])}")
+                        # NOW we can set up the driver.
+                        # TODO: Maybe can prefill this in the socket template or cache the result per-socket
+                        driver_properties = {} # use this dict to pack this data without mutating the driver itself
+                        driver_properties["path_from_id"] = b_object.path_from_id(prop,)
+                        driver_properties['owner'] = b_object # it is the CALLER's responsibility to send the right object!!
+                        # we need a special case here for Bones :/
+                        # TODO: refactor so I don't need this
+                        if mantis_node.__class__.__name__ == 'xFormBone':
+                            from ..xForm_socket_templates import attributes_shared_by_bone_and_pose_bone
+                            if prop in attributes_shared_by_bone_and_pose_bone:
+                                driver_properties['owner'] = b_objects['pose_bone']
+                                driver_properties["path_from_id"] = b_objects['pose_bone'].path_from_id(prop,)
+                        if (vector and not combo):
+                            driver_properties['ind'] = i
+                        if vector and single_driver_input:
+                            driver_properties['ind'] = -1 # the driver will be added to all 3 channels
+                        # TODO: I would like the socket filler to handle the special cases here.
+                        #       even if it means I have to pack another operator in the filler.
+                        resolved_prop_name = prop
+                        match ob_class_name:
+                            case "NodesModifier": # It is a dict element.
+                                resolved_prop_name = "[\""+prop+"\"]"
+                            case "Key": # that is, the outer data block that owns shape keys, not a key-frame.
+                                resolved_prop_name = "key_blocks[\""+prop+"\"].value"
+                        driver_properties["prop"] = resolved_prop_name
+                        driver_index = value_index if vector and not single_driver_input else None
+                        if driver_index: driver = driver[driver_index]
+                        mantis_node.drivers.append( (driver, driver_properties) )
+
+            # We wait until now to skip this because it is important to do the
+            #   is_animatable check above and give the user a meaningful error
+            if prop is None: continue
+            # Some socket values need to be transformed, so we can use the operator to do that.
+            if filler_item.property_value_operator:
+                assign_this_value = filler_item.property_value_operator(mantis_node, evaluated_value)
+            # at this point there is ONE filler per property and we just have to assign the value
+            if ob_class_name not in {'NodesModifier', 'Key'}:
+                setattr(b_object, prop, assign_this_value)
+            elif ob_class_name == "NodesModifier":
+                b_object[prop] = assign_this_value
+            else: # ob_class_name == "Key":
+                b_object.key_blocks[prop].value = assign_this_value
 
 def finish_drivers(mantis_node):
     drivers = []
-    if not hasattr(mantis_node, "drivers"):
-        return # HACK
-    for driver_item, prop in mantis_node.drivers.items():
-        b_objects = [mantis_node.bObject]
-        if mantis_node.node_type == 'LINK':
-            b_objects = mantis_node.bObject # this is already a list
-        for b_object in b_objects:
-            # Mutating the driver_item here is the WORST THING I HAVE EVER SEEN
-            # HACK UGLY BUG TERRIBLE BAD CODE
-            if isinstance(prop, list):
-                for sub_item in prop:
-                        drivers.append(finish_driver(mantis_node, b_object, (driver_item, sub_item[1], 0), sub_item[0]))
-                else:
-                    drivers.append(finish_driver(mantis_node, b_object, (driver_item, sub_item[1], 0), sub_item[0]))
-            else:
-                drivers.append(finish_driver(mantis_node, b_object, driver_item, prop))
+    for driver, driver_properties in mantis_node.drivers:
+        drivers_from_socket = (driver,)
+        if isinstance(driver, (tuple, list)):
+            drivers_from_socket = driver # this is a collection e.g. from a Vector Combine
+        for driver in drivers_from_socket:
+            driver_copy = driver.copy() # it is safe to copy it here because it is guarenteed to be filled out.
+            driver_copy.update(driver_properties)
+            drivers.append(driver_copy)
     from .drivers import CreateDrivers
-    CreateDrivers(drivers)
+    CreateDrivers(drivers)

+ 16 - 0
utilities/socket_filler_operators.py

@@ -0,0 +1,16 @@
+# This file is for defining socket-filler operators by name
+# These operators are defined by name in Socket Templates and used by
+#    the Socket Filler system in the Property-Socket Map to transform
+#    Mantis data to Blender-ready data
+# Primarily, this will involve type-casting and enum handling
+# Probably I will need to expand this system to take arbitrary arguments
+#    but for now we'll take the Mantis Node and the data as arguments
+
+def cast_to_bool(*args, **kwargs):
+    return bool(args[1]) # args[1] is the data
+
+def invert_cast_to_bool(*args, **kwargs):
+    return bool( not args[1]) # args[1] is the data
+
+def resolve_rotation_mode_auto(*args, **kwargs):
+    return 'XYZ' # for now this is hardcoded lmao

+ 14 - 35
xForm_nodes.py

@@ -379,8 +379,8 @@ class xFormBone(xFormNode):
 
         # Setup Deform attributes...
         eb_props_sockets = self.gen_property_socket_map(prop_owner_group="edit_bone")
-        evaluate_sockets(self, eb, eb_props_sockets)
-        
+        b_objects = {'edit_bone':eb, 'pose_bone':None, 'bone':None, 'DEFAULT':None}
+        evaluate_sockets(self, b_objects, eb_props_sockets)
         print( wrapGreen("Created Bone: ") + wrapOrange(eb.name) + wrapGreen(" in ") + wrapWhite(self.bGetParentArmature().name))
 
         parent_xForm_info = get_parent_xForm_info(self)
@@ -454,29 +454,12 @@ class xFormBone(xFormNode):
         except KeyError:
             prRed("Warning: BBone start or end handle not set because of missing bone in armature.")
 
-        import bpy
         pb = self.bGetParentArmature().pose.bones[self.bObject]
-
-        if check_for_driver(self, 'Rotation Order'):
-            # this is stupid and a HACK
-            from .mantis_dataclasses import MantisSocketFiller
-            props_sockets = {"rotation_mode": MantisSocketFiller( socket_name="Rotation Order",
-                property_name="rotation_mode", default_value='XYZ',)}
-            evaluate_sockets(self, pb, props_sockets)
-        else: # This is stupid, but I did it to myself and it will be annoying to fix
-            rotation_mode = self.evaluate_input("Rotation Order")
-            if rotation_mode == "AUTO": rotation_mode = "XYZ"
-            pb.rotation_mode = rotation_mode
-            
-            
-
         pb.id_properties_clear()
         self.setup_custom_props(pb, "Custom Properties")
-        pb_props_sockets = self.gen_property_socket_map(prop_owner_group='pose_bone')
-        evaluate_sockets(self, pb, pb_props_sockets)
-
-        bone_props_socket = self.gen_property_socket_map(prop_owner_group='bone')
-        evaluate_sockets(self, pb.bone, bone_props_socket)
+        b_objects = {'edit_bone':None, 'pose_bone':pb, 'bone':pb.bone, 'DEFAULT':None}
+        props_sockets = self.gen_property_socket_map()
+        evaluate_sockets(self, b_objects, props_sockets)
 
         # and in edit mode we need to explicitly set the bbone properties
         # I don't like this HACK but there seems to be no other way...
@@ -487,8 +470,10 @@ class xFormBone(xFormNode):
 
         finish_drivers(self)
 
+        # TODO: I should not need a special case for this
         pb.custom_shape_transform = None
         pb.custom_shape = None
+        from bpy.types import Object
         if len(self.inputs["Custom Object xForm Override"].links) > 0:
             trace = trace_single_line(self, "Custom Object xForm Override")
             try:
@@ -501,7 +486,7 @@ class xFormBone(xFormNode):
                 ob = trace[0][1].bGetObject()
             except AttributeError:
                 ob=None
-            if type(ob) in [bpy.types.Object]:
+            if type(ob) in {Object}:
                 pb.custom_shape = ob
 
     def bGetObject(self, mode = 'POSE'):
@@ -613,7 +598,7 @@ class xFormGeometryObject(xFormNode):
         # putting this in bTransformPass simply prevents it from being run more than once.
         # maybe I should do that with the rest of bPrepare, too.
         props_sockets = self.property_socket_map
-        evaluate_sockets(self, self.bObject, props_sockets)
+        evaluate_sockets(self, {"DEFAULT":self.bObject}, props_sockets)
         self.executed = True
 
     def bFinalize(self, bContext = None):
@@ -622,9 +607,6 @@ class xFormGeometryObject(xFormNode):
         self.bObject.matrix_world = matrix
         # custom properties
         self.setup_custom_props(self.bGetObject(), "Custom Properties")
-        for i, (driver_key, driver_item) in enumerate(self.drivers.items()):
-            print (wrapGreen(i), wrapWhite(self), wrapPurple(driver_key))
-            prOrange(driver_item)
         finish_drivers(self)
 
 
@@ -700,7 +682,7 @@ class xFormObjectInstance(xFormNode):
             'hide_viewport'    : ("Hide in Viewport", False),
             'hide_render'      : ("Hide in Render", False),
         }
-        evaluate_sockets(self, self.bObject, props_sockets)
+        evaluate_sockets(self, {"DEFAULT":self.bObject}, props_sockets)
         self.executed = True
 
     def bFinalize(self, bContext = None):
@@ -827,13 +809,10 @@ class xFormCurvePin(xFormNode):
         c.use_curve_follow = True
         c.name = "Curve Pin"
 
-        props_sockets = self.gen_property_socket_map()
-        constraint_props_sockets = props_sockets.copy()
-        del constraint_props_sockets['name']; del constraint_props_sockets['empty_display_size']
-        del props_sockets['offset_factor']; del props_sockets['forward_axis']
-        del props_sockets['up_axis']
-        evaluate_sockets(self, c, constraint_props_sockets)
-        evaluate_sockets(self, self.bObject, props_sockets)
+        constraint_props_sockets = self.gen_property_socket_map( prop_owner_group='constraints')
+        props_sockets = self.gen_property_socket_map( prop_owner_group='object')
+        b_objects = {"constraints":c, "object":self.bObject}
+        evaluate_sockets(self, b_objects, props_sockets)
         # this isn't usually run on xForm nodes so for now I need to set the
         #   driver's default values manually if I want a matrix now.
         # because the drivers may not have initialized yet.

+ 14 - 11
xForm_socket_templates.py

@@ -9,7 +9,8 @@ xFormArmatureSockets=[
             default_value='Armature', blender_property='name' ),
     RotationOrderTemplate := SockTemplate(
         name="Rotation Order", is_input=True,  bl_idname='RotationOrderSocket',
-            default_value='XYZ', blender_property='rotation_mode' ),
+            default_value='XYZ', blender_property='rotation_mode',
+            property_value_operator = "resolve_rotation_mode_auto", ),
     ObjectMatrixTemplate := SockTemplate(
         name="Matrix", is_input=True,  bl_idname='MatrixSocket',
             blender_property='matrix_world' ),
@@ -37,7 +38,8 @@ xFormGeometryObjectSockets=[
                                        bl_idname='DeformerSocket', ),
     HideTemplate := SockTemplate(name="Hide in Viewport",
         is_input=True, bl_idname='HideSocket', default_value=False,
-        blender_property='hide_viewport' ),
+        blender_property='hide_viewport',
+        property_value_operator='cast_to_bool' ),
     HideRenderTemplate := SockTemplate(name="Hide in Render",
         is_input=True, bl_idname='BooleanSocket', default_value=False,
         blender_property='hide_render' ),
@@ -61,22 +63,23 @@ xFormGeometryObjectInstanceSockets=[
 ]
 
 xFormCurvePinSockets = [
-    replace(NameTemplate, default_value='Curve Pin'),
+    replace(NameTemplate, default_value='Curve Pin',  prop_owner_group='object'),
     ParentCurveTemplate := SockTemplate(
-        name="Parent Curve", is_input=True,  bl_idname='xFormSocket', ),
+        name="Parent Curve", is_input=True,  bl_idname='xFormSocket',),
     SplineIndexTemplate,
     FactorTemplate := SockTemplate(
         name="Curve Pin Factor", is_input=True,  bl_idname='FloatFactorSocket',
-        default_value=0.0, blender_property='offset_factor' ),
+        default_value=0.0, blender_property='offset_factor', prop_owner_group='constraint'),
     ForwardAxisTemplate := SockTemplate(
         name="Forward Axis", is_input=True,  bl_idname='EnumFollowPathForwardAxis',
-        default_value="FORWARD_Y", blender_property='forward_axis' ),
+        default_value="FORWARD_Y", blender_property='forward_axis', prop_owner_group='constraint' ),
     UpAxisTemplate := SockTemplate(
         name="Up Axis", is_input=True,  bl_idname='EnumUpAxis',
-        default_value="UP_Z", blender_property='up_axis' ),
+        default_value="UP_Z", blender_property='up_axis', prop_owner_group='constraint'),
     CurvePinDisplaySize := SockTemplate(
         name="Display Size", is_input=True,  bl_idname='FloatPositiveSocket',
-        default_value=0.05, blender_property='empty_display_size'),
+        default_value=0.05, blender_property='empty_display_size',
+        prop_owner_group='object'),
     CustomPropsTemplate,
     replace(xFormOutTemplate, traverse_target = None)
 ]
@@ -153,10 +156,10 @@ UseTransformAroundCustomShape = SockTemplate(name="Use as Pivot", category='Disp
 from math import pi
 xFormBoneSockets = [
     replace(NameTemplate, default_value='Bone', category='always_show',
-            is_animatable=False,),
-    replace(RotationOrderTemplate, category='always_show',),
+            is_animatable=False, blender_property=None), # because we have to set the name directly at creation
+    replace(RotationOrderTemplate, category='always_show', prop_owner_group = 'pose_bone'),
     BoneMatrixTemplate := replace(ObjectMatrixTemplate, is_animatable=False,
-            blender_property='matrix', category='always_show',),
+             category='always_show', blender_property=None ,), # None because it is set at creation
     replace(RelationshipInTemplate, category='always_show', is_animatable=False,),
     # IK
     IKStretch := SockTemplate(