12 Комити 555c99e212 ... 9fba5be67f

Аутор SHA1 Порука Датум
  Joseph Brandenburg 9fba5be67f Big Fix Cleanup: Mantis Socket Fillers пре 4 недеља
  Joseph Brandenburg 0cebd85655 Array properties can be given single driver now пре 4 недеља
  Joseph Brandenburg b9c844629e fix: bone display update пре 1 месец
  Joseph Brandenburg f9dc2cc911 fix: s.sync error when tree empty пре 1 месец
  Joseph Brandenburg fd30f33afc Fix: armature and fcurve versioning пре 1 месец
  Joseph Brandenburg 6eb28ad9e9 use generated prop_sockets map in Armature Deformer пре 1 месец
  Joseph Brandenburg ccba82dce3 Fix: selection sync for pre-5.0 blender, deselect пре 1 месец
  Joseph Brandenburg be810b0d49 Fix: Bone Collection Versioning пре 1 месец
  Joseph Brandenburg 30dc2159da Fix Armature and FCurve versioning still broken пре 1 месец
  Joseph Brandenburg c9c5bdd677 Fix: bone Collection Update broke old trees пре 1 месец
  Joseph Brandenburg f36f1322f9 Add versioning disable command line switch пре 1 месец
  Joseph Brandenburg 6555bf8414 Fix: Custom Property Versioning Links Broken пре 1 месец

+ 7 - 1
__init__.py

@@ -54,6 +54,9 @@ if environ.get("ENABLEVIS"):
 DOERROR = False
 DOERROR = False
 if environ.get("DOERROR"):
 if environ.get("DOERROR"):
     DOERROR = True
     DOERROR = True
+DOVERSIONING = True
+if environ.get("DOVERSIONING") in ["0", "False", "false", "off"]:
+    DOVERSIONING = False
 
 
 import nodeitems_utils
 import nodeitems_utils
 from nodeitems_utils import NodeCategory, NodeItem
 from nodeitems_utils import NodeCategory, NodeItem
@@ -325,7 +328,10 @@ def version_update_handler(filename):
                 node_tree.is_exporting=False; node_tree.is_executing=False
                 node_tree.is_exporting=False; node_tree.is_executing=False
     for node_tree in bpy.data.node_groups:
     for node_tree in bpy.data.node_groups:
         if node_tree.bl_idname in ["MantisTree", "SchemaTree"]:
         if node_tree.bl_idname in ["MantisTree", "SchemaTree"]:
-            if (node_tree.mantis_version[0] < MANTIS_VERSION_MAJOR) or \
+            if DOVERSIONING == False:
+                version = node_tree.mantis_version
+                print (f"{node_tree.name}:  v{version[0], version[1], version[2]}")
+            elif (node_tree.mantis_version[0] < MANTIS_VERSION_MAJOR) or \
                (node_tree.mantis_version[1] < MANTIS_VERSION_MINOR) or \
                (node_tree.mantis_version[1] < MANTIS_VERSION_MINOR) or \
                (node_tree.mantis_version[2] < MANTIS_VERSION_SUB):
                (node_tree.mantis_version[2] < MANTIS_VERSION_SUB):
                 do_version_update(node_tree)
                 do_version_update(node_tree)

+ 17 - 3
base_definitions.py

@@ -782,16 +782,30 @@ class MantisNode:
         for key, value in additional_parameters.items():
         for key, value in additional_parameters.items():
             self.parameters[key]=value
             self.parameters[key]=value
 
 
-    def gen_property_socket_map(self) -> dict:
+    def gen_property_socket_map(self, prop_owner_group=None) -> dict:
+        from .mantis_dataclasses import MantisSocketFiller
+        from dataclasses import replace
         props_sockets = {}
         props_sockets = {}
         for s_template in self.socket_templates:
         for s_template in self.socket_templates:
+            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:
             if not s_template.blender_property:
                 continue
                 continue
+            filler = MantisSocketFiller(socket_name=s_template.name)
             if isinstance(s_template.blender_property, str):
             if isinstance(s_template.blender_property, str):
-                props_sockets[s_template.blender_property]=(s_template.name, s_template.default_value)
+                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)):
             elif isinstance(s_template.blender_property, (tuple, list)):
                 for index, sub_prop in enumerate(s_template.blender_property):
                 for index, sub_prop in enumerate(s_template.blender_property):
-                    props_sockets[sub_prop]=( (s_template.name, index),s_template.default_value[index] )
+                    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
         return props_sockets
         return props_sockets
 
 
     def set_traverse(self, traversal_pairs = [(str, str)]) -> None:
     def set_traverse(self, traversal_pairs = [(str, str)]) -> None:

+ 15 - 13
deformer_nodes.py

@@ -265,14 +265,7 @@ class DeformerArmature(MantisDeformerNode):
                 raise RuntimeError(f"Modifier was not created in node {self} -- the object is invalid.")
                 raise RuntimeError(f"Modifier was not created in node {self} -- the object is invalid.")
             self.bObject.append(d)
             self.bObject.append(d)
             d.object = self.bGetParentArmature()
             d.object = self.bGetParentArmature()
-            props_sockets = {
-            'vertex_group'               : ("Blend Vertex Group", ""),
-            'invert_vertex_group'        : ("Invert Vertex Group", ""),
-            'use_deform_preserve_volume' : ("Preserve Volume", False),
-            'use_multi_modifier'         : ("Use Multi Modifier", False),
-            'use_bone_envelopes'         : ("Use Envelopes", False),
-            'use_vertex_groups'          : ("Use Vertex Groups", False),
-            }
+            props_sockets = self.gen_property_socket_map()
             evaluate_sockets(self, d, props_sockets)
             evaluate_sockets(self, d, props_sockets)
             #
             #
             if (skin_method := self.evaluate_input("Skinning Method")) == "AUTOMATIC_HEAT":
             if (skin_method := self.evaluate_input("Skinning Method")) == "AUTOMATIC_HEAT":
@@ -568,7 +561,13 @@ class DeformerMorphTargetDeform(MantisDeformerNode):
             sk.slider_min = -10
             sk.slider_min = -10
             sk.slider_max = 10
             sk.slider_max = 10
             keys[mt_name]=sk
             keys[mt_name]=sk
-            props_sockets[mt_name]= ("Value."+str(i).zfill(3), 1.0)
+            from .mantis_dataclasses import MantisSocketFiller
+            socket_value = MantisSocketFiller(
+                property_name=mt_name,
+                socket_name = "Value."+str(i).zfill(3),
+                default_value= 1.0,
+            )
+            props_sockets[mt_name]= socket_value
         for i, t in enumerate(targets):
         for i, t in enumerate(targets):
             mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
             mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
             if sk_ob is None: continue
             if sk_ob is None: continue
@@ -581,9 +580,6 @@ class DeformerMorphTargetDeform(MantisDeformerNode):
         finish_drivers(self)
         finish_drivers(self)
         prWhite(f"Initializing morph target took {time() -start_time} seconds")
         prWhite(f"Initializing morph target took {time() -start_time} seconds")
 
 
-
-        
-
     def gen_shape_key(self, xf, context):
     def gen_shape_key(self, xf, context):
         # TODO: make this a feature of the node definition that appears only when there are no prior deformers - and shows a warning!
         # TODO: make this a feature of the node definition that appears only when there are no prior deformers - and shows a warning!
         # TODO: the below works well, but it is quite slow. It does not seem to have better performence. Its only advantage is export to FBX.
         # TODO: the below works well, but it is quite slow. It does not seem to have better performence. Its only advantage is export to FBX.
@@ -659,7 +655,13 @@ class DeformerMorphTargetDeform(MantisDeformerNode):
             sk.slider_min = -10
             sk.slider_min = -10
             sk.slider_max = 10
             sk.slider_max = 10
             keys[mt_name]=sk
             keys[mt_name]=sk
-            props_sockets[mt_name]= ("Value."+str(i).zfill(3), 1.0)
+            from .mantis_dataclasses import MantisSocketFiller
+            socket_value = MantisSocketFiller(
+                property_name=mt_name,
+                socket_name = "Value."+str(i).zfill(3),
+                default_value= 1.0,
+            )
+            props_sockets[mt_name]= socket_value
         for i, t in enumerate(targets):
         for i, t in enumerate(targets):
             mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
             mt_node = t.links[0].from_node; sk_ob = mt_node.GetxForm().bGetObject()
             if sk_ob is None: continue
             if sk_ob is None: continue

+ 13 - 6
deformer_socket_templates.py

@@ -10,15 +10,22 @@ Target = ST(name="Target", bl_idname='xFormSocket',
 
 
 DeformArmatureSockets = [ 
 DeformArmatureSockets = [ 
     ArmatureObject := replace(Target, name="Armature Object"),
     ArmatureObject := replace(Target, name="Armature Object"),
-    BlendVertexGroup := ST(name="Blend Vertex Group", bl_idname="StringSocket", is_input=True),
-    InvertBlendVertexGroup := ST(name="Invert Vertex Group", bl_idname="BooleanSocket", is_input=True),
+    BlendVertexGroup := ST(name="Blend Vertex Group", bl_idname="StringSocket", is_input=True,
+                           blender_property='vertex_group'),
+    InvertBlendVertexGroup := ST(name="Invert Vertex Group", bl_idname="BooleanSocket", is_input=True,
+                           blender_property='invert_vertex_group'),
     # QuatBlendVertexGroup := ST(name="Preserve Volume Vertex Group", bl_idname="StringSocket", is_input=True)
     # QuatBlendVertexGroup := ST(name="Preserve Volume Vertex Group", bl_idname="StringSocket", is_input=True)
     # ^ TODO figure out the right UX for automatic dual-quat blending
     # ^ TODO figure out the right UX for automatic dual-quat blending
-    PreserveVolume := ST(name="Preserve Volume", bl_idname="BooleanSocket", is_input=True),
+    PreserveVolume := ST(name="Preserve Volume", bl_idname="BooleanSocket", is_input=True,
+                           blender_property='use_deform_preserve_volume'),
     # ^ TODO: make the above controlled by a vertex group instead.
     # ^ TODO: make the above controlled by a vertex group instead.
-    UseMultiModifier := ST(name="Use Multi Modifier", bl_idname="BooleanSocket", is_input=True), # might just set this auto
-    UseEnvelopes := ST(name="Use Envelopes", bl_idname="BooleanSocket", is_input=True, default_value=False),
-    UseVertexGroups := ST(name="Use Vertex Groups", bl_idname="BooleanSocket", is_input=True, default_value=True),
+    # might just set this below to auto (I don't evern understand it)
+    UseMultiModifier := ST(name="Use Multi Modifier", bl_idname="BooleanSocket", is_input=True,
+                           blender_property='use_multi_modifier'),
+    UseEnvelopes := ST(name="Use Envelopes", bl_idname="BooleanSocket", is_input=True, default_value=False,
+                           blender_property='use_bone_envelopes'),
+    UseVertexGroups := ST(name="Use Vertex Groups", bl_idname="BooleanSocket", is_input=True, default_value=True,
+                           blender_property='use_vertex_groups'),
     DeformerInput := ST(name="Deformer", bl_idname="DeformerSocket", is_input=True),
     DeformerInput := ST(name="Deformer", bl_idname="DeformerSocket", is_input=True),
     CopySkinWeightsFrom := ST(name="Copy Skin Weights From", bl_idname="xFormSocket", is_input=True, hide=True),
     CopySkinWeightsFrom := ST(name="Copy Skin Weights From", bl_idname="xFormSocket", is_input=True, hide=True),
     SkinningMethod := ST(name="Skinning Method", bl_idname="EnumSkinning", is_input=True),
     SkinningMethod := ST(name="Skinning Method", bl_idname="EnumSkinning", is_input=True),

+ 24 - 7
link_nodes.py

@@ -65,7 +65,7 @@ class MantisLinkNode(MantisNode):
             return None
             return None
 
 
         else:
         else:
-            return super().evaluate_input(input_name)
+            return super().evaluate_input(input_name, index)
 
 
     def gen_property_socket_map(self) -> dict:
     def gen_property_socket_map(self) -> dict:
         props_sockets = super().gen_property_socket_map()
         props_sockets = super().gen_property_socket_map()
@@ -319,17 +319,21 @@ class LinkTransformation(MantisLinkNode):
                 for axis in ['x', 'y', 'z']:
                 for axis in ['x', 'y', 'z']:
                     stub='from_min_'+axis
                     stub='from_min_'+axis
                     props_sockets[stub+from_suffix]=props_sockets[stub]
                     props_sockets[stub+from_suffix]=props_sockets[stub]
+                    props_sockets[stub+from_suffix].property_name = stub+from_suffix
                     del props_sockets[stub]
                     del props_sockets[stub]
                     stub='from_max_'+axis
                     stub='from_max_'+axis
                     props_sockets[stub+from_suffix]=props_sockets[stub]
                     props_sockets[stub+from_suffix]=props_sockets[stub]
+                    props_sockets[stub+from_suffix].property_name = stub+from_suffix
                     del props_sockets[stub]
                     del props_sockets[stub]
             if to_replace:
             if to_replace:
                 for axis in ['x', 'y', 'z']:
                 for axis in ['x', 'y', 'z']:
                     stub='to_min_'+axis
                     stub='to_min_'+axis
                     props_sockets[stub+to_replace]=props_sockets[stub]
                     props_sockets[stub+to_replace]=props_sockets[stub]
+                    props_sockets[stub+from_suffix].property_name = stub+to_replace
                     del props_sockets[stub]
                     del props_sockets[stub]
                     stub='to_max_'+axis
                     stub='to_max_'+axis
                     props_sockets[stub+to_replace]=props_sockets[stub]
                     props_sockets[stub+to_replace]=props_sockets[stub]
+                    props_sockets[stub+from_suffix].property_name = stub+to_replace
                     del props_sockets[stub]
                     del props_sockets[stub]
             evaluate_sockets(self, c, props_sockets)
             evaluate_sockets(self, c, props_sockets)
         self.executed = True
         self.executed = True
@@ -766,8 +770,11 @@ class LinkDrivenParameter(MantisLinkNode):
                 def_value=[0.0]*length
                 def_value=[0.0]*length
                 self.parameters["Value"] = tuple( 0.0 if i != index else value for i in range(length))
                 self.parameters["Value"] = tuple( 0.0 if i != index else value for i in range(length))
 
 
+            from .mantis_dataclasses import MantisSocketFiller
             props_sockets = {
             props_sockets = {
-                prop: ("Value", def_value)
+                prop: MantisSocketFiller( property_name=prop,
+                                          socket_name="Value",
+                                          default_value=def_value)
             }
             }
             evaluate_sockets(self, ob, props_sockets)
             evaluate_sockets(self, ob, props_sockets)
 
 
@@ -814,6 +821,7 @@ class LinkArmature(MantisLinkNode):
             #       are all in the same armature
             #       are all in the same armature
             # Mantis should intelligently provide a working armature constraint
             # Mantis should intelligently provide a working armature constraint
             # even if the user supplies targets that would not work in vanilla Blender
             # even if the user supplies targets that would not work in vanilla Blender
+            # TODO find out this is reading the arrays in reverse order? is that bad?
             targets_weights = {}
             targets_weights = {}
             for i in range(num_targets):
             for i in range(num_targets):
                 target = c.targets.new()
                 target = c.targets.new()
@@ -822,7 +830,17 @@ class LinkArmature(MantisLinkNode):
                 if not isinstance(weight_value, float):
                 if not isinstance(weight_value, float):
                     weight_value=0
                     weight_value=0
                 targets_weights[i]=weight_value
                 targets_weights[i]=weight_value
-                props_sockets["targets[%d].weight" % i] = ("Weights", i)
+                from .mantis_dataclasses import MantisSocketFiller
+                default_value = 1.0
+                if self.inputs['Weights'].links:
+                    default_value = 1/len(self.inputs['Weights'].links)
+                filler = MantisSocketFiller(
+                    property_name= (prop_name := "targets[%d].weight" % i),
+                    socket_name="Weights",
+                    default_value=default_value,
+                    array_index=i,
+                )
+                props_sockets[prop_name] = filler
             evaluate_sockets(self, c, props_sockets)
             evaluate_sockets(self, c, props_sockets)
             for target, value in targets_weights.items():
             for target, value in targets_weights.items():
                 c.targets[target].weight=value
                 c.targets[target].weight=value
@@ -904,14 +922,13 @@ class LinkShrinkWrap(MantisLinkNode):
                 wrapGreen(" Constraint for bone: ") +
                 wrapGreen(" Constraint for bone: ") +
                 wrapOrange(xf.bGetObject().name))
                 wrapOrange(xf.bGetObject().name))
             c = xf.bGetObject().constraints.new('SHRINKWRAP')
             c = xf.bGetObject().constraints.new('SHRINKWRAP')
+            self.bObject.append(c)
             self.get_target_and_subtarget(c)
             self.get_target_and_subtarget(c)
             if constraint_name := self.evaluate_input("Name"):
             if constraint_name := self.evaluate_input("Name"):
                 c.name = constraint_name
                 c.name = constraint_name
             props_sockets = self.gen_property_socket_map()
             props_sockets = self.gen_property_socket_map()
-            print (props_sockets.keys())
-            self.bObject.append(c)
-            # self.set_custom_space() # this needs to be overridden for me TODO
             evaluate_sockets(self, c, props_sockets)
             evaluate_sockets(self, c, props_sockets)
+            # self.set_custom_space() # this needs to be overridden for me TODO
         self.executed = True
         self.executed = True
 
 
 
 
@@ -931,10 +948,10 @@ class LinkGeometryAttribute(MantisLinkNode):
         for xf in self.GetxForm():
         for xf in self.GetxForm():
             print
             print
             c = xf.bGetObject().constraints.new('GEOMETRY_ATTRIBUTE')
             c = xf.bGetObject().constraints.new('GEOMETRY_ATTRIBUTE')
+            self.bObject.append(c)
             self.get_target_and_subtarget(c)
             self.get_target_and_subtarget(c)
             if constraint_name := self.evaluate_input("Name"):
             if constraint_name := self.evaluate_input("Name"):
                 c.name = constraint_name
                 c.name = constraint_name
             props_sockets = self.gen_property_socket_map()
             props_sockets = self.gen_property_socket_map()
-            self.bObject.append(c)
             evaluate_sockets(self, c, props_sockets)
             evaluate_sockets(self, c, props_sockets)
         self.executed = True
         self.executed = True

+ 21 - 2
mantis_dataclasses.py

@@ -1,5 +1,5 @@
 from dataclasses import dataclass, field
 from dataclasses import dataclass, field
-from typing import List, Any, Tuple
+from typing import List, Any, Tuple, Optional
 
 
 @dataclass
 @dataclass
 class MantisSocketTemplate():
 class MantisSocketTemplate():
@@ -19,6 +19,7 @@ class MantisSocketTemplate():
     #  where multiple blender objects own the properties, like e.g. bone/posebone
     #  where multiple blender objects own the properties, like e.g. bone/posebone
     prop_owner_group : str = field(default="")
     prop_owner_group : str = field(default="")
     # do? add versioning fields for Mantis, Blender version min/max
     # do? add versioning fields for Mantis, Blender version min/max
+    is_animatable    : bool = field(default=True) # controls whether a driver is legal or not
 
 
 @dataclass
 @dataclass
 class xForm_info():
 class xForm_info():
@@ -78,4 +79,22 @@ class MantisBBoneTransform():
     # Custom Handles
     # Custom Handles
     use_bbone_custom_handles: bool = field(default=False)
     use_bbone_custom_handles: bool = field(default=False)
     bbone_custom_handle_start: str = field(default=None)
     bbone_custom_handle_start: str = field(default=None)
-    bbone_custom_handle_end: str = field(default=None)
+    bbone_custom_handle_end: str = field(default=None)
+
+# used to apply values or drivers to blender properties in a generic way
+@dataclass
+class MantisSocketFiller():
+    socket_name: 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)
+
+    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}" 

+ 4 - 3
misc_nodes_ui.py

@@ -534,10 +534,11 @@ class UtilityCustomProperty(Node, MantisMiscNode):
     socket_templates=UtilityCustomPropertySockets
     socket_templates=UtilityCustomPropertySockets
     
     
     def display_update(self, parsed_tree, context):
     def display_update(self, parsed_tree, context):
-        mantis_node = parsed_tree.get(get_signature_from_edited_tree(self, context))
         prop_type = self.inputs['Type'].default_value
         prop_type = self.inputs['Type'].default_value
-        if mantis_node: # this is done here so I don't have to define yet another custom socket.
-            prop_type = mantis_node.evaluate_input("Type")
+        if context: # I need to be able to call this without context in versioing.
+            if mantis_node := parsed_tree.get(get_signature_from_edited_tree(self, context)):
+                # this is done here so I don't have to define yet another custom socket.
+                prop_type = mantis_node.evaluate_input("Type")
         visible_categories = ['general']
         visible_categories = ['general']
         match prop_type:
         match prop_type:
             case 'STRING': visible_categories.append('string')
             case 'STRING': visible_categories.append('string')

+ 66 - 54
timers.py

@@ -62,60 +62,71 @@ for space in spaces:
 #  - easy to fix, will do it when clearing TODO's here.
 #  - easy to fix, will do it when clearing TODO's here.
 
 
 def timer_update_selection_sync():
 def timer_update_selection_sync():
-    if not ENABLED: return None
-    root_tree = None
-    tree_path = None
-    parsed_tree = None
-    spaces = get_visible_space_data()
-    if not spaces:
-        return SELECTION_POLL_RATE
-
-    selection_state_start = cached_selection_state.copy()
-    selection_state = cached_selection_state.copy()
-    current_tree_selected_total = 0
-
-    # we can't get UI context in this function so we have to checc all of them
-    #maybe the tree itself can load the timer when it is opened in the node editor?
-    for space in spaces:
-        # if we are in the node editor, manage node selection
-        if space.type == "NODE_EDITOR":
-            root_tree = space.path[0].node_tree
-            parsed_tree = root_tree.parsed_tree
-            tree_path = [path_item.node_tree for path_item in space.path]
-            if root_tree is None:
-                return SELECTION_POLL_RATE
-            if root_tree.bl_idname != 'MantisTree':
-                return SELECTION_POLL_RATE
-            active_ui_signature = (get_signature_from_space(tree_path[-1].nodes.active, space))
-            for n in tree_path[-1].nodes:
-                if n.bl_idname not in WATCH_FOR_SELECTION: continue
-                selection_state[ui_signature := get_signature_from_space(n, space)] = n.select
-                current_tree_selected_total += int(n.select)
-    
-    if parsed_tree is None:
-        return SELECTION_POLL_RATE
-    cached_selection_state.update(selection_state)
-    # We only fire the update if things have changed.
-    if selection_state != selection_state_start:
+    try:
+        if not ENABLED: return None
+        root_tree = None
+        tree_path = None
+        parsed_tree = None
+        spaces = get_visible_space_data()
+        if not spaces:
+            return SELECTION_POLL_RATE
+
+        selection_state_start = cached_selection_state.copy()
+        selection_state = cached_selection_state.copy()
+        current_tree_selected_total = 0
+
+        # we can't get UI context in this function so we have to checc all of them
+        #maybe the tree itself can load the timer when it is opened in the node editor?
+        for space in spaces:
+            # if we are in the node editor, manage node selection
+            if space.type == "NODE_EDITOR":
+                if not space.path:
+                    return SELECTION_POLL_RATE # empty tree
+                root_tree = space.path[0].node_tree
+                parsed_tree = root_tree.parsed_tree
+                tree_path = [path_item.node_tree for path_item in space.path]
+                if root_tree is None:
+                    return SELECTION_POLL_RATE
+                if root_tree.bl_idname != 'MantisTree':
+                    return SELECTION_POLL_RATE
+                active_ui_signature = (get_signature_from_space(tree_path[-1].nodes.active, space))
+                for n in tree_path[-1].nodes:
+                    if n.bl_idname not in WATCH_FOR_SELECTION: continue
+                    selection_state[ui_signature := get_signature_from_space(n, space)] = n.select
+                    current_tree_selected_total += int(n.select)
         
         
-        from bpy import context
-        view_layer = context.view_layer
-        deselect = not bool(current_tree_selected_total)
-        try:
-            update_scene_selection_from_node_graph_selection_sync(
-                active_ui_signature, view_layer,
-                selection_state, deselect)
-        except Exception as e:
-            prRed (e)
+        if parsed_tree is None:
             return SELECTION_POLL_RATE
             return SELECTION_POLL_RATE
+        cached_selection_state.update(selection_state)
+        # We only fire the update if things have changed.
+        if selection_state != selection_state_start:
+            from bpy import context
+            view_layer = context.view_layer
+            deselect = not bool(current_tree_selected_total)
+            try:
+                update_scene_selection_from_node_graph_selection_sync(
+                    active_ui_signature, view_layer,
+                    selection_state, deselect)
+            except Exception as e:
+                prRed (e)
+                return SELECTION_POLL_RATE
+    except Exception as e:
+        print ("Error in Selection Sync")
+        print (e)
     return SELECTION_POLL_RATE
     return SELECTION_POLL_RATE
 
 
+def set_bone_selection(pose_bone, selection_state):
+    # in some older blender versions, select was a bone property
+    if hasattr(pose_bone, 'select'):
+        pose_bone.select = selection_state
+    elif hasattr(pose_bone.bone, 'select'):
+        pose_bone.bone.select = selection_state
+
 def update_scene_selection_from_node_graph_selection_sync(
 def update_scene_selection_from_node_graph_selection_sync(
         active_ui_signature, view_layer,
         active_ui_signature, view_layer,
         selection_state, deselect ):
         selection_state, deselect ):
-    from .base_definitions import ( selection_sync_node_to_object,
-                                    selection_sync_object_to_node, )
-    prWhite("Updating Scene Selection from Node Graph Selection")
+    from .base_definitions import selection_sync_node_to_object
+    # prWhite("Updating Scene Selection from Node Graph Selection")
     deselect_do_once = []
     deselect_do_once = []
     for ui_signature, selected in selection_state.items():
     for ui_signature, selected in selection_state.items():
         this_one_is_active = False
         this_one_is_active = False
@@ -123,15 +134,16 @@ def update_scene_selection_from_node_graph_selection_sync(
         for object_key in selection_sync_node_to_object.get(ui_signature, []):
         for object_key in selection_sync_node_to_object.get(ui_signature, []):
             id_data = bpy.data.objects.get(object_key[0])
             id_data = bpy.data.objects.get(object_key[0])
             if object_key[1]: # we have a sub_value, it's a bone
             if object_key[1]: # we have a sub_value, it's a bone
-                if deselect and object_key[0] not in deselect_do_once:
-                    for pb in id_data.pose.bones:
-                        pb.select = False
-                    deselect_do_once.append(object_key[0])
+                if deselect:
+                    if object_key[0] not in deselect_do_once:
+                        for pb in id_data.pose.bones:
+                            set_bone_selection(pb, False)
+                        deselect_do_once.append(object_key[0])
                 else:
                 else:
-                    ob = id_data.pose.bones[object_key[1]]
-                    ob.select = selected
+                    pb = id_data.pose.bones[object_key[1]]
+                    set_bone_selection(pb, selected)
                     if this_one_is_active: # set the active bone
                     if this_one_is_active: # set the active bone
-                        id_data.data.bones.active = ob.bone
+                        id_data.data.bones.active = pb.bone
                     # now do its parent object...
                     # now do its parent object...
                     if selected:
                     if selected:
                         id_data.select_set(True, view_layer=view_layer)
                         id_data.select_set(True, view_layer=view_layer)

+ 91 - 86
utilities/drivers.py

@@ -52,102 +52,107 @@ def CreateDrivers(drivers):
     def brackets(s):
     def brackets(s):
         return "[\""+s+"\"]"
         return "[\""+s+"\"]"
     from bpy.types import Object, Key
     from bpy.types import Object, Key
+    fcurves = []
     for driver in drivers:
     for driver in drivers:
         if (isinstance(driver["owner"], Object)):
         if (isinstance(driver["owner"], Object)):
             ob = driver["owner"]
             ob = driver["owner"]
         else: # Pose Bone:
         else: # Pose Bone:
             ob = driver["owner"].id_data
             ob = driver["owner"].id_data
         if isinstance(driver["owner"], Key):
         if isinstance(driver["owner"], Key):
-            fc = ob.driver_add(driver["prop"])
+            fcurves = ob.driver_add(driver["prop"])
         else:
         else:
-            fc = ob.driver_add(driver["owner"].path_from_id(driver["prop"]), driver["ind"])
-        drv = fc.driver
-        try: # annoyingly, this initializes with a modifier
-            fc.modifiers.remove(fc.modifiers[0])
-        except IndexError: #haven't seen this happen, but should handle
-            pass # perhaps this has been fixed for 3.0?
-        if version >= (5,0,0): # and now it initializes with keys. p
-            fc.keyframe_points.clear()
-        drv.type = driver["type"]
-        if (expr := driver.get("expression")) and isinstance(expr, str):
-            drv.expression = expr
-
-        fc.extrapolation = "CONSTANT"
-        if (extrapolation_mode := driver.get("extrapolation")) in ("CONSTANT", "LINEAR"):
-            fc.extrapolation = extrapolation_mode
-        else:
-            prRed(f"Extrapolation Mode in driver has incorrect data: {extrapolation_mode}")
+            fcurves = ob.driver_add(driver["owner"].path_from_id(driver["prop"]), 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
+        for fc in fcurves:
+            drv = fc.driver
+            try: # annoyingly, this initializes with a modifier
+                fc.modifiers.remove(fc.modifiers[0])
+            except IndexError: #haven't seen this happen, but should handle
+                pass # perhaps this has been fixed for 3.0?
+            if version >= (5,0,0): # and now it initializes with keys. p
+                fc.keyframe_points.clear()
+            drv.type = driver["type"]
+            if (expr := driver.get("expression")) and isinstance(expr, str):
+                drv.expression = expr
+
+            fc.extrapolation = "CONSTANT"
+            if (extrapolation_mode := driver.get("extrapolation")) in ("CONSTANT", "LINEAR"):
+                fc.extrapolation = extrapolation_mode
+            else:
+                prRed(f"Extrapolation Mode in driver has incorrect data: {extrapolation_mode}")
 
 
-        # logic for handling type can go here
+            # logic for handling type can go here
 
 
-        # start by clearing
-        while (len(drv.variables) > 0):
-            v = drv.variables[0]
-            dVar = drv.variables.remove(v)
+            # start by clearing
+            while (len(drv.variables) > 0):
+                v = drv.variables[0]
+                dVar = drv.variables.remove(v)
 
 
-        for v in driver["vars"]:
-            pose_bone = False
-            bone = ''; target2bone = ''
-            vob, target2ob = None, None
-            if (isinstance(v["owner"], Object)):
-                vob = v["owner"]
-            else:
-                pose_bone = True
-                vob = v["owner"].id_data
-                bone = v["owner"].name
-            #
-
-            if "xForm 2" in v.keys() and v["xForm 2"]:
-                if (isinstance(v["xForm 2"], Object)):
-                    target2ob = v["xForm 2"]
+            for v in driver["vars"]:
+                pose_bone = False
+                bone = ''; target2bone = ''
+                vob, target2ob = None, None
+                if (isinstance(v["owner"], Object)):
+                    vob = v["owner"]
                 else:
                 else:
-                    target2ob = v["xForm 2"].id_data
-                    target2bone = v["xForm 2"].name
-
-            dVar = drv.variables.new()
-
-
-            dVar.name = v["name"]
-            dVar.type = v["type"]
-            #for now, assume this is always true:
-            #dVar.targets[0].id_type = "OBJECT"
-            #it's possible to use other datablocks, but this is not commonly done
-            #actually, it looks like Blender figures this out for me.
-
-            dVar.targets[0].id = vob
-            dVar.targets[0].bone_target = bone
-            if len(dVar.targets) > 1:
-                dVar.targets[1].id = target2ob
-                dVar.targets[1].bone_target = target2bone
-
-            if (dVar.type == "TRANSFORMS"):
-                dVar.targets[0].transform_space = v["space"]
-                dVar.targets[0].transform_type = v["channel"]
-            if (dVar.type == 'SINGLE_PROP'):
-                if pose_bone:
-                    stub = "pose.bones[\""+v["owner"].name+"\"]"
-                    dVar.targets[0].data_path = stub + brackets(v["prop"])
-                    if (hasattr( v["owner"], v["prop"] )):
-                        dVar.targets[0].data_path = stub + "."+ (v["prop"])
-                    # else: # the property may be added later.
-                    # TODO BUG I want a guarantee that this property is already there.
+                    pose_bone = True
+                    vob = v["owner"].id_data
+                    bone = v["owner"].name
+                #
+
+                if "xForm 2" in v.keys() and v["xForm 2"]:
+                    if (isinstance(v["xForm 2"], Object)):
+                        target2ob = v["xForm 2"]
+                    else:
+                        target2ob = v["xForm 2"].id_data
+                        target2bone = v["xForm 2"].name
+
+                dVar = drv.variables.new()
+
+
+                dVar.name = v["name"]
+                dVar.type = v["type"]
+                #for now, assume this is always true:
+                #dVar.targets[0].id_type = "OBJECT"
+                #it's possible to use other datablocks, but this is not commonly done
+                #actually, it looks like Blender figures this out for me.
+
+                dVar.targets[0].id = vob
+                dVar.targets[0].bone_target = bone
+                if len(dVar.targets) > 1:
+                    dVar.targets[1].id = target2ob
+                    dVar.targets[1].bone_target = target2bone
+
+                if (dVar.type == "TRANSFORMS"):
+                    dVar.targets[0].transform_space = v["space"]
+                    dVar.targets[0].transform_type = v["channel"]
+                if (dVar.type == 'SINGLE_PROP'):
+                    if pose_bone:
+                        stub = "pose.bones[\""+v["owner"].name+"\"]"
+                        dVar.targets[0].data_path = stub + brackets(v["prop"])
+                        if (hasattr( v["owner"], v["prop"] )):
+                            dVar.targets[0].data_path = stub + "."+ (v["prop"])
+                        # else: # the property may be added later.
+                        # TODO BUG I want a guarantee that this property is already there.
 
 
-                else:
-                    if (hasattr( v["owner"], v["prop"] )):
-                        dVar.targets[0].data_path = (v["prop"])
                     else:
                     else:
-                        dVar.targets[0].data_path = brackets(v["prop"])
-        # setup keyframe points
-        kp = fc.keyframe_points
-        for key in driver["keys"]:
-            k = kp.insert(frame=key["co"][0], value = key["co"][1],)
-
-            k.interpolation     = key["interpolation"]
-            if (key["interpolation"] == 'BEZIER'):
-                k.handle_left_type  = key["handle_left_type" ]
-                k.handle_right_type = key["handle_right_type"]
-                if (k.handle_left_type in ("ALIGNED", "VECTOR", "FREE")):
-                    k.handle_left       = (k.co[0] + key["handle_left"][0], k.co[1] + key["handle_left"][1])
-                if (k.handle_right_type in ("ALIGNED", "VECTOR", "FREE")):
-                    k.handle_right      = (k.co[0] + key["handle_right"][0], k.co[1] + key["handle_right"][1])
-            k.type = key["type"]
+                        if (hasattr( v["owner"], v["prop"] )):
+                            dVar.targets[0].data_path = (v["prop"])
+                        else:
+                            dVar.targets[0].data_path = brackets(v["prop"])
+            # setup keyframe points
+            kp = fc.keyframe_points
+            for key in driver["keys"]:
+                k = kp.insert(frame=key["co"][0], value = key["co"][1],)
+
+                k.interpolation     = key["interpolation"]
+                if (key["interpolation"] == 'BEZIER'):
+                    k.handle_left_type  = key["handle_left_type" ]
+                    k.handle_right_type = key["handle_right_type"]
+                    if (k.handle_left_type in ("ALIGNED", "VECTOR", "FREE")):
+                        k.handle_left       = (k.co[0] + key["handle_left"][0], k.co[1] + key["handle_left"][1])
+                    if (k.handle_right_type in ("ALIGNED", "VECTOR", "FREE")):
+                        k.handle_right      = (k.co[0] + key["handle_right"][0], k.co[1] + key["handle_right"][1])
+                k.type = key["type"]

+ 7 - 1
utilities/geometry_node_graphgen.py

@@ -79,7 +79,13 @@ def gen_morph_target_nodes(mod_name, mod_ob, targets, context, use_offset=True):
             ng.links.new(input=inp.outputs[mt_name+" Value"], output=scale1.inputs["Scale"])
             ng.links.new(input=inp.outputs[mt_name+" Value"], output=scale1.inputs["Scale"])
         add_these.append(scale1)
         add_these.append(scale1)
         object_map["Socket_"+str((i+1)*2)]=mt_node.GetxForm().bGetObject()
         object_map["Socket_"+str((i+1)*2)]=mt_node.GetxForm().bGetObject()
-        props_sockets["Socket_"+str((i+1)*2+1)]= ("Value."+str(i).zfill(3), 1.0)
+        from ..mantis_dataclasses import MantisSocketFiller
+        socket_value = MantisSocketFiller(
+            property_name = (prop := "Socket_"+str((i+1)*2+1)),
+            socket_name = (socket_name := "Value."+str(i).zfill(3)),
+            default_value = 1.0
+        )
+        props_sockets[prop] = socket_value
     
     
     set_position = ng.nodes.new("GeometryNodeSetPosition")
     set_position = ng.nodes.new("GeometryNodeSetPosition")
     bake = ng.nodes.new("GeometryNodeBake")
     bake = ng.nodes.new("GeometryNodeBake")

+ 89 - 49
utilities/node_common.py

@@ -124,15 +124,14 @@ def setup_custom_property_inputs_outputs(mantis_node):
     if mantis_node.signature[0] == 'SCHEMA_AUTOGENERATED':
     if mantis_node.signature[0] == 'SCHEMA_AUTOGENERATED':
         from ..base_definitions import custom_props_types
         from ..base_definitions import custom_props_types
         if mantis_node.__class__.__name__ not in custom_props_types:
         if mantis_node.__class__.__name__ not in custom_props_types:
-            # prRed(f"Reminder: figure out how to deal with custom property setting for Schema Node {mantis_node}")
             raise RuntimeError(wrapRed(f"Custom Properties not allowed for class {mantis_node.__class__.__name__}"))
             raise RuntimeError(wrapRed(f"Custom Properties not allowed for class {mantis_node.__class__.__name__}"))
         return
         return
     else:
     else:
-        ui_node = get_ui_node(mantis_node.signature, mantis_node.base_tree)
+        ui_node = get_ui_node(mantis_node.ui_signature, mantis_node.base_tree)
     if ui_node:
     if ui_node:
         setup_custom_props_from_np(mantis_node, ui_node)
         setup_custom_props_from_np(mantis_node, ui_node)
     else:
     else:
-        prRed("Failed to setup custom properties for: mantis_node")
+        prRed(f"Failed to setup custom properties for: {mantis_node.ui_signature}")
 
 
 def setup_custom_props_from_np(mantis_node, ui_node):
 def setup_custom_props_from_np(mantis_node, ui_node):
     for inp in ui_node.inputs:
     for inp in ui_node.inputs:
@@ -162,13 +161,27 @@ def prepare_parameters(mantis_node):
             mantis_node.parameters[s_name] = sock.links[0].from_node.parameters[sock.name]
             mantis_node.parameters[s_name] = sock.links[0].from_node.parameters[sock.name]
     # should work, this is ugly.
     # should work, this is ugly.
 
 
-def check_for_driver(mantis_node, input_name, index = None):
-    prop = mantis_node.evaluate_input(input_name)
-    if (index is not None):
-        prop = prop[index]
+def check_for_driver(mantis_node, input_name, value_index = None, array_index = 0, combo_property=False):
+    prop = mantis_node.evaluate_input(input_name, array_index)
+    if (value_index is not None):
+        try:
+            prop = prop[value_index]
+        except KeyError: # We'll try to use the driver for all three channels.
+            pass
+        # TODO an Index or Key error here should have a reasonable error message
     return (prop.__class__.__name__ == 'MantisDriver')
     return (prop.__class__.__name__ == 'MantisDriver')
 
 
-# TODO: this should handle sub-properties better
+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,):
 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
     # this is neccesary because some things use dict properties for dynamic properties and setattr doesn't work
     def safe_setattr(ob, att_name, val):
     def safe_setattr(ob, att_name, val):
@@ -181,20 +194,25 @@ def evaluate_sockets(mantis_node, b_object, props_sockets,):
             # it is of the form prop[int].prop2
             # it is of the form prop[int].prop2
             prop=att_name.split('[')[0]
             prop=att_name.split('[')[0]
             prop1=att_name.split('.')[1]
             prop1=att_name.split('.')[1]
-            index = int(att_name.split('[')[1][0])
-            setattr(getattr(b_object, prop)[index], prop1, val)
+            get_index = int(att_name.split('[')[1][0])
+            setattr(getattr(b_object, prop)[get_index], prop1, val)
         else:
         else:
             try:
             try:
                 setattr(ob, att_name, val)
                 setattr(ob, att_name, val)
             except Exception as e:
             except Exception as e:
-                prRed(ob, att_name, val); raise e
-    for prop, (sock, default) in props_sockets.items():
-        # annoyingly, sometimes the socket is an array
-        index = None
-        if isinstance(sock, tuple):
-            index = sock[1]; sock = sock[0]
-        if (check_for_driver(mantis_node, sock, index)):
-            sock = (sock, index)
+                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
             original_prop = prop
             # TODO: deduplicate this terrible hack
             # TODO: deduplicate this terrible hack
             if ("." in prop) and not b_object.__class__.__name__ in ["Key"]: # this is a property of a property...
             if ("." in prop) and not b_object.__class__.__name__ in ["Key"]: # this is a property of a property...
@@ -204,31 +222,25 @@ def evaluate_sockets(mantis_node, b_object, props_sockets,):
                     prop = split_prop[1]
                     prop = split_prop[1]
                     sub_prop = (split_prop[0])
                     sub_prop = (split_prop[0])
                     if ("[" in sub_prop):
                     if ("[" in sub_prop):
-                        sub_prop, index = sub_prop.split("[")
-                        index = int(index[0])
-                        sub_props.append(getattr(sub_props[-1], sub_prop)[index])
+                        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:
                     else:
                         sub_props.append(getattr(sub_props[-1], sub_prop))
                         sub_props.append(getattr(sub_props[-1], sub_prop))
                 safe_setattr(sub_props[-1], prop, default)
                 safe_setattr(sub_props[-1], prop, default)
             # this is really stupid
             # this is really stupid
             else:
             else:
                 safe_setattr(b_object, prop, default)
                 safe_setattr(b_object, prop, default)
-            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))
+            print_driver_item( mantis_node, b_object, original_prop)
             if b_object.__class__.__name__ in ["NodesModifier"]:
             if b_object.__class__.__name__ in ["NodesModifier"]:
-                mantis_node.drivers[sock] = "[\""+original_prop+"\"]" # lol. It is a dict element not a "true" property
+                mantis_node.drivers[driver_key] = "[\""+original_prop+"\"]" # lol. It is a dict element not a "true" property
             elif b_object.__class__.__name__ in ["Key"]:
             elif b_object.__class__.__name__ in ["Key"]:
-                mantis_node.drivers[sock] = "key_blocks[\""+original_prop+"\"].value"
+                mantis_node.drivers[driver_key] = "key_blocks[\""+original_prop+"\"].value"
             else:
             else:
-                mantis_node.drivers[sock] = original_prop
+                mantis_node.drivers[driver_key] = original_prop
         else: # here we can do error checking for the socket if needed
         else: # here we can do error checking for the socket if needed
-            if (index is not None):
-                safe_setattr(b_object, prop, mantis_node.evaluate_input(sock)[index])
+            if (value_index is not None):
+                safe_setattr(b_object, prop, mantis_node.evaluate_input(sock)[value_index])
             else:                    # 'mute' is better than 'enabled'
             else:                    # 'mute' is better than 'enabled'
                 # UGLY HACK          # because it is available in older
                 # UGLY HACK          # because it is available in older
                 if (prop == 'mute'): # Blenders.
                 if (prop == 'mute'): # Blenders.
@@ -237,9 +249,6 @@ def evaluate_sockets(mantis_node, b_object, props_sockets,):
                     safe_setattr(b_object, prop, bool(mantis_node.evaluate_input(sock)))
                     safe_setattr(b_object, prop, bool(mantis_node.evaluate_input(sock)))
                 else:
                 else:
                     try:
                     try:
-                        # prRed(b_object.name, mantis_node, prop, mantis_node.evaluate_input(sock) )
-                        # print( mantis_node.evaluate_input(sock))
-                    # value_eval = mantis_node.evaluate_input(sock)
                     # just wanna see if we are dealing with some collection
                     # 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
                     # 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__"):
                         if hasattr(b_object, prop) and (not isinstance(getattr(b_object, prop), str)) and hasattr(getattr(b_object, prop), "__getitem__"):
@@ -248,7 +257,7 @@ def evaluate_sockets(mantis_node, b_object, props_sockets,):
                                 # assume this will work, both because val should have the right number of elements, and because this should be the right data type.
                                 # 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
                                 from .drivers import MantisDriver
                                 if isinstance(value, MantisDriver):
                                 if isinstance(value, MantisDriver):
-                                    getattr(b_object,prop)[val_index] =  default[val_index]
+                                    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))
                                     print("Adding driver %s to %s in %s" % (wrapPurple(prop), wrapWhite(mantis_node.signature[-1]), mantis_node))
                                     try:
                                     try:
                                         mantis_node.drivers[sock].append((prop, val_index))
                                         mantis_node.drivers[sock].append((prop, val_index))
@@ -257,24 +266,50 @@ def evaluate_sockets(mantis_node, b_object, props_sockets,):
                                 else:
                                 else:
                                     getattr(b_object,prop)[val_index] =  value
                                     getattr(b_object,prop)[val_index] =  value
                         else:
                         else:
-                            # prOrange("Skipping the Thing", getattr(b_object, prop))
                             safe_setattr(b_object, prop, mantis_node.evaluate_input(sock))
                             safe_setattr(b_object, prop, mantis_node.evaluate_input(sock))
                     except Exception as e:
                     except Exception as e:
                         prRed(b_object, mantis_node, prop, sock, mantis_node.evaluate_input(sock))
                         prRed(b_object, mantis_node, prop, sock, mantis_node.evaluate_input(sock))
                         raise e
                         raise e
 
 
 def finish_driver(mantis_node, b_object, driver_item, prop):
 def finish_driver(mantis_node, b_object, driver_item, prop):
-    # prWhite(mantis_node, prop)
-    index = driver_item[1]; driver_sock = driver_item[0]
-    driver_trace = trace_single_line(mantis_node, driver_sock)
+    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]
     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
+
     if index is not None:
     if index is not None:
-        driver = driver_provider.parameters[driver_socket.name][index].copy()
+        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
         # this is harmless and necessary for the weird ones where the property is a vector too
-        driver["ind"] = index
+        if not combo_property:
+            driver["ind"] = index
     else:
     else:
         driver = driver_provider.parameters[driver_socket.name].copy()
         driver = driver_provider.parameters[driver_socket.name].copy()
     if driver:
     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...
         if ("." in prop) and mantis_node.__class__.__name__ != "DeformerMorphTargetDeform": # this is a property of a property...
             sub_props = [b_object]
             sub_props = [b_object]
             while ("." in prop):
             while ("." in prop):
@@ -291,11 +326,14 @@ def finish_driver(mantis_node, b_object, driver_item, prop):
         elif mantis_node.node_type in ['XFORM',] and mantis_node.__class__.__name__ in ['xFormBone']:
         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.
             # 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.
             # NOTE: VERSIONING in Blender 5.0 we have to use pose bone for this, too.
-            if bpy_version <= (5,0,0) and prop  == 'hide': # older blendders, we need the bone
+            # 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
                 bone_col = mantis_node.bGetParentArmature().data.bones
-            elif prop  == 'show_wire': # TODO check if the bug that forced the above affects this
-                bone_col = mantis_node.bGetParentArmature().data.bones
-            else:
+            # 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
                 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
             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
         # HACK having special cases here is indicitave of a deeper problem that should be refactored
@@ -323,11 +361,13 @@ def finish_drivers(mantis_node):
         if mantis_node.node_type == 'LINK':
         if mantis_node.node_type == 'LINK':
             b_objects = mantis_node.bObject # this is already a list
             b_objects = mantis_node.bObject # this is already a list
         for b_object in b_objects:
         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):
             if isinstance(prop, list):
                 for sub_item in prop:
                 for sub_item in prop:
-                        drivers.append(finish_driver(mantis_node, b_object, (driver_item, sub_item[1]), sub_item[0]))
+                        drivers.append(finish_driver(mantis_node, b_object, (driver_item, sub_item[1], 0), sub_item[0]))
                 else:
                 else:
-                    drivers.append(finish_driver(mantis_node, b_object, (driver_item, sub_item[1]), sub_item[0]))
+                    drivers.append(finish_driver(mantis_node, b_object, (driver_item, sub_item[1], 0), sub_item[0]))
             else:
             else:
                 drivers.append(finish_driver(mantis_node, b_object, driver_item, prop))
                 drivers.append(finish_driver(mantis_node, b_object, driver_item, prop))
     from .drivers import CreateDrivers
     from .drivers import CreateDrivers

+ 149 - 68
versioning.py

@@ -7,7 +7,20 @@ from bpy.app import version as bpy_version
 from .utilities.utilities import (prRed, prGreen, prPurple, prWhite,
 from .utilities.utilities import (prRed, prGreen, prPurple, prWhite,
                               prOrange,
                               prOrange,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
-                              wrapOrange,)
+                              wrapOrange,
+                              get_socket_maps, do_relink, )
+
+
+# CHECKLIST FOR DUMMIES:
+# Did you make sure to turn off and re-enable tree updates?
+# Did you restore connections to the node socket after changing things?
+# Did you test FUNCTIONALITY in your test rigs instead of just checking if the tree
+#    would generate? CHANGED FUNCTIONALITY IS STILL A BUG!!
+# Did you make sure to toggle the is_updating flag for ANY node that is touched by
+#    the edits you are doing to the current node (example: removing a socket causes
+#    linked group and schema nodes to update)
+# (add more whenever a weird or difficult to debug problem occurs)
+# Did you check the update script on a tree that doesn't need it?
 
 
 def version_upgrade_very_old(*args, **kwargs):
 def version_upgrade_very_old(*args, **kwargs):
     node = kwargs['node']
     node = kwargs['node']
@@ -78,12 +91,10 @@ def version_upgrade_very_old(*args, **kwargs):
                 node.inputs.move(len(node.inputs)-1, index)
                 node.inputs.move(len(node.inputs)-1, index)
         socket_map = None
         socket_map = None
         if rename_jobs:
         if rename_jobs:
-            from .utilities.utilities import get_socket_maps
             socket_maps = get_socket_maps(node)
             socket_maps = get_socket_maps(node)
         for socket, socket_index, new_bl_idname, new_name, multi in rename_jobs:
         for socket, socket_index, new_bl_idname, new_name, multi in rename_jobs:
             old_id = socket.identifier
             old_id = socket.identifier
             print (f"Renaming socket {socket.identifier} to {new_name} in node {node.name}")
             print (f"Renaming socket {socket.identifier} to {new_name} in node {node.name}")
-            from .utilities.utilities import do_relink
             if socket.is_output:
             if socket.is_output:
                 index = 1
                 index = 1
                 in_out = "OUTPUT"
                 in_out = "OUTPUT"
@@ -105,11 +116,13 @@ def version_upgrade_very_old(*args, **kwargs):
         prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
         prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
         print(e)
         print(e)
 
 
-
 def version_upgrade_bone_0_12_0_from_older(*args, **kwargs):
 def version_upgrade_bone_0_12_0_from_older(*args, **kwargs):
     # we need to check if it has an array collection input and a color input
     # we need to check if it has an array collection input and a color input
     # then we need to solve each task
     # then we need to solve each task
     node = kwargs['node']
     node = kwargs['node']
+    live_updates = node.id_data.do_live_update
+    node.id_data.do_live_update = False
+
     current_major_version = node.id_data.mantis_version[0]
     current_major_version = node.id_data.mantis_version[0]
     current_minor_version = node.id_data.mantis_version[1]
     current_minor_version = node.id_data.mantis_version[1]
     if  current_major_version > 0: # major version must be 0
     if  current_major_version > 0: # major version must be 0
@@ -117,8 +130,14 @@ def version_upgrade_bone_0_12_0_from_older(*args, **kwargs):
     if current_minor_version >= 12: # minor version must be 11 or less
     if current_minor_version >= 12: # minor version must be 11 or less
         return
         return
     # sub version doesn't matter since any subversion of 11 should trigger this task
     # sub version doesn't matter since any subversion of 11 should trigger this task
+    collection_input_is_array = False
     try:
     try:
         collection_input_is_array = node.inputs['Bone Collection'].is_multi_input
         collection_input_is_array = node.inputs['Bone Collection'].is_multi_input
+    except KeyError: # I suppose this happens in very, very old trees
+        pass         # but it is extremely unlikely
+    toggle_updating = []
+    was_updating = {}
+    try:
         if not collection_input_is_array: # it must be made into an array!
         if not collection_input_is_array: # it must be made into an array!
             prPurple(f"Updating \"Bone Collection\" Socket in {node.name}")
             prPurple(f"Updating \"Bone Collection\" Socket in {node.name}")
             from .utilities.utilities import get_socket_maps
             from .utilities.utilities import get_socket_maps
@@ -126,23 +145,19 @@ def version_upgrade_bone_0_12_0_from_older(*args, **kwargs):
             socket_map = socket_maps[0]
             socket_map = socket_maps[0]
             for i, socket in enumerate(node.inputs):
             for i, socket in enumerate(node.inputs):
                 if socket.name == 'Bone Collection': break
                 if socket.name == 'Bone Collection': break
-            old_id = socket.identifier
-            # it is an input
+            # Always guard socket removal in case of connection to e.g. schema
+            for socket_name, links in socket_map.items():
+                if isinstance(links, list):
+                    for link in links:
+                        if hasattr(link, 'node') and hasattr(link.node, 'is_updating'):
+                            toggle_updating.append(link.node)
+                            was_updating[link.node.name] = link.node.is_updating
+                            link.node.is_updating = True
             node.inputs.remove(socket)
             node.inputs.remove(socket)
             s = node.inputs.new('BoneCollectionSocket', 'Bone Collection',
             s = node.inputs.new('BoneCollectionSocket', 'Bone Collection',
                                 identifier='Bone Collection', use_multi_input=True)
                                 identifier='Bone Collection', use_multi_input=True)
             node.inputs.move(len(node.inputs)-1, i)
             node.inputs.move(len(node.inputs)-1, i)
-            socket_map_from_old_socket = socket_map[old_id]
-            # there seems to be an error in do_relink
-            # gonna do it directly instead
-            if isinstance(socket_map_from_old_socket, list):
-                for map_info in socket_map_from_old_socket:
-                    if isinstance(map_info, Node ):
-                        l = node.id_data.links.new(input=map_info.outputs[0], output=s)
-                    elif isinstance(map_info, NodeSocket):
-                        l = node.id_data.links.new(input=map_info, output=s)
-            else:
-                s.default_value = socket_map_from_old_socket
+            do_relink(node, s, socket_map, in_out='INPUT')
         if node.inputs.get('Color') is None:
         if node.inputs.get('Color') is None:
             prPurple(f"Adding \"Color\" Socket to {node.name}")
             prPurple(f"Adding \"Color\" Socket to {node.name}")
             s = node.inputs.new('ColorSetSocket', 'Color',)
             s = node.inputs.new('ColorSetSocket', 'Color',)
@@ -150,7 +165,11 @@ def version_upgrade_bone_0_12_0_from_older(*args, **kwargs):
     except Exception as e:
     except Exception as e:
         prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
         prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
         print(e)
         print(e)
-
+    finally:
+        # Now we have to clean up by removing the unneeded sockets and repositioning.
+        for other_node in toggle_updating:
+            other_node.is_updating = was_updating[other_node.name]
+        node.id_data.do_live_update = live_updates
 
 
 def up_0_12_1_add_inherit_color(*args, **kwargs):
 def up_0_12_1_add_inherit_color(*args, **kwargs):
     # add an inherit color input.
     # add an inherit color input.
@@ -178,7 +197,6 @@ def up_0_12_1_add_inherit_color(*args, **kwargs):
         print(e)
         print(e)
 
 
 def up_0_12_13_add_widget_scale(*args, **kwargs):
 def up_0_12_13_add_widget_scale(*args, **kwargs):
-    # add an inherit color input.
     node = kwargs['node']
     node = kwargs['node']
     current_major_version = node.id_data.mantis_version[0]
     current_major_version = node.id_data.mantis_version[0]
     current_minor_version = node.id_data.mantis_version[1]
     current_minor_version = node.id_data.mantis_version[1]
@@ -198,34 +216,7 @@ def up_0_12_13_add_widget_scale(*args, **kwargs):
         prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
         prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
         print(e)
         print(e)
 
 
-# versioning tasks that involve Blender versions rather than Mantis versions:
-error_description_4_5_0_LTS = 'There is a bug in Blender 4.5.0 LTS, that is why these sockets are blue.'\
-                             ' This will be fixed in future Blender versions.'
-def cleanup_4_5_0_LTS_interface_workaround(*args, **kwargs):
-    # this is a function for cleaning up the workaround up above
-    tree = kwargs['tree']
-    if bpy_version == (4,5,0): return
-    if not hasattr(tree, "interface_helper"):
-        return
-    import json
-    try:
-        interface_helper = json.loads(tree.interface_helper)
-    except:
-        return # this should mean it was never a problem to begin with.
-    prPurple(f"Restoring Tree Interface for {tree.name}.")
-    for interface_item in tree.interface.items_tree:
-        if interface_item.item_type == 'PANEL': continue
-        # now we need to restore the socket types, which are in the interface helper
-        # keep the interface stuff around, I'll use it for marking arrays in the future
-        int_info = interface_helper[interface_item.identifier]
-        socket_type = int_info['bl_socket_idname']
-        interface_item.socket_type = socket_type
-        if interface_item.description == error_description_4_5_0_LTS:
-            interface_item.description = ''
-    # that should be enough!
-
 def up_0_12_25_replace_floor_offset_type(*args, **kwargs):
 def up_0_12_25_replace_floor_offset_type(*args, **kwargs):
-    # add an inherit color input.
     node = kwargs['node']
     node = kwargs['node']
     current_major_version = node.id_data.mantis_version[0]
     current_major_version = node.id_data.mantis_version[0]
     current_minor_version = node.id_data.mantis_version[1]
     current_minor_version = node.id_data.mantis_version[1]
@@ -233,7 +224,6 @@ def up_0_12_25_replace_floor_offset_type(*args, **kwargs):
     if  current_major_version > 0: return# major version must be 0
     if  current_major_version > 0: return# major version must be 0
     if current_minor_version > 12: return# minor version must be 12 or less
     if current_minor_version > 12: return# minor version must be 12 or less
     if current_minor_version == 12 and current_sub_version > 24: return 
     if current_minor_version == 12 and current_sub_version > 24: return 
-    from .utilities.utilities import get_socket_maps, do_relink
     socket_maps = get_socket_maps(node)
     socket_maps = get_socket_maps(node)
     prPurple(f"Fixing \"Offset\" socket in {node.name}")
     prPurple(f"Fixing \"Offset\" socket in {node.name}")
     try:
     try:
@@ -246,13 +236,12 @@ def up_0_12_25_replace_floor_offset_type(*args, **kwargs):
         prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
         prRed(f"Error updating version in node: {node.id_data.name}::{node.name}; see error:")
         print(e)
         print(e)
 
 
-def get_absolute_location(node): # us9ng this for version compatibility
+def get_absolute_location(node): # using this for version compatibility
     location = node.location.copy() # FUTURE: deprecate old blenders and use absolute_location
     location = node.location.copy() # FUTURE: deprecate old blenders and use absolute_location
     while(node.parent):
     while(node.parent):
         node = node.parent
         node = node.parent
         location+= node.location
         location+= node.location
     return location
     return location
-    
 
 
 def up_0_13_0_new_custom_prop_node(*args, **kwargs):
 def up_0_13_0_new_custom_prop_node(*args, **kwargs):
     tree, node = kwargs['node_tree'], kwargs['node']
     tree, node = kwargs['node_tree'], kwargs['node']
@@ -262,8 +251,9 @@ def up_0_13_0_new_custom_prop_node(*args, **kwargs):
     if current_minor_version >= 13: return# minor version must be 12 or less
     if current_minor_version >= 13: return# minor version must be 12 or less
     remove_me, prop_nodes = [], []
     remove_me, prop_nodes = [], []
     from .xForm_nodes_ui import xFormBoneNode
     from .xForm_nodes_ui import xFormBoneNode
+    socket_map_in, socket_map_out = get_socket_maps(node)
     if isinstance(node, xFormBoneNode):
     if isinstance(node, xFormBoneNode):
-        for custom_input in node.inputs:
+        for custom_input in reversed(list(node.inputs)):
             if 'Parameter' not in custom_input.bl_idname:
             if 'Parameter' not in custom_input.bl_idname:
                 continue
                 continue
             custom_prop_node = tree.nodes.new('UtilityCustomProperty')
             custom_prop_node = tree.nodes.new('UtilityCustomProperty')
@@ -288,10 +278,13 @@ def up_0_13_0_new_custom_prop_node(*args, **kwargs):
             elif custom_input.bl_idname == 'ParameterVectorSocket':
             elif custom_input.bl_idname == 'ParameterVectorSocket':
                 prop_type = 'VECTOR'
                 prop_type = 'VECTOR'
                 custom_prop_node.inputs['Default (Vector)'].default_value = getattr(custom_input, "default_value")
                 custom_prop_node.inputs['Default (Vector)'].default_value = getattr(custom_input, "default_value")
-                custom_prop_node.inputs['Min (Float)'].default_value = getattr(custom_input, "min")
-                custom_prop_node.inputs['Max (Float)'].default_value = getattr(custom_input, "max")
-                custom_prop_node.inputs['Soft Min (Float)'].default_value = getattr(custom_input, "soft_min")
-                custom_prop_node.inputs['Soft Max (Float)'].default_value = getattr(custom_input, "soft_max")
+                try:
+                    custom_prop_node.inputs['Min (Float)'].default_value = getattr(custom_input, "min")
+                    custom_prop_node.inputs['Max (Float)'].default_value = getattr(custom_input, "max")
+                    custom_prop_node.inputs['Soft Min (Float)'].default_value = getattr(custom_input, "soft_min")
+                    custom_prop_node.inputs['Soft Max (Float)'].default_value = getattr(custom_input, "soft_max")
+                except AttributeError: # I don't know if I just never implemented these or if it is version specific
+                    pass # anyways this isn't critical and should not trigger even a warning
             elif custom_input.bl_idname == 'ParameterStringSocket':
             elif custom_input.bl_idname == 'ParameterStringSocket':
                 prop_type = 'STRING'
                 prop_type = 'STRING'
                 custom_prop_node.inputs['Default (String)'].default_value = getattr(custom_input, "default_value")
                 custom_prop_node.inputs['Default (String)'].default_value = getattr(custom_input, "default_value")
@@ -299,6 +292,28 @@ def up_0_13_0_new_custom_prop_node(*args, **kwargs):
             custom_prop_node.inputs['Type'].default_value = prop_type
             custom_prop_node.inputs['Type'].default_value = prop_type
             prop_nodes.append(custom_prop_node)
             prop_nodes.append(custom_prop_node)
             remove_me.append(custom_input)
             remove_me.append(custom_input)
+            # now we need to relink things, because the custom property socket could have had an
+            # input supplying the default value
+            relink_value = socket_map_in.get(custom_input.identifier, None)
+            if isinstance(relink_value, list): # this indicates it is a list of links
+                # CRITICALLY, we need to force display update before doing this.
+                custom_prop_node.display_update({}, None) # parsed_tree and context, not needed
+                input_socket = None
+                match prop_type:
+                    case 'BOOL':
+                        input_socket = "Default (Bool)"
+                    case 'INT':
+                        input_socket = "Default (Int)"
+                    case 'FLOAT':
+                        input_socket = "Default (Float)"
+                    case 'VECTOR':
+                        input_socket = "Default (Vector)"
+                    case 'STRING':
+                        input_socket = "Default (String)"
+                    case _: # Should never never happen
+                        raise RuntimeError("Unexpected Code Path Reached, this is a bug.")
+                do_relink(custom_prop_node, custom_prop_node.inputs[input_socket],
+                          {input_socket:relink_value})
         while remove_me:
         while remove_me:
             custom_input = remove_me.pop()
             custom_input = remove_me.pop()
             node.inputs.remove(custom_input)
             node.inputs.remove(custom_input)
@@ -313,7 +328,6 @@ def up_0_13_0_new_custom_prop_node(*args, **kwargs):
         new_input = node.inputs.new("CustomPropSocket", "Custom Properties", use_multi_input=True)
         new_input = node.inputs.new("CustomPropSocket", "Custom Properties", use_multi_input=True)
 
 
 def schema_enable_custom_interface_types(*args, **kwargs):
 def schema_enable_custom_interface_types(*args, **kwargs):
-    # return
     tree = kwargs['tree']
     tree = kwargs['tree']
     current_major_version = tree.mantis_version[0]
     current_major_version = tree.mantis_version[0]
     current_minor_version = tree.mantis_version[1]
     current_minor_version = tree.mantis_version[1]
@@ -352,37 +366,50 @@ def schema_enable_custom_interface_types(*args, **kwargs):
                 n.is_updating = False
                 n.is_updating = False
         tree.do_live_update = True
         tree.do_live_update = True
 
 
-
-
-# since this is basically the same operation, just do it all together
 def update_fcurve_and_armature_node_to_multi_socket(*args, **kwargs):
 def update_fcurve_and_armature_node_to_multi_socket(*args, **kwargs):
+    from .base_definitions import SchemaUINode
     node = kwargs['node']
     node = kwargs['node']
     live_updates = node.id_data.do_live_update
     live_updates = node.id_data.do_live_update
     node.id_data.do_live_update = False
     node.id_data.do_live_update = False
     # First we have to get the socket maps and re-init the sockets
     # First we have to get the socket maps and re-init the sockets
-    from .utilities.utilities import get_socket_maps, do_relink
     convert_keys = {'UtilityFCurve' : [ 'Keyframe', ],
     convert_keys = {'UtilityFCurve' : [ 'Keyframe', ],
                     'LinkArmature'  : [ 'Target', 'Weight', ], }
                     'LinkArmature'  : [ 'Target', 'Weight', ], }
     my_convert_keys = convert_keys[node.bl_idname]
     my_convert_keys = convert_keys[node.bl_idname]
     sockets_in, _sockets_out = get_socket_maps(node,)
     sockets_in, _sockets_out = get_socket_maps(node,)
-    node.inputs.clear()
-    input_templates = [st for st in node.socket_templates if st.is_input == True]
-    node.init_sockets(input_templates)
+    # NOTE IMPORTANT BUG INFO
+    # I was using node.inputs.clear() here before unguarded...
+    # this caused connected Schema nodes to update and mutate the socket map
+    # which, in turn, connected the new socket to the wildcard and extended the schema
+    # resulting in trees that fail to run. This was particularly bad because it was
+    # so hard to debug (due to spooky action at a distance mutation)
+    # So we have to mark the connected nodes as "updating" before we modify the tree.
+    # TODO: should I just mark all nodes as updating during versioning?
+    toggle_updating = []
+    was_updating = {}
+    for socket_name, links in sockets_in.items():
+        if isinstance(links, list):
+            for link in links:
+                if hasattr(link, 'node') and hasattr(link.node, 'is_updating'):
+                    toggle_updating.append(link.node)
+                    was_updating[link.node.name] = link.node.is_updating
+                    link.node.is_updating = True
+    node.inputs.clear() # this is VERY DANGEROUS without the above
+    add_me_sockets = [st for st in node.socket_templates if st.is_input == True]
+    node.init_sockets(add_me_sockets)
     try:
     try:
         new_sockets_in = {}
         new_sockets_in = {}
         for convert_key in my_convert_keys:
         for convert_key in my_convert_keys:
             for socket_name, links in sockets_in.items():
             for socket_name, links in sockets_in.items():
                 if links is None: continue
                 if links is None: continue
                 if convert_key+'.' in socket_name:
                 if convert_key+'.' in socket_name:
-                    print (socket_name, links)
                     if new_sockets_in.get(convert_key+'s'):
                     if new_sockets_in.get(convert_key+'s'):
                                                     # has to be in this order to come back alright.
                                                     # has to be in this order to come back alright.
                                                     # assuming, that is, that the keys are sorted. they should be.
                                                     # assuming, that is, that the keys are sorted. they should be.
                         new_sockets_in[convert_key+'s'] = sockets_in[socket_name] + new_sockets_in[convert_key+'s']
                         new_sockets_in[convert_key+'s'] = sockets_in[socket_name] + new_sockets_in[convert_key+'s']
                     else:
                     else:
                         new_sockets_in[convert_key+'s'] = sockets_in[socket_name]
                         new_sockets_in[convert_key+'s'] = sockets_in[socket_name]
-                    prWhite (new_sockets_in[convert_key+'s'])
-            do_relink(node, node.inputs[convert_key+'s'], new_sockets_in, in_out="INPUT",)
+            if new_sockets_in:
+                do_relink(node, node.inputs[convert_key+'s'], new_sockets_in, in_out="INPUT",)
         for socket_name, links in sockets_in.items():
         for socket_name, links in sockets_in.items():
             proceed = True
             proceed = True
             for convert_key in my_convert_keys:
             for convert_key in my_convert_keys:
@@ -390,10 +417,65 @@ def update_fcurve_and_armature_node_to_multi_socket(*args, **kwargs):
                     proceed = False
                     proceed = False
             if proceed:
             if proceed:
                 do_relink(node, node.inputs[socket_name], sockets_in, in_out='INPUT')
                 do_relink(node, node.inputs[socket_name], sockets_in, in_out='INPUT')
-
     finally:
     finally:
+        # Now we have to clean up by removing the unneeded sockets and repositioning.
+        for other_node in toggle_updating:
+            other_node.is_updating = was_updating[other_node.name]
         node.id_data.do_live_update = live_updates
         node.id_data.do_live_update = live_updates
 
 
+# These BBone Scale-In, Scale-Out properties had the wrong defaults
+# now that they are implemented properly, 
+# since they were not implemented before, we'll do all of them at once.
+# Normally it is wrong to set things directly like this, but they didn't
+# do anything before. Oh well.
+def fix_bbone_default_scale_in_and_out(*args, **kwargs):
+    tree = kwargs['tree']
+    current_major_version = tree.mantis_version[0]
+    current_minor_version = tree.mantis_version[1]
+    current_sub_version = tree.mantis_version[2]
+    if  current_major_version > 0: return# major version must be 0
+    if current_minor_version >= 13: return# minor version must be 12 or less
+    if current_minor_version == 13 and current_sub_version >= 5: return# sub version must be 4 or less
+    prPurple(f"Fixing BBone Settings for {tree.name}")
+    live_updates = tree.do_live_update
+    bones = []
+    for node in tree.nodes:
+        if hasattr(node, 'is_updating'):
+            node.is_updating = True
+        if node.bl_idname == 'xFormBoneNode':
+            bones.append(node)
+    try:
+        for bone_node in bones:
+            bone_node.inputs['BBone Scale-In'].default_value = (1,1,1)
+            bone_node.inputs['BBone Scale-Out'].default_value = (1,1,1)
+    finally:
+        for node in tree.nodes:
+            if hasattr(node, 'is_updating'):
+                node.is_updating = False
+
+# versioning tasks that involve Blender versions rather than Mantis versions:
+error_description_4_5_0_LTS = 'There is a bug in Blender 4.5.0 LTS, that is why these sockets are blue.'\
+                             ' This will be fixed in future Blender versions.'
+def cleanup_4_5_0_LTS_interface_workaround(*args, **kwargs):
+    # this is a function for cleaning up the workaround up above
+    tree = kwargs['tree']
+    if bpy_version == (4,5,0): return
+    if not hasattr(tree, "interface_helper"): return
+    import json
+    try:
+        interface_helper = json.loads(tree.interface_helper)
+    except:
+        return # this should mean it was never a problem to begin with.
+    prPurple(f"Restoring Tree Interface for {tree.name}.")
+    for interface_item in tree.interface.items_tree:
+        if interface_item.item_type == 'PANEL': continue
+        # now we need to restore the socket types, which are in the interface helper
+        # keep the interface stuff around, I'll use it for marking arrays in the future
+        int_info = interface_helper[interface_item.identifier]
+        socket_type = int_info['bl_socket_idname']
+        interface_item.socket_type = socket_type
+        if interface_item.description == error_description_4_5_0_LTS:
+            interface_item.description = ''
 
 
 versioning_tasks = [
 versioning_tasks = [
     # node bl_idname    task                required keyword arguments
     # node bl_idname    task                required keyword arguments
@@ -407,11 +489,10 @@ versioning_tasks = [
     (['SchemaTree'], schema_enable_custom_interface_types, ['tree']),
     (['SchemaTree'], schema_enable_custom_interface_types, ['tree']),
     (['UtilityFCurve', 'LinkArmature'],
     (['UtilityFCurve', 'LinkArmature'],
            update_fcurve_and_armature_node_to_multi_socket, ['node']),
            update_fcurve_and_armature_node_to_multi_socket, ['node']),
+    (['MantisTree', 'SchemaTree'], fix_bbone_default_scale_in_and_out, ['tree']),
 ]
 ]
 
 
 
 
-
-
 # WORKAROUNDS for bugs should go here:
 # WORKAROUNDS for bugs should go here:
 # 4.5.0 LTS valid socket types bug:
 # 4.5.0 LTS valid socket types bug:
 def workaround_4_5_0_interface_update(tree, name, in_out, sock_type, parent_name, do_parent=False):
 def workaround_4_5_0_interface_update(tree, name, in_out, sock_type, parent_name, do_parent=False):

+ 66 - 136
xForm_nodes.py

@@ -76,6 +76,26 @@ class xFormNode(MantisNode):
     def reset_execution(self):
     def reset_execution(self):
         super().reset_execution()
         super().reset_execution()
         self.prepared=False
         self.prepared=False
+
+    def check_drivers(self):
+        for st in self.socket_templates:
+            if st.is_animatable == False:
+                socket = st.name
+                multi = st.use_multi_input
+                vector = isinstance(st.default_value, tuple)
+                # if single property
+                if not multi and not vector:
+                    has_driver = check_for_driver(self, socket)
+                elif not multi and vector:
+                    for i in range(len(st.default_value)):
+                        has_driver = check_for_driver(self, socket, i)
+                        if has_driver: break
+                elif multi:
+                    for i in range(len(self.inputs[socket].links)):
+                        has_driver = check_for_driver(self, socket, 0, i)
+                        if has_driver: break
+                if has_driver:
+                    raise GraphError(f"Error: Socket \"{socket}\" is not animatable and cannot be driven.")
     
     
     def setup_custom_props(self, ob, input):
     def setup_custom_props(self, ob, input):
         from .mantis_dataclasses import custom_prop_template
         from .mantis_dataclasses import custom_prop_template
@@ -149,9 +169,13 @@ class xFormArmature(xFormNode):
 
 
         #check if an object by the name exists
         #check if an object by the name exists
         if (name) and (ob := bpy.data.objects.get(name)):
         if (name) and (ob := bpy.data.objects.get(name)):
+            # Clear the drivers from BOTH the object/pose and the skeleton
             if (ob.animation_data):
             if (ob.animation_data):
                 while (ob.animation_data.drivers):
                 while (ob.animation_data.drivers):
                         ob.animation_data.drivers.remove(ob.animation_data.drivers[-1])
                         ob.animation_data.drivers.remove(ob.animation_data.drivers[-1])
+            if (ob.data.animation_data):
+                while (ob.data.animation_data.drivers):
+                        ob.data.animation_data.drivers.remove(ob.data.animation_data.drivers[-1])
             for pb in ob.pose.bones:
             for pb in ob.pose.bones:
                 # clear it, even after deleting the edit bones,
                 # clear it, even after deleting the edit bones,
                 #  if we create them again the pose bones will be reused
                 #  if we create them again the pose bones will be reused
@@ -269,6 +293,7 @@ class xFormBone(xFormNode):
             eb.inherit_scale = parent_mantis_node.evaluate_input("Inherit Scale")
             eb.inherit_scale = parent_mantis_node.evaluate_input("Inherit Scale")
 
 
     def bPrepare(self, bContext=None):
     def bPrepare(self, bContext=None):
+        self.check_drivers()
         self.parameters['Matrix'] = get_matrix(self)
         self.parameters['Matrix'] = get_matrix(self)
         self.prepared = True
         self.prepared = True
 
 
@@ -341,13 +366,9 @@ class xFormBone(xFormNode):
             raise RuntimeError(wrapRed(f"Could not create edit bone: {name} because bone head was located in the same place as bone tail."))
             raise RuntimeError(wrapRed(f"Could not create edit bone: {name} because bone head was located in the same place as bone tail."))
 
 
         # Setup Deform attributes...
         # Setup Deform attributes...
-        eb.use_deform            = self.evaluate_input("Deform")
-        eb.envelope_distance     = self.evaluate_input("Envelope Distance")
-        eb.envelope_weight       = self.evaluate_input("Envelope Weight")
-        eb.use_envelope_multiply = self.evaluate_input("Envelope Multiply")
-        eb.head_radius           = self.evaluate_input("Envelope Head Radius")
-        eb.tail_radius           = self.evaluate_input("Envelope Tail Radius")
-
+        eb_props_sockets = self.gen_property_socket_map(prop_owner_group="edit_bone")
+        evaluate_sockets(self, eb, eb_props_sockets)
+        
         print( wrapGreen("Created Bone: ") + wrapOrange(eb.name) + wrapGreen(" in ") + wrapWhite(self.bGetParentArmature().name))
         print( wrapGreen("Created Bone: ") + wrapOrange(eb.name) + wrapGreen(" in ") + wrapWhite(self.bGetParentArmature().name))
 
 
         parent_xForm_info = get_parent_xForm_info(self)
         parent_xForm_info = get_parent_xForm_info(self)
@@ -360,7 +381,6 @@ class xFormBone(xFormNode):
             self_edit_name=eb.name,
             self_edit_name=eb.name,
         )
         )
         self.parameters['xForm Out'] = my_info
         self.parameters['xForm Out'] = my_info
-
         self.executed = True
         self.executed = True
 
 
     def set_bone_color(self, b, inherit_color, bContext):
     def set_bone_color(self, b, inherit_color, bContext):
@@ -401,155 +421,68 @@ class xFormBone(xFormNode):
 
 
 
 
     def bFinalize(self, bContext = None):
     def bFinalize(self, bContext = None):
+
         b = self.bGetParentArmature().data.bones[self.bObject]
         b = self.bGetParentArmature().data.bones[self.bObject]
         # let's do bone colors first
         # let's do bone colors first
         inherit_color = self.evaluate_input("Inherit Color")
         inherit_color = self.evaluate_input("Inherit Color")
         if len(self.inputs['Color'].links) > 0:
         if len(self.inputs['Color'].links) > 0:
             inherit_color = False # use the link instead
             inherit_color = False # use the link instead
-        # try: # just in case, this shouldn't cause a failure
         self.set_bone_color(b, inherit_color, bContext)
         self.set_bone_color(b, inherit_color, bContext)
-        # except Exception as e:
-        #     prRed("WARNING: failed to set color because of error, see report below:")
-        #     prOrange(e)
-        #
-        do_bb=False
-        b.bbone_x = self.evaluate_input("BBone X Size"); b.bbone_x = max(b.bbone_x, 0.0002)
-        b.bbone_z = self.evaluate_input("BBone Z Size"); b.bbone_z = max(b.bbone_z, 0.0002)
-        if (segs := self.evaluate_input("BBone Segments")) > 1:
-            do_bb=True
-            b.bbone_segments = segs
-            b.bbone_x = self.evaluate_input("BBone X Size")
-            b.bbone_z = self.evaluate_input("BBone Z Size")
-            if self.evaluate_input("BBone HQ Deformation"):
-                b.bbone_mapping_mode = "CURVED"
-            # 'bbone_handle_type_start'    : ("BBone Start Handle Type", "AUTO"),
-            # 'bbone_handle_type_end'      : ("BBone End Handle Type", "AUTO"),
-            # 'bbone_custom_handle_start'  : ("BBone Custom Start Handle", "AUTO"),
-            # 'bbone_custom_handle_end'    : ("BBone Custom End Handle", "AUTO"),
-            if handle_type := self.evaluate_input("BBone Start Handle Type"):
-                b.bbone_handle_type_start = handle_type
-            if handle_type := self.evaluate_input("BBone End Handle Type"):
-                b.bbone_handle_type_end = handle_type
-
-            try:
-                if (custom_handle := self.evaluate_input("BBone Custom Start Handle")):
-                    b.bbone_custom_handle_start = self.bGetParentArmature().data.bones[custom_handle]
-                if (custom_handle := self.evaluate_input("BBone Custom End Handle")):
-                    b.bbone_custom_handle_end = self.bGetParentArmature().data.bones[custom_handle]
-            except KeyError:
-                prRed("Warning: BBone start or end handle not set because of missing bone in armature.")
-
-            bone_props_socket= {
-                'bbone_curveinx'     : ("BBone X Curve-In", 0.0),
-                'bbone_curveinz'     : ("BBone Z Curve-In", 0.0),
-                'bbone_curveoutx'    : ("BBone X Curve-Out", 0.0),
-                'bbone_curveoutz'    : ("BBone Z Curve-Out", 0.0),
-            }
-            evaluate_sockets(self, b, bone_props_socket)
-            # TODO this section should be done with props-socket thing
-            b.bbone_handle_use_scale_start = self.evaluate_input("BBone Start Handle Scale")
-            b.bbone_handle_use_scale_end = self.evaluate_input("BBone End Handle Scale")
-
+        if self.evaluate_input("BBone HQ Deformation"):
+            b.bbone_mapping_mode = "CURVED"
+        if handle_type := self.evaluate_input("BBone Start Handle Type"):
+            b.bbone_handle_type_start = handle_type
+        if handle_type := self.evaluate_input("BBone End Handle Type"):
+            b.bbone_handle_type_end = handle_type
+        try:
+            if (custom_handle := self.evaluate_input("BBone Custom Start Handle")):
+                b.bbone_custom_handle_start = self.bGetParentArmature().data.bones[custom_handle]
+            if (custom_handle := self.evaluate_input("BBone Custom End Handle")):
+                b.bbone_custom_handle_end = self.bGetParentArmature().data.bones[custom_handle]
+        except KeyError:
+            prRed("Warning: BBone start or end handle not set because of missing bone in armature.")
 
 
         import bpy
         import bpy
-
         pb = self.bGetParentArmature().pose.bones[self.bObject]
         pb = self.bGetParentArmature().pose.bones[self.bObject]
-        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")
 
 
-        if (pb.is_in_ik_chain):
-            # this  props_socket thing wasn't really meant to work here but it does, neat
-            props_sockets = {
-            'ik_stretch'          : ("IK Stretch", 0),
-            'lock_ik_x'           : (("Lock IK", 0), False),
-            'lock_ik_y'           : (("Lock IK", 1), False),
-            'lock_ik_z'           : (("Lock IK", 2), False),
-            'ik_stiffness_x'      : (("IK Stiffness", 0), 0.0),
-            'ik_stiffness_y'      : (("IK Stiffness", 1), 0.0),
-            'ik_stiffness_z'      : (("IK Stiffness", 2), 0.0),
-            'use_ik_limit_x'      : (("Limit IK", 0), False),
-            'use_ik_limit_y'      : (("Limit IK", 1), False),
-            'use_ik_limit_z'      : (("Limit IK", 2), False),
-            'ik_min_x'            : ("X Min", 0),
-            'ik_max_x'            : ("X Max", 0),
-            'ik_min_y'            : ("Y Min", 0),
-            'ik_max_y'            : ("Y Max", 0),
-            'ik_min_z'            : ("Z Min", 0),
-            'ik_max_z'            : ("Z Max", 0),
-            }
+        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)
             evaluate_sockets(self, pb, props_sockets)
-        if do_bb:
-            props_sockets = {
-            'bbone_curveinx'             : ("BBone X Curve-In", pb.bone.bbone_curveinx),
-            'bbone_curveinz'             : ("BBone Z Curve-In", pb.bone.bbone_curveinz),
-            'bbone_curveoutx'            : ("BBone X Curve-Out", pb.bone.bbone_curveoutx),
-            'bbone_curveoutz'            : ("BBone Z Curve-Out", pb.bone.bbone_curveoutz),
-            'bbone_easein'               : ("BBone Ease-In", 0),
-            'bbone_easeout'              : ("BBone Ease-Out", 0),
-            'bbone_rollin'               : ("BBone Roll-In", 0),
-            'bbone_rollout'              : ("BBone Roll-Out", 0),
-            'bbone_scalein'              : ("BBone Scale-In", (1,1,1)),
-            'bbone_scaleout'             : ("BBone Scale-Out", (1,1,1)),
-            }
-            evaluate_sockets(self, pb, props_sockets)
-            # we need to clear this stuff since our only real goal was to get some drivers from the above
-            for attr_name in props_sockets.keys():
-                try:
-                    setattr(pb, attr_name, 0) # just clear it
-                except (ValueError, TypeError): # Blender 5.2 raises TypeError for scalar -> vector
-                    setattr(pb, attr_name, (1.0,1.0,1.0)) # scale needs to be set to 1
-
+        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
+            
+            
 
 
-            # important TODO... all of the drivers and stuff should be handled this way, right?
-        # time to set up drivers!
-
-        # just gonna add this to the end and build off it I guess
-        props_sockets = {
-            "lock_location"               : ("Lock Location", [False, False, False]),
-            "lock_rotation"               : ("Lock Rotation", [False, False, False]),
-            "lock_scale"                  : ("Lock Scale", [False, False, False]),
-            'custom_shape_scale_xyz'      : ("Custom Object Scale",  (0.0,0.0,0.0) ),
-            'custom_shape_translation'    : ("Custom Object Translation",  (0.0,0.0,0.0) ),
-            'custom_shape_rotation_euler' : ("Custom Object Rotation",  (0.0,0.0,0.0) ),
-            'use_custom_shape_bone_size'  : ("Custom Object Scale to Bone Length",  True,),
-        }
-        # in 5.0.0 and newer I have to do pose bone, this is an error in < 5.0
-        if bpy_version >=(5,0,0):
-            props_sockets['hide'] = ("Hide", False)
+        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)
 
 
-        evaluate_sockets(self, pb, props_sockets)
+        bone_props_socket = self.gen_property_socket_map(prop_owner_group='bone')
+        evaluate_sockets(self, pb.bone, bone_props_socket)
 
 
-        # this could probably be moved to bTransformPass
-        # TODO VERSIONING: newer (5.0+ ?) Blenders need to use PoseBone for this
-        props_sockets = {
-            'hide'      : ("Hide", False),
-            'show_wire' : ("Custom Object Wireframe", False),
-        }
-        evaluate_sockets(self, pb.bone, 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...
+        # Better to ask forgiveness than permission. If anyone wants control over this, I'll solve the problem then
+        # IN the near future, in fact, I think I'll make a BBone Rest Pose matrix thing
+        # and allow users to set it from bezier curves
+        pb.bone.bbone_easein = 1; pb.bone.bbone_easeout = 1
 
 
-        # whatever I was doing there.... was stupid. CLEAN UP TODO
-        # this is the right thing to do.
         finish_drivers(self)
         finish_drivers(self)
-        #
-        # OK, visual settings
-        #
-        # Get the override xform's bone:
-
-
 
 
         pb.custom_shape_transform = None
         pb.custom_shape_transform = None
         pb.custom_shape = None
         pb.custom_shape = None
-
         if len(self.inputs["Custom Object xForm Override"].links) > 0:
         if len(self.inputs["Custom Object xForm Override"].links) > 0:
             trace = trace_single_line(self, "Custom Object xForm Override")
             trace = trace_single_line(self, "Custom Object xForm Override")
             try:
             try:
                 pb.custom_shape_transform = trace[0][1].bGetObject()
                 pb.custom_shape_transform = trace[0][1].bGetObject()
             except AttributeError:
             except AttributeError:
                 pass
                 pass
-
         if len(self.inputs["Custom Object"].links) > 0:
         if len(self.inputs["Custom Object"].links) > 0:
             trace = trace_single_line(self, "Custom Object")
             trace = trace_single_line(self, "Custom Object")
             try:
             try:
@@ -668,10 +601,7 @@ class xFormGeometryObject(xFormNode):
         self.has_shape_keys = False
         self.has_shape_keys = False
         # putting this in bTransformPass simply prevents it from being run more than once.
         # putting this in bTransformPass simply prevents it from being run more than once.
         # maybe I should do that with the rest of bPrepare, too.
         # maybe I should do that with the rest of bPrepare, too.
-        props_sockets = {
-            'hide_viewport'    : ("Hide in Viewport", False),
-            'hide_render'      : ("Hide in Render", False),
-        }
+        props_sockets = self.gen_property_socket_map()
         evaluate_sockets(self, self.bObject, props_sockets)
         evaluate_sockets(self, self.bObject, props_sockets)
         self.executed = True
         self.executed = True
 
 

+ 17 - 8
xForm_nodes_ui.py

@@ -145,15 +145,24 @@ class xFormBoneNode(Node, xFormNode):
             if mantis_node and (pb := mantis_node.bGetObject(mode='POSE')):
             if mantis_node and (pb := mantis_node.bGetObject(mode='POSE')):
                 self.display_ik_settings = pb.is_in_ik_chain
                 self.display_ik_settings = pb.is_in_ik_chain
             self.inputs['Name'].display_text = ""
             self.inputs['Name'].display_text = ""
-            if mantis_node and mantis_node.prepared:
+            mantis_node_ready = mantis_node and mantis_node.prepared
+            if self.inputs['Custom Object'].is_linked and mantis_node_ready:
+                self.display_vp_settings = mantis_node.evaluate_input("Custom Object")
+            if self.inputs['Deform'].is_linked and mantis_node_ready:
+                self.display_def_settings = mantis_node.evaluate_input("Deform")
+            else:
+                self.display_def_settings = self.inputs['Deform'].default_value
+            if self.inputs['BBone Segments'].is_linked and mantis_node_ready:
+                segs = mantis_node.evaluate_input("BBone Segments")
                 try:
                 try:
-                    self.inputs['Name'].display_text = mantis_node.evaluate_input("Name")
-                    self.display_vp_settings = mantis_node.inputs["Custom Object"].is_connected
-                    self.display_def_settings = mantis_node.evaluate_input("Deform")
-                    self.display_bb_settings = mantis_node.evaluate_input("BBone Segments") > 1
-                except KeyError:
-                    return # the tree isn't ready yet.
-            
+                    self.display_bb_settings = segs > 1
+                except TypeError:
+                    self.display_bb_settings = True # it is probably a driver
+            else:
+                self.display_bb_settings = self.inputs['BBone Segments'].default_value > 1
+            if self.inputs['Name'].is_linked and mantis_node_ready:
+                self.inputs['Name'].display_text = mantis_node.evaluate_input("Name")
+
             for template in self.socket_templates:
             for template in self.socket_templates:
                 name = template.name
                 name = template.name
                 category = template.category
                 category = template.category

+ 160 - 77
xForm_socket_templates.py

@@ -1,6 +1,7 @@
 from .mantis_dataclasses import MantisSocketTemplate as SockTemplate
 from .mantis_dataclasses import MantisSocketTemplate as SockTemplate
 from .misc_nodes_socket_templates import SplineIndexTemplate
 from .misc_nodes_socket_templates import SplineIndexTemplate
 from dataclasses import replace
 from dataclasses import replace
+from bpy.app import version as bpy_version
 
 
 xFormArmatureSockets=[
 xFormArmatureSockets=[
     NameTemplate := SockTemplate(
     NameTemplate := SockTemplate(
@@ -16,7 +17,7 @@ xFormArmatureSockets=[
         name="Relationship", is_input=True,  bl_idname='RelationshipSocket', ),
         name="Relationship", is_input=True,  bl_idname='RelationshipSocket', ),
     CustomPropsTemplate := SockTemplate(
     CustomPropsTemplate := SockTemplate(
         name="Custom Properties", is_input=True,  bl_idname='CustomPropSocket',
         name="Custom Properties", is_input=True,  bl_idname='CustomPropSocket',
-            use_multi_input=True, ),
+            use_multi_input=True, is_animatable=False, ),
     xFormOutTemplate := SockTemplate(
     xFormOutTemplate := SockTemplate(
         name="xForm Out", is_input=False,  bl_idname='xFormSocket', ),
         name="xForm Out", is_input=False,  bl_idname='xFormSocket', ),
 ]
 ]
@@ -84,151 +85,233 @@ xFormGetBoneSockets = [
 ]
 ]
 
 
 
 
+# Some (especially BBone) properties are good to treat differently
+#  based on bone/posebone or value/driver
+# I'll use these lists to make decisions about that sort of thing
+attributes_shared_by_bone_and_pose_bone = [
+    "bbone_curveinx",
+    "bbone_curveinz",
+    "bbone_curveoutx",
+    "bbone_curveoutz",
+    "bbone_custom_handle_end",
+    "bbone_custom_handle_start",
+    "bbone_easein",
+    "bbone_easeout",
+    "bbone_rollin",
+    "bbone_rollout",
+    "bbone_scalein",
+    "bbone_scaleout",
+]
+
+# these need to be 1 in edit mode and 0 in pose mode, by default. Pain.
+need_different_defaults = [
+    ("bbone_easein", 1, 0 ),
+    ("bbone_easeout", 1, 0),
+]
+
+
+# VERSIONING for Bone
+bone_hide_prop_group='bone'
+if bpy_version >= (5,0,0):
+    bone_hide_prop_group='pose_bone'
+bone_hide_template = replace(HideTemplate, name='Hide', category='always_show',
+                            blender_property='hide', default_value=False,
+                            prop_owner_group = bone_hide_prop_group)
+
+bbone_template = SockTemplate( name="<BBone Template>",
+        default_value = 0.0, blender_property=None, is_input=True,
+        bl_idname='FloatSocket', prop_owner_group='bone',)
+
+# And there is some serious confusing stuff regarding BBones so I'll explain...
+# Blender has duplicated properties for Bone and Pose Bone
+# When there is a driver, you usually want the Pose Bone to hold it
+# but when there isn't you probably want it on the Bone since it isn't animated
+
 # and bones! this one is a bit much...
 # and bones! this one is a bit much...
 from math import pi
 from math import pi
 xFormBoneSockets = [
 xFormBoneSockets = [
-    replace(NameTemplate, default_value='Bone', category='always_show'),
-    replace(RotationOrderTemplate, category='always_show'),
-    BoneMatrixTemplate := replace(ObjectMatrixTemplate,
-            blender_property='matrix', category='always_show'),
-    replace(RelationshipInTemplate, category='always_show'),
+    replace(NameTemplate, default_value='Bone', category='always_show',
+            is_animatable=False,),
+    replace(RotationOrderTemplate, category='always_show',),
+    BoneMatrixTemplate := replace(ObjectMatrixTemplate, is_animatable=False,
+            blender_property='matrix', category='always_show',),
+    replace(RelationshipInTemplate, category='always_show', is_animatable=False,),
     # IK
     # IK
     IKStretch := SockTemplate(
     IKStretch := SockTemplate(
         name="IK Stretch", is_input=True,  bl_idname='FloatFactorSocket',
         name="IK Stretch", is_input=True,  bl_idname='FloatFactorSocket',
-        default_value=0, blender_property='ik_stretch', category='IK'),
+        default_value=0, blender_property='ik_stretch', category='IK',
+        prop_owner_group='pose_bone',),
     IKLock := SockTemplate(
     IKLock := SockTemplate(
         name="Lock IK", is_input=True,  bl_idname='BooleanThreeTupleSocket',
         name="Lock IK", is_input=True,  bl_idname='BooleanThreeTupleSocket',
         default_value=(False, False, False), category='IK',
         default_value=(False, False, False), category='IK',
-        blender_property=('lock_ik_x', 'lock_ik_y', 'lock_ik_z'),),
+        blender_property=('lock_ik_x', 'lock_ik_y', 'lock_ik_z'),
+        prop_owner_group='pose_bone', ),
     IKStiffness := SockTemplate(
     IKStiffness := SockTemplate(
         name="IK Stiffness", is_input=True,  bl_idname='BooleanThreeTupleSocket',
         name="IK Stiffness", is_input=True,  bl_idname='BooleanThreeTupleSocket',
         default_value=(False, False, False), category='IK',
         default_value=(False, False, False), category='IK',
-        blender_property=('ik_stiffness_x', 'ik_stiffness_y', 'ik_stiffness_z')),
+        blender_property=('ik_stiffness_x', 'ik_stiffness_y', 'ik_stiffness_z'),
+        prop_owner_group='pose_bone', ),
     IKLimit := SockTemplate(
     IKLimit := SockTemplate(
         name="Limit IK", is_input=True,  bl_idname='BooleanThreeTupleSocket',
         name="Limit IK", is_input=True,  bl_idname='BooleanThreeTupleSocket',
         default_value=(False, False, False), category='IK',
         default_value=(False, False, False), category='IK',
-        blender_property=('use_ik_limit_x', 'use_ik_limit_y', 'use_ik_limit_z')),
+        blender_property=('use_ik_limit_x', 'use_ik_limit_y', 'use_ik_limit_z'),
+        prop_owner_group='pose_bone', ),
     IKXMin := SockTemplate(
     IKXMin := SockTemplate(
         name="X Min", is_input=True,  bl_idname='NodeSocketFloatAngle',
         name="X Min", is_input=True,  bl_idname='NodeSocketFloatAngle',
-        default_value=-pi, blender_property='ik_min_x', category='IK'),
+        default_value=-pi, blender_property='ik_min_x', category='IK',
+        prop_owner_group='pose_bone', ),
     IKXMax := replace(IKXMin, name="X Max", default_value=pi,
     IKXMax := replace(IKXMin, name="X Max", default_value=pi,
-                      blender_property='ik_max_x'),
+                      blender_property='ik_max_x',),
     IKYMax := replace(IKXMin, name="Y Min", blender_property='ik_min_y'),
     IKYMax := replace(IKXMin, name="Y Min", blender_property='ik_min_y'),
     IKYMax := replace(IKXMax, name="Y Max", blender_property='ik_max_y'),
     IKYMax := replace(IKXMax, name="Y Max", blender_property='ik_max_y'),
     IKZMax := replace(IKXMin, name="Z Min", blender_property='ik_min_z'),
     IKZMax := replace(IKXMin, name="Z Min", blender_property='ik_min_z'),
     IKZMax := replace(IKXMax, name="Z Max", blender_property='ik_max_z'),
     IKZMax := replace(IKXMax, name="Z Max", blender_property='ik_max_z'),
     # Visual Stuff
     # Visual Stuff
     BoneCollectionTemplate := SockTemplate(name="Bone Collection", is_input=True,
     BoneCollectionTemplate := SockTemplate(name="Bone Collection", is_input=True,
-            bl_idname='BoneCollectionSocket', category='Display', use_multi_input=True),
+            bl_idname='BoneCollectionSocket', category='Display', use_multi_input=True,
+            is_animatable=False,),
     CustomObjectTemplate := replace(SourcexFormTemplate, name='Custom Object',
     CustomObjectTemplate := replace(SourcexFormTemplate, name='Custom Object',
-                                    category='Display'),
-    OverrideXFormTemplate := replace(SourcexFormTemplate,
-                    name='Custom Object xForm Override', category='Display'),
+        category='Display', is_animatable=False,),
+    OverrideXFormTemplate := replace(SourcexFormTemplate, is_animatable=False,
+        name='Custom Object xForm Override', category='Display',),
     CustomObjectScaleToBoneTemplate := replace(HideRenderTemplate,
     CustomObjectScaleToBoneTemplate := replace(HideRenderTemplate,
-            name='Custom Object Scale to Bone Length', category='Display',
-            blender_property='use_custom_shape_bone_size',
-            default_value=True,),
+        name='Custom Object Scale to Bone Length', category='Display',
+        blender_property='use_custom_shape_bone_size',
+        default_value=True, prop_owner_group='pose_bone', ),
     CustomObjectWireframeTemplate := replace(HideRenderTemplate,
     CustomObjectWireframeTemplate := replace(HideRenderTemplate,
             name='Custom Object Wireframe', category='Display',
             name='Custom Object Wireframe', category='Display',
-            blender_property='show_wire', default_value=True,),
+            blender_property='show_wire', default_value=True,
+            prop_owner_group='bone', ),
     CustomObjectScaleTemplate := SockTemplate(name="Custom Object Scale",
     CustomObjectScaleTemplate := SockTemplate(name="Custom Object Scale",
             is_input=True, bl_idname='VectorScaleSocket',
             is_input=True, bl_idname='VectorScaleSocket',
-            category='Display', default_value=(1.0,1.0,1.0),),
+            category='Display', default_value=(1.0,1.0,1.0),
+            blender_property = 'custom_shape_scale_xyz',
+            prop_owner_group='pose_bone', ),
     CustomObjectTranslationTemplate := SockTemplate(name="Custom Object Translation",
     CustomObjectTranslationTemplate := SockTemplate(name="Custom Object Translation",
             is_input=True, bl_idname='VectorSocket',
             is_input=True, bl_idname='VectorSocket',
-            category='Display', default_value=(0.0,0.0,0.0),),
+            category='Display', default_value=(0.0,0.0,0.0),
+            blender_property = 'custom_shape_translation',
+            prop_owner_group='pose_bone', ),
     CustomObjectRotationTemplate := SockTemplate(name="Custom Object Rotation",
     CustomObjectRotationTemplate := SockTemplate(name="Custom Object Rotation",
             is_input=True, bl_idname='VectorEulerSocket',
             is_input=True, bl_idname='VectorEulerSocket',
-            category='Display', default_value=(0.0,0.0,0.0),),
+            category='Display', default_value=(0.0,0.0,0.0),
+            blender_property = 'custom_shape_rotation_euler',
+            prop_owner_group='pose_bone', ),
     CustomColor := SockTemplate(name="Color",
     CustomColor := SockTemplate(name="Color",
             is_input=True, bl_idname='ColorSetSocket',
             is_input=True, bl_idname='ColorSetSocket',
-            category='Display',),
+            category='Display', is_animatable=False,),
     InheritColor := SockTemplate(name="Inherit Color",
     InheritColor := SockTemplate(name="Inherit Color",
-            is_input=True, bl_idname='BooleanSocket',
-            category='Display', default_value=True),
+            is_input=True, bl_idname='BooleanSocket', is_animatable=False,
+            category='Display', default_value=True, prop_owner_group='pose_bone', ),
     # Deform Stuff
     # Deform Stuff
     BoneDeformTemplate := replace(HideRenderTemplate, name='Deform',
     BoneDeformTemplate := replace(HideRenderTemplate, name='Deform',
-        category='Deform', blender_property='use_deform', default_value=False,),
+        category='Deform', blender_property='use_deform', default_value=False,
+        prop_owner_group='edit_bone', is_animatable=False,),
     EnvelopeDistanceTemplate := SockTemplate( name='Envelope Distance',
     EnvelopeDistanceTemplate := SockTemplate( name='Envelope Distance',
-            bl_idname='FloatPositiveSocket', category='Deform',
-            blender_property='envelope_distance', default_value=0,
-            is_input=True,),
-    EnvelopeWeightTemplate := replace(EnvelopeDistanceTemplate,
-            name='Envelope Weight', bl_idname = 'FloatFactorSocket',
-            blender_property='envelope_weight', default_value=1.0,),
-    EnvelopeWeightTemplate := replace(EnvelopeDistanceTemplate,
-            name='Envelope Multiply', bl_idname = 'BooleanSocket',
-            blender_property='use_envelope_multiply', default_value=False,),
+        bl_idname='FloatPositiveSocket', category='Deform',
+        blender_property='envelope_distance', default_value=0,
+        is_input=True, prop_owner_group='edit_bone', is_animatable=False,),
     EnvelopeWeightTemplate := replace(EnvelopeDistanceTemplate,
     EnvelopeWeightTemplate := replace(EnvelopeDistanceTemplate,
+        name='Envelope Weight', bl_idname = 'FloatFactorSocket',
+        blender_property='envelope_weight', default_value=1.0,
+        prop_owner_group='edit_bone', is_animatable=False,),
+    EnvelopeMultiplyTemplate := replace(EnvelopeDistanceTemplate,
+        name='Envelope Multiply', bl_idname = 'BooleanSocket',
+        blender_property='use_envelope_multiply', default_value=False,
+        prop_owner_group='edit_bone', is_animatable=False,),
+    EnvelopeHeadRadiusTemplate := replace(EnvelopeDistanceTemplate,
         name='Envelope Head Radius', blender_property='head_radius', default_value=0.0,),
         name='Envelope Head Radius', blender_property='head_radius', default_value=0.0,),
-    EnvelopeWeightTemplate := replace(EnvelopeDistanceTemplate,
+    EnvelopeTailRadiusTemplate := replace(EnvelopeDistanceTemplate,
         name='Envelope Tail Radius', blender_property='tail_radius', default_value=0.0,),
         name='Envelope Tail Radius', blender_property='tail_radius', default_value=0.0,),
     # BBone Stuff
     # BBone Stuff
     BBoneSegmentsTemplate := SockTemplate(name="BBone Segments", is_input=True,
     BBoneSegmentsTemplate := SockTemplate(name="BBone Segments", is_input=True,
         bl_idname='UnsignedIntSocket', category = 'bbone',
         bl_idname='UnsignedIntSocket', category = 'bbone',
-        blender_property='bbone_segments', default_value=1 ),
+        blender_property='bbone_segments', default_value=1,
+        prop_owner_group='bone', ),
     BBoneXSizeTemplate := replace(BBoneSegmentsTemplate, name='BBone X Size',
     BBoneXSizeTemplate := replace(BBoneSegmentsTemplate, name='BBone X Size',
-        bl_idname='FloatSocket', blender_property='bbone_x', default_value=0.0025, ),
+        bl_idname='FloatSocket', blender_property='bbone_x', default_value=0.0025,
+        prop_owner_group='bone',),
     BBoneZSizeTemplate := replace(BBoneXSizeTemplate, name='BBone Z Size',
     BBoneZSizeTemplate := replace(BBoneXSizeTemplate, name='BBone Z Size',
         blender_property='bbone_z', ),
         blender_property='bbone_z', ),
     BBoneHQDeformation := replace(BBoneSegmentsTemplate, name='BBone HQ Deformation',
     BBoneHQDeformation := replace(BBoneSegmentsTemplate, name='BBone HQ Deformation',
-        bl_idname='BooleanSocket', blender_property='', default_value=None ),
+        bl_idname='BooleanSocket', blender_property='', default_value=None, is_animatable=False, ),
     BBoneXCurveInTemplate := replace( BBoneXSizeTemplate, name="BBone X Curve-In",
     BBoneXCurveInTemplate := replace( BBoneXSizeTemplate, name="BBone X Curve-In",
-        bl_idname='FloatSocket', blender_property='bbone_curveinx', default_value=0.0, ),
+        bl_idname='FloatSocket', blender_property='bbone_curveinx', default_value=0.0,
+        prop_owner_group='bone',),
     BBoneZCurveInTemplate := replace(BBoneXCurveInTemplate, name="BBone Z Curve-In",
     BBoneZCurveInTemplate := replace(BBoneXCurveInTemplate, name="BBone Z Curve-In",
-        blender_property='bbone_curveinz', ),
+        blender_property='bbone_curveinz', prop_owner_group='bone', ),
     BBoneXCurveOutTemplate := replace(BBoneXCurveInTemplate, name="BBone X Curve-Out",
     BBoneXCurveOutTemplate := replace(BBoneXCurveInTemplate, name="BBone X Curve-Out",
-        blender_property='bbone_curveoutx', ),
+                                      default_value=0.0,
+        blender_property='bbone_curveoutx', prop_owner_group='bone', ),
     BBoneZCurveOutTemplate := replace(BBoneXCurveInTemplate, name="BBone Z Curve-Out",
     BBoneZCurveOutTemplate := replace(BBoneXCurveInTemplate, name="BBone Z Curve-Out",
-        blender_property='bbone_curveoutz', ),  # I'm tired of assigning variables, not gonna bother anymore lol
-                                                # it's just a conincidence that a lot of these are also unimplemented
-    replace(BBoneXCurveInTemplate, name="BBone Roll-In", blender_property='bbone_rollin', ), # CURRENTLY UNIMPLEMENTED
-    replace(BBoneXCurveInTemplate, name="BBone Roll-Out", blender_property='bbone_rollout', ), # CURRENTLY UNIMPLEMENTED
-    replace(BBoneXCurveInTemplate, name="BBone Inherit End Roll",
-            bl_idname='BooleanSocket', default_value=False,), # CURRENTLY UNIMPLEMENTED
-    replace(BBoneXCurveInTemplate, name="BBone Scale-In",
-            bl_idname='VectorSocket', default_value=(0.0,0.0,0.0),), # CURRENTLY DOESN'T WORK
-    replace(BBoneXCurveInTemplate, name="BBone Scale-Out",
-            bl_idname='VectorSocket', default_value=(0.0,0.0,0.0)), # CURRENTLY DOESN'T WORK
-    replace(BBoneXCurveInTemplate, name="BBone Ease-In",), # CURRENTLY DOESN'T WORK
-    replace(BBoneXCurveInTemplate, name="BBone Ease-Out",), # CURRENTLY DOESN'T WORK
-    replace(BBoneXCurveInTemplate, name="BBone Easing",
-            bl_idname='BooleanSocket', default_value=False,), # CURRENTLY DOESN'T WORK
-    replace(BBoneXCurveInTemplate, name="BBone Start Handle Type",
+        blender_property='bbone_curveoutz', prop_owner_group='bone', ), 
+    replace(bbone_template, name="BBone Roll-In",
+            default_value = 0.0,
+            blender_property='bbone_rollin',),
+    replace(bbone_template, name="BBone Roll-Out", blender_property='bbone_rollout', ),
+    replace(bbone_template, name="BBone Inherit End Roll",
+            bl_idname='BooleanSocket', default_value=False,
+            blender_property = 'use_endroll_as_inroll',
+            is_animatable=False, prop_owner_group='bone',),
+    replace(bbone_template, name="BBone Scale-In",
+            bl_idname='VectorSocket', default_value=(1.0,1.0,1.0),
+            blender_property = 'bbone_scalein'),
+    replace(bbone_template, name="BBone Scale-Out",
+            bl_idname='VectorSocket', default_value=(1.0,1.0,1.0),
+            blender_property = 'bbone_scaleout'),
+    replace(bbone_template, name="BBone Ease-In",
+            blender_property = 'bbone_easein'),
+    replace(bbone_template, name="BBone Ease-Out",
+            blender_property = 'bbone_easeout'),
+    replace(bbone_template, name="BBone Easing",
+            bl_idname='BooleanSocket', default_value=False,
+            blender_property='use_scale_easing',  prop_owner_group='bone',),
+    replace(bbone_template, name="BBone Start Handle Type",
             bl_idname="EnumBBoneHandleType",
             bl_idname="EnumBBoneHandleType",
-            blender_property='bbone_handle_type_start', default_value=None, ),
-    replace(BBoneXCurveInTemplate, name="BBone Custom Start Handle",
+            blender_property='bbone_handle_type_start', default_value=None,
+            prop_owner_group='bone', is_animatable=False,),
+    replace(bbone_template, name="BBone Custom Start Handle",
             bl_idname="StringSocket",
             bl_idname="StringSocket",
-            blender_property='bbone_custom_handle_start', default_value=None, ),
-    replace(BBoneXCurveInTemplate, name="BBone Start Handle Scale",
+            blender_property=None, # not handled by this system
+            default_value=None, is_animatable=False,),
+    replace(bbone_template, name="BBone Start Handle Scale",
             bl_idname="BooleanThreeTupleSocket",
             bl_idname="BooleanThreeTupleSocket",
-            blender_property='bbone_handle_use_scale_start', default_value=None, ),
-    replace(BBoneXCurveInTemplate, name="BBone Start Handle Ease",
-            bl_idname='BooleanSocket', default_value=False),# CURRENTLY DOESN'T WORK),
-    replace(BBoneXCurveInTemplate, name="BBone End Handle Type",
+            default_value = (False, False, False),
+            blender_property='bbone_handle_use_scale_start',
+            prop_owner_group='bone', ),
+    replace(bbone_template, name="BBone Start Handle Ease",
+            bl_idname='BooleanSocket', default_value=False,
+            blender_property = 'bbone_handle_use_ease_start',
+            prop_owner_group='bone',),
+    replace(bbone_template, name="BBone End Handle Type",
             bl_idname="EnumBBoneHandleType",
             bl_idname="EnumBBoneHandleType",
-            blender_property='bbone_handle_type_end', default_value=None, ),
-    replace(BBoneXCurveInTemplate, name="BBone Custom End Handle",
+            blender_property='bbone_handle_type_end', default_value=None,
+            prop_owner_group='bone', is_animatable=False,),
+    replace(bbone_template, name="BBone Custom End Handle",
             bl_idname="StringSocket",
             bl_idname="StringSocket",
-            blender_property='bbone_custom_handle_end', default_value=None, ),
-    replace(BBoneXCurveInTemplate, name="BBone End Handle Scale",
+            blender_property=None, # not handled by this system
+            default_value=None, is_animatable=False,),
+    replace(bbone_template, name="BBone End Handle Scale",
             bl_idname="BooleanThreeTupleSocket",
             bl_idname="BooleanThreeTupleSocket",
+            default_value= (False, False, False),
             blender_property='bbone_handle_use_scale_end',
             blender_property='bbone_handle_use_scale_end',
-            default_value= (False, False, False)),
-    replace(BBoneXCurveInTemplate, name="BBone End Handle Ease",
-            bl_idname='BooleanSocket', default_value=False),# CURRENTLY DOESN'T WORK),
+            prop_owner_group='bone',),
+    replace(bbone_template, name="BBone End Handle Ease",
+            bl_idname='BooleanSocket', default_value=False,
+            blender_property = 'bbone_handle_use_ease_end',
+            prop_owner_group='bone',),
     # locks
     # locks
     LockLocationTemplate := SockTemplate(name="Lock Location",
     LockLocationTemplate := SockTemplate(name="Lock Location",
         is_input=True, bl_idname='BooleanThreeTupleSocket', category = 'lock',
         is_input=True, bl_idname='BooleanThreeTupleSocket', category = 'lock',
-        blender_property='lock_location', default_value=[True, True, True] ),
+        blender_property='lock_location', default_value=[True, True, True],
+        prop_owner_group='pose_bone', ),
     LockRotationTemplate := replace(LockLocationTemplate, name="Lock Rotation",
     LockRotationTemplate := replace(LockLocationTemplate, name="Lock Rotation",
         blender_property='lock_rotation',),
         blender_property='lock_rotation',),
     LockRotationTemplate := replace(LockLocationTemplate, name="Lock Scale",
     LockRotationTemplate := replace(LockLocationTemplate, name="Lock Scale",
         blender_property='lock_scale',),
         blender_property='lock_scale',),
     # hide
     # hide
-    replace(HideTemplate, name='Hide', category='always_show',
-            blender_property='hide', default_value=False,),
+    bone_hide_template,
     CustomPropsTemplate,
     CustomPropsTemplate,
     # Output
     # Output
     xFormOutTemplate,
     xFormOutTemplate,
-]
+]