1
0

40 کامیت‌ها 289b667d5a ... 7251a054fc

نویسنده SHA1 پیام تاریخ
  Joseph Brandenburg 7251a054fc Fix: Spline IK broken 2 هفته پیش
  Joseph Brandenburg ca5229b5c8 Fix: lazy parents broken 2 هفته پیش
  Joseph Brandenburg 234a1fba6b cleanup xForm get_parent_node 2 هفته پیش
  Joseph Brandenburg 38acf70638 Fix: Correctly Sort Links to Group Arrays 2 هفته پیش
  Joseph Brandenburg ce5f91bdb9 Fix: remove many instances of hardcoded node get 2 هفته پیش
  Joseph Brandenburg 0b6fe5d16a clean up useless prints 2 هفته پیش
  Joseph Brandenburg 3c5578c8f6 GroupInterfaceNodes at group in/out 2 هفته پیش
  Brandenburg 7267ed0429 WIP: Route Group I/O through interface nodes 2 هفته پیش
  Joseph Brandenburg 82bef638e8 Initialize Tree with correct version number 3 هفته پیش
  Joseph Brandenburg 6f7b47a52c Fix: make default values work with more socket types 3 هفته پیش
  Joseph Brandenburg 4ef2f84adb Fix: interface panel doesn't have an identifier 3 هفته پیش
  Joseph Brandenburg 781a3b11db New Feature: Default Values for base tree 3 هفته پیش
  Joseph Brandenburg fc4f0acc53 Fix: correct default value type for vectors 3 هفته پیش
  Joseph Brandenburg ae742144db Fix: default value disabled for Matrix 3 هفته پیش
  Joseph Brandenburg 8473d87d04 Disable "Connected To" feature 3 هفته پیش
  Joseph Brandenburg 8a2b0c8b36 Implement Custom Interface Classes 3 هفته پیش
  Joseph Brandenburg e70192469c initial versioning for new interface classes 3 هفته پیش
  Joseph Brandenburg ebd7b7fd65 fix: unbound local error when updating group interface 3 هفته پیش
  Joseph Brandenburg 161e3b37eb update interface draw for correct UI and clarity 3 هفته پیش
  Joseph Brandenburg b17bde021e Interface Classes set the multi and default value now 3 هفته پیش
  Joseph Brandenburg 252574a681 Add Custom Interface Socket Types 3 هفته پیش
  Joseph Brandenburg 0eb22ed488 v 0.12.27 bug fix release 2 هفته پیش
  Joseph Brandenburg 003e6b573a Fix: Nested Choose fails when linked to group output 2 هفته پیش
  Joseph Brandenburg cac82c21f8 Cleanup Drivers 2 هفته پیش
  Joseph Brandenburg 289b667d5a GroupInterfaceNodes at group in/out 2 هفته پیش
  Brandenburg 3968312363 WIP: Route Group I/O through interface nodes 2 هفته پیش
  Joseph Brandenburg ddaefc93f5 Initialize Tree with correct version number 3 هفته پیش
  Joseph Brandenburg be2dff439b Fix: make default values work with more socket types 3 هفته پیش
  Joseph Brandenburg 8c945311a8 Fix: interface panel doesn't have an identifier 3 هفته پیش
  Joseph Brandenburg e1e6865ba8 New Feature: Default Values for base tree 3 هفته پیش
  Joseph Brandenburg f505903137 Fix: correct default value type for vectors 3 هفته پیش
  Joseph Brandenburg 320e929160 Fix: default value disabled for Matrix 3 هفته پیش
  Joseph Brandenburg 87d4515f2f Disable "Connected To" feature 3 هفته پیش
  Joseph Brandenburg 80ca472129 Implement Custom Interface Classes 3 هفته پیش
  Joseph Brandenburg 83415c6b5d Cleanup Drivers 2 هفته پیش
  Joseph Brandenburg e40d0fdc9c initial versioning for new interface classes 3 هفته پیش
  Joseph Brandenburg 4845445001 fix: unbound local error when updating group interface 3 هفته پیش
  Joseph Brandenburg 37976aaffb update interface draw for correct UI and clarity 3 هفته پیش
  Joseph Brandenburg fa05172de2 Interface Classes set the multi and default value now 3 هفته پیش
  Joseph Brandenburg dbb1998052 Add Custom Interface Socket Types 3 هفته پیش
7فایلهای تغییر یافته به همراه147 افزوده شده و 120 حذف شده
  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
 from .utilities import get_socket_maps, relink_socket_map, do_relink
 
 
 FLOAT_EPSILON=0.0001 # used to check against floating point inaccuracy
 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():
 def TellClasses():
     #Why use a function to do this? Because I don't need every class to register.
     #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.
            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.
            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!")
         if args[0] == 'call_on_all_ancestors': raise RuntimeError("Very funny!")
         from .utilities import get_all_dependencies
         from .utilities import get_all_dependencies
         from collections import deque
         from collections import deque
@@ -911,7 +911,6 @@ class MantisNode:
         solved = set()
         solved = set()
         while can_solve:
         while can_solve:
             node = can_solve.pop()
             node = can_solve.pop()
-            print(node)
             method = getattr(node, args[0])
             method = getattr(node, args[0])
             method(*args[0:], **kwargs)
             method(*args[0:], **kwargs)
             solved.add(node)
             solved.add(node)
@@ -1008,7 +1007,7 @@ class NodeLink:
     to_node = None
     to_node = None
     to_socket = 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:
         if from_node.signature == to_node.signature:
             raise RuntimeError("Cannot connect a node to itself.")
             raise RuntimeError("Cannot connect a node to itself.")
         self.from_node = from_node
         self.from_node = from_node
@@ -1017,7 +1016,8 @@ class NodeLink:
         self.to_socket = to_socket
         self.to_socket = to_socket
         self.from_node.outputs[self.from_socket].links.append(self)
         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
         # 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.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_hierarchy = detect_hierarchy_link(from_node, from_socket, to_node, to_socket,)
         self.is_alive = True
         self.is_alive = True
@@ -1066,7 +1066,7 @@ class NodeSocket:
         if (traverse_target):
         if (traverse_target):
             self.can_traverse = True
             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):
         if  (self.is_input):
             to_node   = self.node; from_node = node
             to_node   = self.node; from_node = node
             to_socket = self.name; from_socket = socket
             to_socket = self.name; from_socket = socket
@@ -1083,7 +1083,8 @@ class NodeSocket:
                 from_socket,
                 from_socket,
                 to_node,
                 to_node,
                 to_socket,
                 to_socket,
-                sort_id)
+                sort_id,
+                sub_sort_id)
         return new_link
         return new_link
 
 
     def set_traverse_target(self, traverse_target):
     def set_traverse_target(self, traverse_target):
@@ -1093,7 +1094,7 @@ class NodeSocket:
     def flush_links(self):
     def flush_links(self):
         """ Removes dead links from this socket."""
         """ Removes dead links from this socket."""
         self.links = [l for l in self.links if l.is_alive]
         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)
         self.is_linked = bool(self.links)
 
 
     @property
     @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"]:
         if ('Target' in input_name) and input_name not in  ["Target Space", "Use Target Z"]:
             socket = self.inputs.get(input_name)
             socket = self.inputs.get(input_name)
             if socket.is_linked:
             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
             return None
 
 
         else:
         else:
@@ -61,28 +64,46 @@ class MantisLinkNode(MantisNode):
 
 
     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()
-        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
         return props_sockets
 
 
     def set_custom_space(self):
     def set_custom_space(self):
         for c in self.bObject:
         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"):
     def GetxForm(nc, output_name="Output Relationship"):
         break_condition= lambda node : node.node_type=='XFORM'
         break_condition= lambda node : node.node_type=='XFORM'
@@ -205,20 +226,7 @@ class LinkCopyScale(MantisLinkNode):
             if constraint_name := self.evaluate_input("Name"):
             if constraint_name := self.evaluate_input("Name"):
                 c.name = constraint_name
                 c.name = constraint_name
             self.bObject.append(c)
             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()
             props_sockets = self.gen_property_socket_map()
             evaluate_sockets(self, c, props_sockets)
             evaluate_sockets(self, c, props_sockets)
         self.executed = True
         self.executed = True
@@ -804,7 +812,6 @@ class LinkArmature(MantisLinkNode):
                     weight_value=0
                     weight_value=0
                 targets_weights[i]=weight_value
                 targets_weights[i]=weight_value
                 props_sockets["targets[%d].weight" % i] = (weight_input_name, 0)
                 props_sockets["targets[%d].weight" % i] = (weight_input_name, 0)
-                # targets_weights.append({"weight":(weight_input_name, 0)})
             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
@@ -829,8 +836,12 @@ class LinkSplineIK(MantisLinkNode):
             c = xf.bGetObject().constraints.new('SPLINE_IK')
             c = xf.bGetObject().constraints.new('SPLINE_IK')
             # set the spline - we need to get the right one
             # set the spline - we need to get the right one
             spline_index = self.evaluate_input("Spline Index")
             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
             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)
             curve = get_extracted_spline_object(proto_curve, spline_index, self.mContext)
             # link it to the view layer
             # link it to the view layer
             if (curve.name not in bContext.view_layer.active_layer_collection.collection.objects):
             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=[]
         self.rerouted=[]
 
 
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
+        from .base_definitions import links_sort_key
         if len(self.rerouted)>0:
         if len(self.rerouted)>0:
             self.prepared, self.executed = True, True
             self.prepared, self.executed = True, True
             return #Either it is already done or it doesn't matter.
             return #Either it is already done or it doesn't matter.
         elif self.prepared == False:
         elif self.prepared == False:
             # sort the array entries
             # sort the array entries
             for inp in self.inputs.values():
             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")
             oob   = self.evaluate_input("OoB Behaviour")
             index = self.evaluate_input("Index")
             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
 # TODO: modify this to work with multi-input nodes
 def trace_single_line(node_container, input_name, link_index=0):
 def trace_single_line(node_container, input_name, link_index=0):
-    # DO: refactor this for new link class
     """Traces a line to its input."""
     """Traces a line to its input."""
     nodes = [node_container]
     nodes = [node_container]
     # Trace a single line
     # 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):
 def finish_driver(nc, b_object, driver_item, prop):
     # prWhite(nc, prop)
     # prWhite(nc, prop)
     index = driver_item[1]; driver_sock = driver_item[0]
     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:
     if index is not None:
         driver = driver_provider.parameters[driver_socket.name][index].copy()
         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
         # 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
                         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):
 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:
     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):
         while (in_node_input.links):
             in_link = in_node_input.links.pop()
             in_link = in_node_input.links.pop()
             from_node = in_link.from_node; from_socket = in_link.from_socket
             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:
     for out_node_output in out_node.outputs:
         while (out_node_output.links):
         while (out_node_output.links):
             out_link = out_node_output.links.pop()
             out_link = out_node_output.links.pop()
             to_node = out_link.to_node; to_socket = out_link.to_socket
             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()
             out_link.die()
-            init_dependencies(to_node)
-    init_dependencies(interface); init_connections(interface)
 
 
 def reroute_links_grp(group, all_nodes):
 def reroute_links_grp(group, all_nodes):
     from .internal_containers import GroupInterface
     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?)")
                  "(maybe you are running the tree from inside a node group?)")
 
 
 # FIXME I don't think these signatures are unique.
 # 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):
 def insert_lazy_parents(nc):
     from .link_nodes import LinkInherit
     from .link_nodes import LinkInherit
-    from .base_definitions import NodeLink
     inherit_nc = None
     inherit_nc = None
     if nc.inputs["Relationship"].is_connected:
     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)
             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.
             # 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
     return inherit_nc
 
 
 # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** #
 # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** #
@@ -377,8 +386,6 @@ def parse_tree(base_tree, error_popups=False):
             dummy.reroute_links(dummy, all_mantis_nodes)
             dummy.reroute_links(dummy, all_mantis_nodes)
     prGreen(f"Pulling data from tree took {time.time() - data_start_time} seconds")
     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()
     start_time = time.time()
     solve_only_these = []; solve_only_these.extend(list(all_schema.values()))
     solve_only_these = []; solve_only_these.extend(list(all_schema.values()))
     roots, array_nodes = [], []
     roots, array_nodes = [], []
@@ -661,7 +668,6 @@ def execute_tree(nodes, base_tree, context, error_popups = False):
                     raise e
                     raise e
                 execution_failed = True; break
                 execution_failed = True; break
 
 
-
         switch_mode(mode='OBJECT', objects=switch_me)
         switch_mode(mode='OBJECT', objects=switch_me)
         # switch to pose mode here so that the nodes can use the final pose data
         # switch to pose mode here so that the nodes can use the final pose data
         # this will require them to update the depsgraph.
         # this will require them to update the depsgraph.
@@ -678,7 +684,6 @@ def execute_tree(nodes, base_tree, context, error_popups = False):
                     raise e
                     raise e
                 execution_failed = True; break
                 execution_failed = True; break
 
 
-
         # REST pose for deformer bind, so everything is in the rest position
         # REST pose for deformer bind, so everything is in the rest position
         for ob in switch_me:
         for ob in switch_me:
             ob.data.pose_position = 'REST'
             ob.data.pose_position = 'REST'

+ 18 - 10
schema_solve.py

@@ -4,7 +4,7 @@ from .utilities import (prRed, prGreen, prPurple, prWhite,
                               wrapOrange,)
                               wrapOrange,)
 from .utilities import init_connections, init_dependencies, get_link_in_out
 from .utilities import init_connections, init_dependencies, get_link_in_out
 from .base_definitions import (SchemaUINode, custom_props_types, \
 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
 from .node_container_common import setup_custom_props_from_np
 # a class that solves Schema nodes
 # a class that solves Schema nodes
 from bpy.types import NodeGroupInput, NodeGroupOutput
 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
         # Sort the multi-input nodes in reverse order of ID, this ensures that they are
         #   read in the order they were created
         #   read in the order they were created
         for inp in self.node.inputs.values():
         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
         from bpy.types import NodeGroupInput, NodeGroupOutput
         for ui_node in self.tree.nodes:
         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_node = self.schema_nodes[(*self.node.ui_signature, from_ui_node.bl_idname)]
         from collections import deque
         from collections import deque
         unprepared = deque(from_node.hierarchy_dependencies)
         unprepared = deque(from_node.hierarchy_dependencies)
-        self.prepare_nodes(unprepared)
+        self.prepare_nodes(unprepared, frame_mantis_nodes)
         from .utilities import cap, wrap
         from .utilities import cap, wrap
         get_index = from_node.evaluate_input("Index", self.index) # get the most recent link
         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
         # 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
             to_socket_name=ui_link.to_socket.name
             if to_node.node_type in ['DUMMY_SCHEMA']:
             if to_node.node_type in ['DUMMY_SCHEMA']:
                 to_socket_name=ui_link.to_socket.identifier
                 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()
             to_node.flush_links()
 
 
     def handle_link_to_constant_output(self, frame_mantis_nodes, index, ui_link,  to_ui_node):
     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 False
         return True
         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
         # 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.
         # 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.
         # 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:
         while unprepared:
             nc = unprepared.pop()
             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):
             if nc.node_type == 'DUMMY_SCHEMA' and not self.test_is_sub_schema(nc):
                 forbidden.add(nc) # do NOT add this as a dependency.
                 forbidden.add(nc) # do NOT add this as a dependency.
             if nc in forbidden: continue # trying to resolve dependencies for.
             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")):
             if node.node_type == 'DUMMY_SCHEMA' and (schema_len_in := node.inputs.get("Schema Length")):
                 for l in schema_len_in.links:
                 for l in schema_len_in.links:
                     unprepared.append(l.from_node)
                     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
         # We have to prepare the nodes leading to Array Input Get
         for ui_link in array_input_get_link:
         for ui_link in array_input_get_link:
@@ -700,7 +708,7 @@ class SchemaSolver:
                 unprepared.extend(from_node.hierarchy_dependencies)
                 unprepared.extend(from_node.hierarchy_dependencies)
             else:
             else:
                 raise RuntimeError(" 671 there has been an error parsing the tree. Please report this as a bug.")
                 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
             # and handle the output by the specific type
             if isinstance(to_ui_node, (SchemaConstOutput, NodeGroupOutput)):
             if isinstance(to_ui_node, (SchemaConstOutput, NodeGroupOutput)):
                 self.handle_link_to_constant_output(frame_mantis_nodes, self.index, ui_link,  to_ui_node)
                 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'):
 def get_parent_node(node_container, type = 'XFORM'):
     # type variable for selecting whether to get either
     # type variable for selecting whether to get either
     #   the parent xForm  or the inheritance node
     #   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)):
     for i in range(len(node_line)):
         # check each of the possible parent types.
         # check each of the possible parent types.
         if ( (node_line[ i ].__class__.__name__ == 'LinkInherit') ):
         if ( (node_line[ i ].__class__.__name__ == 'LinkInherit') ):
@@ -269,16 +268,19 @@ class xFormBone(xFormNode):
     def bSetParent(self, eb):
     def bSetParent(self, eb):
         from bpy.types import EditBone
         from bpy.types import EditBone
         parent_nc = get_parent_node(self, type='LINK')
         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')
         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:
         else:
             raise RuntimeError(wrapRed(f"Cannot set parent for node {self}"))
             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):
     def bPrepare(self, bContext=None):
@@ -431,9 +433,6 @@ class xFormBone(xFormNode):
             try:
             try:
                 if (custom_handle := self.evaluate_input("BBone Custom Start Handle")):
                 if (custom_handle := self.evaluate_input("BBone Custom Start Handle")):
                     b.bbone_custom_handle_start = self.bGetParentArmature().data.bones[custom_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")):
                 if (custom_handle := self.evaluate_input("BBone Custom End Handle")):
                     b.bbone_custom_handle_end = self.bGetParentArmature().data.bones[custom_handle]
                     b.bbone_custom_handle_end = self.bGetParentArmature().data.bones[custom_handle]
             except KeyError:
             except KeyError:
@@ -843,8 +842,11 @@ class xFormCurvePin(xFormNode):
         for socket_name in ["Curve Pin Factor", "Forward Axis","Up Axis",]:
         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.get(socket_name) is None: continue # in case it has been bypassed
             if self.inputs[socket_name].is_linked:
             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):
                 if isinstance(driver, UtilityDriver):
                     prop_amount = driver.evaluate_input("Property")
                     prop_amount = driver.evaluate_input("Property")
                 elif isinstance(driver, UtilitySwitch):
                 elif isinstance(driver, UtilitySwitch):