Ver Fonte

Spline Indexes for everything!

This commit ensures that all existing nodes which use curves
have an input for spline index.
now you can rig using constraints and Mantis nodes
on any curve, without having to split it up first. neat!
Joseph Brandenburg há 6 meses atrás
pai
commit
c0fa3562cd

+ 1 - 1
__init__.py

@@ -16,7 +16,7 @@ from .utilities import prRed
 
 MANTIS_VERSION_MAJOR=0
 MANTIS_VERSION_MINOR=10
-MANTIS_VERSION_SUB=8
+MANTIS_VERSION_SUB=9
 
 classLists = [module.TellClasses() for module in [
  link_definitions,

+ 13 - 7
base_definitions.py

@@ -233,11 +233,12 @@ class MantisUINode:
             identifier = template.name
             if template.identifier:
                 identifier = template.identifier
+            use_multi_input = template.use_multi_input if template.is_input else False
             socket = collection.new(
                 template.bl_idname,
                 template.name,
                 identifier=identifier,
-                use_multi_input=template.use_multi_input
+                use_multi_input=use_multi_input
             )
             socket.hide= template.hide
             if template.category:
@@ -247,7 +248,8 @@ class MantisUINode:
                 socket.default_value = template.default_value
                 # this can throw a TypeError - it is the caller's
                 #   responsibility to send the right type.
-        
+            if template.use_multi_input: # this is an array
+                socket.display_shape = 'SQUARE_DOT'
             
 class SchemaUINode(MantisUINode):
     mantis_node_library='.schema_containers'
@@ -574,16 +576,20 @@ SOCKETS_REMOVED=[("UtilityDriverVariable", "Transform Channel"),
                  ("LinkDrivenParameter", "Enable")]
                   # Node Class           #Prior bl_idname  # prior name # new bl_idname #       new name,          # Multi
 SOCKETS_RENAMED=[ ("LinkDrivenParameter", "DriverSocket",   "Driver",     "FloatSocket",        "Value",              False),
-                  ("DeformerHook",        "IntSocket",      "Index",      "UnsignedIntSocket",  "Curve Point Index",  False)]
+                  ("DeformerHook",        "IntSocket",      "Index",      "UnsignedIntSocket",  "Point Index",  False)]
 
                 # 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'),
                ("LinkCopyScale",             'INPUT',  "BooleanSocket", "Additive",     3,      False,    False),
-               ("DeformerHook",              'INPUT',  "FloatFactorSocket", "Influence", 3,     False,    1.0),
-               ("DeformerHook",              'INPUT',  "BooleanSocket", "Auto-Bezier",   4,     False,    True),
-               ("UtilityCompare",            'INPUT',  "EnumCompareOperation", "Comparison", 0,  False,   'EQUAL'),
+               ("DeformerHook",              'INPUT',  "FloatFactorSocket", "Influence",3,      False,    1.0),
+               ("DeformerHook",              'INPUT',  "UnsignedIntSocket", "Spline Index", 2,  False,    0),
+               ("DeformerHook",              'INPUT',  "BooleanSocket", "Auto-Bezier",  5,      False,    True),
+               ("UtilityCompare",            'INPUT',  "EnumCompareOperation", "Comparison", 0, False,    'EQUAL'),
+               ("UtilityMatrixFromCurve",    'INPUT',  "UnsignedIntSocket", "Spline Index",  1, False,    0),
+               ("UtilityMatricesFromCurve",  'INPUT',  "UnsignedIntSocket", "Spline Index",  1, False,    0),
+               ("UtilityPointFromCurve",     'INPUT',  "UnsignedIntSocket", "Spline Index",  1, False,    0),
                ]
 
 # replace names with bl_idnames for reading the tree and solving schemas.
@@ -632,7 +638,7 @@ class MantisExecutionContext():
     ):
         self.base_tree = base_tree
         self.execution_id = base_tree.execution_id
-        self.b_objects=[] # objects created by Mantis during execution
+        self.b_objects={} # objects created by Mantis during execution
 
 class MantisNode:
     """

+ 7 - 1
deformer_containers.py

@@ -300,6 +300,12 @@ class DeformerHook(MantisDeformerNode):
         if isinstance(target, Bone) or isinstance(target, PoseBone):
             subtarget = target.name; target = target.id_data
         ob=self.GetxForm().bGetObject()
+
+        if ob.type == 'CURVE':
+            spline_index = self.evaluate_input("Spline Index")
+            from .utilities import get_extracted_spline_object
+            ob = get_extracted_spline_object(ob, spline_index, self.mContext)
+        
         reuse = False
         for m in ob.modifiers:
             if  m.type == 'HOOK' and m.object == target and m.subtarget == subtarget:
@@ -328,7 +334,7 @@ class DeformerHook(MantisDeformerNode):
             vertices_used = list(filter(lambda a : a != 0, vertices_used))
             if include_0: vertices_used.append(0)
         # now we add the selected vertex to the list, too
-        vertex = self.evaluate_input("Curve Point Index")
+        vertex = self.evaluate_input("Point Index")
         if ob.type == 'CURVE' and ob.data.splines[0].type == 'BEZIER' and auto_bezier:
             if affect_radius:
                 self.driver_for_radius(ob, target_node.bGetObject(), vertex, d.strength)

+ 16 - 0
deformer_definitions.py

@@ -73,6 +73,22 @@ class DeformerHook(Node, DeformerNode):
     def init(self, context):
         self.init_sockets(HookSockets)
         self.initialized = True
+    
+    def display_update(self, parsed_tree, context):
+        curve_sockets = [
+            self.inputs["Affect Curve Radius"],
+            self.inputs['Auto-Bezier'],
+            self.inputs['Spline Index'],
+        ]
+        is_curve_hook=True
+        if self.outputs["Deformer"].is_linked:
+            from bpy.types import Object
+            if (mantis_node := parsed_tree.get(get_signature_from_edited_tree(self, context))):
+                if (xF_node := mantis_node.GetxForm()):
+                    if (ob := xF_node.bObject) and isinstance (xF_node, Object):
+                        if ob.type != 'CURVE': is_curve_hook=False
+        for socket in curve_sockets:
+            socket.hide=not is_curve_hook
 
 
 from .utilities import get_socket_maps, relink_socket_map

+ 6 - 5
deformer_socket_templates.py

@@ -1,7 +1,7 @@
 from .base_definitions import MantisSocketTemplate as SockTemplate
 from dataclasses import replace
 
-
+from .misc_nodes_socket_templates import SplineIndexTemplate
 
 
 Target = SockTemplate(name="Target", bl_idname='xFormSocket',
@@ -12,14 +12,15 @@ HookSockets= [
     DeformerInput := SockTemplate(name="Deformer", bl_idname='DeformerSocket',
         is_input=True,),
     HookTarget := replace(Target, name="Hook Target"),
-    CurvePointIndex := SockTemplate(name="Curve Point Index", bl_idname='UnsignedIntSocket',
-        is_input=True, default_value=0, ),
+    replace(SplineIndexTemplate,),
+    CurvePointIndex := SockTemplate(name="Point Index", bl_idname='UnsignedIntSocket',
+        is_input=True, default_value=0 ),
     Influence := SockTemplate(name="Influence", bl_idname='FloatFactorSocket',
         is_input=True, default_value=1.0, blender_property='strength'),
     HookDrivesRadius := SockTemplate(name="Affect Curve Radius", bl_idname='BooleanSocket',
-        is_input=True, default_value=True),
+        is_input=True, default_value=True,),
     HookAutoBezier := SockTemplate(name="Auto-Bezier", bl_idname='BooleanSocket',
-        is_input=True, default_value=True, ),
+        is_input=True, default_value=True,),
     DeformerOutput := SockTemplate(name="Deformer", bl_idname='DeformerSocket',
         is_input=False,), 
 ]

+ 9 - 1
link_containers.py

@@ -829,9 +829,17 @@ class LinkSplineIK(MantisLinkNode):
 
     def bExecute(self, bContext = None,):
         prepare_parameters(self)
+        if not self.inputs['Target'].is_linked:
+            print(f"INFO: {self} is not connected to any target, it will not be generated.")
+            return
         prGreen("Creating Spline-IK Constraint for bone: \""+ self.GetxForm().bGetObject().name + "\"")
         c = self.GetxForm().bGetObject().constraints.new('SPLINE_IK')
-        self.get_target_and_subtarget(c)
+        # set the spline - we need to get the right one 
+        spline_index = self.evaluate_input("Spline Index")
+        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)
+        c.target=curve
         if constraint_name := self.evaluate_input("Name"):
             c.name = constraint_name
         self.bObject = c

+ 3 - 1
link_socket_templates.py

@@ -1,6 +1,7 @@
 from .base_definitions import MantisSocketTemplate as SockTemplate
 from bpy import app
 
+from .misc_nodes_socket_templates import SplineIndexTemplate
 # Socket Templates we will reuse:
 # inputs:
 InputRelationshipTemplate : SockTemplate = SockTemplate(
@@ -384,6 +385,7 @@ LinkArmatureSockets=[
 LinkSplineIKSockets = [
     InputRelationshipTemplate,
     TargetTemplate,
+    SplineIndexTemplate,
     ChainLengthTemplate,
     SockTemplate(name="Even Divisions", bl_idname="BooleanSocket", is_input=True,
                  default_value=False, blender_property='use_even_divisions'),
@@ -405,4 +407,4 @@ LinkSplineIKSockets = [
 
 # Remove this socket because of Blender changes.
 if (app.version >= (4, 5, 0)):
-    LinkSplineIKSockets.pop(8)
+    LinkSplineIKSockets.pop(9)

+ 30 - 63
misc_nodes.py

@@ -63,13 +63,11 @@ def TellClasses():
              UtilityPrint,
             ]
 
-
 def matrix_from_head_tail(head, tail, normal=None):
     from mathutils import Vector, Quaternion, Matrix
     if normal is None:
         rotation = Vector((0,1,0)).rotation_difference((tail-head).normalized()).to_matrix()
         m= Matrix.LocRotScale(head, rotation, None)
-        m[3][3] = (tail-head).length
     else: # construct a basis matrix
         m = Matrix.Identity(3)
         axis = (tail-head).normalized()
@@ -78,6 +76,8 @@ def matrix_from_head_tail(head, tail, normal=None):
         m[1]=axis
         m[2]=normal
         m = m.transposed().to_4x4()
+        m.translation=head.copy()
+    m[3][3]=(tail-head).length
     return m
 
 def get_mesh_from_curve(curve_name : str, execution_id : str, bContext, ribbon=True):
@@ -278,17 +278,7 @@ class UtilityMatrixFromCurve(MantisNode):
     '''Get a matrix from a curve'''
 
     def __init__(self, signature, base_tree):
-        super().__init__(signature, base_tree)
-        inputs = [
-          "Curve"            ,
-          "Total Divisions"  ,
-          "Matrix Index"     ,
-        ]
-        outputs = [
-          "Matrix" ,
-        ]
-        self.inputs.init_sockets(inputs)
-        self.outputs.init_sockets(outputs)
+        super().__init__(signature, base_tree, MatrixFromCurveSockets)
         self.init_parameters()
         self.node_type = "UTILITY"
 
@@ -311,19 +301,21 @@ class UtilityMatrixFromCurve(MantisNode):
             #
             num_divisions = self.evaluate_input("Total Divisions")
             m_index = self.evaluate_input("Matrix Index")
+            spline_index = self.evaluate_input("Spline Index")
+            splines_factors = [ [] for i in range (spline_index)]
             factors = [1/num_divisions*m_index, 1/num_divisions*(m_index+1)]
-            data = data_from_ribbon_mesh(m, [factors], curve.matrix_world)
-            head=data[0][0][0]
-            tail= data[0][0][1]
+            splines_factors.append(factors)
+            data = data_from_ribbon_mesh(m, splines_factors, curve.matrix_world)
+            head=data[spline_index][0][0]
+            tail= data[spline_index][0][1]
             axis = (tail-head).normalized()
-            normal=data[0][2][0]
+            normal=data[spline_index][2][0]
             # make sure the normal is perpendicular to the tail
             from .utilities import make_perpendicular
             normal = make_perpendicular(axis, normal)
             mat = matrix_from_head_tail(head, tail, normal)
             # this is in world space... let's just convert it back
             mat.translation = head - curve.location
-            mat[3][3]=(tail-head).length
 
             # TODO HACK TODO
             # all the nodes should work in world-space, and it should be the responsibility
@@ -341,16 +333,7 @@ class UtilityPointFromCurve(MantisNode):
     '''Get a point from a curve'''
 
     def __init__(self, signature, base_tree):
-        super().__init__(signature, base_tree)
-        inputs = [
-          "Curve"       ,
-          "Factor"      ,
-        ]
-        outputs = [
-          "Point" ,
-        ]
-        self.inputs.init_sockets(inputs)
-        self.outputs.init_sockets(outputs)
+        super().__init__(signature, base_tree, PointFromCurveSockets)
         self.init_parameters()
         self.node_type = "UTILITY"
 
@@ -367,9 +350,12 @@ class UtilityPointFromCurve(MantisNode):
             from .utilities import data_from_ribbon_mesh
             #
             num_divisions = 1
+            spline_index = self.evaluate_input("Spline Index")
+            splines_factors = [ [] for i in range (spline_index)]
             factors = [self.evaluate_input("Factor")]
-            data = data_from_ribbon_mesh(m, [factors], curve.matrix_world)
-            p = data[0][0][0] - curve.location
+            splines_factors.append(factors)
+            data = data_from_ribbon_mesh(m, splines_factors, curve.matrix_world)
+            p = data[spline_index][0][0] - curve.location
         self.parameters["Point"] = p
         self.prepared, self.executed = True, True
     
@@ -380,16 +366,7 @@ class UtilityMatricesFromCurve(MantisNode):
     '''Get matrices from a curve'''
 
     def __init__(self, signature, base_tree):
-        super().__init__(signature, base_tree)
-        inputs = [
-          "Curve"            ,
-          "Total Divisions"  ,
-        ]
-        outputs = [
-          "Matrices" ,
-        ]
-        self.inputs.init_sockets(inputs)
-        self.outputs.init_sockets(outputs)
+        super().__init__(signature, base_tree, MatricesFromCurveSockets)
         self.init_parameters()
         self.node_type = "UTILITY"
 
@@ -413,17 +390,20 @@ class UtilityMatricesFromCurve(MantisNode):
             mesh = get_mesh_from_curve(curve.name, self.base_tree.execution_id, bContext)
             from .utilities import data_from_ribbon_mesh
             num_divisions = self.evaluate_input("Total Divisions")
+            spline_index = self.evaluate_input("Spline Index")
+            splines_factors = [ [] for i in range (spline_index)]
             factors = [0.0] + [(1/num_divisions*(i+1)) for i in range(num_divisions)]
-            data = data_from_ribbon_mesh(mesh, [factors], curve.matrix_world)
-            
-            # 0 is the spline index. 0 selects points as opposed to normals or whatever.
+            splines_factors.append(factors)
+            data = data_from_ribbon_mesh(mesh, splines_factors, curve.matrix_world)
+            # [spline_index][points,tangents,normals][datapoint_index]
             from .utilities import make_perpendicular
-            matrices = [matrix_from_head_tail(
-                data[0][0][i], 
-                data[0][0][i+1],
-                make_perpendicular((data[0][0][i+1]-data[0][0][i]).normalized(), data[0][2][i]),) \
-                    for i in range(num_divisions)]
-        
+            matrices=[]
+            for i in range(num_divisions):
+                m = matrix_from_head_tail (
+                data[spline_index][0][i], data[spline_index][0][i+1],
+                make_perpendicular((data[spline_index][0][i+1]-data[spline_index][0][i]).normalized(), data[spline_index][2][i]),)
+                m.translation = data[spline_index][0][i] - curve.location
+                matrices.append(m)
 
         for link in self.outputs["Matrices"].links:
             for i, m in enumerate(matrices):
@@ -478,17 +458,7 @@ class UtilityNumberOfCurveSegments(MantisNode):
 
 class UtilityMatrixFromCurveSegment(MantisNode):
     def __init__(self, signature, base_tree):
-        super().__init__(signature, base_tree)
-        inputs = [
-          "Curve"            ,
-          "Spline Index"     ,
-          "Segment Index"    ,
-        ]
-        outputs = [
-          "Matrix"           ,
-        ]
-        self.inputs.init_sockets(inputs)
-        self.outputs.init_sockets(outputs)
+        super().__init__(signature, base_tree, MatrixFromCurveSegmentSockets)
         self.init_parameters()
         self.node_type = "UTILITY"
 
@@ -535,7 +505,6 @@ class UtilityMatrixFromCurveSegment(MantisNode):
             normal = make_perpendicular(axis, normal)
             m = matrix_from_head_tail(head, tail, normal)
             m.translation = head - curve.location
-            m[3][3]=(tail-head).length
             self.parameters["Matrix"] = m
         self.prepared, self.executed = True, True
     
@@ -1713,8 +1682,6 @@ class UtilitySetBoneMatrixTail(MantisNode):
       self.prepared = True
       self.executed = True
 
-
-
 class UtilityPrint(MantisNode):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)

+ 38 - 4
misc_nodes_socket_templates.py

@@ -1,11 +1,46 @@
 from .base_definitions import MantisSocketTemplate as SockTemplate
 from dataclasses import replace
 
-GetCurvePointSockets=[
+SplineIndexTemplate = SockTemplate(name="Spline Index",
+        bl_idname='UnsignedIntSocket', is_input=True, default_value=0,)
+
+MatrixFromCurveSockets=[
     CurveTemplate := SockTemplate(name="Curve", bl_idname='EnumCurveSocket', 
         is_input=True,),
-    SplineIndexTemplate := SockTemplate(name="Spline Index",
+    SplineIndexTemplate,
+    TotalDivisionsTemplate := SockTemplate(name="Total Divisions",
         bl_idname='UnsignedIntSocket', is_input=True, default_value=0,),
+    MatrixIndexTemplate := SockTemplate(name="Matrix Index",
+        bl_idname='UnsignedIntSocket', is_input=True, default_value=0,),
+    MatrixOutTemplate := SockTemplate(
+        name="Matrix", is_input=False,  bl_idname='MatrixSocket', ),
+]
+
+MatricesOutTemplate = replace(MatrixOutTemplate,
+        name='Matrices', use_multi_input=True)
+MatricesFromCurveSockets=MatrixFromCurveSockets.copy()
+MatricesFromCurveSockets[4] = MatricesOutTemplate
+MatricesFromCurveSockets.pop(3)
+
+MatrixFromCurveSegmentSockets=[
+    CurveTemplate,
+    SplineIndexTemplate,
+    SegmentIndexTemplate := replace(SplineIndexTemplate, name="Segment Index"),
+    MatrixOutTemplate,
+]
+
+PointFromCurveSockets=[
+    CurveTemplate,
+    SplineIndexTemplate,
+    FactorTemplate := SockTemplate(name="Factor", bl_idname='FloatFactorSocket', 
+        is_input=True,),
+    PointOutTemplate := SockTemplate(name="Point", bl_idname="VectorSocket",
+        is_input=False, )
+]
+
+GetCurvePointSockets=[
+    CurveTemplate,
+    SplineIndexTemplate,
     IndexTemplate := SockTemplate(name="Index",
         bl_idname='UnsignedIntSocket', is_input=True, default_value=0,),
     OutputPointTemplate := SockTemplate(name="Point",
@@ -28,8 +63,7 @@ GetNearestFactorOnCurveSockets=[
 MatrixInvertSockets=[
     Matrix1Template := SockTemplate(
     name="Matrix 1", is_input=True,  bl_idname='MatrixSocket', ),
-    MatrixOutTemplate := SockTemplate(
-    name="Matrix", is_input=False,  bl_idname='MatrixSocket', ),
+    MatrixOutTemplate,
 ]
 
 MatrixComposeSockets=[

+ 5 - 17
misc_nodes_ui.py

@@ -257,10 +257,7 @@ class UtilityMatrixFromCurve(Node, MantisUINode):
     mantis_node_class_name=bl_idname
     
     def init(self, context):
-        curv = self.inputs.new("EnumCurveSocket", "Curve")
-        self.inputs.new('IntSocket', 'Total Divisions')
-        self.inputs.new('IntSocket', 'Matrix Index')
-        self.outputs.new("MatrixSocket", "Matrix")
+        self.init_sockets(MatrixFromCurveSockets)
         self.initialized = True
 
 class UtilityPointFromCurve(Node, MantisUINode):
@@ -273,9 +270,7 @@ class UtilityPointFromCurve(Node, MantisUINode):
     mantis_node_class_name=bl_idname
     
     def init(self, context):
-        curv = self.inputs.new("EnumCurveSocket", "Curve")
-        self.inputs.new('FloatFactorSocket', 'Factor')
-        self.outputs.new("VectorSocket", "Point")
+        self.init_sockets(PointFromCurveSockets)
         self.initialized = True
 
 class UtilityNumberOfCurveSegments(Node, MantisUINode):
@@ -303,15 +298,12 @@ class UtilityMatrixFromCurveSegment(Node, MantisUINode):
     mantis_node_class_name=bl_idname
     
     def init(self, context):
-        self.inputs.new("EnumCurveSocket", "Curve")
-        self.inputs.new('UnsignedIntSocket', 'Spline Index')
-        self.inputs.new('UnsignedIntSocket', 'Segment Index')
-        self.outputs.new("MatrixSocket", "Matrix")
+        self.init_sockets(MatrixFromCurveSegmentSockets)
         self.initialized = True
 
 class UtilityGetCurvePoint(Node, MantisUINode):
     bl_idname = 'UtilityGetCurvePoint'
-    bl_label = "Get Curve Point"
+    bl_label = "Control Point from Curve"
     bl_icon = 'NODE'
     initialized : bpy.props.BoolProperty(default = False)
     mantis_node_class_name=bl_idname
@@ -351,11 +343,7 @@ class UtilityMatricesFromCurve(Node, MantisUINode):
     mantis_node_class_name=bl_idname
     
     def init(self, context):
-        curv = self.inputs.new("EnumCurveSocket", "Curve")
-        curv.icon = "OUTLINER_OB_CURVE"
-        self.inputs.new('IntSocket', 'Total Divisions')
-        o = self.outputs.new("MatrixSocket", "Matrices")
-        o.display_shape = 'SQUARE_DOT'
+        self.init_sockets(MatricesFromCurveSockets)
         self.initialized = True
 
 def display_update_choose_nearest(self, parsed_tree, context):

+ 41 - 9
utilities.py

@@ -671,21 +671,53 @@ def old_bad_wrap_that_should_be_refactored(val, maxValue, minValue = None):
     return val
     #TODO clean this up
 
+def extract_spline_suffix(spline_index):
+    return ".spline."+str(spline_index).zfill(3)+".extracted"
+
+def do_extract_spline(data, spline):
+    remove_me = []
+    for other_spline in data.splines:
+        if other_spline != spline: remove_me.append(other_spline)
+    while remove_me: data.splines.remove(remove_me.pop())
+
 def extract_spline(curve, spline_index):
-    spline_suffix = "spline."+str(spline_index).zfill(3)+".extracted"
-    new_ob=curve.copy(); new_ob.name=curve.name+spline_suffix
+    """ Given a curve object and spline index, returns a new object
+        containing only the selcted spline. The new object is bound to
+        the original curve.
+    """
+    if len(curve.data.splines) == 1:
+        return curve # nothing to do here.
+    spline_suffix = extract_spline_suffix(spline_index)
+    from bpy import data
+    if (new_ob := data.objects.get(curve.name+spline_suffix)) is None:
+        new_ob=curve.copy(); new_ob.name=curve.name+spline_suffix
+    # if the data exists, it is probably stale, so delete it and start over.
+    if (old_data := data.objects.get(curve.data.name+spline_suffix)) is not None:
+        data.curves.remove(old_data)
     new_data=curve.data.copy(); new_data.name=curve.data.name+spline_suffix
     new_ob.data = new_data
     # do not check for index error here, it is the calling function's responsibility
-    keep_me = new_data.splines[spline_index]
-    remove_me = []
-    for spline in new_data.splines:
-        if spline != keep_me:
-            remove_me.append(spline)
-    while remove_me:
-        new_data.splines.remove(remove_me.pop())
+    do_extract_spline(new_data, new_data.splines[spline_index])
+    # Set up a relationship between the new object and the old object
+    # now, weirdly enough - we can't use parenting very easily because Blender
+    # defines the parent on a curve relative to the evaluated path animation
+    # Setting the inverse matrix is too much work. Use Copy Transforms instead.
+    new_ob.constraints.clear(); new_ob.modifiers.clear()
+    c = new_ob.constraints.new("COPY_TRANSFORMS"); c.target=curve
+    new_ob.parent=curve
     return new_ob
 
+def get_extracted_spline_object(proto_curve, spline_index, mContext):
+    # we're storing it separately like this to ensure all nodes use the same
+    #   object if they extract the same spline for use by Mantis.
+    # this should be transparent to the user since it is working around a
+    #   a limitation in Blender.
+    if ( curve := mContext.b_objects.get(
+                proto_curve.name+extract_spline_suffix(spline_index))) is None:
+        curve = extract_spline(proto_curve, spline_index)
+        mContext.b_objects[curve.name] = curve
+    return curve
+
 def RibbonMeshEdgeLengths(m, ribbon):
     tE = ribbon[0]; bE = ribbon[1]; c = ribbon[2]
     lengths = []

+ 12 - 14
xForm_containers.py

@@ -678,17 +678,6 @@ class xFormGeometryObject(MantisNode):
         # NOW: find out if we need to duplicate the object data.
         dupe_data=False
         node_line = trace_single_line(self, "Deformer")[0]
-        from .deformer_containers import DeformerHook
-        for deformer in node_line:
-            if isinstance(deformer, DeformerHook) and  \
-               deformer.evaluate_input("Affect Curve Radius") == True and \
-               self.bObject.type == 'CURVE':
-                    print(f"INFO: Duplicating data {self.bObject.data.name} in {self} so it can be used for drivers.")
-                    dupe_data=True; break
-        if dupe_data:
-            name = self.bObject.data.name
-            self.bObject.data=self.bObject.data.copy()
-            self.bObject.data.name = name+"_MANTIS"
         reset_object_data(self.bObject)
         matrix= get_matrix(self)
         self.parameters['Matrix'] = matrix
@@ -811,12 +800,14 @@ class xFormObjectInstance(MantisNode):
         return self.bObject
 
 from .base_definitions import MantisSocketTemplate as SockTemplate
+from .misc_nodes_socket_templates import SplineIndexTemplate
 xFormCurvePinSockets = [
     NameTemplate := SockTemplate(
         name="Name", is_input=True,  bl_idname='StringSocket',
         default_value='Curve Pin', blender_property='name' ),
     ParentCurveTemplate := SockTemplate(
         name="Parent Curve", is_input=True,  bl_idname='xFormSocket', ),
+    SplineIndexTemplate,
     FactorTemplate := SockTemplate(
         name="Curve Pin Factor", is_input=True,  bl_idname='FloatFactorSocket',
         default_value=0.0, blender_property='offset_factor' ),
@@ -877,9 +868,6 @@ class xFormCurvePin(MantisNode):
         self.bObject = ob
         
         reset_object_data(ob)
-        # Link to Scene:
-        if (ob.name not in bContext.view_layer.active_layer_collection.collection.objects):
-            bContext.view_layer.active_layer_collection.collection.objects.link(ob)
         
         node_line = trace_single_line(self, "Parent Curve")[0][1:] # slice excludes self
         for other_node in node_line:
@@ -896,6 +884,16 @@ class xFormCurvePin(MantisNode):
                               " not {curve.type}")
         # we'll limit all the transforms so we can parent it
         #  because it is annoying to have a cluttered outliner.
+        #
+        # always do this so that everything stays consistent.
+        spline_index = self.evaluate_input("Spline Index")
+        from .utilities import get_extracted_spline_object
+        curve = get_extracted_spline_object(curve, spline_index, self.mContext)
+        # Link to Scene:
+        for link_me in [ob, curve]:
+            if (link_me.name not in bContext.view_layer.active_layer_collection.collection.objects):
+                bContext.view_layer.active_layer_collection.collection.objects.link(link_me)
+
         c = ob.constraints.new("LIMIT_LOCATION")
         for max_min in ['max','min']:
             for axis in "xyz":