Browse Source

Templates for Copy Loc/Rot/Scl Links

This commit adds socket templates to Copy Location, Rotation, and Scale link nodes.
In addition, it adds new methods for the MantisNode and MantisLinkNode base classes
to deal with socket templates properly.
 - MantisNode.init_sockets() initializes inputs and output sockets in one go
 - MantisNode.gen_property_socket_map() creates a props_sockets map for mantis nodes
Joseph Brandenburg 7 months ago
parent
commit
0f9a9d8d79
3 changed files with 161 additions and 191 deletions
  1. 44 25
      base_definitions.py
  2. 111 125
      link_containers.py
  3. 6 41
      link_definitions.py

+ 44 - 25
base_definitions.py

@@ -141,16 +141,18 @@ from typing import Any
 
 
 @dataclass
 @dataclass
 class MantisSocketTemplate():
 class MantisSocketTemplate():
-    name            : str = field(default="")
-    bl_idname       : str = field(default="")
-    traverse_target : str = field(default="")
-    identifier      : str = field(default="")
-    display_shape   : str = field(default="") # for arrays
-    category        : str = field(default="") # for use in display update
-    is_input        : bool = field(default=False)
-    hide            : bool = field(default=False)
-    use_multi_input : bool = field(default=False)
-    default_value   : Any = field(default=None)
+    name             : str = field(default="")
+    bl_idname        : str = field(default="")
+    traverse_target  : str = field(default="")
+    identifier       : str = field(default="")
+    display_shape    : str = field(default="") # for arrays
+    category         : str = field(default="") # for use in display update
+    blender_property : str | tuple[str] = field(default="") # for props_sockets -> evaluate sockets
+    is_input         : bool = field(default=False)
+    hide             : bool = field(default=False)
+    use_multi_input  : bool = field(default=False)
+    default_value    : Any = field(default=None)
+    
 
 
 
 
 #TODO: do a better job explaining how MantisNode and MantisUINode relate.
 #TODO: do a better job explaining how MantisNode and MantisUINode relate.
@@ -445,8 +447,9 @@ SOCKETS_RENAMED=[ ("LinkDrivenParameter", "DriverSocket",   "Driver",     "Float
 
 
                 # NODE CLASS NAME             IN_OUT    SOCKET TYPE     SOCKET NAME     INDEX   MULTI     DEFAULT
                 # NODE CLASS NAME             IN_OUT    SOCKET TYPE     SOCKET NAME     INDEX   MULTI     DEFAULT
 SOCKETS_ADDED=[("DeformerMorphTargetDeform", 'INPUT', 'BooleanSocket', "Use Shape Key", 1,      False,    False),
 SOCKETS_ADDED=[("DeformerMorphTargetDeform", 'INPUT', 'BooleanSocket', "Use Shape Key", 1,      False,    False),
-               ("DeformerMorphTargetDeform", 'INPUT', 'BooleanSocket', "Use Offset", 2,         False,     True),
-               ("UtilityFCurve",             'INPUT',  "eFCrvExtrapolationMode", "Extrapolation Mode", 0, False, 'CONSTANT')]
+               ("DeformerMorphTargetDeform", 'INPUT', 'BooleanSocket', "Use Offset",    2,      False,    True),
+               ("UtilityFCurve",             'INPUT',  "eFCrvExtrapolationMode", "Extrapolation Mode", 0, False, 'CONSTANT'),
+               ("LinkCopyScale",             'INPUT',  "BooleanSocket", "Additive",     3,      False, False)]
 
 
 # replace names with bl_idnames for reading the tree and solving schemas.
 # replace names with bl_idnames for reading the tree and solving schemas.
 replace_types = ["NodeGroupInput", "NodeGroupOutput", "SchemaIncomingConnection",
 replace_types = ["NodeGroupInput", "NodeGroupOutput", "SchemaIncomingConnection",
@@ -474,7 +477,9 @@ class MantisNode:
         A MantisNode is used internally by Mantis to represent the final evaluated node graph.
         A MantisNode is used internally by Mantis to represent the final evaluated node graph.
         It gets generated with data from a MantisUINode when the graph is read.
         It gets generated with data from a MantisUINode when the graph is read.
     """
     """
-    def __init__(self, signature, base_tree):
+    def __init__(self, signature : tuple,
+                 base_tree : bpy.types.NodeTree,
+                 socket_templates : list[MantisSocketTemplate]=[]):
         self.base_tree=base_tree
         self.base_tree=base_tree
         self.signature = signature
         self.signature = signature
         self.inputs = MantisNodeSocketCollection(node=self, is_input=True)
         self.inputs = MantisNodeSocketCollection(node=self, is_input=True)
@@ -486,8 +491,15 @@ class MantisNode:
         self.hierarchy_dependencies, self.dependencies = [], []
         self.hierarchy_dependencies, self.dependencies = [], []
         self.prepared = False
         self.prepared = False
         self.executed = False
         self.executed = False
+        self.socket_templates = socket_templates
+        if self.socket_templates:
+            self.init_sockets()
+
+    def init_sockets(self) -> None:
+        self.inputs.init_sockets(self.socket_templates)
+        self.outputs.init_sockets(self.socket_templates)
 
 
-    def init_parameters(self, additional_parameters = {}):
+    def init_parameters(self, additional_parameters = {}) -> None:
         for socket in self.inputs:
         for socket in self.inputs:
             self.parameters[socket.name] = None
             self.parameters[socket.name] = None
         for socket in self.outputs:
         for socket in self.outputs:
@@ -495,19 +507,31 @@ 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 set_traverse(self, traversal_pairs = [(str, str)]):
+    def gen_property_socket_map(self) -> dict:
+        props_sockets = {}
+        for s_template in self.socket_templates:
+            if not s_template.blender_property:
+                continue
+            if isinstance(s_template.blender_property, str):
+                props_sockets[s_template.blender_property]=(s_template.name, s_template.default_value)
+            elif isinstance(s_template.blender_property, tuple):
+                for index, sub_prop in enumerate(s_template.blender_property):
+                    props_sockets[sub_prop]=( (s_template.name, index),s_template.default_value[index] )
+        return props_sockets
+    
+    def set_traverse(self, traversal_pairs = [(str, str)]) -> None:
         for (a, b) in traversal_pairs:
         for (a, b) in traversal_pairs:
             self.inputs[a].set_traverse_target(self.outputs[b])
             self.inputs[a].set_traverse_target(self.outputs[b])
             self.outputs[b].set_traverse_target(self.inputs[a])
             self.outputs[b].set_traverse_target(self.inputs[a])
             
             
 
 
-    def flush_links(self):
+    def flush_links(self) -> None:
         for inp in self.inputs.values():
         for inp in self.inputs.values():
             inp.flush_links()
             inp.flush_links()
         for out in self.outputs.values():
         for out in self.outputs.values():
             out.flush_links()
             out.flush_links()
     
     
-    def evaluate_input(self, input_name, index=0):
+    def evaluate_input(self, input_name, index=0)  -> Any:
         from .node_container_common import trace_single_line
         from .node_container_common import trace_single_line
         if not (self.inputs.get(input_name)): # get the named parameter if there is no input
         if not (self.inputs.get(input_name)): # get the named parameter if there is no input
             return self.parameters.get(input_name) # this will return None if the parameter does not exist.
             return self.parameters.get(input_name) # this will return None if the parameter does not exist.
@@ -517,7 +541,7 @@ class MantisNode:
         prop = trace[0][-1].parameters[trace[1].name] #trace[0] = the list of traced nodes; read its parameters
         prop = trace[0][-1].parameters[trace[1].name] #trace[0] = the list of traced nodes; read its parameters
         return prop
         return prop
     
     
-    def fill_parameters(self, ui_node=None):
+    def fill_parameters(self, ui_node=None)  -> None:
         from .utilities import get_node_prototype
         from .utilities import get_node_prototype
         from .node_container_common import get_socket_value
         from .node_container_common import get_socket_value
         if not ui_node:
         if not ui_node:
@@ -555,6 +579,7 @@ class MantisNode:
                         raise RuntimeError(wrapRed("No value found for " + self.__repr__() + " when filling out node parameters for " + ui_node.name + "::"+node_socket.name))
                         raise RuntimeError(wrapRed("No value found for " + self.__repr__() + " when filling out node parameters for " + ui_node.name + "::"+node_socket.name))
                 else:
                 else:
                     pass
                     pass
+    # I don't think this works! but I like the idea
     def call_on_all_ancestors(self, *args, **kwargs):
     def call_on_all_ancestors(self, *args, **kwargs):
         """Resolve the dependencies of this node with the named method and its arguments.
         """Resolve the dependencies of this node with the named method and its arguments.
            First, dependencies are discovered by walking backwards through the tree. Once the root
            First, dependencies are discovered by walking backwards through the tree. Once the root
@@ -586,9 +611,6 @@ class MantisNode:
         #             prOrange(dep)
         #             prOrange(dep)
         return
         return
 
 
-        
-        
-    
     def bPrepare(self, bContext=None):
     def bPrepare(self, bContext=None):
         return
         return
     def bExecute(self, bContext=None):
     def bExecute(self, bContext=None):
@@ -598,9 +620,6 @@ class MantisNode:
     def __repr__(self): 
     def __repr__(self): 
         return self.signature.__repr__()
         return self.signature.__repr__()
 
 
-
-
-
 # do I need this and the link class above?
 # do I need this and the link class above?
 class DummyLink:
 class DummyLink:
     #gonna use this for faking links to keep the interface consistent
     #gonna use this for faking links to keep the interface consistent
@@ -743,7 +762,7 @@ class MantisNodeSocketCollection(dict):
                 if socket.is_input != self.is_input: continue
                 if socket.is_input != self.is_input: continue
                 self[socket.name] = NodeSocket(is_input=self.is_input, name=socket.name, node=self.node)
                 self[socket.name] = NodeSocket(is_input=self.is_input, name=socket.name, node=self.node)
             else:
             else:
-                raise RuntimeError("NodeSocketCollection keys must be str.")
+                raise RuntimeError(f"NodeSocketCollection keys must be str or MantisSocketTemplate, not {type(socket)}")
             
             
     def __delitem__(self, key):
     def __delitem__(self, key):
         """Deletes a node socket by name, and all its links."""
         """Deletes a node socket by name, and all its links."""

+ 111 - 125
link_containers.py

@@ -1,5 +1,5 @@
 from .node_container_common import *
 from .node_container_common import *
-from bpy.types import Bone
+from bpy.types import Bone, NodeTree
 from .base_definitions import MantisNode, GraphError, MantisSocketTemplate, FLOAT_EPSILON
 from .base_definitions import MantisNode, GraphError, MantisSocketTemplate, FLOAT_EPSILON
 
 
 def TellClasses():
 def TellClasses():
@@ -32,9 +32,55 @@ def TellClasses():
              LinkDrivenParameter,
              LinkDrivenParameter,
             ]
             ]
 
 
+# Socket Templates we will reuse:
+# inputs:
+InputRelationshipTemplate : MantisSocketTemplate = MantisSocketTemplate(
+    name="Input Relationship", is_input=True,  bl_idname='RelationshipSocket', )
+TargetTemplate : MantisSocketTemplate = MantisSocketTemplate(
+    name="Target", is_input=True,  bl_idname='xFormSocket', )
+Head_Tail_Template : MantisSocketTemplate = MantisSocketTemplate(
+    name="Head/Tail", is_input=True,  bl_idname='FloatFactorSocket',
+    default_value=1.0, blender_property='head_tail' )
+UseBBoneTemplate : MantisSocketTemplate = MantisSocketTemplate(
+    name="UseBBone", is_input=True,  bl_idname='BooleanSocket',
+    default_value=False, blender_property='use_bbone_shape' )
+AxesTemplate : MantisSocketTemplate = MantisSocketTemplate(
+    name="Axes", is_input=True,  bl_idname='BooleanThreeTupleSocket',
+    default_value=[True, True, True], blender_property=['use_x', 'use_y', 'use_z'])
+AxesInvertTemplate : MantisSocketTemplate = MantisSocketTemplate(
+    name="Invert", is_input=True,  bl_idname='BooleanThreeTupleSocket',
+    default_value=[False, False, False], blender_property=['invert_x', 'invert_y', 'invert_z'])
+TargetSpaceTemplate : MantisSocketTemplate = MantisSocketTemplate(
+    name="Target Space", is_input=True,  bl_idname='TransformSpaceSocket',
+    default_value="WORLD", blender_property='target_space' )
+OwnerSpaceTemplate : MantisSocketTemplate = MantisSocketTemplate(
+    name="Owner Space", is_input=True,  bl_idname='TransformSpaceSocket',
+    default_value="WORLD", blender_property='owner_space' )
+InfluenceTemplate : MantisSocketTemplate = MantisSocketTemplate(
+    name="Influence", is_input=True,  bl_idname='FloatFactorSocket',
+    default_value=1.0, blender_property='influence')
+EnableTemplate : MantisSocketTemplate = MantisSocketTemplate(
+    name="Enable", is_input=True,  bl_idname='EnableSocket',
+    default_value=True, blender_property='mute')
+OffsetTemplate : MantisSocketTemplate = MantisSocketTemplate(
+    name="Offset", bl_idname='BooleanSocket', is_input=True,
+    default_value=False, blender_property='use_offset')
+# outputs:
+OutputRelationshipTemplate : MantisSocketTemplate = MantisSocketTemplate(
+    name="Output Relationship", is_input=False,  bl_idname='RelationshipSocket', )
+
+
+# set the name if it is available, otherwise just use the constraint's nice name
+set_constraint_name = lambda nc : nc.evaluate_input("Name") if nc.evaluate_input("Name") else nc.__class__.__name__
+
+
+
+
 class MantisLinkNode(MantisNode):
 class MantisLinkNode(MantisNode):
-    def __init__(self, signature, base_tree):
-        super().__init__(signature, base_tree)
+    def __init__(self, signature : tuple,
+                 base_tree : NodeTree,
+                 socket_templates : list[MantisSocketTemplate]=[]):
+        super().__init__(signature, base_tree, socket_templates)
         self.node_type = 'LINK'
         self.node_type = 'LINK'
         self.prepared = True
         self.prepared = True
 
 
@@ -48,10 +94,14 @@ class MantisLinkNode(MantisNode):
             
             
         else:
         else:
             return super().evaluate_input(input_name)
             return super().evaluate_input(input_name)
-
-# set the name if it is available, otherwise just use the constraint's nice name
-set_constraint_name = lambda nc : nc.evaluate_input("Name") if nc.evaluate_input("Name") else nc.__class__.__name__
-
+    
+    def gen_property_socket_map(self) -> dict:
+        props_sockets = super().gen_property_socket_map()
+        if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
+            del props_sockets['owner_space']
+        if self.inputs["Target Space"].is_connected and self.inputs["Target Space"].links[0].from_node.node_type == 'XFORM':
+            del props_sockets['target_space']
+        return props_sockets
 
 
 #*#-------------------------------#++#-------------------------------#*#
 #*#-------------------------------#++#-------------------------------#*#
 # L I N K   N O D E S
 # L I N K   N O D E S
@@ -76,9 +126,7 @@ class LinkInherit(MantisLinkNode):
     '''A node representing inheritance'''
     '''A node representing inheritance'''
     
     
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
-        super().__init__(signature, base_tree)
-        self.inputs.init_sockets(LinkInheritSockets)
-        self.outputs.init_sockets(LinkInheritSockets)
+        super().__init__(signature, base_tree, LinkInheritSockets)
         self.init_parameters()
         self.init_parameters()
         self.set_traverse([('Parent', 'Inheritance')])
         self.set_traverse([('Parent', 'Inheritance')])
         self.executed = True
         self.executed = True
@@ -90,36 +138,32 @@ class LinkInherit(MantisLinkNode):
             if (node.node_type == 'XFORM'):
             if (node.node_type == 'XFORM'):
                 return node
                 return node
         raise GraphError("%s is not connected to a downstream xForm" % self)
         raise GraphError("%s is not connected to a downstream xForm" % self)
-    
-    
-        
 
 
+LinkCopyLocationSockets = [
+    InputRelationshipTemplate,
+    Head_Tail_Template,
+    UseBBoneTemplate,
+    AxesTemplate,
+    AxesInvertTemplate,
+    TargetSpaceTemplate,
+    OwnerSpaceTemplate,
+    OffsetTemplate,
+    InfluenceTemplate,
+    TargetTemplate,
+    EnableTemplate,
+    OutputRelationshipTemplate,
+]
 
 
 class LinkCopyLocation(MantisLinkNode):
 class LinkCopyLocation(MantisLinkNode):
     '''A node representing Copy Location'''
     '''A node representing Copy Location'''
     
     
-    def __init__(self, signature, base_tree):
-        super().__init__(signature, base_tree)
-        inputs = [
-            "Input Relationship",
-            "Head/Tail",
-            "UseBBone",
-            "Axes",
-            "Invert",
-            "Target Space",
-            "Owner Space",
-            "Offset",
-            "Influence",
-            "Target",
-            "Enable",
-        ]
+    def __init__(self, signature : tuple,
+                 base_tree : NodeTree,):
+        super().__init__(signature, base_tree, LinkCopyLocationSockets)
         additional_parameters = { "Name":None }
         additional_parameters = { "Name":None }
-        self.inputs.init_sockets(inputs)
-        self.outputs.init_sockets(["Output Relationship"])
         self.init_parameters(additional_parameters=additional_parameters)
         self.init_parameters(additional_parameters=additional_parameters)
         self.set_traverse([("Input Relationship", "Output Relationship")])
         self.set_traverse([("Input Relationship", "Output Relationship")])
         
         
-    
     def GetxForm(self):
     def GetxForm(self):
         return GetxForm(self)
         return GetxForm(self)
 
 
@@ -133,9 +177,7 @@ class LinkCopyLocation(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 = c
         self.bObject = c
-        custom_space_owner, custom_space_target = False, False
         if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
         if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
-            custom_space_owner=True
             c.owner_space='CUSTOM'
             c.owner_space='CUSTOM'
             xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
             xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
             if isinstance(xf, Bone):
             if isinstance(xf, Bone):
@@ -143,66 +185,45 @@ class LinkCopyLocation(MantisLinkNode):
             else:
             else:
                 c.space_object=xf
                 c.space_object=xf
         if self.inputs["Target Space"].is_connected and self.inputs["Target Space"].links[0].from_node.node_type == 'XFORM':
         if self.inputs["Target Space"].is_connected and self.inputs["Target Space"].links[0].from_node.node_type == 'XFORM':
-            custom_space_target=True
             c.target_space='CUSTOM'
             c.target_space='CUSTOM'
             xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
             xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
             if isinstance(xf, Bone):
             if isinstance(xf, Bone):
                 c.space_object=self.inputs["Target Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
                 c.space_object=self.inputs["Target Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
             else:
             else:
                 c.space_object=xf
                 c.space_object=xf
-        props_sockets = {
-        'use_offset'       : ("Offset", False),
-        'head_tail'       : ("Head/Tail", 0),
-        'use_bbone_shape' : ("UseBBone", False),
-        'invert_x'        : ( ("Invert", 0), False),
-        'invert_y'        : ( ("Invert", 1), False),
-        'invert_z'        : ( ("Invert", 2), False),
-        'use_x'           : ( ("Axes", 0), False),
-        'use_y'           : ( ("Axes", 1), False),
-        'use_z'           : ( ("Axes", 2), False),
-        'owner_space'     : ("Owner Space",  'WORLD'),
-        'target_space'    : ("Target Space", 'WORLD'),
-        'influence'       : ("Influence", 1),
-        'mute'            : ("Enable", True),
-        }
-        if custom_space_owner: del props_sockets['owner_space']
-        if custom_space_target: del props_sockets['target_space']
-        #
+        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
         
         
     def bFinalize(self, bContext = None):
     def bFinalize(self, bContext = None):
         finish_drivers(self)
         finish_drivers(self)
     
     
-        
-        
+
+LinkCopyRotationSockets = [
+    InputRelationshipTemplate,
+    MantisSocketTemplate(name='RotationOrder', bl_idname='RotationOrderSocket', is_input=True,
+                         default_value='AUTO', blender_property='euler_order'),
+    MantisSocketTemplate(name='Rotation Mix',  bl_idname='EnumRotationMix', is_input=True,
+                         default_value='REPLACE', blender_property='mix_mode'),
+    AxesTemplate,
+    AxesInvertTemplate,
+    TargetSpaceTemplate,
+    OwnerSpaceTemplate,
+    InfluenceTemplate,
+    TargetTemplate,
+    EnableTemplate,
+    OutputRelationshipTemplate,
+]
 
 
 class LinkCopyRotation(MantisLinkNode):
 class LinkCopyRotation(MantisLinkNode):
     '''A node representing Copy Rotation'''
     '''A node representing Copy Rotation'''
     
     
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
-        super().__init__(signature, base_tree)
-        inputs = [
-            "Input Relationship",
-            "RotationOrder",
-            "Rotation Mix",
-            "Axes",
-            "Invert",
-            "Target Space",
-            "Owner Space",
-            "Influence",
-            "Target",
-            "Enable",
-        ]
+        super().__init__(signature, base_tree, LinkCopyRotationSockets)
         additional_parameters = { "Name":None }
         additional_parameters = { "Name":None }
-        self.inputs.init_sockets(inputs)
-        self.outputs.init_sockets(["Output Relationship"])
         self.init_parameters(additional_parameters=additional_parameters)
         self.init_parameters(additional_parameters=additional_parameters)
         self.set_traverse([("Input Relationship", "Output Relationship")])
         self.set_traverse([("Input Relationship", "Output Relationship")])
         
         
-        
-
-    
     def GetxForm(self):
     def GetxForm(self):
         return GetxForm(self)
         return GetxForm(self)
 
 
@@ -225,9 +246,7 @@ class LinkCopyRotation(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 = c
         self.bObject = c
-        custom_space_owner, custom_space_target = False, False
         if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
         if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
-            custom_space_owner=True
             c.owner_space='CUSTOM'
             c.owner_space='CUSTOM'
             xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
             xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
             if isinstance(xf, Bone):
             if isinstance(xf, Bone):
@@ -235,58 +254,41 @@ class LinkCopyRotation(MantisLinkNode):
             else:
             else:
                 c.space_object=xf
                 c.space_object=xf
         if self.inputs["Target Space"].is_connected and self.inputs["Target Space"].links[0].from_node.node_type == 'XFORM':
         if self.inputs["Target Space"].is_connected and self.inputs["Target Space"].links[0].from_node.node_type == 'XFORM':
-            custom_space_target=True
             c.target_space='CUSTOM'
             c.target_space='CUSTOM'
             xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
             xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
             if isinstance(xf, Bone):
             if isinstance(xf, Bone):
                 c.space_object=self.inputs["Target Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
                 c.space_object=self.inputs["Target Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
             else:
             else:
                 c.space_object=xf
                 c.space_object=xf
-        props_sockets = {
-        'euler_order' : ("RotationOrder", 'AUTO'),
-        'mix_mode'       : ("Rotation Mix", 'REPLACE'),
-        'invert_x'       : ( ("Invert", 0), False),
-        'invert_y'       : ( ("Invert", 1), False),
-        'invert_z'       : ( ("Invert", 2), False),
-        'use_x'          : ( ("Axes", 0), False),
-        'use_y'          : ( ("Axes", 1), False),
-        'use_z'          : ( ("Axes", 2), False),
-        'owner_space'    : ("Owner Space",  'WORLD'),
-        'target_space'   : ("Target Space", 'WORLD'),
-        'influence'      : ("Influence", 1),
-        'mute'            : ("Enable", True),
-        }
-        if custom_space_owner: del props_sockets['owner_space']
-        if custom_space_target: del props_sockets['target_space']
-        #
+        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
             
             
     def bFinalize(self, bContext = None):
     def bFinalize(self, bContext = None):
         finish_drivers(self)
         finish_drivers(self)
     
     
-        
+LinkCopyScaleSockets = [
+    InputRelationshipTemplate,
+    OffsetTemplate,
+    MantisSocketTemplate(name='Average', bl_idname = 'BooleanSocket', is_input=True,
+                         default_value=False, blender_property='use_make_uniform'),
+    MantisSocketTemplate(name='Additive', bl_idname = 'BooleanSocket', is_input=True,
+                         default_value=False, blender_property='use_add'),
+    AxesTemplate,
+    TargetSpaceTemplate,
+    OwnerSpaceTemplate,
+    InfluenceTemplate,
+    TargetTemplate,
+    EnableTemplate,
+    OutputRelationshipTemplate,
+]
         
         
 class LinkCopyScale(MantisLinkNode):
 class LinkCopyScale(MantisLinkNode):
     '''A node representing Copy Scale'''
     '''A node representing Copy Scale'''
     
     
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
-        super().__init__(signature, base_tree)
-        inputs = [
-            "Input Relationship",
-            "Offset",
-            "Average",
-            "Additive",
-            "Axes",
-            "Target Space",
-            "Owner Space",
-            "Influence",
-            "Target",
-            "Enable",
-        ]
+        super().__init__(signature, base_tree, LinkCopyScaleSockets)
         additional_parameters = { "Name":None }
         additional_parameters = { "Name":None }
-        self.inputs.init_sockets(inputs)
-        self.outputs.init_sockets(["Output Relationship"])
         self.init_parameters(additional_parameters=additional_parameters)
         self.init_parameters(additional_parameters=additional_parameters)
         self.set_traverse([("Input Relationship", "Output Relationship")])
         self.set_traverse([("Input Relationship", "Output Relationship")])
     
     
@@ -303,9 +305,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 = c
         self.bObject = c
-        custom_space_owner, custom_space_target = False, False
         if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
         if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
-            custom_space_owner=True
             c.owner_space='CUSTOM'
             c.owner_space='CUSTOM'
             xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
             xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
             if isinstance(xf, Bone):
             if isinstance(xf, Bone):
@@ -313,27 +313,13 @@ class LinkCopyScale(MantisLinkNode):
             else:
             else:
                 c.space_object=xf
                 c.space_object=xf
         if self.inputs["Target Space"].is_connected and self.inputs["Target Space"].links[0].from_node.node_type == 'XFORM':
         if self.inputs["Target Space"].is_connected and self.inputs["Target Space"].links[0].from_node.node_type == 'XFORM':
-            custom_space_target=True
             c.target_space='CUSTOM'
             c.target_space='CUSTOM'
             xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
             xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
             if isinstance(xf, Bone):
             if isinstance(xf, Bone):
                 c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
                 c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
             else:
             else:
                 c.space_object=xf
                 c.space_object=xf
-        props_sockets = {
-        'use_offset'       : ("Offset", False),
-        'use_make_uniform' : ("Average", False),
-        'owner_space'      : ("Owner Space",  'WORLD'),
-        'target_space'     : ("Target Space", 'WORLD'),
-        'use_x'            : ( ("Axes", 0), False),
-        'use_y'            : ( ("Axes", 1), False),
-        'use_z'            : ( ("Axes", 2), False),
-        'influence'        : ("Influence", 1),
-        'mute'             : ("Enable", True),
-        }
-        if custom_space_owner: del props_sockets['owner_space']
-        if custom_space_target: del props_sockets['target_space']
-        #
+        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 
             
             

+ 6 - 41
link_definitions.py

@@ -134,20 +134,8 @@ class LinkCopyLocationNode(Node, LinkNode):
     mantis_node_class_name=bl_idname
     mantis_node_class_name=bl_idname
 
 
     def init(self, context):
     def init(self, context):
-        self.inputs.new ('RelationshipSocket', "Input Relationship")
-        self.inputs.new ('FloatFactorSocket', "Head/Tail")
-        self.inputs.new ('BooleanSocket', "UseBBone")
-        self.inputs.new ('BooleanThreeTupleSocket', "Axes")
-        self.inputs.new ('BooleanThreeTupleSocket', "Invert")
-        self.inputs.new ('TransformSpaceSocket', "Target Space")
-        self.inputs.new ('TransformSpaceSocket', "Owner Space")
-        self.inputs.new ('BooleanSocket', "Offset")
-        self.inputs.new ('FloatFactorSocket', "Influence")
-        self.inputs.new ('xFormSocket', "Target")
-        self.inputs.new ('EnableSocket', "Enable")
-        #
-        self.outputs.new('RelationshipSocket', "Output Relationship")
-        # color
+        from .link_containers import LinkCopyLocationSockets
+        self.init_sockets(LinkCopyLocationSockets)
         self.use_custom_color = True
         self.use_custom_color = True
         self.color = linkColor
         self.color = linkColor
         self.initialized = True
         self.initialized = True
@@ -162,19 +150,8 @@ class LinkCopyRotationNode(Node, LinkNode):
     mantis_node_class_name=bl_idname
     mantis_node_class_name=bl_idname
 
 
     def init(self, context):
     def init(self, context):
-        self.inputs.new ('RelationshipSocket', "Input Relationship")
-        self.inputs.new ('RotationOrderSocket', "RotationOrder")
-        self.inputs.new ('EnumRotationMix', "Rotation Mix")
-        self.inputs.new ('BooleanThreeTupleSocket', "Axes")
-        self.inputs.new ('BooleanThreeTupleSocket', "Invert")
-        self.inputs.new ('TransformSpaceSocket', "Target Space")
-        self.inputs.new ('TransformSpaceSocket', "Owner Space")
-        self.inputs.new ('FloatFactorSocket', "Influence")
-        self.inputs.new ('xFormSocket', "Target")
-        self.inputs.new ('EnableSocket', "Enable")
-        #
-        self.outputs.new('RelationshipSocket', "Output Relationship")
-        # color
+        from .link_containers import LinkCopyRotationSockets
+        self.init_sockets(LinkCopyRotationSockets)
         self.use_custom_color = True
         self.use_custom_color = True
         self.color = linkColor
         self.color = linkColor
         self.initialized = True
         self.initialized = True
@@ -190,20 +167,8 @@ class LinkCopyScaleNode(Node, LinkNode):
     mantis_node_class_name=bl_idname
     mantis_node_class_name=bl_idname
 
 
     def init(self, context):
     def init(self, context):
-        self.inputs.new ('RelationshipSocket', "Input Relationship")
-        self.inputs.new ('BooleanSocket', "Offset")
-        self.inputs.new ('BooleanSocket', "Average")
-        self.inputs.new ('BooleanThreeTupleSocket', "Axes")
-        #self.inputs.new ('BooleanThreeTupleSocket', "Invert")
-        # dingus, this one doesn't have inverts
-        self.inputs.new ('TransformSpaceSocket', "Target Space")
-        self.inputs.new ('TransformSpaceSocket', "Owner Space")
-        self.inputs.new ('FloatFactorSocket', "Influence")
-        self.inputs.new ('xFormSocket', "Target")
-        self.inputs.new ('EnableSocket', "Enable")
-        #
-        self.outputs.new('RelationshipSocket', "Output Relationship")
-        # color
+        from .link_containers import LinkCopyScaleSockets
+        self.init_sockets(LinkCopyScaleSockets)
         self.use_custom_color = True
         self.use_custom_color = True
         self.color = linkColor
         self.color = linkColor
         self.initialized = True
         self.initialized = True