1
0

40 Commits 289b667d5a ... 7251a054fc

Autor SHA1 Nachricht Datum
  Joseph Brandenburg 7251a054fc Fix: Spline IK broken vor 2 Wochen
  Joseph Brandenburg ca5229b5c8 Fix: lazy parents broken vor 2 Wochen
  Joseph Brandenburg 234a1fba6b cleanup xForm get_parent_node vor 2 Wochen
  Joseph Brandenburg 38acf70638 Fix: Correctly Sort Links to Group Arrays vor 2 Wochen
  Joseph Brandenburg ce5f91bdb9 Fix: remove many instances of hardcoded node get vor 2 Wochen
  Joseph Brandenburg 0b6fe5d16a clean up useless prints vor 2 Wochen
  Joseph Brandenburg 3c5578c8f6 GroupInterfaceNodes at group in/out vor 2 Wochen
  Brandenburg 7267ed0429 WIP: Route Group I/O through interface nodes vor 2 Wochen
  Joseph Brandenburg 82bef638e8 Initialize Tree with correct version number vor 3 Wochen
  Joseph Brandenburg 6f7b47a52c Fix: make default values work with more socket types vor 3 Wochen
  Joseph Brandenburg 4ef2f84adb Fix: interface panel doesn't have an identifier vor 3 Wochen
  Joseph Brandenburg 781a3b11db New Feature: Default Values for base tree vor 3 Wochen
  Joseph Brandenburg fc4f0acc53 Fix: correct default value type for vectors vor 3 Wochen
  Joseph Brandenburg ae742144db Fix: default value disabled for Matrix vor 3 Wochen
  Joseph Brandenburg 8473d87d04 Disable "Connected To" feature vor 3 Wochen
  Joseph Brandenburg 8a2b0c8b36 Implement Custom Interface Classes vor 3 Wochen
  Joseph Brandenburg e70192469c initial versioning for new interface classes vor 3 Wochen
  Joseph Brandenburg ebd7b7fd65 fix: unbound local error when updating group interface vor 3 Wochen
  Joseph Brandenburg 161e3b37eb update interface draw for correct UI and clarity vor 3 Wochen
  Joseph Brandenburg b17bde021e Interface Classes set the multi and default value now vor 3 Wochen
  Joseph Brandenburg 252574a681 Add Custom Interface Socket Types vor 3 Wochen
  Joseph Brandenburg 0eb22ed488 v 0.12.27 bug fix release vor 2 Wochen
  Joseph Brandenburg 003e6b573a Fix: Nested Choose fails when linked to group output vor 2 Wochen
  Joseph Brandenburg cac82c21f8 Cleanup Drivers vor 2 Wochen
  Joseph Brandenburg 289b667d5a GroupInterfaceNodes at group in/out vor 2 Wochen
  Brandenburg 3968312363 WIP: Route Group I/O through interface nodes vor 2 Wochen
  Joseph Brandenburg ddaefc93f5 Initialize Tree with correct version number vor 3 Wochen
  Joseph Brandenburg be2dff439b Fix: make default values work with more socket types vor 3 Wochen
  Joseph Brandenburg 8c945311a8 Fix: interface panel doesn't have an identifier vor 3 Wochen
  Joseph Brandenburg e1e6865ba8 New Feature: Default Values for base tree vor 3 Wochen
  Joseph Brandenburg f505903137 Fix: correct default value type for vectors vor 3 Wochen
  Joseph Brandenburg 320e929160 Fix: default value disabled for Matrix vor 3 Wochen
  Joseph Brandenburg 87d4515f2f Disable "Connected To" feature vor 3 Wochen
  Joseph Brandenburg 80ca472129 Implement Custom Interface Classes vor 3 Wochen
  Joseph Brandenburg 83415c6b5d Cleanup Drivers vor 2 Wochen
  Joseph Brandenburg e40d0fdc9c initial versioning for new interface classes vor 3 Wochen
  Joseph Brandenburg 4845445001 fix: unbound local error when updating group interface vor 3 Wochen
  Joseph Brandenburg 37976aaffb update interface draw for correct UI and clarity vor 3 Wochen
  Joseph Brandenburg fa05172de2 Interface Classes set the multi and default value now vor 3 Wochen
  Joseph Brandenburg dbb1998052 Add Custom Interface Socket Types vor 3 Wochen
7 geänderte Dateien mit 147 neuen und 120 gelöschten Zeilen
  1. 8 7
      base_definitions.py
  2. 46 35
      link_nodes.py
  3. 2 1
      misc_nodes.py
  4. 2 3
      node_container_common.py
  5. 55 50
      readtree.py
  6. 18 10
      schema_solve.py
  7. 16 14
      xForm_nodes.py

+ 8 - 7
base_definitions.py

@@ -13,6 +13,7 @@ from .utilities import (prRed, prGreen, prPurple, prWhite,
 from .utilities import get_socket_maps, relink_socket_map, do_relink
 
 FLOAT_EPSILON=0.0001 # used to check against floating point inaccuracy
+links_sort_key= lambda a : (-a.multi_input_sort_id, -a.sub_sort_id)
 
 def TellClasses():
     #Why use a function to do this? Because I don't need every class to register.
@@ -900,7 +901,6 @@ class MantisNode:
            nodes are discovered, the method is called by each node in dependency order.
            The first argument MUST be the name of the method as a string.
         """
-        prGreen(self)
         if args[0] == 'call_on_all_ancestors': raise RuntimeError("Very funny!")
         from .utilities import get_all_dependencies
         from collections import deque
@@ -911,7 +911,6 @@ class MantisNode:
         solved = set()
         while can_solve:
             node = can_solve.pop()
-            print(node)
             method = getattr(node, args[0])
             method(*args[0:], **kwargs)
             solved.add(node)
@@ -1008,7 +1007,7 @@ class NodeLink:
     to_node = None
     to_socket = None
 
-    def __init__(self, from_node, from_socket, to_node, to_socket, multi_input_sort_id=0):
+    def __init__(self, from_node, from_socket, to_node, to_socket, multi_input_sort_id=0, sub_sort_id=0):
         if from_node.signature == to_node.signature:
             raise RuntimeError("Cannot connect a node to itself.")
         self.from_node = from_node
@@ -1017,7 +1016,8 @@ class NodeLink:
         self.to_socket = to_socket
         self.from_node.outputs[self.from_socket].links.append(self)
         # it is the responsibility of the node that uses these links to sort them correctly based on the sort_id
-        self.multi_input_sort_id = multi_input_sort_id
+        self.multi_input_sort_id = multi_input_sort_id # this is the sort_id of the link in the UI
+        self.sub_sort_id = sub_sort_id # this is for sorting within a  bundled link (one link in the UI)
         self.to_node.inputs[self.to_socket].links.append(self)
         self.is_hierarchy = detect_hierarchy_link(from_node, from_socket, to_node, to_socket,)
         self.is_alive = True
@@ -1066,7 +1066,7 @@ class NodeSocket:
         if (traverse_target):
             self.can_traverse = True
 
-    def connect(self, node, socket, sort_id=0):
+    def connect(self, node, socket, sort_id=0, sub_sort_id=0):
         if  (self.is_input):
             to_node   = self.node; from_node = node
             to_socket = self.name; from_socket = socket
@@ -1083,7 +1083,8 @@ class NodeSocket:
                 from_socket,
                 to_node,
                 to_socket,
-                sort_id)
+                sort_id,
+                sub_sort_id)
         return new_link
 
     def set_traverse_target(self, traverse_target):
@@ -1093,7 +1094,7 @@ class NodeSocket:
     def flush_links(self):
         """ Removes dead links from this socket."""
         self.links = [l for l in self.links if l.is_alive]
-        self.links.sort(key=lambda a : -a.multi_input_sort_id)
+        self.links.sort(key=links_sort_key)
         self.is_linked = bool(self.links)
 
     @property

+ 46 - 35
link_nodes.py

@@ -53,7 +53,10 @@ class MantisLinkNode(MantisNode):
         if ('Target' in input_name) and input_name not in  ["Target Space", "Use Target Z"]:
             socket = self.inputs.get(input_name)
             if socket.is_linked:
-                return socket.links[0].from_node
+                node_line, _last_socket = trace_single_line(self, input_name, index)
+                for other_node in node_line:
+                    if other_node.node_type == 'XFORM':
+                        return other_node
             return None
 
         else:
@@ -61,28 +64,46 @@ class MantisLinkNode(MantisNode):
 
     def gen_property_socket_map(self) -> dict:
         props_sockets = super().gen_property_socket_map()
-        if (os := self.inputs.get("Owner Space")) and os.is_connected and os.links[0].from_node.node_type == 'XFORM':
-            del props_sockets['owner_space']
-        if (ts := self.inputs.get("Target Space")) and ts.is_connected and ts.links[0].from_node.node_type == 'XFORM':
-            del props_sockets['target_space']
+        if (os := self.inputs.get("Owner Space")) and os.is_connected:
+            node_line, _last_socket = trace_single_line(self, "Owner Space")
+            for other_node in node_line:
+                if other_node.node_type == 'XFORM':
+                    del props_sockets['owner_space']; break
+        if (ts := self.inputs.get("Target Space")) and ts.is_connected:
+            node_line, _last_socket = trace_single_line(self, "Target Space")
+            for other_node in node_line:
+                if other_node.node_type == 'XFORM':
+                    del props_sockets['target_space']; break
         return props_sockets
 
     def set_custom_space(self):
         for c in self.bObject:
-            if (os := self.inputs.get("Owner Space")) and os.is_connected and os.links[0].from_node.node_type == 'XFORM':
-                c.owner_space='CUSTOM'
-                xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
-                if isinstance(xf, Bone):
-                    c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
-                else:
-                    c.space_object=xf
-            if ts := self.inputs.get("Target_Space") and ts.is_connected and ts.links[0].from_node.node_type == 'XFORM':
-                c.target_space='CUSTOM'
-                xf = self.inputs["Target_Space Space"].links[0].from_node.bGetObject(mode="OBJECT")
-                if isinstance(xf, Bone):
-                    c.space_object=self.inputs["Target_Space Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
-                else:
-                    c.space_object=xf
+            if (os := self.inputs.get("Owner Space")) and os.is_connected:
+                node_line, _last_socket = trace_single_line(self, "Owner Space")
+                owner_space_target = None
+                for other_node in node_line:
+                    if other_node.node_type == 'XFORM':
+                        owner_space_target = other_node; break
+                if owner_space_target:
+                    c.owner_space='CUSTOM'
+                    xf = owner_space_target.bGetObject(mode="OBJECT")
+                    if isinstance(xf, Bone):
+                        c.space_object=owner_space_target.bGetParentArmature(); c.space_subtarget=xf.name
+                    else:
+                        c.space_object=xf
+            if (ts := self.inputs.get("Target Space")) and ts.is_connected:
+                node_line, _last_socket = trace_single_line(self, "Target Space")
+                target_space_target = None
+                for other_node in node_line:
+                    if other_node.node_type == 'XFORM':
+                        target_space_target = other_node; break
+                if target_space_target:
+                    c.target_space='CUSTOM'
+                    xf = target_space_target.bGetObject(mode="OBJECT")
+                    if isinstance(xf, Bone):
+                        c.space_object=target_space_target.bGetParentArmature(); c.space_subtarget=xf.name
+                    else:
+                        c.space_object=xf
 
     def GetxForm(nc, output_name="Output Relationship"):
         break_condition= lambda node : node.node_type=='XFORM'
@@ -205,20 +226,7 @@ class LinkCopyScale(MantisLinkNode):
             if constraint_name := self.evaluate_input("Name"):
                 c.name = constraint_name
             self.bObject.append(c)
-            if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
-                c.owner_space='CUSTOM'
-                xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
-                if isinstance(xf, Bone):
-                    c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
-                else:
-                    c.space_object=xf
-            if self.inputs["Target Space"].is_connected and self.inputs["Target Space"].links[0].from_node.node_type == 'XFORM':
-                c.target_space='CUSTOM'
-                xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
-                if isinstance(xf, Bone):
-                    c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
-                else:
-                    c.space_object=xf
+            self.set_custom_space()
             props_sockets = self.gen_property_socket_map()
             evaluate_sockets(self, c, props_sockets)
         self.executed = True
@@ -804,7 +812,6 @@ class LinkArmature(MantisLinkNode):
                     weight_value=0
                 targets_weights[i]=weight_value
                 props_sockets["targets[%d].weight" % i] = (weight_input_name, 0)
-                # targets_weights.append({"weight":(weight_input_name, 0)})
             evaluate_sockets(self, c, props_sockets)
             for target, value in targets_weights.items():
                 c.targets[target].weight=value
@@ -829,8 +836,12 @@ class LinkSplineIK(MantisLinkNode):
             c = xf.bGetObject().constraints.new('SPLINE_IK')
             # set the spline - we need to get the right one
             spline_index = self.evaluate_input("Spline Index")
+            proto_curve = None
+            node_line, _last_socket = trace_single_line(self, "Target")
+            for other_node in node_line: # trace and get the input
+                if other_node.node_type == 'XFORM':
+                    proto_curve = other_node.bGetObject(); break
             from .utilities import get_extracted_spline_object
-            proto_curve = self.inputs['Target'].links[0].from_node.bGetObject()
             curve = get_extracted_spline_object(proto_curve, spline_index, self.mContext)
             # link it to the view layer
             if (curve.name not in bContext.view_layer.active_layer_collection.collection.objects):

+ 2 - 1
misc_nodes.py

@@ -1935,13 +1935,14 @@ class UtilityArrayGet(MantisNode):
         self.rerouted=[]
 
     def bPrepare(self, bContext = None,):
+        from .base_definitions import links_sort_key
         if len(self.rerouted)>0:
             self.prepared, self.executed = True, True
             return #Either it is already done or it doesn't matter.
         elif self.prepared == False:
             # sort the array entries
             for inp in self.inputs.values():
-                inp.links.sort(key=lambda a : -a.multi_input_sort_id)
+                inp.links.sort(key=links_sort_key)
             oob   = self.evaluate_input("OoB Behaviour")
             index = self.evaluate_input("Index")
 

+ 2 - 3
node_container_common.py

@@ -19,7 +19,6 @@ def get_socket_value(node_socket):
 
 # TODO: modify this to work with multi-input nodes
 def trace_single_line(node_container, input_name, link_index=0):
-    # DO: refactor this for new link class
     """Traces a line to its input."""
     nodes = [node_container]
     # Trace a single line
@@ -231,8 +230,8 @@ def evaluate_sockets(nc, b_object, props_sockets,):
 def finish_driver(nc, b_object, driver_item, prop):
     # prWhite(nc, prop)
     index = driver_item[1]; driver_sock = driver_item[0]
-    driver_trace = trace_single_line(nc, driver_sock)
-    driver_provider, driver_socket = driver_trace[0][-1], driver_trace[1]
+    node_line, last_socket = trace_single_line(nc, driver_sock)
+    driver_provider, driver_socket = node_line[-1], last_socket
     if index is not None:
         driver = driver_provider.parameters[driver_socket.name][index].copy()
         # this is harmless and necessary for the weird ones where the property is a vector too

+ 55 - 50
readtree.py

@@ -2,30 +2,48 @@ from .utilities import prRed, prGreen, prPurple, prWhite, prOrange, \
                         wrapRed, wrapGreen, wrapPurple, wrapWhite, wrapOrange
 
 
-
-# we need to reroute the incoming links to a new GroupInterface node
-# then we need to reroute the outgoing links from the GroupInterface
-# down into the tree -  or visa versa (NodeGroupOutput input to Group output)
+# this function is kind of confusing and is very important,
+# so it bears a full explanation: its purpose is to connect
+# the links going into a group to the nodes in that group.
+# FIRST we connect all the incoming links into the Group Node to
+# a Group Interface node that does nothing but mark the entrance.
+# Then, we connect all the outgoing links back to the nodes
+# that had incoming links, so the nodes OUTSIDE the Node Group
+# are connected directly to BOTH the GroupInterface and the
+# nodes INSIDE the node group.
+# we give the GroupInterface nodes an obscenely high
+# multi_input_sort_id so that they are always last.
+# but since these links are going IN, they shouldn't cause any problems.
+# the sub_sort_id is set here in case there are UI links which represent
+# multiple Mantis links - the mantis links are sorted within the UI links
+# and the UI links are sorted as normal, so all the links are in the right
+# order.... probably. BUG here?
+# I want the Group Interface nodes to be part of the hierarchy...
+# but I want to cut the links. hmmm what to do? Fix it if it causes problems.
+# solution to hypothetical BUG could be to do traversal on the links
+# instead of the sockets.
 def grp_node_reroute_common(in_node, out_node, interface):
+    from .base_definitions import links_sort_key
     for in_node_input in in_node.inputs:
+        i = 0
+        if len(in_node_input.links)>1: # sort here to ensure correct sub_sort_id
+            in_node_input.links.sort(key=links_sort_key)
         while (in_node_input.links):
             in_link = in_node_input.links.pop()
             from_node = in_link.from_node; from_socket = in_link.from_socket
-            # the inputs/outputs on the group and in/out nodes are IDs
-            from_node.outputs[from_socket].connect(
-                interface,in_node_input.name, sort_id = in_link.multi_input_sort_id)
-            in_link.die()
-            init_connections(from_node)
+            link = from_node.outputs[from_socket].connect(
+                interface,in_node_input.name, sort_id = 2**16, sub_sort_id=i)
+            i += 1; in_link.die()
     for out_node_output in out_node.outputs:
         while (out_node_output.links):
             out_link = out_node_output.links.pop()
             to_node = out_link.to_node; to_socket = out_link.to_socket
-            for l in interface.inputs[out_node_output.name].links:
-                interface.outputs[out_node_output.name].connect(
-                    to_node, to_socket, sort_id = l.multi_input_sort_id)
+            for j, l in enumerate(interface.inputs[out_node_output.name].links):
+                # we are connecting the link from the ORIGINAL output to the FINAL input.
+                link = l.from_node.outputs[l.from_socket].connect(
+                    to_node, to_socket, sort_id = out_link.multi_input_sort_id)
+                link.sub_sort_id = j
             out_link.die()
-            init_dependencies(to_node)
-    init_dependencies(interface); init_connections(interface)
 
 def reroute_links_grp(group, all_nodes):
     from .internal_containers import GroupInterface
@@ -52,44 +70,35 @@ def reroute_links_grpout(group_output, all_nodes):
                  "(maybe you are running the tree from inside a node group?)")
 
 # FIXME I don't think these signatures are unique.
+# TODO this is a really silly and bad and also really dumb way to do this
 def insert_lazy_parents(nc):
     from .link_nodes import LinkInherit
-    from .base_definitions import NodeLink
     inherit_nc = None
     if nc.inputs["Relationship"].is_connected:
-        link = nc.inputs["Relationship"].links[0]
-        from_nc = link.from_node
-        if from_nc.node_type in ["XFORM"] and link.from_socket in ["xForm Out"]:
+        from .node_container_common import trace_single_line
+        node_line, last_socket = trace_single_line(nc, 'Relationship')
+        # if last_socket is from a valid XFORM, it is the relationship in
+        # because it was traversed from the xForm Out... so get the traverse target.
+        if last_socket.traverse_target is None:
+            return # this is not a valid lazy parent.
+        for other_node in node_line[1:]: # skip the first one, it is the same node
+            if other_node.node_type == 'LINK':
+                return # this one has a realtionship connection.
+            elif other_node.node_type == 'XFORM':
+                break
+        if other_node.node_type in ["XFORM"] and last_socket.traverse_target.name in ["xForm Out"]:
+            for link in other_node.outputs['xForm Out'].links:
+                if link.to_node == nc: link.die()
             inherit_nc = LinkInherit(("MANTIS_AUTOGENERATED", *nc.signature[1:], "LAZY_INHERIT"), nc.base_tree)
-            for from_link in from_nc.outputs["xForm Out"].links:
-                if from_link.to_node == nc and from_link.to_socket == "Relationship":
-                    break # this is it
-            from_link.to_node = inherit_nc; from_link.to_socket="Parent"
-            from_link.to_node.inputs[from_link.to_socket].is_linked=True
-
-            links=[]
-            while (nc.inputs["Relationship"].links):
-                to_link = nc.inputs["Relationship"].links.pop()
-                if to_link.from_node == from_nc and to_link.from_socket == "xForm Out":
-                    continue # don't keep this one
-                links.append(to_link)
-                to_link.from_node.outputs[from_link.from_socket].is_linked=True
-
-            nc.inputs["Relationship"].links=links
-            link=NodeLink(from_node=inherit_nc, from_socket="Inheritance", to_node=nc, to_socket="Relationship")
-            inherit_nc.inputs["Parent"].links.append(from_link)
-
-            inherit_nc.parameters = {
-                                     "Parent":None,
-                                     "Inherit Rotation":True,
-                                     "Inherit Scale":'FULL',
-                                     "Connected":False,
-                                    }
+            l = other_node.outputs['xForm Out'].connect(inherit_nc, 'Parent')
+            l1 = inherit_nc.outputs['Inheritance'].connect(nc, 'Relationship')
+            inherit_nc.parameters = { "Parent":None,
+                                      "Inherit Rotation":True,
+                                      "Inherit Scale":'FULL',
+                                      "Connected":False, }
             # because the from node may have already been done.
-            init_connections(from_nc)
-            init_dependencies(from_nc)
-            init_connections(inherit_nc)
-            init_dependencies(inherit_nc)
+            init_connections(other_node); init_dependencies(other_node)
+            init_connections(inherit_nc); init_dependencies(inherit_nc)
     return inherit_nc
 
 # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** #
@@ -377,8 +386,6 @@ def parse_tree(base_tree, error_popups=False):
             dummy.reroute_links(dummy, all_mantis_nodes)
     prGreen(f"Pulling data from tree took {time.time() - data_start_time} seconds")
 
-    # base_tree.parsed_tree = all_mantis_nodes # for debugging
-
     start_time = time.time()
     solve_only_these = []; solve_only_these.extend(list(all_schema.values()))
     roots, array_nodes = [], []
@@ -661,7 +668,6 @@ def execute_tree(nodes, base_tree, context, error_popups = False):
                     raise e
                 execution_failed = True; break
 
-
         switch_mode(mode='OBJECT', objects=switch_me)
         # switch to pose mode here so that the nodes can use the final pose data
         # this will require them to update the depsgraph.
@@ -678,7 +684,6 @@ def execute_tree(nodes, base_tree, context, error_popups = False):
                     raise e
                 execution_failed = True; break
 
-
         # REST pose for deformer bind, so everything is in the rest position
         for ob in switch_me:
             ob.data.pose_position = 'REST'

+ 18 - 10
schema_solve.py

@@ -4,7 +4,7 @@ from .utilities import (prRed, prGreen, prPurple, prWhite,
                               wrapOrange,)
 from .utilities import init_connections, init_dependencies, get_link_in_out
 from .base_definitions import (SchemaUINode, custom_props_types, \
-    MantisNodeGroup, SchemaGroup, replace_types, GraphError)
+    MantisNodeGroup, SchemaGroup, replace_types, GraphError, links_sort_key)
 from .node_container_common import setup_custom_props_from_np
 # a class that solves Schema nodes
 from bpy.types import NodeGroupInput, NodeGroupOutput
@@ -61,7 +61,7 @@ class SchemaSolver:
         # Sort the multi-input nodes in reverse order of ID, this ensures that they are
         #   read in the order they were created
         for inp in self.node.inputs.values():
-            inp.links.sort(key=lambda a : -a.multi_input_sort_id)
+            inp.links.sort(key=links_sort_key)
 
         from bpy.types import NodeGroupInput, NodeGroupOutput
         for ui_node in self.tree.nodes:
@@ -313,7 +313,7 @@ class SchemaSolver:
         from_node = self.schema_nodes[(*self.node.ui_signature, from_ui_node.bl_idname)]
         from collections import deque
         unprepared = deque(from_node.hierarchy_dependencies)
-        self.prepare_nodes(unprepared)
+        self.prepare_nodes(unprepared, frame_mantis_nodes)
         from .utilities import cap, wrap
         get_index = from_node.evaluate_input("Index", self.index) # get the most recent link
         # getting the link at self.index just saves the trouble of killing the old links
@@ -386,7 +386,8 @@ class SchemaSolver:
             to_socket_name=ui_link.to_socket.name
             if to_node.node_type in ['DUMMY_SCHEMA']:
                 to_socket_name=ui_link.to_socket.identifier
-            connection = NodeLink(l.from_node, l.from_socket, to_node, to_socket_name, l.multi_input_sort_id)
+            connection = NodeLink(l.from_node, l.from_socket, to_node, to_socket_name,
+                                  l.multi_input_sort_id, l.sub_sort_id)
             to_node.flush_links()
 
     def handle_link_to_constant_output(self, frame_mantis_nodes, index, ui_link,  to_ui_node):
@@ -514,15 +515,22 @@ class SchemaSolver:
                 return False
         return True
 
-    def prepare_nodes(self, unprepared):
+    def prepare_nodes(self, unprepared, frame_nodes):
         # At this point, we've already run a pretty exhaustive preperation phase to prep the schema's dependencies
         # So we should not need to add any new dependencies unless there is a bug elsewhere.
         # and in fact, I could skip this in some cases, and should investigate if profiling reveals a slowdown here.
-        forbidden=set()
-        e = None
-        # forbid some nodes - they aren't necessary to solve the schema & cause problems.
+        from .misc_nodes import UtilityChoose, UtilityKDChoosePoint, UtilityKDChooseXForm
+        forbidden=set() # forbid some nodes - they aren't necessary to solve the schema & cause problems.
         while unprepared:
             nc = unprepared.pop()
+            if isinstance(nc, (UtilityChoose, UtilityKDChoosePoint, UtilityKDChooseXForm)): 
+                can_skip=True                 # NOTE: this bug can also be fixed by adding
+                for output in nc.outputs:     # a no-op node between the choose and the output.
+                    for l in output.links:    # try that instead if this causes problems.
+                        if l.to_node in frame_nodes.values():
+                            can_skip = False; break
+                    if can_skip == False: break # don't keep looking
+                if can_skip: continue # do nothing because its links are not ready.
             if nc.node_type == 'DUMMY_SCHEMA' and not self.test_is_sub_schema(nc):
                 forbidden.add(nc) # do NOT add this as a dependency.
             if nc in forbidden: continue # trying to resolve dependencies for.
@@ -670,7 +678,7 @@ class SchemaSolver:
             if node.node_type == 'DUMMY_SCHEMA' and (schema_len_in := node.inputs.get("Schema Length")):
                 for l in schema_len_in.links:
                     unprepared.append(l.from_node)
-            self.prepare_nodes(unprepared)
+            self.prepare_nodes(unprepared, frame_mantis_nodes)
 
         # We have to prepare the nodes leading to Array Input Get
         for ui_link in array_input_get_link:
@@ -700,7 +708,7 @@ class SchemaSolver:
                 unprepared.extend(from_node.hierarchy_dependencies)
             else:
                 raise RuntimeError(" 671 there has been an error parsing the tree. Please report this as a bug.")
-            self.prepare_nodes(unprepared) # prepare only the dependencies we need for this link
+            self.prepare_nodes(unprepared, frame_mantis_nodes) # prepare only the dependencies we need for this link
             # and handle the output by the specific type
             if isinstance(to_ui_node, (SchemaConstOutput, NodeGroupOutput)):
                 self.handle_link_to_constant_output(frame_mantis_nodes, self.index, ui_link,  to_ui_node)

+ 16 - 14
xForm_nodes.py

@@ -26,8 +26,7 @@ def reset_object_data(ob):
 def get_parent_node(node_container, type = 'XFORM'):
     # type variable for selecting whether to get either
     #   the parent xForm  or the inheritance node
-    node_line, socket = trace_single_line(node_container, "Relationship")
-    parent_nc = None
+    node_line, _last_socket = trace_single_line(node_container, "Relationship")
     for i in range(len(node_line)):
         # check each of the possible parent types.
         if ( (node_line[ i ].__class__.__name__ == 'LinkInherit') ):
@@ -269,16 +268,19 @@ class xFormBone(xFormNode):
     def bSetParent(self, eb):
         from bpy.types import EditBone
         parent_nc = get_parent_node(self, type='LINK')
+        if parent_nc is None:
+            raise RuntimeError(wrapRed(f"Cannot set parent for node {self}"))
         node_lines, _last_socket = trace_single_line(parent_nc, 'Parent')
-        if node_lines[-1].node_type == 'XFORM':
-            parent = node_lines[-1].bGetObject(mode = 'EDIT')
-            if isinstance(parent, EditBone): # otherwise, no need to do anything.
-                eb.parent = parent
-                eb.use_connect = parent_nc.evaluate_input("Connected")
-                eb.use_inherit_rotation = parent_nc.evaluate_input("Inherit Rotation")
-                eb.inherit_scale = parent_nc.evaluate_input("Inherit Scale")
+        for other_node in node_lines:
+            if isinstance(other_node, (xFormArmature, xFormBone)):
+                parent = other_node.bGetObject(mode = 'EDIT'); break
         else:
             raise RuntimeError(wrapRed(f"Cannot set parent for node {self}"))
+        if isinstance(parent, EditBone): # otherwise, no need to do anything.
+            eb.parent = parent
+            eb.use_connect = parent_nc.evaluate_input("Connected")
+            eb.use_inherit_rotation = parent_nc.evaluate_input("Inherit Rotation")
+            eb.inherit_scale = parent_nc.evaluate_input("Inherit Scale")
 
 
     def bPrepare(self, bContext=None):
@@ -431,9 +433,6 @@ class xFormBone(xFormNode):
             try:
                 if (custom_handle := self.evaluate_input("BBone Custom Start Handle")):
                     b.bbone_custom_handle_start = self.bGetParentArmature().data.bones[custom_handle]
-                # hypothetically we should support xForm inputs.... but we won't do that for now
-                # elif custom_handle is None:
-                #     b.bbone_custom_handle_start = self.inputs["BBone Custom Start Handle"].links[0].from_node.bGetObject().name
                 if (custom_handle := self.evaluate_input("BBone Custom End Handle")):
                     b.bbone_custom_handle_end = self.bGetParentArmature().data.bones[custom_handle]
             except KeyError:
@@ -843,8 +842,11 @@ class xFormCurvePin(xFormNode):
         for socket_name in ["Curve Pin Factor", "Forward Axis","Up Axis",]:
             if self.inputs.get(socket_name) is None: continue # in case it has been bypassed
             if self.inputs[socket_name].is_linked:
-                link = self.inputs[socket_name].links[0]
-                driver = link.from_node
+                node_line, _last_socket = trace_single_line(self, socket_name)
+                driver = None
+                for other_node in node_line:
+                    if other_node.node_type == 'DRIVER':
+                        driver = other_node; break
                 if isinstance(driver, UtilityDriver):
                     prop_amount = driver.evaluate_input("Property")
                 elif isinstance(driver, UtilitySwitch):