diff --git a/docs/nodes/surface/nurbs_birail.rst b/docs/nodes/surface/nurbs_birail.rst index 2770f18a98..dbd0bca6ee 100644 --- a/docs/nodes/surface/nurbs_birail.rst +++ b/docs/nodes/surface/nurbs_birail.rst @@ -25,7 +25,10 @@ path's curve T parameter, at which profile curves must be placed; otherwise, the node will place them automatically evenly along T parameter of the path curve. -It is supposed that initially th eprovided profile curve(s) lie in XOY plane. +By default it is supposed that initially the provided profile curve(s) lie in +XOY plane. However, there is an option to instruct the node to try to figure +out correct rotation of profile curve(s). Note that this option may result in +precision loss in some cases. The node works by placing several copies of profile curve along the path curves, and then lofting (skinning) between them. If several profile curves @@ -99,6 +102,11 @@ This node has the following parameters: * **Scale all axes**. If not checked, profile curves will be scaled along one axis only, in order to fill the space between two paths. If checked, profile curves will be scaled along all axes uniformly. Checked by default. +* **Auto rotate profiles**. If not checked, the node will assume that all + profile curves lie in the XOY plane. If checked, the node will work with + arbitrarily rotated profile curves. Enabled option requires more + computations, and so, may make the node slower and less precise. Unchecked by + default. * **Explicit V Values**. If checked, then the user has the ability to provide values of path curves parameter values, at which the provided path curves must be placed; otherwise, the node will calculate these parameters diff --git a/nodes/surface/nurbs_birail.py b/nodes/surface/nurbs_birail.py index dbe04b5ec7..4f9ce0a389 100644 --- a/nodes/surface/nurbs_birail.py +++ b/nodes/surface/nurbs_birail.py @@ -100,9 +100,16 @@ def update_sockets(self, context): default = True, update = updateNode) + auto_rotate_profiles : BoolProperty( + name = "Auto rotate profiles", + description = "If checked, then the node will try to rotate provided profile curves appropriately. Otherwise, the node expects provided profile curves to lie in XOY plane.", + default = False, + update = updateNode) + def draw_buttons(self, context, layout): layout.prop(self, 'nurbs_implementation', text='') layout.prop(self, "scale_uniform") + layout.prop(self, "auto_rotate_profiles") layout.prop(self, "explicit_v") def draw_buttons_ext(self, context, layout): @@ -179,6 +186,7 @@ def process(self): degree_v = degree_v, metric = self.metric, scale_uniform = self.scale_uniform, + auto_rotate = self.auto_rotate_profiles, implementation = self.nurbs_implementation ) new_surfaces.append(surface) diff --git a/utils/curve/nurbs_algorithms.py b/utils/curve/nurbs_algorithms.py index 4f20ee9652..5290d01ba9 100644 --- a/utils/curve/nurbs_algorithms.py +++ b/utils/curve/nurbs_algorithms.py @@ -8,7 +8,7 @@ import numpy as np from collections import defaultdict -from sverchok.utils.geom import Spline +from sverchok.utils.geom import Spline, linear_approximation from sverchok.utils.nurbs_common import SvNurbsBasisFunctions, SvNurbsMaths, from_homogenous from sverchok.utils.curve import knotvector as sv_knotvector from sverchok.utils.curve.algorithms import unify_curves_degree @@ -122,3 +122,21 @@ def concatenate_nurbs_curves(curves): raise Exception(f"Can't append curve #{i+1}: {e}") return result +def nurbs_curve_to_xoy(curve): + cpts = curve.get_control_points() + + approx = linear_approximation(cpts) + plane = approx.most_similar_plane() + normal = plane.normal + + xx = cpts[-1] - cpts[0] + xx /= np.linalg.norm(xx) + + yy = np.cross(normal, xx) + + matrix = np.stack((xx, yy, normal)).T + matrix = np.linalg.inv(matrix) + center = approx.center + new_cpts = np.array([matrix @ (cpt - center) for cpt in cpts]) + return curve.copy(control_points = new_cpts) + diff --git a/utils/geom.py b/utils/geom.py index 313b297d5b..a7b92b779a 100644 --- a/utils/geom.py +++ b/utils/geom.py @@ -825,7 +825,7 @@ def two_vectors(self, normalize=False): def get_matrix(self, invert_y=False): x = self.second_vector().normalized() z = self.normal.normalized() - y = z.cross(x) + y = z.cross(x).normalized() if invert_y: y = - y return Matrix([x, y, z]).transposed() diff --git a/utils/surface/nurbs.py b/utils/surface/nurbs.py index e05882b5e9..85b1b2195e 100644 --- a/utils/surface/nurbs.py +++ b/utils/surface/nurbs.py @@ -8,7 +8,7 @@ nurbs_divide, from_homogenous ) from sverchok.utils.curve import knotvector as sv_knotvector -from sverchok.utils.curve.nurbs_algorithms import interpolate_nurbs_curve, unify_curves +from sverchok.utils.curve.nurbs_algorithms import interpolate_nurbs_curve, unify_curves, nurbs_curve_to_xoy from sverchok.utils.curve.algorithms import unify_curves_degree, SvCurveFrameCalculator from sverchok.utils.surface.core import UnsupportedSurfaceTypeException from sverchok.utils.surface import SvSurface, SurfaceCurvatureCalculator, SurfaceDerivativesData @@ -903,7 +903,14 @@ def nurbs_sweep(path, profiles, ts, min_profiles, algorithm, knots_u = 'UNIFY', knots_u=knots_u, metric=metric, implementation=implementation) -def nurbs_birail(path1, path2, profiles, ts1 = None, ts2 = None, min_profiles = 10, knots_u = 'UNIFY', degree_v = None, metric = 'DISTANCE', scale_uniform = True, implementation = SvNurbsSurface.NATIVE): +def nurbs_birail(path1, path2, profiles, + ts1 = None, ts2 = None, + min_profiles = 10, + knots_u = 'UNIFY', + degree_v = None, metric = 'DISTANCE', + scale_uniform = True, + auto_rotate = False, + implementation = SvNurbsSurface.NATIVE): """ NURBS BiRail. @@ -1007,7 +1014,10 @@ def nurbs_birail(path1, path2, profiles, ts1 = None, ts2 = None, min_profiles = scales = scales.flatten() placed_profiles = [] - for pt1, profile, scale, matrix in zip(points1, profiles, scales, matrices): + for pt1, pt2, profile, scale, matrix in zip(points1, points2, profiles, scales, matrices): + if auto_rotate: + profile = nurbs_curve_to_xoy(profile) + t_min, t_max = profile.get_u_bounds() pr_start = profile.evaluate(t_min) pr_end = profile.evaluate(t_max) @@ -1024,6 +1034,7 @@ def nurbs_birail(path1, path2, profiles, ts1 = None, ts2 = None, min_profiles = (0, 0, 1) ]) + src_scale = scale scale /= pr_length if scale_uniform: scale_m = np.array([ @@ -1039,6 +1050,7 @@ def nurbs_birail(path1, path2, profiles, ts1 = None, ts2 = None, min_profiles = ]) cpts = [matrix @ scale_m @ rotation @ (pt - pr_start) + pt1 for pt in profile.get_control_points()] cpts = np.array(cpts) + profile = profile.copy(control_points = cpts) placed_profiles.append(profile)