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
def test_from_homogenous(self): points = np.array([[0, 0, 1, 1], [0, 0, 4, 2], [0, 0, 9, 3]]) result, weights = from_homogenous(points) expected_points = np.array([[0, 0, 1], [0, 0, 2], [0, 0, 3]]) expected_weights = np.array([1, 2, 3]) self.assert_numpy_arrays_equal(weights, expected_weights, precision=8) self.assert_numpy_arrays_equal(result, expected_points, precision=8)
def elevate_degree(self, delta=None, target=None): if delta is None and target is None: delta = 1 if delta is not None and target is not None: raise Exception( "Of delta and target, only one parameter can be specified") degree = self.get_degree() if delta is None: delta = target - degree if delta < 0: raise Exception( f"Curve already has degree {degree}, which is greater than target {target}" ) if delta == 0: return self if self.is_bezier(): control_points = self.get_homogenous_control_points() control_points = elevate_bezier_degree(degree, control_points, delta) control_points, weights = from_homogenous(control_points) knotvector = self.get_knotvector() knotvector = sv_knotvector.elevate_degree(knotvector, delta) return SvNurbsCurve.build(self.get_nurbs_implementation(), degree + delta, knotvector, control_points, weights) else: raise UnsupportedCurveTypeException( "Degree elevation is not implemented for non-bezier curves yet" )
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 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 elevate_degree(self, delta=1): if self.is_bezier(): control_points = self.get_homogenous_control_points() degree = self.get_degree() control_points = elevate_bezier_degree(degree, control_points, delta) control_points, weights = from_homogenous(control_points) knotvector = self.get_knotvector() knotvector = sv_knotvector.elevate_degree(knotvector, delta) return SvNurbsCurve.build(self.get_nurbs_implementation(), degree+delta, knotvector, control_points, weights) else: raise Exception("Not implemented yet!")
def to_nurbs(self, implementation=SvNurbsMaths.NATIVE): control_points = self.get_control_points() if control_points.shape[1] == 4: control_points, weights = from_homogenous(control_points) else: weights = None degree = self.get_degree() knotvector = sv_knotvector.generate(degree, len(control_points)) u1, u2 = self.get_u_bounds() knotvector = (u2 - u1) * knotvector + u1 nurbs = SvNurbsMaths.build_curve(implementation, degree=degree, knotvector=knotvector, control_points=control_points, weights=weights) #return nurbs.reparametrize(*self.get_u_bounds()) return nurbs
def elevate_degree(self, delta=None, target=None): orig_delta, orig_target = delta, target if delta is None and target is None: delta = 1 if delta is not None and target is not None: raise Exception( "Of delta and target, only one parameter can be specified") degree = self.get_degree() if delta is None: delta = target - degree if delta < 0: raise Exception( f"Curve already has degree {degree}, which is greater than target {target}" ) if delta == 0: return self if self.is_bezier(): control_points = self.get_homogenous_control_points() control_points = elevate_bezier_degree(degree, control_points, delta) control_points, weights = from_homogenous(control_points) knotvector = self.get_knotvector() knotvector = sv_knotvector.elevate_degree(knotvector, delta) return SvNurbsCurve.build(self.get_nurbs_implementation(), degree + delta, knotvector, control_points, weights) else: src_t_min, src_t_max = self.get_u_bounds() segments = self.to_bezier_segments(to_bezier_class=False) segments = [ segment.elevate_degree(orig_delta, orig_target) for segment in segments ] result = segments[0] for segment in segments[1:]: result = result.concatenate(segment) result = result.reparametrize(src_t_min, src_t_max) return result
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)
def simple_loft(curves, degree_v=None, knots_u='UNIFY', metric='DISTANCE', tknots=None, implementation=SvNurbsSurface.NATIVE): """ Loft between given NURBS curves (a.k.a skinning). inputs: * degree_v - degree of resulting surface along V parameter; by default - use the same degree as provided curves * knots_u - one of: - 'UNIFY' - unify knotvectors of given curves by inserting additional knots - 'AVERAGE' - average knotvectors of given curves; this will work only if all curves have the same number of control points * metric - metric for interpolation; most useful are 'DISTANCE' and 'CENTRIPETAL' * implementation - NURBS maths implementation output: tuple: * list of curves - input curves after unification * list of NURBS curves along V direction * generated NURBS surface. """ if knots_u not in {'UNIFY', 'AVERAGE'}: raise Exception(f"Unsupported knots_u option: {knots_u}") curve_class = type(curves[0]) curves = unify_curves_degree(curves) if knots_u == 'UNIFY': curves = unify_curves(curves) else: kvs = [len(curve.get_control_points()) for curve in curves] max_kv, min_kv = max(kvs), min(kvs) if max_kv != min_kv: raise Exception( f"U knotvector averaging is not applicable: Curves have different number of control points: {kvs}" ) degree_u = curves[0].get_degree() if degree_v is None: degree_v = degree_u if degree_v > len(curves): raise Exception( f"V degree ({degree_v}) must be not greater than number of curves ({len(curves)}) minus 1" ) src_points = [curve.get_homogenous_control_points() for curve in curves] # lens = [len(pts) for pts in src_points] # max_len, min_len = max(lens), min(lens) # if max_len != min_len: # raise Exception(f"Unify error: curves have different number of control points: {lens}") src_points = np.array(src_points) #print("Src:", src_points) src_points = np.transpose(src_points, axes=(1, 0, 2)) v_curves = [ interpolate_nurbs_curve(curve_class, degree_v, points, metric=metric, tknots=tknots) for points in src_points ] control_points = [ curve.get_homogenous_control_points() for curve in v_curves ] control_points = np.array(control_points) #weights = [curve.get_weights() for curve in v_curves] #weights = np.array([curve.get_weights() for curve in curves]).T n, m, ndim = control_points.shape control_points = control_points.reshape((n * m, ndim)) control_points, weights = from_homogenous(control_points) control_points = control_points.reshape((n, m, 3)) weights = weights.reshape((n, m)) mean_v_vector = control_points.mean(axis=0) tknots_v = Spline.create_knots(mean_v_vector, metric=metric) knotvector_v = sv_knotvector.from_tknots(degree_v, tknots_v) if knots_u == 'UNIFY': knotvector_u = curves[0].get_knotvector() else: knotvectors = np.array([curve.get_knotvector() for curve in curves]) knotvector_u = knotvectors.mean(axis=0) surface = SvNurbsSurface.build(implementation, degree_u, degree_v, knotvector_u, knotvector_v, control_points, weights) surface.u_bounds = curves[0].get_u_bounds() return curves, v_curves, surface