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]})"
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)}")
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 __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 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
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
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
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
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
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