Ver Fonte

Add Curve Segment nodes

New Nodes:
 - Number of Curve Segments
 - Matrix from Curve Segment
Joseph Brandenburg há 6 meses atrás
pai
commit
10bc506dc0
4 ficheiros alterados com 161 adições e 20 exclusões
  1. 2 0
      __init__.py
  2. 120 18
      misc_nodes.py
  3. 34 2
      misc_nodes_ui.py
  4. 5 0
      utilities.py

+ 2 - 0
__init__.py

@@ -131,6 +131,8 @@ matrix_category = [
         NodeItem("UtilityMetaRig"),
         NodeItem("UtilityMatrixFromCurve"),
         NodeItem("UtilityMatricesFromCurve"),
+        NodeItem("UtilityNumberOfCurveSegments"),
+        NodeItem("UtilityMatrixFromCurveSegment"),
         NodeItem("UtilityPointFromCurve"),
         NodeItem("UtilityPointFromBoneMatrix"),
         NodeItem("UtilitySetBoneLength"),

+ 120 - 18
misc_nodes.py

@@ -24,6 +24,8 @@ def TellClasses():
              UtilityPointFromCurve,
              UtilityMatrixFromCurve,
              UtilityMatricesFromCurve,
+             UtilityNumberOfCurveSegments,
+             UtilityMatrixFromCurveSegment,
              UtilityMetaRig,
              UtilityBoneProperties,
              UtilityDriverVariable,
@@ -56,13 +58,29 @@ def TellClasses():
             ]
 
 
-def matrix_from_head_tail(head, tail):
+def matrix_from_head_tail(head, tail, normal=None):
     from mathutils import Vector, Quaternion, Matrix
-    rotation = Vector((0,1,0)).rotation_difference((tail-head).normalized()).to_matrix()
-    m= Matrix.LocRotScale(head, rotation, None)
-    m[3][3] = (tail-head).length
+    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()
+        conormal = axis.cross(normal)
+        m[0]=conormal
+        m[1]=axis
+        m[2]=normal
+        m = m.transposed().to_4x4()
     return m
 
+def cleanup_curve(curve_name : str, execution_id : str) -> None:
+        import bpy
+        curve = bpy.data.objects.get(curve_name)
+        m_name = curve.name+'.'+ execution_id
+        if (mesh := bpy.data.meshes.get(m_name)):
+            bpy.data.meshes.remove(mesh)
+
 #*#-------------------------------#++#-------------------------------#*#
 # U T I L I T Y   N O D E S
 #*#-------------------------------#++#-------------------------------#*#
@@ -235,12 +253,7 @@ class UtilityMatrixFromCurve(MantisNode):
         self.executed = True
     
     def bFinalize(self, bContext=None):
-        import bpy
-        curve_name = self.evaluate_input("Curve")
-        curve = bpy.data.objects.get(curve_name)
-        m_name = curve.name+'.'+self.base_tree.execution_id
-        if (mesh := bpy.data.meshes.get(m_name)):
-            bpy.data.meshes.remove(mesh)
+        cleanup_curve(self.evaluate_input("Curve"), self.base_tree.execution_id)
 
 
 class UtilityPointFromCurve(MantisNode):
@@ -261,7 +274,6 @@ class UtilityPointFromCurve(MantisNode):
         self.node_type = "UTILITY"
 
     def bPrepare(self, bContext = None,):
-        from mathutils import Matrix
         import bpy
         curve = bpy.data.objects.get(self.evaluate_input("Curve"))
         if not curve:
@@ -286,12 +298,7 @@ class UtilityPointFromCurve(MantisNode):
         self.prepared, self.executed = True, True
     
     def bFinalize(self, bContext=None):
-        import bpy
-        curve_name = self.evaluate_input("Curve")
-        curve = bpy.data.objects.get(curve_name)
-        m_name = curve.name+'.'+self.base_tree.execution_id
-        if (mesh := bpy.data.meshes.get(m_name)):
-            bpy.data.meshes.remove(mesh)
+        cleanup_curve(self.evaluate_input("Curve"), self.base_tree.execution_id)
 
 class UtilityMatricesFromCurve(MantisNode):
     '''Get matrices from a curve'''
@@ -302,7 +309,7 @@ class UtilityMatricesFromCurve(MantisNode):
           "Curve"            ,
           "Total Divisions"  ,
         ]
-        self.outputs = [
+        outputs = [
           "Matrices" ,
         ]
         self.inputs.init_sockets(inputs)
@@ -362,6 +369,101 @@ class UtilityMatricesFromCurve(MantisNode):
             prGreen(f"Freeing mesh data {m_name}...")
             bpy.data.meshes.remove(mesh)
 
+class UtilityNumberOfCurveSegments(MantisNode):
+    def __init__(self, signature, base_tree):
+        super().__init__(signature, base_tree)
+        inputs = [
+          "Curve"            ,
+          "Spline Index"     ,
+        ]
+        outputs = [
+          "Number of Segments" ,
+        ]
+        self.inputs.init_sockets(inputs)
+        self.outputs.init_sockets(outputs)
+        self.init_parameters()
+        self.node_type = "UTILITY"
+    
+    def bPrepare(self, bContext = None,):
+        import bpy
+        curve_name = self.evaluate_input("Curve")
+        curve = bpy.data.objects.get(curve_name)
+        spline = curve.data.splines[self.evaluate_input("Spline Index")]
+        if spline.type == "BEZIER":
+            self.parameters["Number of Segments"] = len(spline.bezier_points)-1
+        else:
+            self.parameters["Number of Segments"] = len(spline.points)-1
+        self.prepared = True
+        self.executed = True
+
+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)
+        self.init_parameters()
+        self.node_type = "UTILITY"
+
+    def bPrepare(self, bContext = None,):
+        from mathutils import Matrix
+        import bpy
+        curve = bpy.data.objects.get(self.evaluate_input("Curve"))
+        if not curve:
+            raise RuntimeError(f"No curve found for {self}.")
+        else:
+            from .utilities import mesh_from_curve, data_from_ribbon_mesh
+            if not bContext: bContext = bpy.context
+            m_name = curve.name+'.'+self.base_tree.execution_id
+            if not (m := bpy.data.meshes.get(m_name)):
+                m = mesh_from_curve(curve, bContext)
+                m.name = m_name
+            # this section is dumb, but it is because the data_from_ribbon_mesh
+            #  function is designed to pull data from many splines at once (for optimization)
+            #  so we have to give it empty splines for each one we skip.
+            # TODO: Refactor this to make it so I can select spline index
+            spline_index = self.evaluate_input("Spline Index")
+            spline = curve.data.splines[spline_index]
+            splines_factors = [ [] for i in range (spline_index-1)]
+            factors = [0.0]
+            points = spline.bezier_points if spline.type == 'BEZIER' else spline.points
+            total_length=0.0
+            for i in range(len(points)-1):
+                total_length+= (seg_length := (points[i+1].co - points[i].co).length)
+                factors.append(seg_length)
+            prev_length = 0.0
+            for i in range(len(factors)):
+                factors[i] = prev_length+factors[i]/total_length
+                prev_length=factors[i]
+                # Why does this happen? Floating point error?
+                if factors[i]>1.0: factors[i] = 1.0
+            splines_factors.append(factors)
+            #
+            data = data_from_ribbon_mesh(m, splines_factors, curve.matrix_world)
+            segment_index = self.evaluate_input("Segment Index")
+            head=data[spline_index][0][segment_index]
+            tail= data[spline_index][0][segment_index+1]
+            axis = (tail-head).normalized()
+            normal=data[spline_index][2][segment_index]
+            # make sure the normal is perpendicular to the tail
+            from .utilities import make_perpendicular
+            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
+    
+    def bFinalize(self, bContext=None):
+        cleanup_curve(self.evaluate_input("Curve"), self.base_tree.execution_id)
+
 class UtilityMetaRig(MantisNode):
     '''A node representing an armature object'''
 

+ 34 - 2
misc_nodes_ui.py

@@ -25,9 +25,11 @@ def TellClasses():
              UtilityNameOfXForm,
             #  ComposeMatrixNode,
              MetaRigMatrixNode,
-             UtilityMatrixFromCurve,
              UtilityPointFromCurve,
+             UtilityMatrixFromCurve,
              UtilityMatricesFromCurve,
+             UtilityNumberOfCurveSegments,
+             UtilityMatrixFromCurveSegment,
             #  ScaleBoneLengthNode,
              UtilityMetaRigNode,
              UtilityBonePropertiesNode,
@@ -254,7 +256,6 @@ class UtilityMatrixFromCurve(Node, MantisUINode):
         self.outputs.new("MatrixSocket", "Matrix")
         self.initialized = True
 
-
 class UtilityPointFromCurve(Node, MantisUINode):
     """Gets a point from a curve."""
     bl_idname = "UtilityPointFromCurve"
@@ -270,6 +271,37 @@ class UtilityPointFromCurve(Node, MantisUINode):
         self.outputs.new("VectorSocket", "Point")
         self.initialized = True
 
+class UtilityNumberOfCurveSegments(Node, MantisUINode):
+    """Tells the number of segments in a curve."""
+    bl_idname = "UtilityNumberOfCurveSegments"
+    bl_label = "Number of Curve Segments"
+    bl_icon = "NODE"
+    
+    initialized : bpy.props.BoolProperty(default = False)
+    mantis_node_class_name=bl_idname
+    
+    def init(self, context):
+        self.inputs.new("EnumCurveSocket", "Curve")
+        self.inputs.new('UnsignedIntSocket', 'Spline Index')
+        self.outputs.new("UnsignedIntSocket", "Number of Segments")
+        self.initialized = True
+    
+class UtilityMatrixFromCurveSegment(Node, MantisUINode):
+    """Gets a matrix from a curve segment."""
+    bl_idname = "UtilityMatrixFromCurveSegment"
+    bl_label = "Matrix from Curve Segment"
+    bl_icon = "NODE"
+    
+    initialized : bpy.props.BoolProperty(default = False)
+    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.initialized = True
+    
 class UtilityMatricesFromCurve(Node, MantisUINode):
     """Gets a matrix from a curve."""
     bl_idname = "UtilityMatricesFromCurve"

+ 5 - 0
utilities.py

@@ -633,6 +633,11 @@ def gen_nc_input_for_data(socket):
 # CURVE STUFF
 ####################################
 
+def make_perpendicular(v1, v2):
+    projected = (v2.dot(v1) / v1.dot(v1)) * v1
+    perpendicular = v2 - projected
+    return perpendicular
+
 # this stuff could be branchless but I don't use it much TODO
 def cap(val, maxValue):
     if (val > maxValue):