def extract_nurbs_isocurve(n, p, uk, m, q, vk, cpw, u=None, v=None): """ Extract iso-curve from NURBS surface. :param int n: Number of control points - 1 in u-direction. :param int p: Degree in u-direction. :param ndarray uk: Knot vector in u-direction. :param int m: Number of control points - 1 in v-direction. :param int q: Degree in v-direction. :param ndarray vk: Knot vector in v-direction. :param ndarray cpw: Control points. :param float u: Location of *u* split (in v-direction). :param float v: Location of *v* split (in u-direction). :return: Knot vector and control points for NURBS iso-curve in specified direction (uq, qw). :rtype: tuple """ if u is not None: # Split at u in v-direction. uq = array(vk, dtype=float64) # Special case if a >= u >= b. if u <= uk[p]: return uq, cpw[0, :] if u >= uk[n + 1]: return uq, cpw[n, :] # Determine multiplicity. k, s = find_span_mult(n, p, u, uk) qw = zeros((m + 1, 4), dtype=float64) for j in range(0, m + 1): _, qwi = curve_knot_ins(n, p, uk, cpw[:, j], u, p - s) qw[j] = qwi[k - s] return uq, qw elif v is not None: # Split at v in u-direction. uq = array(uk, dtype=float64) # Special case if a >= v >= b. if v <= vk[q]: return uq, cpw[:, 0] if v >= vk[m + 1]: return uq, cpw[:, m] # Determine multiplicity. k, s = find_span_mult(m, q, v, vk) qw = zeros((n + 1, 4), dtype=float64) for i in range(0, n + 1): _, qwi = curve_knot_ins(m, q, vk, cpw[i, :], v, q - s) qw[i] = qwi[k - s] return uq, qw
def curve_knot_ins(n, p, uk, cpw, u, r): """ Curve knot insertion. :param int n: Number of control points - 1. :param int p: Degree. :param ndarray uk: Knot vector. :param ndarray cpw: Control points. :param float u: Knot value to insert. :param int r: Number of times to insert knot. :return: Knot vector and control points after knot insertion (knots, cpw). :rtype: tuple *Reference:* Algorithm A5.1 from "The NURBS Book." """ # Find span and multiplicity. k, s = find_span_mult(n, p, u, uk) if r + s > p: r = p - s # Generate knot vector nq = n + r uq = zeros(nq + p + 2, dtype=float64) for i in range(0, k + 1): uq[i] = uk[i] for i in range(1, r + 1): uq[k + i] = u for i in range(k + 1, n + p + 2): uq[i + r] = uk[i] # Save unchanged control points. qw = zeros((nq + 1, 4), dtype=float64) for i in range(0, k - p + 1): qw[i] = cpw[i] for i in range(k - s, n + 1): qw[i + r] = cpw[i] rw = zeros((p + 1, 4), dtype=float64) for i in range(0, p - s + 1): rw[i] = cpw[k - p + i] # Insert knot r times. t = k - p for j in range(1, r + 1): t = k - p + j for i in range(0, p - j - s + 1): alpha = (u - uk[t + i]) / (uk[i + k + 1] - uk[t + i]) rw[i] = alpha * rw[i + 1] + (1.0 - alpha) * rw[i] qw[t] = rw[0] qw[k + r - j - s] = rw[p - j - s] # Load remaining control points. for i in range(t + 1, k - s): qw[i] = rw[i - t] return uq, qw
def split_nurbs_curve(n, p, uk, cpw, u): """ Split NURBS curve into two segments at *u*. :param int n: Number of control points - 1. :param int p: Degree. :param ndarray uk: Knot vector. :param ndarray cpw: Control points. :param float u: Parametric point to split curve at. :return: New knot vector and control points for two curves (uk1, qw1, uk2, qw2). :rtype: tuple """ # Special case if a >= u >= b. # if u <= uk[p]: ptol = Settings.ptol if CmpFlt.le(u, uk[p], ptol): qw1 = zeros((n + 1, 4), dtype=float64) qw1[:] = cpw[0] uk1 = array(uk, dtype=float64) uk1[:] = uk[p] qw2 = array(cpw, dtype=float64) uk2 = array(uk, dtype=float64) return uk1, qw1, uk2, qw2 # elif u >= uk[n + 1]: elif CmpFlt.ge(u, uk[n + 1], ptol): qw1 = array(cpw, dtype=float64) uk1 = array(uk, dtype=float64) qw2 = zeros((n + 1, 4), dtype=float64) qw2[:] = cpw[n] uk2 = array(uk, dtype=float64) uk2[:] = uk[n + 1] return uk1, qw1, uk2, qw2 # Find multiplicity of knot. k, s = find_span_mult(n, p, u, uk) # Insert knot p - s times and update knot vector and control points. if s >= p: uq, qw = array(uk, dtype=float64), array(cpw, dtype=float64) else: uq, qw = curve_knot_ins(n, p, uk, cpw, u, p - s) # Control points and knot vectors. qw1 = qw[:k - s + 1] qw2 = qw[k - s:] m = n + p + 1 uk1 = zeros((k + 1) + (p - s + 1), dtype=float64) uk2 = zeros((m - k) + p + 1, dtype=float64) uk1[:-1] = uq[:k + (p - s) + 1] uk1[-1] = u uk2[1:] = uq[k - s + 1:] uk2[0] = u return uk1, qw1, uk2, qw2
def filter_knot_vect(n, p, uk, x): """ Filter the knot vector *x* so that the knot multiplicity is less than or equal to the degree *p* when inserted into *uk*. :param int n: Number of control points - 1. :param int p: Degree. :param ndarray uk: Knot vector (sorted). :param ndarray x: Knots to add to existing knot vector (sorted). :return: Filtered knot vector *x*. :rtype: ndarray """ temp = array(uk) xnew = [] for i in range(0, len(x)): span, s = find_span_mult(n, p, x[i], temp) if s <= p: temp = insert(temp, span, x[i]) n += 1 xnew.append(x[i]) return array(xnew, dtype=float64)
def move_nurbs_surface_seam(n, p, uk, m, q, vk, cpw, uv, d): """ Move the seam of a closed surface to a new parameter value. :param int n: Number of control points - 1 in u-direction. :param int p: Degree in u-direction. :param ndarray uk: Knot vector in u-direction. :param int m: Number of control points - 1 in v-direction. :param int q: Degree in v-direction. :param ndarray vk: Knot vector in v-direction. :param ndarray cpw: Control points. :param float uv: New seam parameter. :param str d: Direction to move seam in (surface must be closed in that direction). :return: Knot vector and control points of new surface. :rtype: tuple """ ptol = Settings.ptol if d.lower() in ['u']: if CmpFlt.le(uv, uk[p], ptol) or CmpFlt.ge(uv, uk[n + 1], ptol): return uk, vk, cpw # Check multiplicity of seam parameter. _, s = find_span_mult(n, p, uv, uk) # Insert seam parameter s - p times. uq, vq, qw = surface_knot_ins(n, p, uk, m, q, vk, cpw, 'u', uv, p - s) # Find span of seam parameter in new knot vector. nw = qw.shape[0] - 1 k = find_span(nw, p, uv, uq) # Get index for first row of control points. n0 = k - p # Generate control points for new seam. qw_seam = array(qw) indx = 0 # For new seam to end of old seam for i in range(n0, nw + 1): qw_seam[indx, :, :] = qw[i, :, :] indx += 1 # From start of old seam to new seam. for i in range(1, n0): qw_seam[indx, :, :] = qw[i, :, :] indx += 1 # Set last row. qw_seam[-1, :, :] = qw_seam[0, :, :] # Generate new knot vector. uq_seam = array(uq) # From seam to end indx = p + 1 for i in range(k + 1, nw + p + 1): uq_seam[indx] = uq[i] - uv indx += 1 # From start to seam for i in range(p + 1, k + 1 - p): uq_seam[indx] = uq_seam[indx - 1] + (uq[i] - uq[i - 1]) indx += 1 uq_seam[-p - 1:] = uq[-1] return uq_seam, vk, qw_seam if d.lower() in ['v']: if CmpFlt.le(uv, vk[q], ptol) or CmpFlt.ge(uv, vk[m + 1], ptol): return uk, vk, cpw # Check multiplicity of seam parameter. _, s = find_span_mult(m, q, uv, vk) # Insert seam parameter s - q times. uq, vq, qw = surface_knot_ins(n, p, uk, m, q, vk, cpw, 'v', uv, q - s) # Find span of seam parameter in new knot vector. mw = qw.shape[1] - 1 k = find_span(mw, q, uv, vq) # Get index for first row of control points. m0 = k - q # Generate control points for new seam. qw_seam = array(qw) indx = 0 # For new seam to end of old seam for i in range(m0, mw + 1): qw_seam[:, indx, :] = qw[:, i, :] indx += 1 # From start of old seam to new seam. for i in range(1, m0): qw_seam[:, indx, :] = qw[:, i, :] indx += 1 # Set last row. qw_seam[:, -1, :] = qw_seam[:, 0, :] # Generate new knot vector. vq_seam = array(vq) # From seam to end indx = q + 1 for i in range(k + 1, mw + q + 1): vq_seam[indx] = vq[i] - uv indx += 1 # From start to seam for i in range(q + 1, k + 1 - q): vq_seam[indx] = vq_seam[indx - 1] + (vq[i] - vq[i - 1]) indx += 1 vq_seam[-q - 1:] = vq[-1] return uk, vq_seam, qw_seam
def surface_knot_ins(n, p, uk, m, q, vk, cpw, d, uv, r): """ Surface knot insertion. :param int n: Number of control points - 1 in u-direction. :param int p: Degree in u-direction. :param ndarray uk: Knot vector in u-direction. :param int m: Number of control points - 1 in v-direction. :param int q: Degree in v-direction. :param ndarray vk: Knot vector in v-direction. :param ndarray cpw: Control points. :param str d: Direction to insert knot *uv* ("u": u-direction, "v": v-direction. :param float uv: Knot to insert. :param int r: Number of times to insert knot. :return: Knot vector and control points after knot insertion (uq, vq, cpw). :rtype: tuple *Reference:* Algorithm A5.3 from "The NURBS Book." """ if d.lower() in ['u']: # Generate u-direction knot vector. k, s = find_span_mult(n, p, uv, uk) if r + s > p: r = p - s nq = n + r uq = zeros(nq + p + 2, dtype=float64) for i in range(0, k + 1): uq[i] = uk[i] for i in range(1, r + 1): uq[k + i] = uv for i in range(k + 1, n + p + 2): uq[i + r] = uk[i] # Copy v-direction knot vector. vq = array(vk) # Save alphas. alpha = zeros((p + 1, r + 1), dtype=float64) for j in range(1, r + 1): t = k - p + j for i in range(0, p - j - s + 1): alpha[i, j] = (uv - uk[t + i]) / (uk[i + k + 1] - uk[t + i]) # Insert knot for each row at each column. qw = zeros((nq + 1, m + 1, 4), dtype=float64) for col in range(0, m + 1): # Save unchanged control points. for i in range(0, k - p + 1): qw[i, col] = cpw[i, col] for i in range(k - s, n + 1): qw[i + r, col] = cpw[i, col] # Load auxiliary control points. rw = zeros((p + 1, 4), dtype=float64) for i in range(0, p - s + 1): rw[i] = cpw[k - p + i, col] # Insert the knot r times. t = k - p for j in range(1, r + 1): t = k - p + j for i in range(0, p - j - s + 1): rw[i] = (alpha[i, j] * rw[i + 1] + (1.0 - alpha[i, j]) * rw[i]) qw[t, col] = rw[0] qw[k + r - j - s, col] = rw[p - j - s] # Load remaining control points. for i in range(t + 1, k - s): qw[i, col] = rw[i - t] return uq, vq, qw if d.lower() in ['v']: # Generate v-direction knot vector. k, s = find_span_mult(m, q, uv, vk) if r + s > q: r = q - s mq = m + r vq = zeros(mq + q + 2, dtype=float64) for i in range(0, k + 1): vq[i] = vk[i] for i in range(1, r + 1): vq[k + i] = uv for i in range(k + 1, m + q + 2): vq[i + r] = vk[i] # Copy u-direction knot vector. uq = array(uk) # Save alphas. alpha = zeros((q + 1, r + 1), dtype=float64) for j in range(1, r + 1): t = k - q + j for i in range(0, q - j - s + 1): alpha[i, j] = (uv - vk[t + i]) / (vk[i + k + 1] - vk[t + i]) # Insert knot for each column at each row. qw = zeros((n + 1, mq + 1, 4), dtype=float64) for row in range(0, n + 1): # Save unchanged control points. for i in range(0, k - q + 1): qw[row, i] = cpw[row, i] for i in range(k - s, m + 1): qw[row, i + r] = cpw[row, i] # Load auxiliary control points. rw = zeros((q + 1, 4), dtype=float64) for i in range(0, q - s + 1): rw[i] = cpw[row, k - q + i] # Insert the knot r times. t = k - q for j in range(1, r + 1): t = k - q + j for i in range(0, q - j - s + 1): rw[i] = (alpha[i, j] * rw[i + 1] + (1.0 - alpha[i, j]) * rw[i]) qw[row, t] = rw[0] qw[row, k + r - j - s] = rw[q - j - s] # Load remaining control points. for i in range(t + 1, k - s): qw[row, i] = rw[i - t] return uq, vq, qw
def split_nurbs_surface(n, p, uk, m, q, vk, cpw, u=None, v=None): """ Split NURBS surface into two segments in specified direction. :param int n: Number of control points - 1 in u-direction. :param int p: Degree in u-direction. :param ndarray uk: Knot vector in u-direction. :param int m: Number of control points - 1 in v-direction. :param int q: Degree in v-direction. :param ndarray vk: Knot vector in v-direction. :param ndarray cpw: Control points. :param u: Location of *u* split (in v-direction). :type u: float or None :param v: Location of *v* split (in u-direction). :type v: float or None :return: New knot vectors and control points for two surfaces (uk1, vk1, qw1, uk2, vk2, qw2). :rtype: tuple """ if u is not None: # Split at u in v-direction. vk12 = array(vk, dtype=float64) if u <= uk[p]: qw1 = zeros((n + 1, m + 1, 4), dtype=float64) for i in range(0, n + 1): qw1[i, :] = cpw[0, :] qw2 = array(cpw, dtype=float64) uk1 = array(uk, dtype=float64) uk1[:] = uk[p] uk2 = array(uk, dtype=float64) return uk1, vk12, qw1, uk2, vk12, qw2 if u >= uk[n + 1]: qw1 = array(cpw, dtype=float64) qw2 = zeros((n + 1, m + 1, 4), dtype=float64) for i in range(0, n + 1): qw2[i, :] = cpw[n, :] uk1 = array(uk, dtype=float64) uk2 = array(uk, dtype=float64) uk2[:] = uk[n + 1] return uk1, vk12, qw1, uk2, vk12, qw2 # Find multiplicity of knot. k, s = find_span_mult(n, p, u, uk) # Insert knot p - s times and update knot vector and control points. if s >= p: uq, qw = array(uk, dtype=float64), array(cpw, dtype=float64) else: uq, _, qw = surface_knot_ins(n, p, uk, m, q, vk, cpw, 'u', u, p - s) qw1 = qw[:k - s + 1, :] qw2 = qw[k - s:, :] r = n + p + 1 uk1 = zeros((k + 1) + (p - s + 1), dtype=float64) uk2 = zeros((r - k) + p + 1, dtype=float64) uk1[:-1] = uq[:k + (p - s) + 1] uk1[-1] = u uk2[1:] = uq[k - s + 1:] uk2[0] = u return uk1, vk12, qw1, uk2, vk12, qw2 elif v is not None: # Split at v in u-direction. uk12 = array(uk, dtype=float64) if v <= vk[q]: qw1 = zeros((n + 1, m + 1, 4), dtype=float64) for j in range(0, m + 1): qw1[:, j] = cpw[:, 0] qw2 = array(cpw, dtype=float64) vk1 = array(vk, dtype=float64) vk1[:] = vk[q] vk2 = array(vk, dtype=float64) return uk12, vk1, qw1, uk12, vk2, qw2 if v >= vk[m + 1]: qw1 = array(cpw, dtype=float64) qw2 = zeros((n + 1, m + 1, 4), dtype=float64) for j in range(0, m + 1): qw2[:, j] = cpw[:, m] vk1 = array(vk, dtype=float64) vk2 = array(vk, dtype=float64) vk2[:] = vk[m + 1] return uk12, vk1, qw1, uk12, vk2, qw2 # Find multiplicity of knot. k, s = find_span_mult(m, q, v, vk) # Insert knot q - s times and update knot vector and control points. if s >= q: vq, qw = array(vk, dtype=float64), array(cpw, dtype=float64) else: _, vq, qw = surface_knot_ins(n, p, uk, m, q, vk, cpw, 'v', v, q - s) qw1 = qw[:, :k - s + 1] qw2 = qw[:, k - s:] r = m + q + 1 vk1 = zeros((k + 1) + (q - s + 1), dtype=float64) vk2 = zeros((r - k) + q + 1, dtype=float64) vk1[:-1] = vq[:k + (q - s) + 1] vk1[-1] = v vk2[1:] = vq[k - s + 1:] vk2[0] = v return uk12, vk1, qw1, uk12, vk2, qw2
def interpolate_curves(curves, q=3, method='chord', inplace=False, auto_reverse=True): """ Create a surface by interpolating the list of section curves. :param list curves: List of NURBS curves to skin. Order of the curves in the list will determine the longitudinal (*v*) direction. :param int q: Degree in v-direction. Will be adjusted if not enough skinning curves are provided (q = ncurves - 1). :param str method: Option to specify method for selecting parameters ("uniform", "chord", or "centripetal"). :param bool inplace: Option to modify the curves in the list in-place. If this option is *False*, then new curves will be created. If this option is *True*, then the curves in the list will be modified in-place. :param bool auto_reverse: Option to check direction of curves and reverse if necessary. :return: NURBS surface data (cpw, uk, vk, p, q). :rtype: tuple *Reference:* "The NURBS Book" Section 10.3. """ # Step 0: Adjust desired degree in case not enough curves are provided. if len(curves) - 1 < q: q = len(curves) - 1 # Step 1: Make sure each curve in a NURBS curve. nurbs_curves = [] for c in curves: if inplace: nurbs_curves.append(c) else: nurbs_curves.append(c.copy()) if not nurbs_curves: return None # Check direction of curves by comparing the derivatives at the midpoint. if auto_reverse: cu_0 = nurbs_curves[0].deriv(0.5, 1, rtype='ndarray') for c in nurbs_curves[1:]: cu_i = c.deriv(0.5, 1, rtype='ndarray') if dot(cu_0, cu_i) < 0.: c.reverse() cu_0 = cu_i # Step 2: Make sure each curve has a common degree. pmax = 0 for c in nurbs_curves: if c.p > pmax: pmax = c.p for c in nurbs_curves: if c.p < pmax: c.elevate_degree(pmax, inplace=True) # Step 3: Make sure each curve has a similar knot vector. # Find each unique interior knot value in every curve. a, b = [], [] for c in nurbs_curves: a.append(c.a) b.append(c.b) amin = min(a) bmax = max(b) for c in nurbs_curves: c.set_domain(amin, bmax) all_knots = [] for c in nurbs_curves: nu, _, knots = c.get_mult_and_knots() for uk in knots[:nu]: all_knots.append(uk) all_knots.sort() unique_knots = [] m = len(all_knots) - 1 i = 0 while i <= m: unique_knots.append(all_knots[i]) # while i <= m and all_knots[i] == unique_knots[-1]: while i <= m and CmpFlt.eq(all_knots[i], unique_knots[-1], Settings.ptol): i += 1 # For each curve, find the multiplicity and then the max multiplicity of # each unique knot value. all_mult = [] for c in nurbs_curves: row = [] for ui in unique_knots: _, mult = find_span_mult(c.n, c.p, ui, c.uk) row.append(mult) all_mult.append(row) mult_arr = array(all_mult, dtype=int32) max_mult = amax(mult_arr, axis=0) # For each curve, insert the knot the required number of times, if any. for mult, ui in zip(max_mult, unique_knots): for c in nurbs_curves: _, c_mult = find_span_mult(c.n, c.p, ui, c.uk) if c_mult < mult: c.insert_knot(ui, mult - c_mult, inplace=True, domain='global') # Step 4: Compute v-direction parameters between [0, 1]. # Find parameters between each curve by averaging each segment. temp = array([c.cpw for c in nurbs_curves], dtype=float64) pnts_matrix = temp.transpose((1, 0, 2)) c0 = nurbs_curves[0] n = c0.n m = len(nurbs_curves) - 1 v_matrix = zeros((n + 1, m + 1), dtype=float64) for i in range(0, n + 1): if method.lower() in ['u', 'uniform']: v = uniform(pnts_matrix[i, :], 0., 1.) elif method.lower() in ['ch', 'chord']: v = chord_length(pnts_matrix[i, :], 0., 1.) else: v = centripetal(pnts_matrix[i, :], 0., 1.) v_matrix[i] = v # Average each column. v = mean(v_matrix, axis=0, dtype=float64) v[0] = 0.0 v[-1] = 1.0 # Step 5: Compute knot vector by averaging. uk = c0.uk s = m + q + 1 vk = zeros(s + 1, dtype=float64) vk[s - q:] = 1.0 for j in range(1, m - q + 1): temp = 0. for i in range(j, j + q): temp += v[i] vk[j + q] = 1.0 / q * temp # Step 6: Perform n + 1 interpolations for v-direction. cpw = zeros((n + 1, m + 1, 4), dtype=float64) for i in range(0, n + 1): qp = pnts_matrix[i] # Set up system of linear equations. a = zeros((m + 1, m + 1), dtype=float64) for j in range(0, m + 1): span = find_span(m, q, v[j], vk) a[j, span - q:span + 1] = basis_funs(span, v[j], q, vk) # Solve for [a][cp] = [qp] using LU decomposition. lu, piv = lu_factor(a, overwrite_a=True, check_finite=False) cpw[i, :] = lu_solve((lu, piv), qp, trans=0, overwrite_b=True, check_finite=True) return cpw, uk, vk, pmax, q