def remove_knot(self, u, count=1, target=None, if_possible=False): if (count is None) == (target is None): raise Exception("Either count or target must be specified") knotvector = self.get_knotvector() orig_multiplicity = sv_knotvector.find_multiplicity(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 curve = self.copy() curve = operations.remove_knot(curve.curve, [u], [count]) result = SvGeomdlCurve(curve) result.u_bounds = self.u_bounds new_kv = result.get_knotvector() new_multiplicity = sv_knotvector.find_multiplicity(new_kv, u) if not if_possible and (orig_multiplicity - count < new_multiplicity): raise CantRemoveKnotException( f"Asked to remove knot t={u} for {count} times, but could remove it only {orig_multiplicity - count} times" ) return result
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_remove_2(self): points = np.array( [[0, 0, 0], [0, 1, 0], [1, 2, 0], [2, 2, 0], [3, 1, 0], [3, 0, 0]], dtype=np.float64) degree = 3 kv = np.array([0, 0, 0, 0, 0.25, 0.75, 1, 1, 1, 1]) weights = [1, 1, 1, 1, 1, 1] curve = SvNativeNurbsCurve(degree, kv, points, weights) kv_err = sv_knotvector.check(degree, kv, len(points)) if kv_err is not None: raise Exception(kv_err) knot = 0.1 inserted = curve.insert_knot(knot, 1) self.assertEquals(len(inserted.get_control_points()), len(points) + 1) expected_inserted_kv = np.array( [0, 0, 0, 0, 0.1, 0.25, 0.75, 1, 1, 1, 1]) self.assert_numpy_arrays_equal(inserted.get_knotvector(), expected_inserted_kv, precision=8) inserted_kv = inserted.get_knotvector() k = np.searchsorted(inserted_kv, knot, side='right') - 1 s = sv_knotvector.find_multiplicity(inserted_kv, knot) print("K:", k, "S:", s) removed = inserted.remove_knot(knot, 1) self.assert_numpy_arrays_equal(removed.get_knotvector(), kv, precision=8)
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
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 _split_at(self, t): # Split without building SvNurbsCurve objects: # Some implementations (geomdl in particular) # can check number of control points vs curve degree, # and that can be bad for very small segments; # on the other hand, we may not care about it # if we are throwing away that small segment and # going to use only the bigger one. t_min, t_max = self.get_u_bounds() # corner cases if t <= t_min: return None, (self.get_knotvector(), self.get_control_points(), self.get_weights()) if t >= t_max: return (self.get_knotvector(), self.get_control_points(), self.get_weights()), None current_multiplicity = sv_knotvector.find_multiplicity( self.get_knotvector(), t) to_add = self.get_degree() - current_multiplicity # + 1 curve = self.insert_knot(t, count=to_add) knot_span = np.searchsorted(curve.get_knotvector(), t) ts = np.full((self.get_degree() + 1, ), t) knotvector1 = np.concatenate((curve.get_knotvector()[:knot_span], ts)) knotvector2 = np.insert(curve.get_knotvector()[knot_span:], 0, t) control_points_1 = curve.get_control_points()[:knot_span] control_points_2 = curve.get_control_points()[knot_span - 1:] weights_1 = curve.get_weights()[:knot_span] weights_2 = curve.get_weights()[knot_span - 1:] #print(f"S: ctlpts1: {len(control_points_1)}, 2: {len(control_points_2)}") kv_error = sv_knotvector.check(curve.get_degree(), knotvector1, len(control_points_1)) if kv_error is not None: raise Exception(kv_error) kv_error = sv_knotvector.check(curve.get_degree(), knotvector2, len(control_points_2)) if kv_error is not None: raise Exception(kv_error) curve1 = (knotvector1, control_points_1, weights_1) curve2 = (knotvector2, control_points_2, weights_2) return curve1, curve2
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 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