Эх сурвалжийг харах

Update FCurve node to array socket

original title
Fix: do_relink was unable to link single socket more than once

In some rare configurations, the user may have 2 links coming from one
socket into two sockets that are combined in a versioning update, which
get eaten during relink this fix adds a reroute node automatically to
take care of that.

VSCode also magnanimously decided to add all the files I didn't have
staged into the commit, so this commit also contains the update for
fCurve to have an array socket instead of a bunch of individual sockets
Joseph Brandenburg 1 сар өмнө
parent
commit
7d024e045b

+ 7 - 2
__init__.py

@@ -275,13 +275,14 @@ def execute_handler(scene):
 
 from .versioning import versioning_tasks
 def node_version_update(node, do_once):
+    print("AAAAA")
     from .xForm_nodes_ui import xFormNode
     for bl_idname, task, required_kwargs in versioning_tasks:
         arg_map = {}
         if 'node' in required_kwargs:
             arg_map['node']=node
-        if 'node_tree' in required_kwargs:
-            arg_map['node_tree']=node.id_data
+        if 'tree' in required_kwargs or 'node_tree' in required_kwargs:
+            arg_map['tree']=node.id_data; arg_map['node_tree']=node.id_data
         # we'll match all, or categories, or specific bl_idname as needed.
         if ('ALL' in bl_idname) or \
                 ('XFORM' in bl_idname) and isinstance(node, xFormNode) or\
@@ -293,6 +294,7 @@ def node_version_update(node, do_once):
 
 def do_version_update(node_tree):
     # set updating status for dynamic nodes to prevent bugs in socket remapping
+    print("BBBBB")
     do_once = True
     for node in node_tree.nodes:
         if hasattr(node, 'is_updating'):
@@ -320,11 +322,14 @@ def do_version_update(node_tree):
 
 @persistent
 def version_update_handler(filename):
+    print ("CCCCC")
     for node_tree in bpy.data.node_groups: # ensure it can update again after file load.
         if node_tree.bl_idname in ["MantisTree", "SchemaTree"]:
                 node_tree.is_exporting=False; node_tree.is_executing=False
     for node_tree in bpy.data.node_groups:
         if node_tree.bl_idname in ["MantisTree", "SchemaTree"]:
+            print (node_tree.mantis_version[0], node_tree.mantis_version[1], node_tree.mantis_version[2])
+            print ("DDDDD")
             if (node_tree.mantis_version[0] < MANTIS_VERSION_MAJOR) or \
                (node_tree.mantis_version[1] < MANTIS_VERSION_MINOR) or \
                (node_tree.mantis_version[2] < MANTIS_VERSION_SUB):

+ 2 - 7
base_definitions.py

@@ -83,7 +83,7 @@ def hash_tree(tree):
 
 MANTIS_VERSION_MAJOR=0
 MANTIS_VERSION_MINOR=13
-MANTIS_VERSION_SUB=4
+MANTIS_VERSION_SUB=5
 
 class MantisTree(NodeTree):
     '''A custom node tree type that will show up in the editor type list'''
@@ -104,7 +104,7 @@ class MantisTree(NodeTree):
     execution_id:StringProperty(default='')
     # prev_execution_id:StringProperty(default='')
     mantis_version:IntVectorProperty(default=[
-        MANTIS_VERSION_MAJOR, MANTIS_VERSION_MINOR, MANTIS_VERSION_SUB])
+        MANTIS_VERSION_MAJOR, MANTIS_VERSION_MINOR-1, MANTIS_VERSION_SUB-1])
     # this prevents the node group from executing on the next depsgraph update
     # because I don't always have control over when the dg upadte happens.
     prevent_next_exec:BoolProperty(default=False)
@@ -1093,10 +1093,6 @@ class NodeLink:
             init_dependencies(to_node)
 
 class NodeSocket:
-    # @property # this is a read-only property.
-    # def is_linked(self):
-    #     return bool(self.links)
-
     def __init__(self, is_input = False,
                  node = None, name = None,
                  traverse_target = None):
@@ -1146,7 +1142,6 @@ class NodeSocket:
     def is_connected(self):
         return len(self.links)>0
 
-
     def __repr__(self):
         return self.node.__repr__() + "::" + self.name
 

+ 4 - 1
mantis_dataclasses.py

@@ -16,7 +16,10 @@ class MantisSocketTemplate():
     use_multi_input  : bool = field(default=False)
     ui_only          : bool = field(default=False)
     default_value    : Any = field(default=None)
-    # TODO: add versioning fields for Mantis, Blender version min/max
+    # this is a hint for the props_sockets system for complex situations
+    #  where multiple blender objects own the properties, like e.g. bone/posebone
+    prop_owner_group : str = field(default="")
+    # do? add versioning fields for Mantis, Blender version min/max
 
 @dataclass
 class xForm_info():

+ 8 - 16
misc_nodes.py

@@ -803,7 +803,8 @@ class UtilityDriverVariable(MantisNode):
         self.parameters["Driver Variable"] = None
         self.prepared=True
 
-    def evaluate_input(self, input_name):
+    # NOTE _index for interface consistency
+    def evaluate_input(self, input_name, _index=0):
         if input_name == 'Property':
             if self.inputs.get('Property'):
                 if self.inputs['Property'].is_linked:
@@ -908,28 +909,18 @@ class UtilityFCurve(MantisNode):
         super().reset_execution()
         self.prepared=True
 
-    def evaluate_input(self, input_name):
-        return super().evaluate_input(input_name)
-
     def bTransformPass(self, bContext = None,):
         prepare_parameters(self)
         extrap_mode = self.evaluate_input("Extrapolation Mode")
-        keys = [] # ugly but whatever
-        #['amplitude', 'back', 'bl_rna', 'co', 'co_ui', 'easing', 'handle_left', 'handle_left_type', 'handle_right', 'handle_right_type',
-        # 'interpolation', 'period', 'rna_type', 'select_control_point', 'select_left_handle', 'select_right_handle', 'type']
-        for k in self.inputs.keys():
-            if k == 'Extrapolation Mode' : continue
-            # print (self.inputs[k])
-            if (key := self.evaluate_input(k)) is None:
-                prOrange(f"WARN: No keyframe connected to {self}:{k}. Skipping Link.")
-            else:
-                keys.append(key)
+        keys = []
+        for i in range(len(self.inputs['Keyframes'].links)):
+            keys.append( self.evaluate_input('Keyframes', i) )
         if len(keys) <1:
             prOrange(f"WARN: no keys in fCurve {self}.")
         keys.append(extrap_mode)
         self.parameters["fCurve"] = keys
         self.executed = True
-#TODO make the fCurve data a data class instead of a dict
+#TODO make the keyframe, fCurve data classes instead of dicts!
 
 class UtilityDriver(MantisNode):
     '''A node representing an armature object'''
@@ -999,7 +990,8 @@ class UtilitySwitch(MantisNode):
         self.node_type = "DRIVER" # MUST be run in Pose mode
         self.prepared = True
 
-    def evaluate_input(self, input_name):
+    # NOTE: _index for interface consistency
+    def evaluate_input(self, input_name, _index=0):
         if input_name == 'Parameter':
             if self.inputs['Parameter'].is_connected:
                 trace = trace_single_line(self, input_name)

+ 2 - 0
misc_nodes_socket_templates.py

@@ -168,6 +168,8 @@ DriverVariableSockets = [
 FCurveSockets = [
     ExtrapolationMode := ST( name="Extrapolation Mode",
         bl_idname="eFCrvExtrapolationMode", is_input=True),
+    Keyframes := ST(name='Keyframes', bl_idname="KeyframeSocket", is_input=True,
+        use_multi_input=True,),
     fCurveOutput := ST(name="fCurve", bl_idname="FCurveSocket", is_input=False),
 ]
 

+ 0 - 6
misc_nodes_ui.py

@@ -412,12 +412,6 @@ class UtilityFCurveNode(Node, MantisMiscNode):
     mantis_node_class_name=bl_idname
     socket_templates=FCurveSockets
     use_kf_nodes   : bpy.props.BoolProperty(default=True)
-            
-    def draw_buttons(self, context, layout):
-        layout.operator( 'mantis.fcurve_node_add_kf' )
-        if (len(self.inputs) > 1):
-            layout.operator( 'mantis.fcurve_node_remove_kf' )
-
 
 class UtilityDriverNode(Node, MantisMiscNode):
     """Represents a Driver relationship"""

+ 21 - 1
utilities.py

@@ -177,6 +177,7 @@ def do_relink(node, socket, map, in_out='INPUT', parent_name = ''):
         tree = node.node_tree
         interface_in_out=in_out
     from bpy.types import NodeSocket, Node
+    from bpy.app import version as bpy_version
     get_string = '__extend__'
     if socket: get_string = socket.identifier
     from .base_definitions import SchemaUINode
@@ -184,6 +185,11 @@ def do_relink(node, socket, map, in_out='INPUT', parent_name = ''):
         # this happens when we are creating a new node group and need to update it from nothing.
         return
     val = map[get_string] # this will throw an error if the socket isn't there. Good!
+    # IF there are multiple  links going from one node-socket to this socket, then we have to relink
+    #  every socket but the first one before it can go in
+    # Counter for every socket starts at 0 and if it is greater than 0, add a reroute intervening
+    # then decrement the counter while relinking,
+    socket_counters = {}
     if isinstance(val, list):
         for sub_val in val:
             # this will only happen once because it assigns socket, so it is safe to do in the for loop.
@@ -210,7 +216,21 @@ def do_relink(node, socket, map, in_out='INPUT', parent_name = ''):
             if isinstance(sub_val, NodeSocket):
                 l = None
                 if in_out =='INPUT':
-                    l = node.id_data.links.new(input=sub_val, output=socket)
+                    counter_id=sub_val.node.name+"::"+sub_val.identifier
+                    if socket_counters.get(counter_id) is None:
+                        socket_counters[counter_id] = 0
+                    if socket_counters[counter_id] == 0:
+                        l = node.id_data.links.new(input=sub_val, output=socket)
+                    else:
+                        print ("i have to make a reroute node here lol")
+                        reroute = node.id_data.nodes.new('NodeReroute')
+                        # NOTE that this doesn't really do a good job of placing it. Whatever.
+                        reroute.location = node.location.lerp(sub_val.node.location, 0.25)
+                        l = node.id_data.links.new(input=sub_val, output=reroute.inputs[0])
+                        l = node.id_data.links.new(input=reroute.outputs[0], output=socket)
+                        if (bpy_version >= (4,4,0)): # correct the reroute color if possible
+                            reroute.socket_idname = socket.bl_idname
+                    socket_counters[counter_id] += 1
                 else:
                     l = node.id_data.links.new(input=socket, output=sub_val)
                 if l is None:

+ 37 - 4
versioning.py

@@ -3,7 +3,11 @@
 
 from bpy.types import Node, NodeSocket
 from bpy.app import version as bpy_version
-from .utilities import prRed, prGreen, prPurple
+
+from .utilities import (prRed, prGreen, prPurple, prWhite,
+                              prOrange,
+                              wrapRed, wrapGreen, wrapPurple, wrapWhite,
+                              wrapOrange,)
 
 def version_upgrade_very_old(*args, **kwargs):
     node = kwargs['node']
@@ -348,9 +352,37 @@ def schema_enable_custom_interface_types(*args, **kwargs):
                 n.is_updating = False
         tree.do_live_update = True
             
-
-
-
+def update_fcurve_node_to_multi_socket(*args, **kwargs):
+    # return
+    node = kwargs['node']
+    prRed("BEANIS")
+    print (node)
+    live_updates = node.id_data.do_live_update
+    node.id_data.do_live_update = False
+    try:
+        from .utilities import get_socket_maps, do_relink
+        sockets_in, sockets_out = get_socket_maps(node,)
+        new_sockets_in = {}
+        for socket_name, links in sockets_in.items():
+            if links is None: continue
+            if 'Keyframe.' in socket_name:
+                print (socket_name, links)
+                if new_sockets_in.get('Keyframes'):
+                                                  # has to be in this order to come back alright.
+                                                  # assuming, that is, that the keys are sorted. they should be.
+                    new_sockets_in["Keyframes"] = sockets_in[socket_name] + new_sockets_in['Keyframes']
+                else:
+                    new_sockets_in["Keyframes"] = sockets_in[socket_name]
+                prWhite (new_sockets_in['Keyframes'])
+        do_relink(node, node.inputs['Keyframes'], new_sockets_in, in_out="INPUT",)
+        delete_me = []
+        for socket in node.inputs:
+            if 'Keyframe.' in socket.name:
+                delete_me.append(socket)
+        for socket in delete_me:
+            node.inputs.remove(socket)
+    finally:
+        node.id_data.do_live_update = live_updates
 
 
 
@@ -364,6 +396,7 @@ versioning_tasks = [
     (['LinkFloor'], up_0_12_25_replace_floor_offset_type, ['node']),
     (['XFORM'], up_0_13_0_new_custom_prop_node, ['node', 'node_tree']),
     (['SchemaTree'], schema_enable_custom_interface_types, ['tree']),
+    (['UtilityFCurve'], update_fcurve_node_to_multi_socket, ['node']),
 ]