Skip to content

Commit

Permalink
Merge pull request #4840 from nortikin/tangent_curve_nurbs
Browse files Browse the repository at this point in the history
"Tangent Curve" node - NURBS mode
  • Loading branch information
portnov authored Dec 25, 2022
2 parents 12ea912 + a9b85d9 commit da72e2c
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 24 deletions.
55 changes: 39 additions & 16 deletions docs/nodes/curve/tangent_curve.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,28 @@ the curve must pass, and tangent vectors of the curve at those points.
.. image:: https://user-images.githubusercontent.com/14288520/205862183-f8eb5c09-2ba5-4315-8a68-1dab9c102fca.png
:target: https://user-images.githubusercontent.com/14288520/205862183-f8eb5c09-2ba5-4315-8a68-1dab9c102fca.png

The curve is generated as a series of cubic Bezier curves. Control points of
Bezier curves are defined as follows:
The node can use one of two algorithms:

* For each segment defined by a pair of input points, one Bezier curve is
generated - for example, one curve for first and second point, one curve for
second and third point, and so on.
* For each segment, the first point is the starting point of Bezier curve, and
the second point is the end point of Bezier curve.
* Provided tangent vectors are placed so that the middle point of each vector
is at corresponding input point - middle of the first tangent vector at the
first input point, and so on. Then end points of these vectors will define
additional control points for Bezier curves.
* Hermite_ spline. The curve is generated as a series of cubic Bezier curves.
Control points of Bezier curves are defined as follows:

Generated curves may be optionally concatenated into one Curve object.
* For each segment defined by a pair of input points, one Bezier curve is
generated - for example, one curve for first and second point, one curve for
second and third point, and so on.
* For each segment, the first point is the starting point of Bezier curve, and
the second point is the end point of Bezier curve.
* Provided tangent vectors are placed so that the middle point of each vector
is at corresponding input point - middle of the first tangent vector at the
first input point, and so on. Then end points of these vectors will define
additional control points for Bezier curves.

Generated curves may be optionally concatenated into one Curve object.

* NURBS curve. The node creates interpolating NURBS curve (of 3rd degree)
through the specified points, with additional condition that it should have
specified tangents at those points.

.. _Hermite: https://en.wikipedia.org/wiki/Cubic_Hermite_spline

Inputs
------
Expand All @@ -44,15 +51,23 @@ Parameters

This node has the following parameters:

* **Curve type**. This defines the algorithm to be used by the node, and the
type of resulting curve. The available options are:

* **Hermite**. Use Hermite_ spline algorithm, and generate either a list of
Bezier curves, or concatenated NURBS curve.
* **NURBS**. Use NURBS interpolation algotithm.

* **Cyclic**. If checked, then the node will generate additional Bezier curve
segment to connect the last point with the first one. Unchecked by default.

.. image:: https://user-images.githubusercontent.com/14288520/205867628-5f3de9ae-ab0a-435b-9ebb-3dc409ff4e51.png
:target: https://user-images.githubusercontent.com/14288520/205867628-5f3de9ae-ab0a-435b-9ebb-3dc409ff4e51.png

* **Concatenate**. If checked, then the node will concatenate all generated
Bezier curve segments into one Curve object. Otherwise, it will output each
segment as a separate Curve object. Checked by default.
* **Concatenate**. This parameter is available only when **Curve type**
parameter is set to **Hermite**. If checked, then the node will concatenate
all generated Bezier curve segments into one Curve object. Otherwise, it will
output each segment as a separate Curve object. Checked by default.

.. image:: https://user-images.githubusercontent.com/14288520/205943520-e9ec6cd6-54de-483f-8af9-753d4720d27e.png
:target: https://user-images.githubusercontent.com/14288520/205943520-e9ec6cd6-54de-483f-8af9-753d4720d27e.png
Expand Down Expand Up @@ -101,4 +116,12 @@ More complex example: draw a smooth curve so that it would touch three circles i
* List->List Struct-> :doc:`List Slice </nodes/list_struct/slice>`
* List->List Struct-> :doc:`List Split </nodes/list_struct/split>`
* Viz-> :doc:`Viewer Draw Curve </nodes/viz/viewer_draw_curve>`
* Viz-> :doc:`Viewer Draw </nodes/viz/viewer_draw_mk4>`
* Viz-> :doc:`Viewer Draw </nodes/viz/viewer_draw_mk4>`

---------

Example of NURBS mode usage:

.. image:: https://user-images.githubusercontent.com/284644/209460542-535e4c0a-9c0f-44e4-a127-46fa104cd335.png
:target: https://user-images.githubusercontent.com/284644/209460542-535e4c0a-9c0f-44e4-a127-46fa104cd335.png

44 changes: 38 additions & 6 deletions nodes/curve/tangent_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

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 supported_metrics
from sverchok.utils.curve import SvCurve, SvBezierCurve, SvConcatCurve, SvCubicBezierCurve
from sverchok.utils.curve.nurbs_solver_applications import interpolate_nurbs_curve_with_tangents

class SvTangentsCurveNode(SverchCustomTreeNode, bpy.types.Node):
"""
Expand Down Expand Up @@ -36,12 +38,34 @@ class SvTangentsCurveNode(SverchCustomTreeNode, bpy.types.Node):
default = False,
update = updateNode)

curve_modes = [
('HERMITE', "Hermite", "Use Bezier curve representation of Hermite spline", 0),
('NURBS', "NURBS", "Use NURBS curve interpolation", 1)
]

curve_mode : EnumProperty(
name = "Curve type",
description = "Type of algorithm to be used and type of generated curve",
items = curve_modes,
default = 'HERMITE',
update = updateNode)

metric: EnumProperty(name='Metric',
description = "Knot mode",
default="DISTANCE", items=supported_metrics,
update=updateNode)

def draw_buttons(self, context, layout):
layout.prop(self, 'cyclic', toggle=True)
layout.prop(self, 'concat', toggle=True)
layout.prop(self, 'curve_mode')
layout.prop(self, 'cyclic')
if self.curve_mode == 'HERMITE':
layout.prop(self, 'concat')
elif self.curve_mode == 'NURBS':
layout.prop(self, 'metric')

def draw_buttons_ext(self, context, layout):
layout.prop(self, 'make_nurbs', toggle=True)
if self.curve_mode == 'HERMITE':
layout.prop(self, 'make_nurbs', toggle=True)

def sv_init(self, context):
self.inputs.new('SvVerticesSocket', "Points")
Expand All @@ -67,9 +91,17 @@ def process(self):
curves_i = []
controls_i = []
for points, tangents in zip_long_repeat(*params):
new_controls, new_curve = SvBezierCurve.build_tangent_curve(points, tangents,
cyclic = self.cyclic, concat = self.concat,
as_nurbs = self.make_nurbs)
if self.curve_mode == 'HERMITE':
new_controls, new_curve = SvBezierCurve.build_tangent_curve(points, tangents,
cyclic = self.cyclic, concat = self.concat,
as_nurbs = self.make_nurbs)
else:
new_curve = interpolate_nurbs_curve_with_tangents(3,
points, tangents,
metric = self.metric,
cyclic = self.cyclic,
logger = self.get_logger())
new_controls = new_curve.get_control_points().tolist()
curves_i.append(new_curve)
controls_i.append(new_controls)
if output_nested:
Expand Down
10 changes: 8 additions & 2 deletions utils/curve/bezier.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def blend_third_derivatives(cls, p0, v0, a0, k0, p7, v7, a7, k7):
return SvBezierCurve([p0, p1, p2, p3, p4, p5, p6, p7])

@classmethod
def build_tangent_curve(cls, points, tangents, cyclic=False, concat=False, as_nurbs=False):
def build_tangent_curve(cls, points, tangents, hermite=True, cyclic=False, concat=False, as_nurbs=False):
"""
Build cubic Bezier curve spline, which goes through specified `points',
having specified `tangents' at these points.
Expand All @@ -143,6 +143,8 @@ def build_tangent_curve(cls, points, tangents, cyclic=False, concat=False, as_nu
* points, tangents: lists of 3-tuples
* cyclic: whether the curve should be closed (cyclic)
* concat: whether to concatenate all curve segments into single Curve object
* hermite: if true, use Hermite spline - divide tangent vector by 3 to
obtain middle control points; otherwise, divide by 2.
outputs: tuple:
* list of curve control points - list of lists of 3-tuples
Expand All @@ -155,13 +157,17 @@ def build_tangent_curve(cls, points, tangents, cyclic=False, concat=False, as_nu
segments = list(zip(pairs, pairs[1:]))
if cyclic:
segments.append((pairs[-1], pairs[0]))
if hermite:
d = 3.0
else:
d = 2.0

for pair1, pair2 in segments:
point1, tangent1 = pair1
point2, tangent2 = pair2
point1, tangent1 = np.array(point1), np.array(tangent1)
point2, tangent2 = np.array(point2), np.array(tangent2)
tangent1, tangent2 = tangent1/3.0, tangent2/3.0
tangent1, tangent2 = tangent1/d, tangent2/d
curve = SvCubicBezierCurve(
point1,
point1 + tangent1,
Expand Down
47 changes: 47 additions & 0 deletions utils/surface/nurbs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,53 @@ def loft_by_binormals(curves, degree_v = 3,
control_points, weights)
return surface

def loft_with_tangents(curves, tangent_fields, degree_v = 3,
metric = 'DISTANCE', tknots=None,
knotvector_accuracy = 6,
implementation = SvNurbsMaths.NATIVE,
logger = None):

if logger is None:
logger = getLogger()

n_curves = len(curves)
curves = unify_curves_degree(curves)
curves = unify_curves(curves, accuracy=knotvector_accuracy)
degree_u = curves[0].get_degree()

src_points = [curve.get_homogenous_control_points() for curve in curves]
src_points = np.array(src_points)
src_points = np.transpose(src_points, axes=(1,0,2))

tangents = [field.evaluate_array(curve.get_control_points()) for curve, field in zip(curves, tangent_fields)]
tangents = np.array(tangents)
tangents = np.transpose(tangents, axes=(2,0,1))

n,m,ndim = tangents.shape
tangents = np.concatenate((tangents, np.zeros((n,m,1))), axis=2)

tknots_vs = [Spline.create_knots(src_points[i,:], metric=metric) for i in range(n_curves)]
tknots_vs = np.array(tknots_vs)
tknots_v = np.mean(tknots_vs, axis=0)

v_curves = [interpolate_nurbs_curve_with_tangents(degree_v, points, tangents, tknots=tknots_v, implementation=implementation, logger=logger) for points, tangents in zip(src_points, tangents)]
control_points = [curve.get_homogenous_control_points() for curve in v_curves]
control_points = np.array(control_points)
n,m,ndim = control_points.shape
control_points = control_points.reshape((n*m, ndim))
control_points, weights = from_homogenous(control_points)
control_points = control_points.reshape((n,m,3))
weights = weights.reshape((n,m))

knotvector_u = curves[0].get_knotvector()
knotvector_v = v_curves[0].get_knotvector()

surface = SvNurbsSurface.build(SvNurbsSurface.NATIVE,
degree_u, degree_v,
knotvector_u, knotvector_v,
control_points, weights)
return surface

def interpolate_nurbs_curves(curves, base_vs, target_vs,
degree_v = None, knots_u = 'UNIFY',
implementation = SvNurbsSurface.NATIVE):
Expand Down

0 comments on commit da72e2c

Please sign in to comment.