Sfoglia il codice sorgente

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 mesi fa
parent
commit
0f9a9d8d79
3 ha cambiato i file con 161 aggiunte e 191 eliminazioni
  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
 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.
@@ -445,8 +447,9 @@ SOCKETS_RENAMED=[ ("LinkDrivenParameter", "DriverSocket",   "Driver",     "Float
 
                 # NODE CLASS NAME             IN_OUT    SOCKET TYPE     SOCKET NAME     INDEX   MULTI     DEFAULT
 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_types = ["NodeGroupInput", "NodeGroupOutput", "SchemaIncomingConnection",
@@ -474,7 +477,9 @@ class MantisNode:
         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.
     """
-    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.signature = signature
         self.inputs = MantisNodeSocketCollection(node=self, is_input=True)
@@ -486,8 +491,15 @@ class MantisNode:
         self.hierarchy_dependencies, self.dependencies = [], []
         self.prepared = 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:
             self.parameters[socket.name] = None
         for socket in self.outputs:
@@ -495,19 +507,31 @@ class MantisNode:
         for key, value in additional_parameters.items():
             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:
             self.inputs[a].set_traverse_target(self.outputs[b])
             self.outputs[b].set_traverse_target(self.inputs[a])
             
 
-    def flush_links(self):
+    def flush_links(self) -> None:
         for inp in self.inputs.values():
             inp.flush_links()
         for out in self.outputs.values():
             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
         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.
@@ -517,7 +541,7 @@ class MantisNode:
         prop = trace[0][-1].parameters[trace[1].name] #trace[0] = the list of traced nodes; read its parameters
         return prop
     
-    def fill_parameters(self, ui_node=None):
+    def fill_parameters(self, ui_node=None)  -> None:
         from .utilities import get_node_prototype
         from .node_container_common import get_socket_value
         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))
                 else:
                     pass
+    # I don't think this works! but I like the idea
     def call_on_all_ancestors(self, *args, **kwargs):
         """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
@@ -586,9 +611,6 @@ class MantisNode:
         #             prOrange(dep)
         return
 
-        
-        
-    
     def bPrepare(self, bContext=None):
         return
     def bExecute(self, bContext=None):
@@ -598,9 +620,6 @@ class MantisNode:
     def __repr__(self): 
         return self.signature.__repr__()
 
-
-
-
 # do I need this and the link class above?
 class DummyLink:
     #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
                 self[socket.name] = NodeSocket(is_input=self.is_input, name=socket.name, node=self.node)
             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):
         """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 bpy.types import Bone
+from bpy.types import Bone, NodeTree
 from .base_definitions import MantisNode, GraphError, MantisSocketTemplate, FLOAT_EPSILON
 
 def TellClasses():
@@ -32,9 +32,55 @@ def TellClasses():
              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):
-    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.prepared = True
 
@@ -48,10 +94,14 @@ class MantisLinkNode(MantisNode):
             
         else:
             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
@@ -76,9 +126,7 @@ class LinkInherit(MantisLinkNode):
     '''A node representing inheritance'''
     
     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.set_traverse([('Parent', 'Inheritance')])
         self.executed = True
@@ -90,36 +138,32 @@ class LinkInherit(MantisLinkNode):
             if (node.node_type == 'XFORM'):
                 return node
         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):
     '''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 }
-        self.inputs.init_sockets(inputs)
-        self.outputs.init_sockets(["Output Relationship"])
         self.init_parameters(additional_parameters=additional_parameters)
         self.set_traverse([("Input Relationship", "Output Relationship")])
         
-    
     def GetxForm(self):
         return GetxForm(self)
 
@@ -133,9 +177,7 @@ class LinkCopyLocation(MantisLinkNode):
         if constraint_name := self.evaluate_input("Name"):
             c.name = constraint_name
         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':
-            custom_space_owner=True
             c.owner_space='CUSTOM'
             xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
             if isinstance(xf, Bone):
@@ -143,66 +185,45 @@ class LinkCopyLocation(MantisLinkNode):
             else:
                 c.space_object=xf
         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'
             xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
             if isinstance(xf, Bone):
                 c.space_object=self.inputs["Target Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
             else:
                 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)
         self.executed = True
         
     def bFinalize(self, bContext = None):
         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):
     '''A node representing Copy Rotation'''
     
     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 }
-        self.inputs.init_sockets(inputs)
-        self.outputs.init_sockets(["Output Relationship"])
         self.init_parameters(additional_parameters=additional_parameters)
         self.set_traverse([("Input Relationship", "Output Relationship")])
         
-        
-
-    
     def GetxForm(self):
         return GetxForm(self)
 
@@ -225,9 +246,7 @@ class LinkCopyRotation(MantisLinkNode):
         if constraint_name := self.evaluate_input("Name"):
             c.name = constraint_name
         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':
-            custom_space_owner=True
             c.owner_space='CUSTOM'
             xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
             if isinstance(xf, Bone):
@@ -235,58 +254,41 @@ class LinkCopyRotation(MantisLinkNode):
             else:
                 c.space_object=xf
         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'
             xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
             if isinstance(xf, Bone):
                 c.space_object=self.inputs["Target Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
             else:
                 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)
         self.executed = True
             
     def bFinalize(self, bContext = None):
         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):
     '''A node representing Copy Scale'''
     
     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 }
-        self.inputs.init_sockets(inputs)
-        self.outputs.init_sockets(["Output Relationship"])
         self.init_parameters(additional_parameters=additional_parameters)
         self.set_traverse([("Input Relationship", "Output Relationship")])
     
@@ -303,9 +305,7 @@ class LinkCopyScale(MantisLinkNode):
         if constraint_name := self.evaluate_input("Name"):
             c.name = constraint_name
         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':
-            custom_space_owner=True
             c.owner_space='CUSTOM'
             xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
             if isinstance(xf, Bone):
@@ -313,27 +313,13 @@ class LinkCopyScale(MantisLinkNode):
             else:
                 c.space_object=xf
         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'
             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
-        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)   
         self.executed = True 
             

+ 6 - 41
link_definitions.py

@@ -134,20 +134,8 @@ class LinkCopyLocationNode(Node, LinkNode):
     mantis_node_class_name=bl_idname
 
     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.color = linkColor
         self.initialized = True
@@ -162,19 +150,8 @@ class LinkCopyRotationNode(Node, LinkNode):
     mantis_node_class_name=bl_idname
 
     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.color = linkColor
         self.initialized = True
@@ -190,20 +167,8 @@ class LinkCopyScaleNode(Node, LinkNode):
     mantis_node_class_name=bl_idname
 
     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.color = linkColor
         self.initialized = True