Module sverchok.utils.curve.nurbs_solver_applications

This module contains several algorithms which are based on the NURBS curve solver (sverchok.utils.curve.nurbs_solver module).

Expand source code
# This file is part of project Sverchok. It's copyrighted by the contributors
# recorded in the version control history of the file, available from
# its original location https://github.com/nortikin/sverchok/commit/master
#
# SPDX-License-Identifier: GPL3
# License-Filename: LICENSE

"""
This module contains several algorithms which are based on the NURBS curve
solver (`sverchok.utils.curve.nurbs_solver` module).
"""

import numpy as np

from sverchok.utils.math import falloff_array
from sverchok.utils.geom import Spline
from sverchok.utils.curve import knotvector as sv_knotvector
from sverchok.utils.curve.algorithms import SvCurveOnSurface
from sverchok.utils.nurbs_common import SvNurbsMaths
from sverchok.utils.curve.nurbs_algorithms import refine_curve, remove_excessive_knots
from sverchok.utils.curve.nurbs_solver import SvNurbsCurvePoints, SvNurbsCurveTangents, SvNurbsCurveCotangents, SvNurbsCurveSolver

def adjust_curve_points(curve, us_bar, points):
    """
    Modify NURBS curve so that it would pass through specified points
    at specified parameter values.

    Args:
        curve: an instance of SvNurbsCurve
        us_bar: values of curve parameter, np.array of shape (n,)
        points: new positions of curve points at specified parameter values, np.array of shape (n,3).

    Returns:
        an instance of SvNurbsCurve.
    """
    n_target_points = len(us_bar)
    if len(points) != n_target_points:
        raise Exception("Number of U parameters must be equal to number of points")

    solver = SvNurbsCurveSolver(src_curve=curve)
    solver.add_goal(SvNurbsCurvePoints(us_bar, points))
    solver.set_curve_params(len(curve.get_control_points()))
    return solver.solve()

def deform_curve_with_falloff(curve, length_solver, u_bar, falloff_delta, falloff_type, vector, refine_samples=30, tolerance=1e-4):
    """
    Modify NURBS curve by moving it's point at parameter value at u_bar by specified vector,
    and moving nearby points within specified falloff_delta according to provided falloff function.

    Args:
        curve: an instance of SvNurbsCurve
        length_solver: a prepared instance of SvCurveLengthSolver
        u_bar: float: parameter value, the point at which is to be moved
        falloff_delta: half of length of curve segment, which is to be modified
        falloff_type: falloff function type, see sverchok.utils.math.proportional_falloff_types
        vector: np.array of shape (3,): the movement vector
        refine_samples: number of additional knots to be inserted. More knots mean more precise
            transformation.
        tolerance: tolerance for removing excessive knots at the end of procedure.

    Returns:
        an instance of SvNurbsCurve.
    """
    l_bar = length_solver.calc_length_params(np.array([u_bar]))[0]
    u_min, u_max = length_solver.solve(np.array([l_bar - falloff_delta, l_bar + falloff_delta]))
    curve = refine_curve(curve, refine_samples)
                #t_min = u_min, t_max = u_max)
    us = curve.calc_greville_ts()
    ls = length_solver.calc_length_params(us)
    weights = falloff_array(falloff_type, 1.0, falloff_delta)(abs(ls - l_bar))
    nonzero = np.where(weights > 0)
    us_nonzero = us[nonzero]
    weights_nonzero = weights[nonzero]
    points = curve.evaluate_array(us_nonzero)
    new_points = weights_nonzero[np.newaxis].T * vector
    points_goal = SvNurbsCurvePoints(us_nonzero, new_points, relative=True)
    zero_tangents = np.zeros_like(points)
    #tangents_goal = SvNurbsCurveTangents(us_nonzero, zero_tangents, weights=weights_nonzero, relative=True)
    solver = SvNurbsCurveSolver(src_curve=curve)
    solver.add_goal(points_goal)
    #solver.add_goal(tangents_goal)
    solver.set_curve_params(len(curve.get_control_points()))
    result = solver.solve()
    return remove_excessive_knots(result, tolerance)

def approximate_nurbs_curve(degree, n_cpts, points, weights=None, metric='DISTANCE', implementation=SvNurbsMaths.NATIVE):
    """
    Approximate points by a NURBS curve.

    Args:
        degree: curve degree (usually 3 or 5).
        n_cpts: number of curve control points. If this is equal to number of
            points being approximated, then this method will do interpolation.
        points: points to be approximated. np.array of shape (n, 3).
        weights: points weights. Bigger weight means that the curve should be
            attracted to corresponding point more than to points with smaller
            weights. None means all weights are equal.
        metric: metric to be used.
        implementation: NURBS mathematics implementation.

    Returns:
        an instance of SvNurbsCurve.
    """
    points = np.asarray(points)
    tknots = Spline.create_knots(points, metric=metric)
    knotvector = sv_knotvector.from_tknots(degree, tknots, n_cpts)
    goal = SvNurbsCurvePoints(tknots, points, weights = weights, relative=False)
    solver = SvNurbsCurveSolver(degree=degree)
    solver.set_curve_params(n_cpts, knotvector = knotvector)
    solver.add_goal(goal)
    return solver.solve(implementation=implementation)

def prepare_solver_for_interpolation(degree, points, metric='DISTANCE', tknots=None, cyclic=False):
    n_points = len(points)
    points = np.asarray(points)
    if points.ndim != 2:
        raise Exception(f"Array of points was expected, but got {points.shape}: {points}")
    ndim = points.shape[1] # 3 or 4
    if ndim not in {3,4}:
        raise Exception(f"Only 3D and 4D points are supported, but ndim={ndim}")
    if cyclic:
        points = np.concatenate((points, points[0][np.newaxis]))
    if tknots is None:
        tknots = Spline.create_knots(points, metric=metric)
    solver = SvNurbsCurveSolver(degree=degree, ndim=ndim)
    solver.add_goal(SvNurbsCurvePoints(tknots, points, relative=False))
    if cyclic:
        k = 1.0/float(degree)
        tangent = k*(points[1] - points[-2])
        solver.add_goal(SvNurbsCurveTangents.single(0.0, tangent))
        solver.add_goal(SvNurbsCurveTangents.single(1.0, tangent))
        knotvector = sv_knotvector.from_tknots(degree, tknots, include_endpoints=True)
    else:
        knotvector = sv_knotvector.from_tknots(degree, tknots)

    n_cpts = solver.guess_n_control_points()
    solver.set_curve_params(n_cpts, knotvector)
    return solver

def interpolate_nurbs_curve(degree, points, metric='DISTANCE', tknots=None, cyclic=False, implementation=SvNurbsMaths.NATIVE, logger=None):
    """
    Interpolate points by a NURBS curve.

    Args:
        degree: curve degree (usually 3 or 5).
        points: points to be approximated. np.array of shape (n,3).
        metric: metric to be used.
        tknots: curve parameter values corresponding to points. np.array of
            shape (n,). If None, these values will be calculated based on metric.
        cyclic: if True, this will generate cyclic (closed) curve.
        implementation: NURBS mathematics implementation.

    Returns:
        an instance of SvNurbsCurve.
    """
    solver = prepare_solver_for_interpolation(degree, points,
                    metric = metric, tknots = tknots,
                    cyclic = cyclic)
    problem_type, residue, curve = solver.solve_ex(problem_types = {SvNurbsCurveSolver.PROBLEM_WELLDETERMINED},
                                    implementation = implementation,
                                    logger = logger)
    return curve

def knotvector_with_tangents_from_tknots(degree, u):
    n = len(u)
    if degree == 2:
        kv = [u[0], u[0], u[0]]
        for i in range(1, n-1):
            kv.append((u[i-1] + u[i]) / 2.0)
            kv.append(u[i])
        kv.extend([u[-1], u[-1]])
        return np.array(kv)
    elif degree == 3:
        if len(u) == 2:
            return np.array([u[0], u[0], u[0], u[0],
                             u[1], u[1], u[1], u[1]])
        kv = [u[0], u[0], u[0],u[0]]
        kv.append(u[1]/2.0)
        for i in range(1, n-2):
            u1 = (2*u[i] + u[i+1]) / 3.0
            kv.append(u1)
            u2 = (u[i] + 2*u[i+1]) / 3.0
            kv.append(u2)
        kv.append((u[-2] + u[-1])/2.0)
        kv.extend([u[-1], u[-1], u[-1], u[-1]])
        return np.array(kv)
    else:
        raise Exception(f"Degrees other than 2 and 3 are not supported yet")

def interpolate_nurbs_curve_with_tangents(degree, points, tangents,
            metric='DISTANCE', tknots=None,
            cyclic = False,
            implementation = SvNurbsMaths.NATIVE,
            logger = None):

    n_points = len(points)
    points = np.asarray(points)
    tangents = np.asarray(tangents)
    if len(points) != len(tangents):
        raise Exception(f"Number of points ({len(points)}) must be equal to number of tangent vectors ({len(tangents)})")
    ndim = points.shape[-1]
    if ndim not in {3,4}:
        raise Exception(f"Points must be 3 or 4 dimensional, not {ndim}")

    if cyclic:
        points = np.append(points, [points[0]], axis=0)
        tangents = np.append(tangents, [tangents[0]], axis=0)

    if tknots is None:
        tknots = Spline.create_knots(points, metric=metric)

    solver = SvNurbsCurveSolver(degree=degree, ndim=ndim)
    solver.add_goal(SvNurbsCurvePoints(tknots, points, relative=False))
    solver.add_goal(SvNurbsCurveTangents(tknots, tangents, relative=False))
    knotvector = knotvector_with_tangents_from_tknots(degree, tknots)
    n_cpts = solver.guess_n_control_points()
    solver.set_curve_params(n_cpts, knotvector)
    problem_type, residue, curve = solver.solve_ex(problem_types = {SvNurbsCurveSolver.PROBLEM_WELLDETERMINED},
                                    implementation = implementation,
                                    logger = logger)
    return curve

def curve_to_nurbs(degree, curve, samples, metric = 'DISTANCE', use_tangents = False, logger=None):
    is_cyclic = curve.is_closed()
    t_min, t_max = curve.get_u_bounds()
    ts = np.linspace(t_min, t_max, num=samples)
    if is_cyclic:
        ts = ts[:-1]
    points = curve.evaluate_array(ts)
    if use_tangents:
        tangents = curve.tangent_array(ts)
        return interpolate_nurbs_curve_with_tangents(degree, points, tangents, metric=metric, logger=logger)
    else:
        return interpolate_nurbs_curve(degree, points, cyclic=is_cyclic, metric=metric, logger=logger)

def curve_on_surface_to_nurbs(degree, uv_curve, surface, samples, metric = 'DISTANCE', use_tangents = False, logger = None):
    #is_cyclic = uv_curve.is_closed()
    is_cyclic = False
    t_min, t_max = uv_curve.get_u_bounds()
    ts = np.linspace(t_min, t_max, num=samples)
    curve = SvCurveOnSurface(uv_curve, surface, axis=2)
    if is_cyclic:
        ts = ts[:-1]
    uv_points = uv_curve.evaluate_array(ts)
    points = surface.evaluate_array(uv_points[:,0], uv_points[:,1])
    tknots = Spline.create_knots(points, metric=metric)
    if use_tangents:
        tangents = curve.tangent_array(ts)
        return interpolate_nurbs_curve_with_tangents(degree, points, tangents, tknots = tknots, logger=logger)
    else:
        return interpolate_nurbs_curve(degree, points, cyclic=is_cyclic, tknots = tknots, logger=logger)

Functions

def adjust_curve_points(curve, us_bar, points)

Modify NURBS curve so that it would pass through specified points at specified parameter values.

Args

curve
an instance of SvNurbsCurve
us_bar
values of curve parameter, np.array of shape (n,)
points
new positions of curve points at specified parameter values, np.array of shape (n,3).

Returns

an instance of SvNurbsCurve.

Expand source code
def adjust_curve_points(curve, us_bar, points):
    """
    Modify NURBS curve so that it would pass through specified points
    at specified parameter values.

    Args:
        curve: an instance of SvNurbsCurve
        us_bar: values of curve parameter, np.array of shape (n,)
        points: new positions of curve points at specified parameter values, np.array of shape (n,3).

    Returns:
        an instance of SvNurbsCurve.
    """
    n_target_points = len(us_bar)
    if len(points) != n_target_points:
        raise Exception("Number of U parameters must be equal to number of points")

    solver = SvNurbsCurveSolver(src_curve=curve)
    solver.add_goal(SvNurbsCurvePoints(us_bar, points))
    solver.set_curve_params(len(curve.get_control_points()))
    return solver.solve()
def approximate_nurbs_curve(degree, n_cpts, points, weights=None, metric='DISTANCE', implementation='NATIVE')

Approximate points by a NURBS curve.

Args

degree
curve degree (usually 3 or 5).
n_cpts
number of curve control points. If this is equal to number of points being approximated, then this method will do interpolation.
points
points to be approximated. np.array of shape (n, 3).
weights
points weights. Bigger weight means that the curve should be attracted to corresponding point more than to points with smaller weights. None means all weights are equal.
metric
metric to be used.
implementation
NURBS mathematics implementation.

Returns

an instance of SvNurbsCurve.

Expand source code
def approximate_nurbs_curve(degree, n_cpts, points, weights=None, metric='DISTANCE', implementation=SvNurbsMaths.NATIVE):
    """
    Approximate points by a NURBS curve.

    Args:
        degree: curve degree (usually 3 or 5).
        n_cpts: number of curve control points. If this is equal to number of
            points being approximated, then this method will do interpolation.
        points: points to be approximated. np.array of shape (n, 3).
        weights: points weights. Bigger weight means that the curve should be
            attracted to corresponding point more than to points with smaller
            weights. None means all weights are equal.
        metric: metric to be used.
        implementation: NURBS mathematics implementation.

    Returns:
        an instance of SvNurbsCurve.
    """
    points = np.asarray(points)
    tknots = Spline.create_knots(points, metric=metric)
    knotvector = sv_knotvector.from_tknots(degree, tknots, n_cpts)
    goal = SvNurbsCurvePoints(tknots, points, weights = weights, relative=False)
    solver = SvNurbsCurveSolver(degree=degree)
    solver.set_curve_params(n_cpts, knotvector = knotvector)
    solver.add_goal(goal)
    return solver.solve(implementation=implementation)
def curve_on_surface_to_nurbs(degree, uv_curve, surface, samples, metric='DISTANCE', use_tangents=False, logger=None)
Expand source code
def curve_on_surface_to_nurbs(degree, uv_curve, surface, samples, metric = 'DISTANCE', use_tangents = False, logger = None):
    #is_cyclic = uv_curve.is_closed()
    is_cyclic = False
    t_min, t_max = uv_curve.get_u_bounds()
    ts = np.linspace(t_min, t_max, num=samples)
    curve = SvCurveOnSurface(uv_curve, surface, axis=2)
    if is_cyclic:
        ts = ts[:-1]
    uv_points = uv_curve.evaluate_array(ts)
    points = surface.evaluate_array(uv_points[:,0], uv_points[:,1])
    tknots = Spline.create_knots(points, metric=metric)
    if use_tangents:
        tangents = curve.tangent_array(ts)
        return interpolate_nurbs_curve_with_tangents(degree, points, tangents, tknots = tknots, logger=logger)
    else:
        return interpolate_nurbs_curve(degree, points, cyclic=is_cyclic, tknots = tknots, logger=logger)
def curve_to_nurbs(degree, curve, samples, metric='DISTANCE', use_tangents=False, logger=None)
Expand source code
def curve_to_nurbs(degree, curve, samples, metric = 'DISTANCE', use_tangents = False, logger=None):
    is_cyclic = curve.is_closed()
    t_min, t_max = curve.get_u_bounds()
    ts = np.linspace(t_min, t_max, num=samples)
    if is_cyclic:
        ts = ts[:-1]
    points = curve.evaluate_array(ts)
    if use_tangents:
        tangents = curve.tangent_array(ts)
        return interpolate_nurbs_curve_with_tangents(degree, points, tangents, metric=metric, logger=logger)
    else:
        return interpolate_nurbs_curve(degree, points, cyclic=is_cyclic, metric=metric, logger=logger)
def deform_curve_with_falloff(curve, length_solver, u_bar, falloff_delta, falloff_type, vector, refine_samples=30, tolerance=0.0001)

Modify NURBS curve by moving it's point at parameter value at u_bar by specified vector, and moving nearby points within specified falloff_delta according to provided falloff function.

Args

curve
an instance of SvNurbsCurve
length_solver
a prepared instance of SvCurveLengthSolver
u_bar
float: parameter value, the point at which is to be moved
falloff_delta
half of length of curve segment, which is to be modified
falloff_type
falloff function type, see sverchok.utils.math.proportional_falloff_types
vector
np.array of shape (3,): the movement vector
refine_samples
number of additional knots to be inserted. More knots mean more precise transformation.
tolerance
tolerance for removing excessive knots at the end of procedure.

Returns

an instance of SvNurbsCurve.

Expand source code
def deform_curve_with_falloff(curve, length_solver, u_bar, falloff_delta, falloff_type, vector, refine_samples=30, tolerance=1e-4):
    """
    Modify NURBS curve by moving it's point at parameter value at u_bar by specified vector,
    and moving nearby points within specified falloff_delta according to provided falloff function.

    Args:
        curve: an instance of SvNurbsCurve
        length_solver: a prepared instance of SvCurveLengthSolver
        u_bar: float: parameter value, the point at which is to be moved
        falloff_delta: half of length of curve segment, which is to be modified
        falloff_type: falloff function type, see sverchok.utils.math.proportional_falloff_types
        vector: np.array of shape (3,): the movement vector
        refine_samples: number of additional knots to be inserted. More knots mean more precise
            transformation.
        tolerance: tolerance for removing excessive knots at the end of procedure.

    Returns:
        an instance of SvNurbsCurve.
    """
    l_bar = length_solver.calc_length_params(np.array([u_bar]))[0]
    u_min, u_max = length_solver.solve(np.array([l_bar - falloff_delta, l_bar + falloff_delta]))
    curve = refine_curve(curve, refine_samples)
                #t_min = u_min, t_max = u_max)
    us = curve.calc_greville_ts()
    ls = length_solver.calc_length_params(us)
    weights = falloff_array(falloff_type, 1.0, falloff_delta)(abs(ls - l_bar))
    nonzero = np.where(weights > 0)
    us_nonzero = us[nonzero]
    weights_nonzero = weights[nonzero]
    points = curve.evaluate_array(us_nonzero)
    new_points = weights_nonzero[np.newaxis].T * vector
    points_goal = SvNurbsCurvePoints(us_nonzero, new_points, relative=True)
    zero_tangents = np.zeros_like(points)
    #tangents_goal = SvNurbsCurveTangents(us_nonzero, zero_tangents, weights=weights_nonzero, relative=True)
    solver = SvNurbsCurveSolver(src_curve=curve)
    solver.add_goal(points_goal)
    #solver.add_goal(tangents_goal)
    solver.set_curve_params(len(curve.get_control_points()))
    result = solver.solve()
    return remove_excessive_knots(result, tolerance)
def interpolate_nurbs_curve(degree, points, metric='DISTANCE', tknots=None, cyclic=False, implementation='NATIVE', logger=None)

Interpolate points by a NURBS curve.

Args

degree
curve degree (usually 3 or 5).
points
points to be approximated. np.array of shape (n,3).
metric
metric to be used.
tknots
curve parameter values corresponding to points. np.array of shape (n,). If None, these values will be calculated based on metric.
cyclic
if True, this will generate cyclic (closed) curve.
implementation
NURBS mathematics implementation.

Returns

an instance of SvNurbsCurve.

Expand source code
def interpolate_nurbs_curve(degree, points, metric='DISTANCE', tknots=None, cyclic=False, implementation=SvNurbsMaths.NATIVE, logger=None):
    """
    Interpolate points by a NURBS curve.

    Args:
        degree: curve degree (usually 3 or 5).
        points: points to be approximated. np.array of shape (n,3).
        metric: metric to be used.
        tknots: curve parameter values corresponding to points. np.array of
            shape (n,). If None, these values will be calculated based on metric.
        cyclic: if True, this will generate cyclic (closed) curve.
        implementation: NURBS mathematics implementation.

    Returns:
        an instance of SvNurbsCurve.
    """
    solver = prepare_solver_for_interpolation(degree, points,
                    metric = metric, tknots = tknots,
                    cyclic = cyclic)
    problem_type, residue, curve = solver.solve_ex(problem_types = {SvNurbsCurveSolver.PROBLEM_WELLDETERMINED},
                                    implementation = implementation,
                                    logger = logger)
    return curve
def interpolate_nurbs_curve_with_tangents(degree, points, tangents, metric='DISTANCE', tknots=None, cyclic=False, implementation='NATIVE', logger=None)
Expand source code
def interpolate_nurbs_curve_with_tangents(degree, points, tangents,
            metric='DISTANCE', tknots=None,
            cyclic = False,
            implementation = SvNurbsMaths.NATIVE,
            logger = None):

    n_points = len(points)
    points = np.asarray(points)
    tangents = np.asarray(tangents)
    if len(points) != len(tangents):
        raise Exception(f"Number of points ({len(points)}) must be equal to number of tangent vectors ({len(tangents)})")
    ndim = points.shape[-1]
    if ndim not in {3,4}:
        raise Exception(f"Points must be 3 or 4 dimensional, not {ndim}")

    if cyclic:
        points = np.append(points, [points[0]], axis=0)
        tangents = np.append(tangents, [tangents[0]], axis=0)

    if tknots is None:
        tknots = Spline.create_knots(points, metric=metric)

    solver = SvNurbsCurveSolver(degree=degree, ndim=ndim)
    solver.add_goal(SvNurbsCurvePoints(tknots, points, relative=False))
    solver.add_goal(SvNurbsCurveTangents(tknots, tangents, relative=False))
    knotvector = knotvector_with_tangents_from_tknots(degree, tknots)
    n_cpts = solver.guess_n_control_points()
    solver.set_curve_params(n_cpts, knotvector)
    problem_type, residue, curve = solver.solve_ex(problem_types = {SvNurbsCurveSolver.PROBLEM_WELLDETERMINED},
                                    implementation = implementation,
                                    logger = logger)
    return curve
def knotvector_with_tangents_from_tknots(degree, u)
Expand source code
def knotvector_with_tangents_from_tknots(degree, u):
    n = len(u)
    if degree == 2:
        kv = [u[0], u[0], u[0]]
        for i in range(1, n-1):
            kv.append((u[i-1] + u[i]) / 2.0)
            kv.append(u[i])
        kv.extend([u[-1], u[-1]])
        return np.array(kv)
    elif degree == 3:
        if len(u) == 2:
            return np.array([u[0], u[0], u[0], u[0],
                             u[1], u[1], u[1], u[1]])
        kv = [u[0], u[0], u[0],u[0]]
        kv.append(u[1]/2.0)
        for i in range(1, n-2):
            u1 = (2*u[i] + u[i+1]) / 3.0
            kv.append(u1)
            u2 = (u[i] + 2*u[i+1]) / 3.0
            kv.append(u2)
        kv.append((u[-2] + u[-1])/2.0)
        kv.extend([u[-1], u[-1], u[-1], u[-1]])
        return np.array(kv)
    else:
        raise Exception(f"Degrees other than 2 and 3 are not supported yet")
def prepare_solver_for_interpolation(degree, points, metric='DISTANCE', tknots=None, cyclic=False)
Expand source code
def prepare_solver_for_interpolation(degree, points, metric='DISTANCE', tknots=None, cyclic=False):
    n_points = len(points)
    points = np.asarray(points)
    if points.ndim != 2:
        raise Exception(f"Array of points was expected, but got {points.shape}: {points}")
    ndim = points.shape[1] # 3 or 4
    if ndim not in {3,4}:
        raise Exception(f"Only 3D and 4D points are supported, but ndim={ndim}")
    if cyclic:
        points = np.concatenate((points, points[0][np.newaxis]))
    if tknots is None:
        tknots = Spline.create_knots(points, metric=metric)
    solver = SvNurbsCurveSolver(degree=degree, ndim=ndim)
    solver.add_goal(SvNurbsCurvePoints(tknots, points, relative=False))
    if cyclic:
        k = 1.0/float(degree)
        tangent = k*(points[1] - points[-2])
        solver.add_goal(SvNurbsCurveTangents.single(0.0, tangent))
        solver.add_goal(SvNurbsCurveTangents.single(1.0, tangent))
        knotvector = sv_knotvector.from_tknots(degree, tknots, include_endpoints=True)
    else:
        knotvector = sv_knotvector.from_tknots(degree, tknots)

    n_cpts = solver.guess_n_control_points()
    solver.set_curve_params(n_cpts, knotvector)
    return solver