Skip to content

Commit

Permalink
"Twist along Curve Field" node (#5168)
Browse files Browse the repository at this point in the history
* "Frame along curve Field" initial implementation.

* Support for two interpolation modes.

* More features for "frame along curve" node.

* Node documentation

* Add node to other menu presets

* Remove useless property.

* Add examples to documentation.
  • Loading branch information
portnov authored Nov 15, 2024
1 parent 86eaa59 commit 7ca02ff
Show file tree
Hide file tree
Showing 6 changed files with 579 additions and 0 deletions.
129 changes: 129 additions & 0 deletions docs/nodes/field/frame_along_curve.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
Twist along Curve Field
=======================

Functionality
-------------

This node generates a vector field, which does a generic type of space
transformation. It is similar to what "Bend Along Curve Field" node does, but
rotation of space around the curve is controlled by user, not calculated by
some algorithm. So with this node you can generate a field that does a
combination of two transformations: 1) bending along some curve, and 2)
arbitrary twisting around that curve.

Inputs
------

This node has the following inputs:

* **Curve**. The curve to bend and twist around. This input is mandatory.
* **Matrices**. This input is available and mandatory only when **Input**
parameter is set to **Matrices**. This input should contain a list of
matrices, that specify rotation of space around the curve. Locations of
matrices define places on the curve where corresponding rotation should be
done.
* **Quaternions**. This input is availale and mandatory only when **Input**
parameter is set to **Quaternions**. This input should contain a lsit of
quaternions, which define rotation of space around the curve. Locations of
these rotations on the curve are defined by values in **T**/**Length** input.
* **Vectors**. This input is available and mandatory only when **Input**
parameter is set to **Track Vectors**. This input should contain a list of
vectors, which point in a direction to which tracking axis must be rotated.
Locations of these rotations on the curve are defined by values in
**T**/**Length** input.
* **T**/**Length**. This input is available and mandatory when **Input**
parameter is set to **Quaternions** or **Track Vectors**. Values define
places on the curve where corresponding rotations should be placed. If
**Parametrization** parameter is set to **Curve Parameter**, then this input
should contain curve T parameter values; otherwise, this input should contain
arc length parameters (lengths from the beginning on the curve).
* **LengthResolution**. This input is available and mandatory only when
**Parametrization** parameter is set to **Curve Length**. This defines the
resolution used to calculate curve lengths. Higher values give more
precision, but take more time to calculate. The default value is 50.

Parameters
----------

This node has the following parameters:

* **Input**. This defines how rotations around the curve will be specified. The
available options are:

* **Matrices**. The user provides a list of matrices. Locations of matrices
in 3D space define locations on the curve where rotations should be placed.
One of matrix axes (called orientation axis, see **Orientation** parameter
below) is supposed to be directed along the curve, but not necessarily
precisely. Rotation of the matrix along orientation curve define the
rotaiton of the space around the curve. Scale and skew components of
matrices are ignored.
* **Quaternions**. The user provides a list of quaternions, which define
rotations around the curve. Places of these rotations along the curve are
defined by values in **T**/**Length** input.
* **Track Vectors**. The user provides a list of vectors, which define
directions where "tracking axis" should look to. Rotations are always done
around curve tangent vectors. The places on the curve where these rotations
should be placed are defined by values in **T**/**Length** input.

The default option is **Matrices**.

* **Parametrization**. This defines what type of curve parametrization should
be used. This also defines the part of 3D space which will be bent and
rotated around the curve. The available options are:

* **Curve Parameter**. Original coordinates in 3D space along axis specified
in **Orientation** parameter will be used as curve T parameters. So, for
example, if curve domain is from 0 to 1, then the part of space which will
be bent is from 0 to 1 along orientation axis.
* **Curve Length**. Original coordinates in 3D space along orientation axis
will be used as curve arc length parameters (length of parts of curve from
beginning of the curve). So, for example, if curve length is 11.5, then the
part of space to be bent will be from 0 to 11.5 along orientation axis.

The default option is **Curve Parameter**.

* **Orientation**. This parameter defines which axis of original 3D space
should be bent along the curve. The default value is **Z**.
* **Track Axis**. This parameter is only available when **Input** parameter is
set to **Track Vectors**. This defines which axis of original 3D space should
be looking along user-provided tracking vectors after rotations. Tracking
axis must not coincide with orientation axis. The default option is **X**.
* **Interpolation**. This defines the type of interpolation between
user-provided rotations of the space. The available options are **Linear**
and **Spline**. The default option is **Spline**.

Outputs
-------

This node has the following output:

* **Field**. The generated vector field.

Examples of Usage
-----------------

Example nodes setup:

.. image:: https://github.com/user-attachments/assets/f699e348-5f7d-4e1f-a3e2-9cd3d3ea8348
:target: https://github.com/user-attachments/assets/f699e348-5f7d-4e1f-a3e2-9cd3d3ea8348

Result:

.. image:: https://github.com/user-attachments/assets/f0aced76-351a-4adb-99ab-3485506c1d9e
:target: https://github.com/user-attachments/assets/f0aced76-351a-4adb-99ab-3485506c1d9e

Same, but with Linear interpolation mode:

.. image:: https://github.com/user-attachments/assets/bfa083e6-27f2-49b1-98d2-b5b1132dc651
:target: https://github.com/user-attachments/assets/bfa083e6-27f2-49b1-98d2-b5b1132dc651

Example setup for Matrices input mode:

.. image:: https://github.com/user-attachments/assets/723ad29f-f6a9-49dc-ae7b-2df595815e19
:target: https://github.com/user-attachments/assets/723ad29f-f6a9-49dc-ae7b-2df595815e19

Result:

.. image:: https://github.com/user-attachments/assets/008d709e-4b2f-4ad5-aa12-6ba1f353892f
:target: https://github.com/user-attachments/assets/008d709e-4b2f-4ad5-aa12-6ba1f353892f

1 change: 1 addition & 0 deletions index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@
- SvExMergeScalarFieldsNode
- ---
- SvExBendAlongCurveFieldNode
- SvSlerpCurveFieldNode
- SvBendAlongSurfaceFieldMk2Node
- ---
- SvExScalarFieldEvaluateNode
Expand Down
1 change: 1 addition & 0 deletions menus/full_by_data_type.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@
- SvExNoiseVectorFieldNode
- ---
- SvExBendAlongCurveFieldNode
- SvSlerpCurveFieldNode
- SvBendAlongSurfaceFieldMk2Node
- Field Operations:
- icon_name: TOOL_SETTINGS
Expand Down
1 change: 1 addition & 0 deletions menus/full_nortikin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@
- SvExMergeScalarFieldsNode
- ---
- SvExBendAlongCurveFieldNode
- SvSlerpCurveFieldNode
- SvBendAlongSurfaceFieldMk2Node
- M SOLIDS:
- icon_name: MESH_CUBE
Expand Down
205 changes: 205 additions & 0 deletions nodes/field/frame_along_curve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import numpy as np

import bpy
from mathutils import Quaternion
from bpy.props import EnumProperty, IntProperty, FloatProperty
from mathutils import Matrix

from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level
from sverchok.utils.math import xyz_axes
from sverchok.utils.curve.core import SvCurve
from sverchok.utils.field.frame_along_curve import IntersectionParams, SvFrameAlongCurveField

class SvSlerpCurveFieldNode(SverchCustomTreeNode, bpy.types.Node):
"""
Triggers: Twist Curve Field
Tooltip: Twist along Curve
"""
bl_idname = 'SvSlerpCurveFieldNode'
bl_label = 'Twist along Curve Field'
bl_icon = 'OUTLINER_OB_EMPTY'
sv_icon = 'SV_INTERP_FRAME'
sv_dependencies = {'scipy'}

interp_modes = [
(SvFrameAlongCurveField.INTERP_LINEAR, "Linear", "Linear quaternion interpolation", 0),
(SvFrameAlongCurveField.INTERP_SPLINE, "Spline", "Spline quaternion interpolation", 1)
]

interpolation_mode : EnumProperty(
name = "Interpolation",
description = "Rotation interpolation mode",
items = interp_modes,
default = SvFrameAlongCurveField.INTERP_SPLINE,
update = updateNode)

param_modes = [
(SvFrameAlongCurveField.CURVE_PARAMETER, "Curve Parameter", "Orientation axis corresponds to curve parameter", 0),
(SvFrameAlongCurveField.CURVE_LENGTH, "Curve Length", "Orientation axis corresponds to curve arc length", 1)
]

input_modes = [
('MAT', "Matrices", "Matrices", 0),
('QUAT', "Quaternions", "Quaternions", 1),
('TRACK', "Track Vectors", "Track Vectors", 2)
]

def update_sockets(self, context):
self.inputs['LengthResolution'].hide_safe = self.input_mode != 'MAT' or self.param_mode != SvFrameAlongCurveField.CURVE_LENGTH
self.inputs['Quaternions'].hide_safe = self.input_mode != 'QUAT'
self.inputs['Matrices'].hide_safe = self.input_mode != 'MAT'
self.inputs['Vectors'].hide_safe = self.input_mode != 'TRACK'
self.inputs['T'].label = 'T' if self.param_mode == SvFrameAlongCurveField.CURVE_PARAMETER else 'Length'
self.inputs['T'].hide_safe = self.input_mode == 'MAT'
updateNode(self, context)

param_mode : EnumProperty(
name = "Parametrization",
items = param_modes,
default = SvFrameAlongCurveField.CURVE_PARAMETER,
update = update_sockets)

input_mode : EnumProperty(
name = "Input",
items = input_modes,
default = 'MAT',
update = update_sockets)

samples : IntProperty(
name = "Curve Resolution",
description = "A number of segments to subdivide the curve in; defines the maximum number of intersection points that is possible to find.",
default = 5,
min = 1,
update = updateNode)

accuracy : IntProperty(
name = "Accuracy",
description = "Accuracy level - number of exact digits after decimal points.",
default = 4,
min = 1,
update = updateNode)

length_resolution : IntProperty(
name = "Length Resolution",
min = 3,
default = 50,
update = updateNode)

z_axis : EnumProperty(
name = "Orientation",
description = "Which axis of the provided frames points along the curve",
items = xyz_axes,
default = 'Z',
update = updateNode)

track_axis : EnumProperty(
name = "Track Axis",
items = xyz_axes,
default = 'X',
update = updateNode)

def draw_buttons(self, context, layout):
layout.label(text='Input:')
layout.prop(self, 'input_mode', text='')
layout.label(text='Parametrization:')
layout.prop(self, 'param_mode', text='')
layout.label(text='Orientation:')
layout.prop(self, 'z_axis', expand=True)
if self.input_mode == 'TRACK':
layout.label(text='Track Axis:')
layout.prop(self, 'track_axis', expand=True)
layout.label(text='Interpolation:')
layout.prop(self, 'interpolation_mode', text='')

def draw_buttons_ext(self, context, layout):
self.draw_buttons(context, layout)
layout.prop(self, 'samples')
layout.prop(self, 'accuracy')

def sv_init(self, context):
self.inputs.new('SvCurveSocket', "Curve")
self.inputs.new('SvMatrixSocket', "Matrices")
self.inputs.new('SvQuaternionSocket', "Quaternions")
p = self.inputs.new('SvVerticesSocket', "Vectors")
p.use_prop = True
p.default_property = (1.0, 0.0, 0.0)
self.inputs.new('SvStringsSocket', "T")
self.inputs.new('SvStringsSocket', "LengthResolution").prop_name = 'length_resolution'
self.outputs.new('SvVectorFieldSocket', 'Field')
self.update_sockets(context)

def process(self):
if not any(socket.is_linked for socket in self.outputs):
return

curves_s = self.inputs['Curve'].sv_get()
input_level = get_data_nesting_level(curves_s, data_types=(SvCurve,))
flat_output = input_level > 1
curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,))
if self.input_mode == 'MAT':
frames_s = self.inputs['Matrices'].sv_get()
# list of frames per curve
frames_s = ensure_nesting_level(frames_s, 3, data_types=(Matrix,))
quats_s = [[[None]]]
track_s = [[[None]]]
ts_s = [[[None]]]
elif self.input_mode == 'QUAT':
quats_s = self.inputs['Quaternions'].sv_get()
quats_s = ensure_nesting_level(quats_s, 4)
frames_s = [[[None]]]
track_s = [[[None]]]
ts_s = self.inputs['T'].sv_get()
ts_s = ensure_nesting_level(ts_s, 3)
elif self.input_mode == 'TRACK':
track_s = self.inputs['Vectors'].sv_get()
track_s = ensure_nesting_level(track_s, 4)
frames_s = [[[None]]]
quats_s = [[[None]]]
ts_s = self.inputs['T'].sv_get()
ts_s = ensure_nesting_level(ts_s, 3)
resolution_s = self.inputs['LengthResolution'].sv_get()
resolution_s = ensure_nesting_level(resolution_s, 2)

tolerance = 10**(-self.accuracy)
intersection_params = IntersectionParams(self.samples, tolerance)

fields = []
for params in zip_long_repeat(curves_s, frames_s, quats_s, track_s, ts_s, resolution_s):
new_fields = []
for curve, frames, quats, tracks, ts, resolution in zip_long_repeat(*params):
if self.input_mode == 'MAT':
field = SvFrameAlongCurveField.from_matrices(curve, frames,
z_axis = self.z_axis,
interpolation = self.interpolation_mode,
parametrization = self.param_mode,
intersection_params = intersection_params)
elif self.input_mode == 'QUAT':
field = SvFrameAlongCurveField.from_quaternions(curve, ts, quats,
z_axis = self.z_axis,
interpolation = self.interpolation_mode,
parametrization = self.param_mode,
length_resolution = resolution)
else: # TRACK:
field = SvFrameAlongCurveField.from_track_vectors(curve, ts, tracks,
z_axis = self.z_axis,
track_axis = self.track_axis,
interpolation = self.interpolation_mode,
parametrization = self.param_mode,
length_resolution = resolution)

new_fields.append(field)
if flat_output:
fields.extend(new_fields)
else:
fields.append(new_fields)

self.outputs['Field'].sv_set(fields)

def register():
bpy.utils.register_class(SvSlerpCurveFieldNode)


def unregister():
bpy.utils.unregister_class(SvSlerpCurveFieldNode)

Loading

0 comments on commit 7ca02ff

Please sign in to comment.