Пример #1
0
 def __init__(self,
              degree_u,
              degree_v,
              knotvector_u,
              knotvector_v,
              control_points,
              weights,
              normalize_knots=False):
     self.degree_u = degree_u
     self.degree_v = degree_v
     self.knotvector_u = np.array(knotvector_u)
     self.knotvector_v = np.array(knotvector_v)
     if normalize_knots:
         self.knotvector_u = sv_knotvector.normalize(self.knotvector_u)
         self.knotvector_v = sv_knotvector.normalize(self.knotvector_v)
     self.control_points = np.array(control_points)
     c_ku, c_kv, _ = self.control_points.shape
     if weights is None:
         self.weights = weights = np.ones((c_ku, c_kv))
     else:
         self.weights = np.array(weights)
         w_ku, w_kv = self.weights.shape
         if c_ku != w_ku or c_kv != w_kv:
             raise Exception(
                 f"Shape of control_points ({c_ku}, {c_kv}) does not match to shape of weights ({w_ku}, {w_kv})"
             )
     self.basis_u = SvNurbsBasisFunctions(knotvector_u)
     self.basis_v = SvNurbsBasisFunctions(knotvector_v)
     self.u_bounds = (self.knotvector_u.min(), self.knotvector_u.max())
     self.v_bounds = (self.knotvector_v.min(), self.knotvector_v.max())
     self.normal_delta = 0.0001
     self.__description__ = f"Native NURBS (degree={degree_u}x{degree_v}, pts={self.control_points.shape[0]}x{self.control_points.shape[1]})"
Пример #2
0
def interpolate_nurbs_curve(cls,
                            degree,
                            points,
                            metric='DISTANCE',
                            tknots=None):
    n = len(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}")
    #points3d = points[:,:3]
    #print("pts:", points)
    if tknots is None:
        tknots = Spline.create_knots(
            points, metric=metric)  # In 3D or in 4D, in general?
    knotvector = sv_knotvector.from_tknots(degree, tknots)
    functions = SvNurbsBasisFunctions(knotvector)
    coeffs_by_row = [
        functions.function(idx, degree)(tknots) for idx in range(n)
    ]
    A = np.zeros((ndim * n, ndim * n))
    for equation_idx, t in enumerate(tknots):
        for unknown_idx in range(n):
            coeff = coeffs_by_row[unknown_idx][equation_idx]
            row = ndim * equation_idx
            col = ndim * unknown_idx
            for d in range(ndim):
                A[row + d, col + d] = coeff
    B = np.zeros((ndim * n, 1))
    for point_idx, point in enumerate(points):
        row = ndim * point_idx
        B[row:row + ndim] = point[:, np.newaxis]

    x = np.linalg.solve(A, B)

    control_points = []
    for i in range(n):
        row = i * ndim
        control = x[row:row + ndim, 0].T
        control_points.append(control)
    control_points = np.array(control_points)
    if ndim == 3:
        weights = np.ones((n, ))
    else:  # 4
        control_points, weights = from_homogenous(control_points)

    if type(cls) == type:
        return cls.build(cls.get_nurbs_implementation(), degree, knotvector,
                         control_points, weights)
    elif isinstance(cls, str):
        return SvNurbsMaths.build_curve(cls, degree, knotvector,
                                        control_points, weights)
    else:
        raise TypeError(f"Unsupported type of `cls` parameter: {type(cls)}")
Пример #3
0
 def __init__(self, degree, knotvector, control_points, weights=None):
     self.control_points = np.array(control_points) # (k, 3)
     k = len(control_points)
     if weights is not None:
         self.weights = np.array(weights) # (k, )
     else:
         self.weights = np.ones((k,))
     self.knotvector = np.array(knotvector)
     self.degree = degree
     self.basis = SvNurbsBasisFunctions(knotvector)
     self.tangent_delta = 0.001
     self.__description__ = f"Native NURBS (degree={degree}, pts={k})"
Пример #4
0
 def __init__(self, degree_u, degree_v, knotvector_u, knotvector_v,
              control_points, weights):
     self.degree_u = degree_u
     self.degree_v = degree_v
     self.knotvector_u = np.array(knotvector_u)
     self.knotvector_v = np.array(knotvector_v)
     self.control_points = np.array(control_points)
     self.weights = np.array(weights)
     c_ku, c_kv, _ = self.control_points.shape
     w_ku, w_kv = self.weights.shape
     if c_ku != w_ku or c_kv != w_kv:
         raise Exception(
             f"Shape of control_points ({c_ku}, {c_kv}) does not match to shape of weights ({w_ku}, {w_kv})"
         )
     self.basis_u = SvNurbsBasisFunctions(knotvector_u)
     self.basis_v = SvNurbsBasisFunctions(knotvector_v)
     self.u_bounds = (self.knotvector_u.min(), self.knotvector_u.max())
     self.v_bounds = (self.knotvector_v.min(), self.knotvector_v.max())
     self.normal_delta = 0.0001
Пример #5
0
    def interpolate_list(cls, degree, points, metric='DISTANCE'):
        n_curves, n_points, _ = points.shape
        tknots = [
            Spline.create_knots(points[i], metric=metric)
            for i in range(n_curves)
        ]
        knotvectors = [
            sv_knotvector.from_tknots(degree, tknots[i])
            for i in range(n_curves)
        ]
        functions = [
            SvNurbsBasisFunctions(knotvectors[i]) for i in range(n_curves)
        ]
        coeffs_by_row = [[
            functions[curve_idx].function(idx, degree)(tknots[curve_idx])
            for idx in range(n_points)
        ] for curve_idx in range(n_curves)]
        coeffs_by_row = np.array(coeffs_by_row)
        A = np.zeros((n_curves, 3 * n_points, 3 * n_points))
        for curve_idx in range(n_curves):
            for equation_idx, t in enumerate(tknots[curve_idx]):
                for unknown_idx in range(n_points):
                    coeff = coeffs_by_row[curve_idx][unknown_idx][equation_idx]
                    row = 3 * equation_idx
                    col = 3 * unknown_idx
                    A[curve_idx, row, col] = A[curve_idx, row + 1,
                                               col + 1] = A[curve_idx, row + 2,
                                                            col + 2] = coeff

        B = np.zeros((n_curves, 3 * n_points, 1))
        for curve_idx in range(n_curves):
            for point_idx, point in enumerate(points[curve_idx]):
                row = 3 * point_idx
                B[curve_idx, row:row + 3] = point[:, np.newaxis]

        x = np.linalg.solve(A, B)

        curves = []
        weights = np.ones((n_points, ))
        for curve_idx in range(n_curves):
            control_points = []
            for i in range(n_points):
                row = i * 3
                control = x[curve_idx][row:row + 3, 0].T
                control_points.append(control)
            control_points = np.array(control_points)

            curve = SvNurbsCurve.build(cls.get_nurbs_implementation(), degree,
                                       knotvectors[curve_idx], control_points,
                                       weights)
            curves.append(curve)

        return curves
Пример #6
0
class SvNativeNurbsCurve(SvNurbsCurve):
    def __init__(self,
                 degree,
                 knotvector,
                 control_points,
                 weights=None,
                 normalize_knots=False):
        self.control_points = np.array(control_points)  # (k, 3)
        k = len(control_points)
        if weights is not None:
            self.weights = np.array(weights)  # (k, )
        else:
            self.weights = np.ones((k, ))
        self.knotvector = np.array(knotvector)
        if normalize_knots:
            self.knotvector = sv_knotvector.normalize(self.knotvector)
        self.degree = degree
        self.basis = SvNurbsBasisFunctions(knotvector)
        self.tangent_delta = 0.001
        self.u_bounds = None  # take from knotvector
        self.__description__ = f"Native NURBS (degree={degree}, pts={k})"

    @classmethod
    def build(cls,
              implementation,
              degree,
              knotvector,
              control_points,
              weights=None,
              normalize_knots=False):
        return SvNativeNurbsCurve(degree, knotvector, control_points, weights,
                                  normalize_knots)

    def is_rational(self):
        w, W = self.weights.min(), self.weights.max()
        return w < W

    def get_control_points(self):
        return self.control_points

    def get_weights(self):
        return self.weights

    def get_knotvector(self):
        return self.knotvector

    def get_degree(self):
        return self.degree

    def evaluate(self, t):
        #return self.evaluate_array(np.array([t]))[0]
        numerator, denominator = self.fraction_single(0, t)
        if denominator == 0:
            return np.array([0, 0, 0])
        else:
            return numerator / denominator

    def fraction(self, deriv_order, ts):
        n = len(ts)
        p = self.degree
        k = len(self.control_points)
        ns = np.array([
            self.basis.derivative(i, p, deriv_order)(ts) for i in range(k)
        ])  # (k, n)
        coeffs = ns * self.weights[np.newaxis].T  # (k, n)
        coeffs_t = coeffs[np.newaxis].T  # (n, k, 1)
        numerator = (coeffs_t * self.control_points)  # (n, k, 3)
        numerator = numerator.sum(axis=1)  # (n, 3)
        denominator = coeffs.sum(axis=0)  # (n,)

        return numerator, denominator[np.newaxis].T

    def fraction_single(self, deriv_order, t):
        p = self.degree
        k = len(self.control_points)
        ts = np.array([t])
        ns = np.array([
            self.basis.derivative(i, p, deriv_order)(ts)[0] for i in range(k)
        ])  # (k,)
        coeffs = ns * self.weights  # (k, )
        coeffs_t = coeffs[np.newaxis].T
        numerator = (coeffs_t * self.control_points)  # (k, 3)
        numerator = numerator.sum(axis=0)  # (3,)
        denominator = coeffs.sum(axis=0)  # ()

        return numerator, denominator

    def evaluate_array(self, ts):
        numerator, denominator = self.fraction(0, ts)
        #         if (denominator == 0).any():
        #             print("Num:", numerator)
        #             print("Denom:", denominator)
        return nurbs_divide(numerator, denominator)

    def tangent(self, t):
        return self.tangent_array(np.array([t]))[0]

    def tangent_array(self, ts):
        # curve = numerator / denominator
        # ergo:
        # numerator = curve * denominator
        # ergo:
        # numerator' = curve' * denominator + curve * denominator'
        # ergo:
        # curve' = (numerator' - curve*denominator') / denominator
        numerator, denominator = self.fraction(0, ts)
        curve = numerator / denominator
        numerator1, denominator1 = self.fraction(1, ts)
        curve1 = (numerator1 - curve * denominator1) / denominator
        return curve1

    def second_derivative(self, t):
        return self.second_derivative_array(np.array([t]))[0]

    def second_derivative_array(self, ts):
        # numerator'' = (curve * denominator)'' =
        #  = curve'' * denominator + 2 * curve' * denominator' + curve * denominator''
        numerator, denominator = self.fraction(0, ts)
        curve = numerator / denominator
        numerator1, denominator1 = self.fraction(1, ts)
        curve1 = (numerator1 - curve * denominator1) / denominator
        numerator2, denominator2 = self.fraction(2, ts)
        curve2 = (numerator2 - 2 * curve1 * denominator1 -
                  curve * denominator2) / denominator
        return curve2

    def third_derivative_array(self, ts):
        # numerator''' = (curve * denominator)''' =
        #  = curve''' * denominator + 3 * curve'' * denominator' + 3 * curve' * denominator'' + denominator'''
        numerator, denominator = self.fraction(0, ts)
        curve = numerator / denominator
        numerator1, denominator1 = self.fraction(1, ts)
        curve1 = (numerator1 - curve * denominator1) / denominator
        numerator2, denominator2 = self.fraction(2, ts)
        curve2 = (numerator2 - 2 * curve1 * denominator1 -
                  curve * denominator2) / denominator
        numerator3, denominator3 = self.fraction(3, ts)

        curve3 = (numerator3 - 3 * curve2 * denominator1 - 3 * curve1 *
                  denominator2 - curve * denominator3) / denominator
        return curve3

    def derivatives_array(self, n, ts):
        result = []
        if n >= 1:
            numerator, denominator = self.fraction(0, ts)
            curve = numerator / denominator
            numerator1, denominator1 = self.fraction(1, ts)
            curve1 = (numerator1 - curve * denominator1) / denominator
            result.append(curve1)
        if n >= 2:
            numerator2, denominator2 = self.fraction(2, ts)
            curve2 = (numerator2 - 2 * curve1 * denominator1 -
                      curve * denominator2) / denominator
            result.append(curve2)
        if n >= 3:
            numerator3, denominator3 = self.fraction(3, ts)
            curve3 = (numerator3 - 3 * curve2 * denominator1 - 3 * curve1 *
                      denominator2 - curve * denominator3) / denominator
            result.append(curve3)
        return result

    def get_u_bounds(self):
        if self.u_bounds is None:
            m = self.knotvector.min()
            M = self.knotvector.max()
            return (m, M)
        else:
            return self.u_bounds

    def extrude_along_vector(self, vector):
        vector = np.array(vector)
        other_control_points = self.control_points + vector
        control_points = np.stack((self.control_points, other_control_points))
        control_points = np.transpose(control_points, axes=(1, 0, 2))
        weights = np.stack((self.weights, self.weights)).T
        knotvector_v = sv_knotvector.generate(1, 2, clamped=True)
        surface = SvNativeNurbsSurface(degree_u=self.degree,
                                       degree_v=1,
                                       knotvector_u=self.knotvector,
                                       knotvector_v=knotvector_v,
                                       control_points=control_points,
                                       weights=weights)
        return surface

    @classmethod
    def get_nurbs_implementation(cls):
        return SvNurbsCurve.NATIVE

    def insert_knot(self, u_bar, count=1):
        # "The NURBS book", 2nd edition, p.5.2, eq. 5.11
        s = sv_knotvector.find_multiplicity(self.knotvector, u_bar)
        #print(f"I: kv {len(self.knotvector)}{self.knotvector}, u_bar {u_bar} => s {s}")
        k = np.searchsorted(self.knotvector, u_bar, side='right') - 1
        p = self.degree
        u = self.knotvector
        new_knotvector = sv_knotvector.insert(self.knotvector, u_bar, count)
        N = len(self.control_points)
        control_points = self.get_homogenous_control_points()

        for r in range(1, count + 1):
            prev_control_points = control_points
            control_points = []
            for i in range(N + 1):
                #print(f"I: i {i}, k {k}, p {p}, r {r}, s {s}, k-p+r-1 {k-p+r-1}, k-s {k-s}")
                if i <= k - p + r - 1:
                    point = prev_control_points[i]
                    #print(f"P[{r},{i}] := {i}{prev_control_points[i]}")
                elif k - p + r <= i <= k - s:
                    denominator = u[i + p - r + 1] - u[i]
                    alpha = (u_bar - u[i]) / denominator
                    point = alpha * prev_control_points[i] + (
                        1.0 - alpha) * prev_control_points[i - 1]
                    #print(f"P[{r},{i}]: alpha {alpha}, pts {i}{prev_control_points[i]}, {i-1}{prev_control_points[i-1]} => {point}")
                else:
                    point = prev_control_points[i - 1]
                    #print(f"P[{r},{i}] := {i-1}{prev_control_points[i-1]}")
                control_points.append(point)
            N += 1

        control_points, weights = from_homogenous(np.array(control_points))
        curve = SvNativeNurbsCurve(self.degree, new_knotvector, control_points,
                                   weights)
        return curve
Пример #7
0
class SvNativeNurbsCurve(SvNurbsCurve):
    def __init__(self, degree, knotvector, control_points, weights=None):
        self.control_points = np.array(control_points) # (k, 3)
        k = len(control_points)
        if weights is not None:
            self.weights = np.array(weights) # (k, )
        else:
            self.weights = np.ones((k,))
        self.knotvector = np.array(knotvector)
        self.degree = degree
        self.basis = SvNurbsBasisFunctions(knotvector)
        self.tangent_delta = 0.001
        self.__description__ = f"Native NURBS (degree={degree}, pts={k})"

    def get_control_points(self):
        return self.control_points

    def get_weights(self):
        return self.weights

    def get_knotvector(self):
        return self.knotvector

    def get_degree(self):
        return self.degree

    def evaluate(self, t):
        return self.evaluate_array(np.array([t]))[0]

    def fraction(self, deriv_order, ts):
        n = len(ts)
        p = self.degree
        k = len(self.control_points)
        ns = np.array([self.basis.derivative(i, p, deriv_order)(ts) for i in range(k)]) # (k, n)
        coeffs = ns * self.weights[np.newaxis].T # (k, n)
        coeffs_t = coeffs[np.newaxis].T # (n, k, 1)
        numerator = (coeffs_t * self.control_points) # (n, k, 3)
        numerator = numerator.sum(axis=1) # (n, 3)
        denominator = coeffs.sum(axis=0) # (n,)

        return numerator, denominator[np.newaxis].T

    def evaluate_array(self, ts):
        numerator, denominator = self.fraction(0, ts)
#         if (denominator == 0).any():
#             print("Num:", numerator)
#             print("Denom:", denominator)
        return nurbs_divide(numerator, denominator)

    def tangent(self, t):
        return self.tangent_array(np.array([t]))[0]

    def tangent_array(self, ts):
        # curve = numerator / denominator
        # ergo:
        # numerator = curve * denominator
        # ergo:
        # numerator' = curve' * denominator + curve * denominator'
        # ergo:
        # curve' = (numerator' - curve*denominator') / denominator
        numerator, denominator = self.fraction(0, ts)
        curve = numerator / denominator
        numerator1, denominator1 = self.fraction(1, ts)
        curve1 = (numerator1 - curve*denominator1) / denominator
        return curve1

    def second_derivative(self, t):
        return self.second_derivative_array(np.array([t]))[0]

    def second_derivative_array(self, ts):
        # numerator'' = (curve * denominator)'' =
        #  = curve'' * denominator + 2 * curve' * denominator' + curve * denominator''
        numerator, denominator = self.fraction(0, ts)
        curve = numerator / denominator
        numerator1, denominator1 = self.fraction(1, ts)
        curve1 = (numerator1 - curve*denominator1) / denominator
        numerator2, denominator2 = self.fraction(2, ts)
        curve2 = (numerator2 - 2*curve1*denominator1 - curve*denominator2) / denominator
        return curve2

    def third_derivative_array(self, ts):
        # numerator''' = (curve * denominator)''' = 
        #  = curve''' * denominator + 3 * curve'' * denominator' + 3 * curve' * denominator'' + denominator'''
        numerator, denominator = self.fraction(0, ts)
        curve = numerator / denominator
        numerator1, denominator1 = self.fraction(1, ts)
        curve1 = (numerator1 - curve*denominator1) / denominator
        numerator2, denominator2 = self.fraction(2, ts)
        curve2 = (numerator2 - 2*curve1*denominator1 - curve*denominator2) / denominator
        numerator3, denominator3 = self.fraction(3, ts)

        curve3 = (numerator3 - 3*curve2*denominator1 - 3*curve1*denominator2 - curve*denominator3) / denominator
        return curve3

    def derivatives_array(self, n, ts):
        result = []
        if n >= 1:
            numerator, denominator = self.fraction(0, ts)
            curve = numerator / denominator
            numerator1, denominator1 = self.fraction(1, ts)
            curve1 = (numerator1 - curve*denominator1) / denominator
            result.append(curve1)
        if n >= 2:
            numerator2, denominator2 = self.fraction(2, ts)
            curve2 = (numerator2 - 2*curve1*denominator1 - curve*denominator2) / denominator
            result.append(curve2)
        if n >= 3:
            numerator3, denominator3 = self.fraction(3, ts)
            curve3 = (numerator3 - 3*curve2*denominator1 - 3*curve1*denominator2 - curve*denominator3) / denominator
            result.append(curve3)
        return result

    def get_u_bounds(self):
        m = self.knotvector.min()
        M = self.knotvector.max()
        return (m, M)

#     def concatenate(self, curve2):
#         curve1 = self
#         curve2 = SvNurbsCurve.to_nurbs(curve2)
#         if curve2 is None:
#             raise UnsupportedCurveTypeException("second curve is not NURBS")
# 
#         w1 = curve1.get_weights()[-1]
#         w2 = curve1.get_weights()[0]
#         if w1 != w2:
#             raise UnsupportedCurveTypeException("Weights at endpoints do not match")
# 
#         p1, p2 = curve1.get_degree(), curve2.get_degree()
#         if p1 > p2:
#             curve2 = curve2.elevate_degree(delta = p1-p2)
#         elif p2 > p1:
#             curve1 = curve1.elevate_degree(delta = p2-p1)
# 
#         #cp1 = curve1.get_control_points()[-1]
#         #cp2 = curve2.get_control_points()[0]
# 
#         knotvector = sv_knotvector.concatenate(curve1.get_knotvector(), curve2.get_knotvector())
#         #print("KV:", knotvector)
#         weights = np.concatenate((curve1.get_weights(), curve2.get_weights()[1:]))
#         control_points = np.concatenate((curve1.get_control_points(), curve2.get_control_points()[1:]))
#         return SvNativeNurbsCurve(curve1.degree, knotvector, control_points, weights)

    def extrude_along_vector(self, vector):
        vector = np.array(vector)
        other_control_points = self.control_points + vector
        control_points = np.stack((self.control_points, other_control_points))
        control_points = np.transpose(control_points, axes=(1,0,2))
        weights = np.stack((self.weights, self.weights)).T
        knotvector_v = sv_knotvector.generate(1, 2, clamped=True)
        surface = SvNativeNurbsSurface(degree_u = self.degree, degree_v = 1,
                        knotvector_u = self.knotvector, knotvector_v = knotvector_v,
                        control_points = control_points,
                        weights = weights)
        return surface

    def make_ruled_surface(self, curve2, vmin, vmax):
        curve = self
        curve2 = SvNurbsCurve.to_nurbs(curve2)
        if curve2 is None:
            raise UnsupportedCurveTypeException("second curve is not NURBS")
        if curve.get_degree() != curve2.get_degree():
            raise UnsupportedCurveTypeException("curves have different degrees")

        #print(f"kv1: {curve.get_knotvector().shape}, kv2: {curve2.get_knotvector().shape}")
        kv1, kv2 = curve.get_knotvector(), curve2.get_knotvector()
        if kv1.shape != kv2.shape or (kv1 != kv2).any():
            curve, curve2 = unify_curves(curve, curve2)
            #raise UnsupportedCurveTypeException("curves have different knot vectors")

        my_control_points = curve.control_points
        other_control_points = curve2.get_control_points()
        if len(my_control_points) != len(other_control_points):
            raise UnsupportedCurveTypeException("curves have different number of control points")

        if vmin != 0:
            my_control_points = (1 - vmin) * my_control_points + vmin * other_control_points
        if vmax != 0:
            other_control_points = (1 - vmax) * my_control_points + vmax * other_control_points

        control_points = np.stack((my_control_points, other_control_points))
        control_points = np.transpose(control_points, axes=(1,0,2))

        weights = np.stack((curve.weights, curve2.get_weights())).T
        knotvector_v = sv_knotvector.generate(1, 2, clamped=True)

        surface = SvNativeNurbsSurface(degree_u = curve.degree, degree_v = 1,
                        knotvector_u = curve.knotvector, knotvector_v = knotvector_v,
                        control_points = control_points,
                        weights = weights)
        return surface

    def get_nurbs_implementation(self):
        return SvNurbsCurve.NATIVE

    def insert_knot(self, u_bar, count=1):
        # "The NURBS book", 2nd edition, p.5.2, eq. 5.11
        s = sv_knotvector.find_multiplicity(self.knotvector, u_bar)
        #print(f"I: kv {self.knotvector}, u_bar {u_bar} => s {s}")
        k = np.searchsorted(self.knotvector, u_bar, side='right')-1
        p = self.degree
        u = self.knotvector
        new_knotvector = sv_knotvector.insert(self.knotvector, u_bar, count)
        N = len(self.control_points)
        control_points = self.get_homogenous_control_points()

        for r in range(1, count+1):
            prev_control_points = control_points
            control_points = []
            for i in range(N+1):
                #print(f"I: i {i}, k {k}, p {p}, r {r}, s {s}, k-p+r-1 {k-p+r-1}, k-s {k-s}")
                if i <= k-p+r-1:
                    point = prev_control_points[i]
                    #print(f"P[{r},{i}] := {i}{prev_control_points[i]}")
                elif k - p + r <= i <= k - s:
                    denominator = u[i+p-r+1] - u[i]
                    alpha = (u_bar - u[i]) / denominator
                    point = alpha * prev_control_points[i] + (1.0 - alpha) * prev_control_points[i-1]
                    #print(f"P[{r},{i}]: alpha {alpha}, pts {i}{prev_control_points[i]}, {i-1}{prev_control_points[i-1]} => {point}")
                else:
                    point = prev_control_points[i-1]
                    #print(f"P[{r},{i}] := {i-1}{prev_control_points[i-1]}")
                control_points.append(point)
            N += 1

        control_points, weights = from_homogenous(np.array(control_points))
        curve = SvNativeNurbsCurve(self.degree, new_knotvector,
                    control_points, weights)
        return curve

    def split_at(self, t):
        current_multiplicity = sv_knotvector.find_multiplicity(self.knotvector, t)
        to_add = self.degree - current_multiplicity # + 1
        curve = self.insert_knot(t, count=to_add)
        knot_span = np.searchsorted(curve.knotvector, t)

        ts = np.full((self.degree+1,), t)
        knotvector1 = np.concatenate((curve.knotvector[:knot_span], ts))
        knotvector2 = np.insert(curve.knotvector[knot_span:], 0, t)

        control_points_1 = curve.control_points[:knot_span]
        control_points_2 = curve.control_points[knot_span-1:]
        weights_1 = curve.weights[:knot_span]
        weights_2 = curve.weights[knot_span-1:]

        kv_error = sv_knotvector.check(curve.degree, knotvector1, len(control_points_1))
        if kv_error is not None:
            raise Exception(kv_error)
        kv_error = sv_knotvector.check(curve.degree, knotvector2, len(control_points_2))
        if kv_error is not None:
            raise Exception(kv_error)

        curve1 = SvNativeNurbsCurve(curve.degree, knotvector1,
                    control_points_1, weights_1)
        curve2 = SvNativeNurbsCurve(curve.degree, knotvector2,
                    control_points_2, weights_2)
        return curve1, curve2
Пример #8
0
class SvNativeNurbsSurface(SvNurbsSurface):
    def __init__(self, degree_u, degree_v, knotvector_u, knotvector_v,
                 control_points, weights):
        self.degree_u = degree_u
        self.degree_v = degree_v
        self.knotvector_u = np.array(knotvector_u)
        self.knotvector_v = np.array(knotvector_v)
        self.control_points = np.array(control_points)
        self.weights = np.array(weights)
        c_ku, c_kv, _ = self.control_points.shape
        w_ku, w_kv = self.weights.shape
        if c_ku != w_ku or c_kv != w_kv:
            raise Exception(
                f"Shape of control_points ({c_ku}, {c_kv}) does not match to shape of weights ({w_ku}, {w_kv})"
            )
        self.basis_u = SvNurbsBasisFunctions(knotvector_u)
        self.basis_v = SvNurbsBasisFunctions(knotvector_v)
        self.u_bounds = (self.knotvector_u.min(), self.knotvector_u.max())
        self.v_bounds = (self.knotvector_v.min(), self.knotvector_v.max())
        self.normal_delta = 0.0001

    def get_degree_u(self):
        return self.degree_u

    def get_degree_v(self):
        return self.degree_v

    def get_knotvector_u(self):
        return self.knotvector_u

    def get_knotvector_v(self):
        return self.knotvector_v

    def get_control_points(self):
        return self.control_points

    def get_weights(self):
        return self.weights

    def get_u_min(self):
        return self.u_bounds[0]

    def get_u_max(self):
        return self.u_bounds[1]

    def get_v_min(self):
        return self.v_bounds[0]

    def get_v_max(self):
        return self.v_bounds[1]

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def fraction(self, deriv_order_u, deriv_order_v, us, vs):
        pu = self.degree_u
        pv = self.degree_v
        ku, kv, _ = self.control_points.shape
        nsu = np.array([
            self.basis_u.derivative(i, pu, deriv_order_u)(us)
            for i in range(ku)
        ])  # (ku, n)
        nsv = np.array([
            self.basis_v.derivative(i, pv, deriv_order_v)(vs)
            for i in range(kv)
        ])  # (kv, n)
        nsu = np.transpose(nsu[np.newaxis], axes=(1, 0, 2))  # (ku, 1, n)
        nsv = nsv[np.newaxis]  # (1, kv, n)
        ns = nsu * nsv  # (ku, kv, n)
        weights = np.transpose(self.weights[np.newaxis],
                               axes=(1, 2, 0))  # (ku, kv, 1)
        coeffs = ns * weights  # (ku, kv, n)
        coeffs = np.transpose(coeffs[np.newaxis],
                              axes=(3, 1, 2, 0))  # (n,ku,kv,1)
        controls = self.control_points  # (ku,kv,3)

        numerator = coeffs * controls  # (n,ku,kv,3)
        numerator = numerator.sum(axis=1).sum(axis=1)  # (n,3)
        denominator = coeffs.sum(axis=1).sum(axis=1)

        return numerator, denominator

    def evaluate_array(self, us, vs):
        numerator, denominator = self.fraction(0, 0, us, vs)
        return numerator / denominator

    def normal(self, u, v):
        return self.normal_array(np.array([u]), np.array([v]))[0]

    def normal_array(self, us, vs):
        numerator, denominator = self.fraction(0, 0, us, vs)
        surface = numerator / denominator
        numerator_u, denominator_u = self.fraction(1, 0, us, vs)
        numerator_v, denominator_v = self.fraction(0, 1, us, vs)
        surface_u = (numerator_u - surface * denominator_u) / denominator
        surface_v = (numerator_v - surface * denominator_v) / denominator
        normal = np.cross(surface_u, surface_v)
        n = np.linalg.norm(normal, axis=1, keepdims=True)
        normal = normal / n
        return normal

    def derivatives_data_array(self, us, vs):
        numerator, denominator = self.fraction(0, 0, us, vs)
        surface = numerator / denominator
        numerator_u, denominator_u = self.fraction(1, 0, us, vs)
        numerator_v, denominator_v = self.fraction(0, 1, us, vs)
        surface_u = (numerator_u - surface * denominator_u) / denominator
        surface_v = (numerator_v - surface * denominator_v) / denominator
        return SurfaceDerivativesData(surface, surface_u, surface_v)

    def curvature_calculator(self, us, vs, order=True):

        numerator, denominator = self.fraction(0, 0, us, vs)
        surface = numerator / denominator
        numerator_u, denominator_u = self.fraction(1, 0, us, vs)
        numerator_v, denominator_v = self.fraction(0, 1, us, vs)
        surface_u = (numerator_u - surface * denominator_u) / denominator
        surface_v = (numerator_v - surface * denominator_v) / denominator

        normal = np.cross(surface_u, surface_v)
        n = np.linalg.norm(normal, axis=1, keepdims=True)
        normal = normal / n

        numerator_uu, denominator_uu = self.fraction(2, 0, us, vs)
        surface_uu = (numerator_uu - 2 * surface_u * denominator_u -
                      surface * denominator_uu) / denominator
        numerator_vv, denominator_vv = self.fraction(0, 2, us, vs)
        surface_vv = (numerator_vv - 2 * surface_v * denominator_v -
                      surface * denominator_vv) / denominator

        numerator_uv, denominator_uv = self.fraction(1, 1, us, vs)
        surface_uv = (numerator_uv - surface_v * denominator_u - surface_u *
                      denominator_v - surface * denominator_uv) / denominator

        nuu = (surface_uu * normal).sum(axis=1)
        nvv = (surface_vv * normal).sum(axis=1)
        nuv = (surface_uv * normal).sum(axis=1)

        duu = np.linalg.norm(surface_u, axis=1)**2
        dvv = np.linalg.norm(surface_v, axis=1)**2
        duv = (surface_u * surface_v).sum(axis=1)

        calc = SurfaceCurvatureCalculator(us, vs, order=order)
        calc.set(surface, normal, surface_u, surface_v, duu, dvv, duv, nuu,
                 nvv, nuv)
        return calc
Пример #9
0
class SvNativeNurbsCurve(SvNurbsCurve):
    def __init__(self,
                 degree,
                 knotvector,
                 control_points,
                 weights=None,
                 normalize_knots=False):
        self.control_points = np.array(control_points)  # (k, 3)
        k = len(control_points)
        if weights is not None:
            self.weights = np.array(weights)  # (k, )
        else:
            self.weights = np.ones((k, ))
        self.knotvector = np.array(knotvector)
        if normalize_knots:
            self.knotvector = sv_knotvector.normalize(self.knotvector)
        self.degree = degree
        self.basis = SvNurbsBasisFunctions(knotvector)
        self.tangent_delta = 0.001
        self.u_bounds = None  # take from knotvector
        self.__description__ = f"Native NURBS (degree={degree}, pts={k})"

    @classmethod
    def build(cls,
              implementation,
              degree,
              knotvector,
              control_points,
              weights=None,
              normalize_knots=False):
        return SvNativeNurbsCurve(degree, knotvector, control_points, weights,
                                  normalize_knots)

    def is_rational(self, tolerance=1e-6):
        w, W = self.weights.min(), self.weights.max()
        return (W - w) > tolerance

    def get_control_points(self):
        return self.control_points

    def get_weights(self):
        return self.weights

    def get_knotvector(self):
        return self.knotvector

    def get_degree(self):
        return self.degree

    def evaluate(self, t):
        #return self.evaluate_array(np.array([t]))[0]
        numerator, denominator = self.fraction_single(0, t)
        if denominator == 0:
            return np.array([0, 0, 0])
        else:
            return numerator / denominator

    def fraction(self, deriv_order, ts):
        n = len(ts)
        p = self.degree
        k = len(self.control_points)
        ns = np.array([
            self.basis.derivative(i, p, deriv_order)(ts) for i in range(k)
        ])  # (k, n)
        coeffs = ns * self.weights[np.newaxis].T  # (k, n)
        coeffs_t = coeffs[np.newaxis].T  # (n, k, 1)
        numerator = (coeffs_t * self.control_points)  # (n, k, 3)
        numerator = numerator.sum(axis=1)  # (n, 3)
        denominator = coeffs.sum(axis=0)  # (n,)

        return numerator, denominator[np.newaxis].T

    def fraction_single(self, deriv_order, t):
        p = self.degree
        k = len(self.control_points)
        ts = np.array([t])
        ns = np.array([
            self.basis.derivative(i, p, deriv_order)(ts)[0] for i in range(k)
        ])  # (k,)
        coeffs = ns * self.weights  # (k, )
        coeffs_t = coeffs[np.newaxis].T
        numerator = (coeffs_t * self.control_points)  # (k, 3)
        numerator = numerator.sum(axis=0)  # (3,)
        denominator = coeffs.sum(axis=0)  # ()

        return numerator, denominator

    def evaluate_array(self, ts):
        numerator, denominator = self.fraction(0, ts)
        #         if (denominator == 0).any():
        #             print("Num:", numerator)
        #             print("Denom:", denominator)
        return nurbs_divide(numerator, denominator)

    def tangent(self, t):
        return self.tangent_array(np.array([t]))[0]

    def tangent_array(self, ts):
        # curve = numerator / denominator
        # ergo:
        # numerator = curve * denominator
        # ergo:
        # numerator' = curve' * denominator + curve * denominator'
        # ergo:
        # curve' = (numerator' - curve*denominator') / denominator
        numerator, denominator = self.fraction(0, ts)
        curve = numerator / denominator
        numerator1, denominator1 = self.fraction(1, ts)
        curve1 = (numerator1 - curve * denominator1) / denominator
        return curve1

    def second_derivative(self, t):
        return self.second_derivative_array(np.array([t]))[0]

    def second_derivative_array(self, ts):
        # numerator'' = (curve * denominator)'' =
        #  = curve'' * denominator + 2 * curve' * denominator' + curve * denominator''
        numerator, denominator = self.fraction(0, ts)
        curve = numerator / denominator
        numerator1, denominator1 = self.fraction(1, ts)
        curve1 = (numerator1 - curve * denominator1) / denominator
        numerator2, denominator2 = self.fraction(2, ts)
        curve2 = (numerator2 - 2 * curve1 * denominator1 -
                  curve * denominator2) / denominator
        return curve2

    def third_derivative_array(self, ts):
        # numerator''' = (curve * denominator)''' =
        #  = curve''' * denominator + 3 * curve'' * denominator' + 3 * curve' * denominator'' + denominator'''
        numerator, denominator = self.fraction(0, ts)
        curve = numerator / denominator
        numerator1, denominator1 = self.fraction(1, ts)
        curve1 = (numerator1 - curve * denominator1) / denominator
        numerator2, denominator2 = self.fraction(2, ts)
        curve2 = (numerator2 - 2 * curve1 * denominator1 -
                  curve * denominator2) / denominator
        numerator3, denominator3 = self.fraction(3, ts)

        curve3 = (numerator3 - 3 * curve2 * denominator1 - 3 * curve1 *
                  denominator2 - curve * denominator3) / denominator
        return curve3

    def derivatives_array(self, n, ts):
        result = []
        if n >= 1:
            numerator, denominator = self.fraction(0, ts)
            curve = numerator / denominator
            numerator1, denominator1 = self.fraction(1, ts)
            curve1 = (numerator1 - curve * denominator1) / denominator
            result.append(curve1)
        if n >= 2:
            numerator2, denominator2 = self.fraction(2, ts)
            curve2 = (numerator2 - 2 * curve1 * denominator1 -
                      curve * denominator2) / denominator
            result.append(curve2)
        if n >= 3:
            numerator3, denominator3 = self.fraction(3, ts)
            curve3 = (numerator3 - 3 * curve2 * denominator1 - 3 * curve1 *
                      denominator2 - curve * denominator3) / denominator
            result.append(curve3)
        return result

    def get_u_bounds(self):
        if self.u_bounds is None:
            m = self.knotvector.min()
            M = self.knotvector.max()
            return (m, M)
        else:
            return self.u_bounds

    def extrude_along_vector(self, vector):
        vector = np.array(vector)
        other_control_points = self.control_points + vector
        control_points = np.stack((self.control_points, other_control_points))
        control_points = np.transpose(control_points, axes=(1, 0, 2))
        weights = np.stack((self.weights, self.weights)).T
        knotvector_v = sv_knotvector.generate(1, 2, clamped=True)
        surface = SvNativeNurbsSurface(degree_u=self.degree,
                                       degree_v=1,
                                       knotvector_u=self.knotvector,
                                       knotvector_v=knotvector_v,
                                       control_points=control_points,
                                       weights=weights)
        return surface

    @classmethod
    def get_nurbs_implementation(cls):
        return SvNurbsCurve.NATIVE

    def insert_knot(self, u_bar, count=1, if_possible=False):
        # "The NURBS book", 2nd edition, p.5.2, eq. 5.11
        N = len(self.control_points)
        u = self.get_knotvector()
        s = sv_knotvector.find_multiplicity(u, u_bar)
        #print(f"I: kv {len(u)}{u}, u_bar {u_bar} => s {s}")
        #k = np.searchsorted(u, u_bar, side='right')-1
        k = sv_knotvector.find_span(u, N, u_bar)
        p = self.get_degree()
        new_knotvector = sv_knotvector.insert(u, u_bar, count)
        control_points = self.get_homogenous_control_points()

        if (u_bar == u[0] or u_bar == u[-1]):
            if s + count > p + 1:
                if if_possible:
                    count = (p + 1) - s
                else:
                    raise CantInsertKnotException(
                        f"Can't insert first/last knot t={u_bar} for {count} times"
                    )
        else:
            if s + count > p:
                if if_possible:
                    count = p - s
                else:
                    raise CantInsertKnotException(
                        f"Can't insert knot t={u_bar} for {count} times")

        for r in range(1, count + 1):
            prev_control_points = control_points
            control_points = []
            for i in range(N + 1):
                #print(f"I: i {i}, k {k}, p {p}, r {r}, s {s}, k-p+r-1 {k-p+r-1}, k-s {k-s}")
                if i <= k - p + r - 1:
                    point = prev_control_points[i]
                    #print(f"P[{r},{i}] := {i}{prev_control_points[i]}")
                elif k - p + r <= i <= k - s:
                    denominator = u[i + p - r + 1] - u[i]
                    if abs(denominator) < 1e-6:
                        raise Exception(
                            f"Can't insert the knot t={u_bar} for {i}th time: u[i+p-r+1]={u[i+p-r+1]}, u[i]={u[i]}, denom={denominator}"
                        )
                    alpha = (u_bar - u[i]) / denominator
                    point = alpha * prev_control_points[i] + (
                        1.0 - alpha) * prev_control_points[i - 1]
                    #print(f"P[{r},{i}]: alpha {alpha}, pts {i}{prev_control_points[i]}, {i-1}{prev_control_points[i-1]} => {point}")
                else:
                    point = prev_control_points[i - 1]
                    #print(f"P[{r},{i}] := {i-1}{prev_control_points[i-1]}")
                control_points.append(point)
            N += 1

        control_points, weights = from_homogenous(np.array(control_points))
        curve = SvNativeNurbsCurve(self.degree, new_knotvector, control_points,
                                   weights)
        return curve

    def remove_knot(self,
                    u,
                    count=1,
                    target=None,
                    tolerance=1e-6,
                    if_possible=False):
        # Implementation adapted from Geomdl
        logger = getLogger()

        if (count is None) == (target is None):
            raise Exception("Either count or target must be specified")

        orig_multiplicity = sv_knotvector.find_multiplicity(
            self.get_knotvector(), u)

        if count == SvNurbsCurve.ALL:
            count = orig_multiplicity
        elif count == SvNurbsCurve.ALL_BUT_ONE:
            count = orig_multiplicity - 1
        elif count is None:
            count = orig_multiplicity - target

        degree = self.get_degree()
        order = degree + 1

        if not if_possible and (count > orig_multiplicity):
            raise CantRemoveKnotException(
                f"Asked to remove knot t={u} for {count} times, but it's multiplicity is only {orig_multiplicity}"
            )

        # Edge case
        if count < 1:
            return self

        def knot_removal_alpha_i(u, knotvector, idx):
            return (u - knotvector[idx]) / (knotvector[idx + order] -
                                            knotvector[idx])

        def knot_removal_alpha_j(u, knotvector, idx):
            return (u - knotvector[idx]) / (knotvector[idx + order] -
                                            knotvector[idx])

        def point_distance(p1, p2):
            return np.linalg.norm(p1 - p2)
            #return np.linalg.norm(np.array(p1) - np.array(p2))

        def remove_one_knot(curve):
            ctrlpts = curve.get_homogenous_control_points()
            N = len(ctrlpts)
            knotvector = curve.get_knotvector()
            orig_multiplicity = sv_knotvector.find_multiplicity(knotvector, u)
            knot_span = sv_knotvector.find_span(knotvector, N, u)

            # Initialize variables
            first = knot_span - degree
            last = knot_span - orig_multiplicity

            # Don't change input variables, prepare new ones for updating
            ctrlpts_new = deepcopy(ctrlpts)

            # Initialize temp array for storing new control points
            temp_i = np.zeros((2 * degree + 1, 4))
            temp_j = np.zeros((2 * degree + 1, 4))

            removed_count = 0
            # Loop for Eqs 5.28 & 5.29
            t = 0
            offset = first - 1  # difference in index between `temp` and ctrlpts
            temp_i[0] = ctrlpts[offset]
            temp_j[last + 1 - offset] = ctrlpts[last + 1]
            i = first
            j = last
            ii = 1
            jj = last - offset
            can_remove = False

            # Compute control points for one removal step
            while j - i > t:
                alpha_i = knot_removal_alpha_i(u, knotvector, i)
                alpha_j = knot_removal_alpha_j(u, knotvector, j)

                temp_i[ii] = (ctrlpts[i] -
                              (1.0 - alpha_i) * temp_i[ii - 1]) / alpha_i
                temp_j[jj] = (ctrlpts[j] -
                              alpha_j * temp_j[jj + 1]) / (1.0 - alpha_j)

                i += 1
                j -= 1
                ii += 1
                jj -= 1

            # Check if the knot is removable
            if j - i < t:
                dist = point_distance(temp_i[ii - 1], temp_j[jj + 1])
                if dist <= tolerance:
                    can_remove = True
                else:
                    logger.debug(f"remove_knot: stop, distance={dist}")
            else:
                alpha_i = knot_removal_alpha_i(u, knotvector, i)
                ptn = alpha_i * temp_j[ii + t + 1] + (1.0 -
                                                      alpha_i) * temp_i[ii - 1]
                dist = point_distance(ctrlpts[i], ptn)
                if dist <= tolerance:
                    can_remove = True
                else:
                    logger.debug(f"remove_knot: stop, distance={dist}")

            # Check if we can remove the knot and update new control points array
            if can_remove:
                i = first
                j = last
                while j - i > t:
                    ctrlpts_new[i] = temp_i[i - offset]
                    ctrlpts_new[j] = temp_j[j - offset]
                    i += 1
                    j -= 1
                # Update indices
                first -= 1
                last += 1
                removed_count += 1

            else:
                raise CantRemoveKnotException()

            new_kv = np.copy(curve.get_knotvector())

            if removed_count > 0:
                m = N + degree + 1
                for k in range(knot_span + 1, m):
                    new_kv[k - removed_count] = new_kv[k]
                new_kv = new_kv[:m - removed_count]
                #new_kv = np.delete(curve.get_knotvector(), np.s_[(r-t+1):(r+1)])

                # Shift control points (refer to p.183 of The NURBS Book, 2nd Edition)
                j = int((2 * knot_span - orig_multiplicity - degree) /
                        2)  # first control point out
                i = j
                for k in range(1, removed_count):
                    if k % 2 == 1:
                        i += 1
                    else:
                        j -= 1
                for k in range(i + 1, N):
                    ctrlpts_new[j] = ctrlpts_new[k]
                    j += 1

                # Slice to get the new control points
                ctrlpts_new = ctrlpts_new[0:-removed_count]

            ctrlpts_new = np.array(ctrlpts_new)
            control_points, weights = from_homogenous(ctrlpts_new)

            return curve.copy(knotvector=new_kv,
                              control_points=control_points,
                              weights=weights)

        curve = self
        removed_count = 0
        for i in range(count):
            try:
                curve = remove_one_knot(curve)
                removed_count += 1
            except CantRemoveKnotException as e:
                break

        if not if_possible and (removed_count < count):
            raise CantRemoveKnotException(
                f"Asked to remove knot t={u} for {count} times, but could remove it only {removed_count} times"
            )
        #print(f"Removed knot t={u} for {removed_count} times")
        return curve
Пример #10
0
class SvNativeNurbsSurface(SvNurbsSurface):
    def __init__(self,
                 degree_u,
                 degree_v,
                 knotvector_u,
                 knotvector_v,
                 control_points,
                 weights,
                 normalize_knots=False):
        self.degree_u = degree_u
        self.degree_v = degree_v
        self.knotvector_u = np.array(knotvector_u)
        self.knotvector_v = np.array(knotvector_v)
        if normalize_knots:
            self.knotvector_u = sv_knotvector.normalize(self.knotvector_u)
            self.knotvector_v = sv_knotvector.normalize(self.knotvector_v)
        self.control_points = np.array(control_points)
        c_ku, c_kv, _ = self.control_points.shape
        if weights is None:
            self.weights = weights = np.ones((c_ku, c_kv))
        else:
            self.weights = np.array(weights)
            w_ku, w_kv = self.weights.shape
            if c_ku != w_ku or c_kv != w_kv:
                raise Exception(
                    f"Shape of control_points ({c_ku}, {c_kv}) does not match to shape of weights ({w_ku}, {w_kv})"
                )
        self.basis_u = SvNurbsBasisFunctions(knotvector_u)
        self.basis_v = SvNurbsBasisFunctions(knotvector_v)
        self.u_bounds = (self.knotvector_u.min(), self.knotvector_u.max())
        self.v_bounds = (self.knotvector_v.min(), self.knotvector_v.max())
        self.normal_delta = 0.0001
        self.__description__ = f"Native NURBS (degree={degree_u}x{degree_v}, pts={self.control_points.shape[0]}x{self.control_points.shape[1]})"

    @classmethod
    def build(cls,
              implementation,
              degree_u,
              degree_v,
              knotvector_u,
              knotvector_v,
              control_points,
              weights=None,
              normalize_knots=False):
        return SvNativeNurbsSurface(degree_u, degree_v, knotvector_u,
                                    knotvector_v, control_points, weights,
                                    normalize_knots)

    @classmethod
    def get_nurbs_implementation(cls):
        return SvNurbsSurface.NATIVE

    def insert_knot(self, direction, parameter, count=1):
        if direction == SvNurbsSurface.U:
            new_points = []
            new_weights = []
            new_u_degree = None
            for i in range(self.get_control_points().shape[1]):
                fixed_v_points = self.get_control_points()[:, i]
                fixed_v_weights = self.get_weights()[:, i]
                fixed_v_curve = SvNurbsMaths.build_curve(
                    SvNurbsMaths.NATIVE, self.degree_u, self.knotvector_u,
                    fixed_v_points, fixed_v_weights)
                fixed_v_curve = fixed_v_curve.insert_knot(parameter, count)
                fixed_v_knotvector = fixed_v_curve.get_knotvector()
                new_u_degree = fixed_v_curve.get_degree()
                fixed_v_points = fixed_v_curve.get_control_points()
                fixed_v_weights = fixed_v_curve.get_weights()
                new_points.append(fixed_v_points)
                new_weights.append(fixed_v_weights)

            new_points = np.transpose(np.array(new_points), axes=(1, 0, 2))
            new_weights = np.array(new_weights).T

            return SvNativeNurbsSurface(new_u_degree, self.degree_v,
                                        fixed_v_knotvector, self.knotvector_v,
                                        new_points, new_weights)

        elif direction == SvNurbsSurface.V:
            new_points = []
            new_weights = []
            new_v_degree = None
            for i in range(self.get_control_points().shape[0]):
                fixed_u_points = self.get_control_points()[i, :]
                fixed_u_weights = self.get_weights()[i, :]
                fixed_u_curve = SvNurbsMaths.build_curve(
                    SvNurbsMaths.NATIVE, self.degree_v, self.knotvector_v,
                    fixed_u_points, fixed_u_weights)
                fixed_u_curve = fixed_u_curve.insert_knot(parameter, count)
                fixed_u_knotvector = fixed_u_curve.get_knotvector()
                new_v_degree = fixed_u_curve.get_degree()
                fixed_u_points = fixed_u_curve.get_control_points()
                fixed_u_weights = fixed_u_curve.get_weights()
                new_points.append(fixed_u_points)
                new_weights.append(fixed_u_weights)

            new_points = np.array(new_points)
            new_weights = np.array(new_weights)

            return SvNativeNurbsSurface(self.degree_u, new_v_degree,
                                        self.knotvector_u, fixed_u_knotvector,
                                        new_points, new_weights)
        else:
            raise Exception("Unsupported direction")

    def get_degree_u(self):
        return self.degree_u

    def get_degree_v(self):
        return self.degree_v

    def get_knotvector_u(self):
        return self.knotvector_u

    def get_knotvector_v(self):
        return self.knotvector_v

    def get_control_points(self):
        return self.control_points

    def get_weights(self):
        return self.weights

    def get_u_min(self):
        return self.u_bounds[0]

    def get_u_max(self):
        return self.u_bounds[1]

    def get_v_min(self):
        return self.v_bounds[0]

    def get_v_max(self):
        return self.v_bounds[1]

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def fraction(self, deriv_order_u, deriv_order_v, us, vs):
        pu = self.degree_u
        pv = self.degree_v
        ku, kv, _ = self.control_points.shape
        nsu = np.array([
            self.basis_u.derivative(i, pu, deriv_order_u)(us)
            for i in range(ku)
        ])  # (ku, n)
        nsv = np.array([
            self.basis_v.derivative(i, pv, deriv_order_v)(vs)
            for i in range(kv)
        ])  # (kv, n)
        nsu = np.transpose(nsu[np.newaxis], axes=(1, 0, 2))  # (ku, 1, n)
        nsv = nsv[np.newaxis]  # (1, kv, n)
        ns = nsu * nsv  # (ku, kv, n)
        weights = np.transpose(self.weights[np.newaxis],
                               axes=(1, 2, 0))  # (ku, kv, 1)
        coeffs = ns * weights  # (ku, kv, n)
        coeffs = np.transpose(coeffs[np.newaxis],
                              axes=(3, 1, 2, 0))  # (n,ku,kv,1)
        controls = self.control_points  # (ku,kv,3)

        numerator = coeffs * controls  # (n,ku,kv,3)
        numerator = numerator.sum(axis=1).sum(axis=1)  # (n,3)
        denominator = coeffs.sum(axis=1).sum(axis=1)

        return numerator, denominator

    def evaluate_array(self, us, vs):
        numerator, denominator = self.fraction(0, 0, us, vs)
        return nurbs_divide(numerator, denominator)

    def normal(self, u, v):
        return self.normal_array(np.array([u]), np.array([v]))[0]

    def normal_array(self, us, vs):
        numerator, denominator = self.fraction(0, 0, us, vs)
        surface = nurbs_divide(numerator, denominator)
        numerator_u, denominator_u = self.fraction(1, 0, us, vs)
        numerator_v, denominator_v = self.fraction(0, 1, us, vs)
        surface_u = (numerator_u - surface * denominator_u) / denominator
        surface_v = (numerator_v - surface * denominator_v) / denominator
        normal = np.cross(surface_u, surface_v)
        n = np.linalg.norm(normal, axis=1, keepdims=True)
        normal = normal / n
        return normal

    def iso_curve(self, fixed_direction, param, flip=False):
        controls = self.get_control_points()
        weights = self.get_weights()
        k_u, k_v = weights.shape
        if fixed_direction == SvNurbsSurface.U:
            q_curves = [
                SvNurbsMaths.build_curve(self.get_nurbs_implementation(),
                                         self.get_degree_u(),
                                         self.get_knotvector_u(),
                                         controls[:, j], weights[:, j])
                for j in range(k_v)
            ]
            q_controls = [q_curve.evaluate(param) for q_curve in q_curves]
            q_weights = [
                q_curve.fraction_single(0, param)[1] for q_curve in q_curves
            ]
            curve = SvNurbsMaths.build_curve(self.get_nurbs_implementation(),
                                             self.get_degree_v(),
                                             self.get_knotvector_v(),
                                             q_controls, q_weights)
            if flip:
                return curve.reverse()
            else:
                return curve
        elif fixed_direction == SvNurbsSurface.V:
            q_curves = [
                SvNurbsMaths.build_curve(self.get_nurbs_implementation(),
                                         self.get_degree_v(),
                                         self.get_knotvector_v(),
                                         controls[i, :], weights[i, :])
                for i in range(k_u)
            ]
            q_controls = [q_curve.evaluate(param) for q_curve in q_curves]
            q_weights = [
                q_curve.fraction_single(0, param)[1] for q_curve in q_curves
            ]
            curve = SvNurbsMaths.build_curve(self.get_nurbs_implementation(),
                                             self.get_degree_u(),
                                             self.get_knotvector_u(),
                                             q_controls, q_weights)
            if flip:
                return curve.reverse()
            else:
                return curve

    def derivatives_data_array(self, us, vs):
        numerator, denominator = self.fraction(0, 0, us, vs)
        surface = nurbs_divide(numerator, denominator)
        numerator_u, denominator_u = self.fraction(1, 0, us, vs)
        numerator_v, denominator_v = self.fraction(0, 1, us, vs)
        surface_u = (numerator_u - surface * denominator_u) / denominator
        surface_v = (numerator_v - surface * denominator_v) / denominator
        return SurfaceDerivativesData(surface, surface_u, surface_v)

    def curvature_calculator(self, us, vs, order=True):

        numerator, denominator = self.fraction(0, 0, us, vs)
        surface = nurbs_divide(numerator, denominator)
        numerator_u, denominator_u = self.fraction(1, 0, us, vs)
        numerator_v, denominator_v = self.fraction(0, 1, us, vs)
        surface_u = (numerator_u - surface * denominator_u) / denominator
        surface_v = (numerator_v - surface * denominator_v) / denominator

        normal = np.cross(surface_u, surface_v)
        n = np.linalg.norm(normal, axis=1, keepdims=True)
        normal = normal / n

        numerator_uu, denominator_uu = self.fraction(2, 0, us, vs)
        surface_uu = (numerator_uu - 2 * surface_u * denominator_u -
                      surface * denominator_uu) / denominator
        numerator_vv, denominator_vv = self.fraction(0, 2, us, vs)
        surface_vv = (numerator_vv - 2 * surface_v * denominator_v -
                      surface * denominator_vv) / denominator

        numerator_uv, denominator_uv = self.fraction(1, 1, us, vs)
        surface_uv = (numerator_uv - surface_v * denominator_u - surface_u *
                      denominator_v - surface * denominator_uv) / denominator

        nuu = (surface_uu * normal).sum(axis=1)
        nvv = (surface_vv * normal).sum(axis=1)
        nuv = (surface_uv * normal).sum(axis=1)

        duu = np.linalg.norm(surface_u, axis=1)**2
        dvv = np.linalg.norm(surface_v, axis=1)**2
        duv = (surface_u * surface_v).sum(axis=1)

        calc = SurfaceCurvatureCalculator(us, vs, order=order)
        calc.set(surface, normal, surface_u, surface_v, duu, dvv, duv, nuu,
                 nvv, nuv)
        return calc