From 501e5f1e22948dbea141b445af2e8118f9b9904c Mon Sep 17 00:00:00 2001 From: Ilya Portnov Date: Mon, 2 Jan 2023 13:38:54 +0500 Subject: [PATCH 1/3] Generalize Coons Patch node. Now it is possible to build the patch from three curves. --- nodes/surface/coons_patch.py | 36 ++++++++++++++++++----- utils/curve/bezier.py | 2 +- utils/curve/primitives.py | 57 ++++++++++++++++++++++++++++++++++++ utils/surface/coons.py | 5 ++-- 4 files changed, 89 insertions(+), 11 deletions(-) diff --git a/nodes/surface/coons_patch.py b/nodes/surface/coons_patch.py index cc90b66e44..44b091e0ec 100644 --- a/nodes/surface/coons_patch.py +++ b/nodes/surface/coons_patch.py @@ -6,16 +6,17 @@ from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level -from sverchok.utils.curve import SvCurve +from sverchok.utils.curve.core import SvCurve +from sverchok.utils.curve.primitives import SvLine, SvPointCurve from sverchok.utils.surface.coons import coons_surface, GENERIC, NURBS_ONLY, NURBS_IF_POSSIBLE class SvCoonsPatchNode(SverchCustomTreeNode, bpy.types.Node): """ - Triggers: Coons Patch / Surface form four curves - Tooltip: Generate a surface (Coons Patch) from four boundary curves + Triggers: Coons Patch / Surface form three or four boundary curves + Tooltip: Generate a surface (Coons Patch) from three or four boundary curves """ bl_idname = 'SvCoonsPatchNode' - bl_label = 'Surface from Four Curves' + bl_label = 'Surface from Boundary Curves' bl_icon = 'SURFACE_DATA' sv_icon = 'SV_COONS_PATCH' @@ -53,8 +54,8 @@ def update_sockets(self, context): updateNode(self, context) modes = [ - ('LIST', "List of curves", "Input is provided as a list of curves, which must have 4 items", 0), - ('FOUR', "4 curves", "Four separate Curve inputs are provided", 1) + ('LIST', "List of curves", "Input is provided as a list of curves, which must have 3 or 4 items", 0), + ('FOUR', "Separate inputs", "Three or four separate Curve inputs are provided", 1) ] input_mode : EnumProperty( @@ -83,6 +84,8 @@ def run_check(self, curves): pairs = list(zip(curves, curves[1:])) pairs.append((curves[-1], curves[0])) for idx, (curve1, curve2) in enumerate(pairs): + if curve1 is None or curve2 is None: + continue _, t_max_1 = curve1.get_u_bounds() t_min_2, _ = curve2.get_u_bounds() end1 = curve1.evaluate(t_max_1) @@ -91,11 +94,22 @@ def run_check(self, curves): if distance > self.max_rho: raise Exception("Distance between the end of {}'th curve and the start of {}'th curve is {} - too much".format(idx, idx+1, distance)) + def make_closing_curve(self, first_curve, last_curve): + pt1 = last_curve.get_end_points()[1] + pt2 = first_curve.get_end_points()[0] + if np.linalg.norm(pt1 - pt2) < 1e-6: + return SvPointCurve(pt1) + else: + return SvLine.from_two_points(pt1, pt2) + def get_input(self): if self.input_mode == 'LIST': curve_list_s = self.inputs['Curves'].sv_get() curve_list_s = ensure_nesting_level(curve_list_s, 2, data_types=(SvCurve,)) for curves in curve_list_s: + if len(curves) == 3: + curve4 = None + curves.append(curve4) if len(curves) != 4: raise Exception("List of curves must contain exactly 4 curve objects!") yield curves @@ -103,12 +117,16 @@ def get_input(self): curve1_s = self.inputs['Curve1'].sv_get() curve2_s = self.inputs['Curve2'].sv_get() curve3_s = self.inputs['Curve3'].sv_get() - curve4_s = self.inputs['Curve4'].sv_get() curve1_s = ensure_nesting_level(curve1_s, 1, data_types=(SvCurve,)) curve2_s = ensure_nesting_level(curve2_s, 1, data_types=(SvCurve,)) curve3_s = ensure_nesting_level(curve3_s, 1, data_types=(SvCurve,)) - curve4_s = ensure_nesting_level(curve4_s, 1, data_types=(SvCurve,)) + + if self.inputs['Curve4'].is_linked: + curve4_s = self.inputs['Curve4'].sv_get() + curve4_s = ensure_nesting_level(curve4_s, 1, data_types=(SvCurve,)) + else: + curve4_s = [None] for curve1, curve2, curve3, curve4 in zip_long_repeat(curve1_s, curve2_s, curve3_s, curve4_s): yield [curve1, curve2, curve3, curve4] @@ -121,6 +139,8 @@ def process(self): for curves in self.get_input(): if self.check: self.run_check(curves) + if curves[3] is None: + curves[3] = self.make_closing_curve(curves[0], curves[2]) surface = coons_surface(*curves, use_nurbs=self.use_nurbs) surface_out.append(surface) diff --git a/utils/curve/bezier.py b/utils/curve/bezier.py index 878482d25e..4100a2ca9b 100644 --- a/utils/curve/bezier.py +++ b/utils/curve/bezier.py @@ -75,7 +75,7 @@ class SvBezierCurve(SvCurve, SvBezierSplitMixin): Bezier curve of arbitrary degree. """ def __init__(self, points): - self.points = points + self.points = np.asarray(points) self.tangent_delta = 0.001 n = self.degree = len(points) - 1 self.__description__ = "Bezier[{}]".format(n) diff --git a/utils/curve/primitives.py b/utils/curve/primitives.py index dc0928fb28..57a900c7e2 100644 --- a/utils/curve/primitives.py +++ b/utils/curve/primitives.py @@ -145,6 +145,63 @@ def get_polyline_vertices(self): def is_closed(self, *args): return False + def extrude_along_vector(self, vector): + return self.to_nurbs().extrude_along_vector(vector) + + def make_revolution_surface(self, point, direction, v_min, v_max, global_origin): + return self.to_nurbs().make_revolution_surface(point, direction, v_min, v_max, global_origin) + + def make_ruled_surface(self, curve2, vmin, vmax): + return self.to_nurbs().make_ruled_surface(curve2, vmin, vmax) + + def extrude_to_point(self, point): + return self.to_nurbs().extrude_to_point(point) + + def lerp_to(self, curve2, coefficient): + return self.to_nurbs().lerp_to(curve2, coefficient) + +class SvPointCurve(SvCurve): + __description__ = "Single-Point" + + def __init__(self, point): + self.point = np.asarray(point) + + def evaluate(self, t): + return self.point + + def evaluate_array(self, ts): + points = np.empty((len(ts),3)) + points[:] = self.point + return points + + def get_u_bounds(self): + return (0.0, 1.0) + + def get_degree(self): + return 1 + + def to_bezier(self): + u_min, u_max = self.get_u_bounds() + p1 = self.evaluate(u_min) + p2 = self.evaluate(u_max) + return SvBezierCurve([p1, p2]) + + def to_bezier_segments(self): + return [self.to_bezier()] + + def is_closed(self, *args): + return False + + def concatenate(self, curve2, *args): + return curve2 + + def to_nurbs(self, implementation = SvNurbsMaths.NATIVE): + return self.to_bezier().to_nurbs() + + def reverse(self): + return SvPointCurve(self.point) + + def rotate_radius(radius, normal, thetas): """Internal method""" ct = np.cos(thetas)[np.newaxis].T diff --git a/utils/surface/coons.py b/utils/surface/coons.py index a798813271..9b47027833 100644 --- a/utils/surface/coons.py +++ b/utils/surface/coons.py @@ -12,7 +12,7 @@ from sverchok.utils.curve import knotvector as sv_knotvector from sverchok.utils.curve.core import UnsupportedCurveTypeException from sverchok.utils.curve.nurbs import SvNurbsCurve -from sverchok.utils.curve.algorithms import reverse_curve, reparametrize_curve +from sverchok.utils.curve.algorithms import reverse_curve, reparametrize_curve, unify_curves_degree from sverchok.utils.curve.nurbs_algorithms import unify_curves, unify_two_curves from sverchok.utils.surface.core import SvSurface from sverchok.utils.surface.nurbs import SvNurbsSurface @@ -83,10 +83,11 @@ def coons_surface(curve1, curve2, curve3, curve4, use_nurbs=NURBS_IF_POSSIBLE): return SvCoonsSurface(*curves) try: nurbs_curves = [c.reparametrize(0,1) for c in nurbs_curves] - degrees = [c.get_degree() for c in nurbs_curves] implementation = nurbs_curves[0].get_nurbs_implementation() + nurbs_curves[0], nurbs_curves[2] = unify_curves_degree([nurbs_curves[0], nurbs_curves[2]]) nurbs_curves[0], nurbs_curves[2] = unify_curves([nurbs_curves[0], nurbs_curves[2]]) + nurbs_curves[1], nurbs_curves[3] = unify_curves_degree([nurbs_curves[1], nurbs_curves[3]]) nurbs_curves[1], nurbs_curves[3] = unify_curves([nurbs_curves[1], nurbs_curves[3]]) degree_u = nurbs_curves[0].get_degree() From 1d68ade87bda977c14af81638f368783bddbb663 Mon Sep 17 00:00:00 2001 From: Ilya Portnov Date: Mon, 2 Jan 2023 13:47:29 +0500 Subject: [PATCH 2/3] Update documentation. --- docs/nodes/surface/coons_patch.rst | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/nodes/surface/coons_patch.rst b/docs/nodes/surface/coons_patch.rst index 0bc718d1c4..82996e567f 100644 --- a/docs/nodes/surface/coons_patch.rst +++ b/docs/nodes/surface/coons_patch.rst @@ -1,5 +1,5 @@ -Surface from Four Curves -======================== +Surface from Boundary Curves +============================ Functionality ------------- @@ -18,6 +18,11 @@ That is, the second curve must begin at the point where the first curve ends; and the third curve must begin at the point where the second curve ends; and so on. +It is also possible to omit the fourth curve, thus to build a surface from +three boundary curves. If the third curve does not end in the same point where +the first curve started, the node will use a straight line segment as fourth +curve. + The surface is calculated as a Coons patch, see https://en.wikipedia.org/wiki/Coons_patch. When all provided curves are NURBS or NURBS-like, then the node will try to @@ -34,12 +39,13 @@ This node has the following inputs: * **Curves**. The list of curves to build a surface form. This input can accept data with nesting level of 1 or 2 (list of curves or list of lists of - curves). Each list of curves must have length of 4. This input is available + curves). Each list of curves must have length of 3 or 4. This input is available and mandatory only if **Input** parameter is set to **List of Curves**. * **Curve1**, **Curve2**, **Curve3**, **Curve4**. Curves to build surface from. These inputs can accept data with nesting level 1 only (list of curves). - These inputs are available and mandatory only if **Input** parameter is set - to **4 Curves**. + These inputs are available only if **Input** parameter is set + to **4 Curves**. Inputs **Curve1**, **Curve2** and **Curve3** are mandatory. + **Curve4** input is optional. Parameters ---------- @@ -103,3 +109,12 @@ It is possible to use the node together with "Split Curve" node to generate a su .. image:: https://user-images.githubusercontent.com/284644/82479760-3deb1f80-9aec-11ea-8411-22ffd273259f.png +It is possible to use only three boundary curves: + +.. image:: https://user-images.githubusercontent.com/284644/210209607-113759b7-9992-4e5d-870d-6aa6dafe0c32.png + +If the third curve end does not coincide with the beginning of the first curve, +the node will close the cycle with a straight line segment: + +.. image:: https://user-images.githubusercontent.com/284644/210209611-052c63b6-ef39-4c12-a047-d7d369f3469c.png + From b4febdc806ce8d3ba481630642a3b2bd90d3d9c8 Mon Sep 17 00:00:00 2001 From: Ilya Portnov Date: Mon, 2 Jan 2023 13:52:47 +0500 Subject: [PATCH 3/3] fix documentation. --- docs/nodes/surface/coons_patch.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/nodes/surface/coons_patch.rst b/docs/nodes/surface/coons_patch.rst index 82996e567f..5ab9317787 100644 --- a/docs/nodes/surface/coons_patch.rst +++ b/docs/nodes/surface/coons_patch.rst @@ -55,7 +55,7 @@ This node has the following parameters: * **Input**. This defines how the curves are provided. The following options are available: * **List of curves**. All curves are provided in a single input **Curves**. - * **4 Curves**. Each curve is provided in separate input **Curve1** - **Curve4**. + * **Separate inputs**. Each curve is provided in separate input **Curve1** - **Curve4**. The default value is **List of Curves**.