Skip to content

Commit

Permalink
Merge pull request #4177 from nortikin/nurbs_birail_update_2
Browse files Browse the repository at this point in the history
Nurbs birail update 2
  • Loading branch information
portnov authored Jun 20, 2021
2 parents 06793d4 + f7aba33 commit 5bc1be6
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 10 deletions.
36 changes: 35 additions & 1 deletion docs/nodes/surface/nurbs_birail.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ curve.
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.
precision loss in some cases. Or, in other cases, it can guess desired profile
rotation incorrectly. In such cases, you will have to place your profile curves
in XOY plane and disable "Auto rotate" flag.

The node works by placing several copies of profile curve along the path
curves, and then lofting (skinning) between them. If several profile curves
Expand Down Expand Up @@ -107,6 +109,22 @@ This node has the following parameters:
arbitrarily rotated profile curves. Enabled option requires more
computations, and so, may make the node slower and less precise. Unchecked by
default.
* **Profile Rotation**. This defines how profile curves will be placed along
the path curves. The available options are:

* **Path Normal Average**. The node will try to place profile curves so that
they will lie in normal planes of both path curves. Since normal planes of
two path curves can differ, the node will calculate average normal plane.
* **Path 1 Normal**. The node will place profile curves so that they lie in
normal plane of the first path curve.
* **Path 2 Normal**. The node will place profile curves so that they lie in
normal plane of the second path curve.
* **By profile**. The node will try to place profile curves so that they be
parallel to initial location of the path curve. This is not always
possible, but the node will try to keep it as parallel as possible.

The default option is **Path Normal Average**.

* **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
Expand Down Expand Up @@ -166,3 +184,19 @@ Examples of usage

.. image:: https://user-images.githubusercontent.com/284644/98482009-321d2200-2220-11eb-82a8-21ca366b573c.png

Create a circular arc (path 1) and S-shaped curve (path 2); use random profile curve in YOZ plane. Profile rotation = Path Normal Average (default one):

.. image:: https://user-images.githubusercontent.com/284644/122664399-9347a380-d1ba-11eb-98fc-6ce8c10ad6fe.png

Same with Profile rotation = Path 1 Normal (i.e. profiles are perpendicular to the lower curve, circular arc):

.. image:: https://user-images.githubusercontent.com/284644/122664398-92af0d00-d1ba-11eb-921f-9cf0289b5356.png

Same with Profile rotation = Path 2 Normal (i.e. profiles are perpendicular to the upper, S-shaped curve):

.. image:: https://user-images.githubusercontent.com/284644/122664396-92af0d00-d1ba-11eb-8a40-95fde688941e.png

Same with Profile rotation = By profile, i.e. try to keep profile curves parallel to the original profile:

.. image:: https://user-images.githubusercontent.com/284644/122664393-917de000-d1ba-11eb-880a-44b53bf159bd.png

17 changes: 17 additions & 0 deletions nodes/surface/nurbs_birail.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,26 @@ def update_sockets(self, context):
default = False,
update = updateNode)

rotate_options = [
('PATHS_AVG', "Paths Normal Average", "Rotate profile(s), trying to make it perpendicular to both paths", 0),
('FROM_PATH1', "Path 1 Normal", "Rotate profile(s), trying to make it perpendicular to the first path", 1),
('FROM_PATH2', "Path 2 Normal", "Rotate profile(s), trying to make it perpendicular to the second path", 2),
('FROM_PROFILE', "By profile", "Try to use initial rotation of profile curve(s)", 3)
]

profile_rotation : EnumProperty(
name = "Profile rotation",
description = "Defines how profile curves should be rotated",
items = rotate_options,
default = 'PATHS_AVG',
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.label(text="Profile rotation:")
layout.prop(self, "profile_rotation", text='')
layout.prop(self, "explicit_v")

def draw_buttons_ext(self, context, layout):
Expand Down Expand Up @@ -187,6 +203,7 @@ def process(self):
metric = self.metric,
scale_uniform = self.scale_uniform,
auto_rotate = self.auto_rotate_profiles,
use_tangents = self.profile_rotation,
implementation = self.nurbs_implementation
)
new_surfaces.append(surface)
Expand Down
24 changes: 22 additions & 2 deletions utils/curve/nurbs_algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,18 @@ def concatenate_nurbs_curves(curves):
raise Exception(f"Can't append curve #{i+1}: {e}")
return result

def nurbs_curve_to_xoy(curve):
def nurbs_curve_to_xoy(curve, target_normal=None):
cpts = curve.get_control_points()

approx = linear_approximation(cpts)
plane = approx.most_similar_plane()
normal = plane.normal

if target_normal is not None:
a = np.dot(normal, target_normal)
if a > 0:
normal = -normal

xx = cpts[-1] - cpts[0]
xx /= np.linalg.norm(xx)

Expand All @@ -139,4 +144,19 @@ def nurbs_curve_to_xoy(curve):
center = approx.center
new_cpts = np.array([matrix @ (cpt - center) for cpt in cpts])
return curve.copy(control_points = new_cpts)


def nurbs_curve_matrix(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
return matrix

37 changes: 30 additions & 7 deletions utils/surface/nurbs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, nurbs_curve_to_xoy
from sverchok.utils.curve.nurbs_algorithms import interpolate_nurbs_curve, unify_curves, nurbs_curve_to_xoy, nurbs_curve_matrix
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
Expand Down Expand Up @@ -910,6 +910,7 @@ def nurbs_birail(path1, path2, profiles,
degree_v = None, metric = 'DISTANCE',
scale_uniform = True,
auto_rotate = False,
use_tangents = 'PATHS_AVG',
implementation = SvNurbsSurface.NATIVE):
"""
NURBS BiRail.
Expand All @@ -931,6 +932,8 @@ def nurbs_birail(path1, path2, profiles,
* scale_uniform: If True, profile curves will be scaled along all axes
uniformly; if False, they will be scaled only along one axis, in order to
fill space between two path curves.
* auto_rotate: if False, the profile curves are supposed to lie in XOY plane.
Otherwise, try to figure out their rotation automatically.
* implementation: surface implementation
output: tuple:
Expand Down Expand Up @@ -992,16 +995,34 @@ def nurbs_birail(path1, path2, profiles,
points1 = path1.evaluate_array(ts1)
points2 = path2.evaluate_array(ts2)

tangents1 = path1.tangent_array(ts1)
tangents2 = path2.tangent_array(ts2)
tangents = 0.5 * (tangents1 + tangents2)
tangents /= np.linalg.norm(tangents, axis=1, keepdims=True)
orig_profiles = profiles[:]

if use_tangents == 'PATHS_AVG':
tangents1 = path1.tangent_array(ts1)
tangents2 = path2.tangent_array(ts2)
tangents = 0.5 * (tangents1 + tangents2)
tangents /= np.linalg.norm(tangents, axis=1, keepdims=True)
elif use_tangents == 'FROM_PATH1':
tangents = path1.tangent_array(ts1)
tangents /= np.linalg.norm(tangents, axis=1, keepdims=True)
elif use_tangents == 'FROM_PATH2':
tangents = path2.tangent_array(ts2)
tangents /= np.linalg.norm(tangents, axis=1, keepdims=True)
elif use_tangents == 'FROM_PROFILE':
tangents = []
for profile in orig_profiles:
matrix = nurbs_curve_matrix(profile)
yy = matrix @ np.array([0, 0, -1])
yy /= np.linalg.norm(yy)
tangents.append(yy)
tangents = np.array(tangents)

binormals = points2 - points1
scales = np.linalg.norm(binormals, axis=1, keepdims=True)
if scales.min() < 1e-6:
raise Exception("Paths go too close")
binormals /= scales

normals = np.cross(tangents, binormals)
normals /= np.linalg.norm(normals, axis=1, keepdims=True)

Expand All @@ -1014,9 +1035,11 @@ def nurbs_birail(path1, path2, profiles,

scales = scales.flatten()
placed_profiles = []
for pt1, pt2, profile, scale, matrix in zip(points1, points2, profiles, scales, matrices):
prev_normal = None
for pt1, pt2, profile, tangent, scale, matrix in zip(points1, points2, profiles, tangents, scales, matrices):

if auto_rotate:
profile = nurbs_curve_to_xoy(profile)
profile = nurbs_curve_to_xoy(profile, tangent)

t_min, t_max = profile.get_u_bounds()
pr_start = profile.evaluate(t_min)
Expand Down

0 comments on commit 5bc1be6

Please sign in to comment.