-
Notifications
You must be signed in to change notification settings - Fork 233
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
"Twist along Curve Field" node (#5168)
* "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
Showing
6 changed files
with
579 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
Oops, something went wrong.