Source code for lib.nurbs_e

#
##
##  SPDX-FileCopyrightText: © 2007-2023 Benedict Verhegghe <bverheg@gmail.com>
##  SPDX-License-Identifier: GPL-3.0-or-later
##
##  This file is part of pyFormex 3.4  (Thu Nov 16 18:07:39 CET 2023)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: https://pyformex.org
##  Project page: https://savannah.nongnu.org/projects/pyformex/
##  Development: https://gitlab.com/bverheg/pyformex
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##
#
"""Python equivalents of the functions in lib.nurbs_c

The functions in this module should be exact emulations of the
external functions in the compiled library. Currently however this
module only contains a few of the functions in lib.nurbs_c, making
nurbs functionality in pyFormex currently only available when using
the compiled lib.
"""


# There should be no other imports here but numpy
import numpy as np

# And Set the version from pyformexld be defined
import pyformex
__version__ = pyformex.__version__
_accelerated = False

# Since binomial is already defined in curve, we can just import it here.
from pyformex.curve import binomial   #  this is math.comb


[docs]def length(A): """Return the length of an ndim vector.""" return np.linalg.norm(A)
[docs]def bernstein(i, n, u): """Compute the value of the Bernstein polynomial B(i,n) at u. Parameters ---------- i: int The index of the polynomial n: int The degree of the polynomial u: float The parametric value where the polynomial is evaluated Returns ------- float The value of the i-th Bernstein polynomials of degree n at parameter value u. >>> bernstein(2, 5, 0.4) 0.3456 """ # THIS IS NOT OPTIMIZED ! return allBernstein(n, u)[i]
[docs]def allBernstein(n, u): """Compute the value of all n-th degree Bernstein polynomials. Parameters: - `n`: int, degree of the polynomials - `u`: float, parametric value where the polynomials are evaluated Returns: an (n+1,) shaped float array with the value of all n-th degree Bernstein polynomials B(i,n) at parameter value u. Algorithm A1.3 from 'The NURBS Book' p20. """ # THIS IS NOT OPTIMIZED FOR PYTHON. B = np.zeros(n+1) B[0] = 1.0 u1 = 1.0-u for j in range(1, n+1): saved = 0.0 for k in range(j): temp = B[k] B[k] = saved + u1*temp saved = u * temp B[j] = saved return B
[docs]def find_span(U, u, p, n): """Find the knot span index of the parametric point u. Parameters ---------- U: float array (m+1,) The non-descending knot sequence: U[0] .. U[m] u: float The parametric value U[0] <= u <= U[m] for which to find the span p: int The degree of the B-spline basis functions n: int The number of control points - 1 = m - p - 1 Returns ------- int The index of the knot span. Notes ----- Algorithm A2.1 from 'The NURBS Book' p.68. """ if u == U[n+1]: # special case return(n) # do binary search low = p high = n + 1 mid = (low + high) // 2 cnt = 0 while u < U[mid] or u >= U[mid+1]: if u < U[mid]: high = mid else: low = mid mid = (low + high) // 2 cnt += 1 if cnt > 20: break return mid
[docs]def basis_funs(U, u, p, i): """Compute the nonvanishing B-spline basis functions for index span i. Parameters ---------- U: float array (m+1,) The knot sequence: U[0] .. U[m], non-descending. u: float The parametric value U[0] <= u <= U[m] where to compute the functions. p: int Degree of the B-spline basis functions i: int Index of the knot span for value u (from find_span()) Returns ------- float array (p+1) The (p+1) values of nonzero basis functions at u. Notes ----- Algorithm A2.2 from 'The NURBS Book' p.70. """ N = np.empty((p+1,)) # return array left = np.empty((p+1,)) # workspace right = np.empty((p+1,)) # workspace N[0] = 1.0 for j in range(1, p+1): left[j] = u - U[i+1-j] right[j] = U[i+j] - u saved = 0.0 for r in range(j): temp = N[r] / (right[r+1] + left[j-r]) N[r] = saved + right[r+1] * temp saved = left[j-r] * temp N[j] = saved return N
[docs]def basis_derivs(U, u, p, i, n): """Compute the nonvanishing B-spline basis functions and derivatives. Parameters ---------- U: float array (m+1,) The knot sequence: U[0] .. U[m], non-descending. u: float The parametric value U[0] <= u <= U[m] where to compute the functions. p: int Degree of the B-spline basis functions i: int Index of the knot span for value u (from find_span()) n: int Number of derivatives to compute (n <= p) Returns ------- float array (n+1, p+1) The (n+1, p+1) values of the nonzero basis functions and their first n derivatives at u Notes ----- Algorithm A2.3 from 'The NURBS Book' p.72. """ dN = np.empty((n+1, p+1)) # return array # workspaces ndu = np.empty((p+1, p+1)) a = np.empty((2*(p+1),)) left = np.empty((p+1,)) right = np.empty((p+1,)) ndu[0][0] = 1.0 for j in range(1, p+1): left[j] = u - U[i+1-j] right[j] = U[i+j]-u saved = 0.0 for r in range(j): # Lower triangle ndu[j][r] = right[r+1] + left[j-r] temp = ndu[r][j-1]/ndu[j][r] # Upper Triangle ndu[r][j] = saved + right[r+1]*temp saved = left[j-r]*temp ndu[j][j] = saved # Load the basis functions dN[0] = ndu[:,p] # Compute the derivatives (Eq. 2.9) for r in range(p+1): # Loop over function index s1, s2 = 0, p+1 # Alternate rows in array a a[0] = 1.0 # Loop to compute kth derivative for k in range(1, n+1): der = 0.0 rk = r-k; pk = p-k if r >= k: a[s2] = a[s1] / ndu[pk+1][rk] der = a[s2] * ndu[rk][pk] if rk >= -1: j1 = 1 else: j1 = -rk if r-1 <= pk: j2 = k-1 else: j2 = p-r for j in range(j1, j2+1): a[s2+j] = (a[s1+j] - a[s1+j-1]) / ndu[pk+1][rk+j] der += a[s2+j] * ndu[rk+j][pk] if r <= pk: a[s2+k] = -a[s1+k-1] / ndu[pk+1][r] der += a[s2+k] * ndu[r][pk] dN[k,r] = der s1, s2 = s2, s1 # Switch rows # Multiply by the correct factors r = p for k in range(1, n+1): dN[k] *= r r *= (p-k) return dN
# TODO: def basisFuns # TODO: def basisDerivs ipv basis_derivs # TODO: def curvePoints # TODO: def curveDerivs # TODO: def curveDecompose # TODO: def curveKnotRefine # TODO: def curveKnotRemove
[docs]def curveDegreeElevate(Pw, U, t): """Elevate the degree of the Nurbs curve. Parameters: - `Pw`: float array (nk,nd): nk=n+1 control points - `U`: int array(nu): nu=m+1 knot values - `t`: int: how much to elevate the degree Returns a tuple: - `Qw`: nh+1 new control points - `Uh`: mh+1 new knot values - `nh`: highest control point index - `mh`: highest knot index This is based on algorithm A5.9 from 'The NURBS Book' pg206. """ nk, nd = Pw.shape n = nk-1 m = U.shape[0]-1 p = m-n-1 # Workspace for return arrays Uh = np.zeros((U.shape[0]*(t+1))) Qw = np.zeros((Pw.shape[0]*(t+1), nd)) # Workspaces bezalfs = np.zeros((p+t+1, p+1)) bpts = np.zeros((p+1, nd)) ebpts = np.zeros((p+t+1, nd)) Nextbpts = np.zeros((p-1, nd)) alfs = np.zeros((p-1,)) ph = p+t ph2 = ph//2 # Compute Bezier degree elevation coefficients bezalfs[0, 0] = bezalfs[ph, p] = 1.0 for i in range(1, ph2+1): inv = 1.0 / binomial(ph, i) mpi = min(p, i) for j in range(max(0, i-t), mpi+1): bezalfs[i, j] = inv * binomial(p, j) * binomial(t, i-j) for i in range(ph2+1, ph): mpi = min(p, i) for j in range(max(0, i-t), mpi+1): bezalfs[i, j] = bezalfs[ph-i, p-j] # print("bezalfs:",bezalfs) mh = ph kind = ph+1 r = -1 a = p b = p+1 cind = 1 ua = U[0] Qw[0] = Pw[0] for i in range(ph+1): Uh[i] = ua # Initialize first Bezier segment for i in range(p+1): bpts[i] = Pw[i] # print("bpts =\n%s" % bpts[:p+1]) # Big loop thru knot vector while b < m: # print("Big loop b = %s < m = %s" % (b,m)) i = b while b < m and U[b] == U[b+1]: b += 1; mul = b-i+1 mh += mul+t ub = U[b] oldr = r r = p-mul # Insert knot ub r times lbz = (oldr+2)//2 if oldr > 0 else 1 rbz = ph-(r+1)//2 if r > 0 else ph if r > 0: # Insert knot to get Bezier segment numer = ub-ua for k in range(p, mul, -1): alfs[k-mul-1] = numer / (U[a+k] - ua) # print("alfs = %s" % alfs[:p]) for j in range(1, r+1): save = r-j s = mul+j for k in range(p, s-1, -1): bpts[k] = alfs[k-s]*bpts[k] + (1.0-alfs[k-s])*bpts[k-1] Nextbpts[save] = bpts[p] # print("Nextbpts %s = %s" %(save,Nextbpts[save])) # print("bpts =\n%s" % bpts[:p+1]) # Degree elevate Bezier for i in range(lbz, ph+1): # Only points lbz..ph are used below ebpts[i] = 0.0 mpi = min(p, i) for j in range(max(0, i-t), mpi+1): ebpts[i] += bezalfs[i, j]*bpts[j] # print("ebpts =\n%s" % ebpts[lbz:ph+1]) if oldr > 1: # Must remove knot U[a] oldr times first = kind-2 last = kind den = ub-ua bet = (ub-Uh[kind-1]/den) for tr in range(1, oldr): # Knot removal loop i = first j = last kj = j-kind+1 while j-i > tr: # Compute new control points if i < cind: alf = (ub-Uh[i]) / (ua-Uh[i]) Qw[i] = alf*Qw[i] + (1.0-alf)*Qw[i-1] if j >= lbz: if j-tr <= kind-ph+oldr: gam = (ub-Uh[j-tr]) / den ebpts[kj] = gam*ebpts[kj] + (1.0-gam)*ebpts[kj+1] else: ebpts[kj] = bet*ebpts[kj] + (1.0-bet)*ebpts[kj+1] i += 1 j -= 1 kj -= 1 first -= 1 last +=1 if a != p: # Load the knot ua for i in range(ph-oldr): Uh[kind] = ua kind += 1 for j in range(lbz, rbz+1): # Load control points into Qw Qw[cind] = ebpts[j] cind += 1 # print(Qw[:cind]) # print("Now b=%s, m=%s" % (b,m)) if b < m: # Set up for next passcthru loop for j in range(r): bpts[j] = Nextbpts[j] for j in range(r, p+1): bpts[j] = Pw[b-p+j] a = b b += 1 ua = ub else: # End knot for i in range(ph+1): Uh[kind+i] = ub nh = mh - ph - 1 Uh = Uh[:mh+1] Qw = Qw[:nh+1] return np.array(Pw), np.array(Uh), nh, mh
[docs]def BezDegreeReduce(Q, return_errfunc=False): """Degree reduce a Bezier curve. Parameters ---------- Q: float array (nk, nd) The control points of a Bezier curve of degree p = nk-1 return_errfunc: bool If True, also returns a function to evaluate the error along the parametric values. Returns ------- P: float array (nk-1, nd) The control points of a Bezier curve of degree p-1 that is as close as possible to the original curve. maxerr: float An upper bound on the error introduced by the degree reduction. errfunc: function A callable to evaluate the error as function of the parameter u. Only returned if return_errfunc is True. Notes ----- Based on The NURBS Book 5.6. """ nk, nd = Q.shape p = nk - 1 r = (p-1) // 2 alfs = np.arange(p) / p P = np.zeros((p, nd)) P[0] = Q[0] for i in range(1, r+1): P[i] = (Q[i] - alfs[i]*P[i-1]) / (1.0-alfs[i]) P[p-1] = Q[p] for i in range(p-2, r, -1): P[i] = (Q[i+1] - (1-alfs[i+1])*P[i+1]) / alfs[i+1] if p % 2 == 1: PrR = (Q[r+1] - (1-alfs[r+1])*P[r+1]) / alfs[r+1] Err = 0.5 * (1.0-alfs[r]) * length(P[r] - PrR) P[r] = 0.5 * (P[r] + PrR) else: Err = length(Q[r+1]-0.5*(P[r]+P[r+1])) # Max error # Note that maximum of Bernstein polynom (i,p) is at i/p if p % 2 == 0: errfunc = lambda u: Err * bernstein(r+1, p, u) # Note that maximum of Bernstein polynom (i,p) is at i/p maxerr = errfunc((r+1.)/p) else: errfunc = lambda u: Err * abs(bernstein(r, p, u) - bernstein(r+1, p, u)) # We guess that maximum is close to middle of r/p and r+1/p maxerr = max(errfunc(float(r)/p), errfunc(float(r+1)/p)) if return_errfunc: return P, maxerr, errfunc else: return P, maxerr
#from pyformex.plugins.nurbs import *
[docs]def curveDegreeReduce(Qw, U, tol=1.0): """Reduce the degree of the Nurbs curve. Parameters ---------- Qw: float array (nc, nd) The nc control points of the Nurbs curve U: float array (nu) The nu knot values of the Nurbs curve Returns ------- Pw: float array (nctrl, nd) The new control points U: float array (nknots) The new knot vector err: float array (nerr) The error vector This is algorithm A5.11 from 'The NURBS Book' pg223. """ nc, nd = Qw.shape n = nc-1 m = U.shape[0]-1 p = m-n-1 #print("Reduce degree of curve from %s to %s" % (p, p-1)) # Workspace for return arrays Uh = np.zeros((2*m+1,)) Pw = np.zeros((2*nc+1, nd)) # Set up workspaces bpts = np.zeros((p+1, nd)) # Bezier control points of current segment Nextbpts = np.zeros((p-1, nd)) # leftmost control points of next Bezier segment rbpts = np.zeros((p, nd)) # degree reduced Bezier control points alfs = np.zeros((p-1,)) # knot insertion alphas err = np.zeros((m,)) # error vector # Initialize some variables ph = p-1 mh = ph kind = ph+1 r = -1 a = p b = p+1 cind = 1 mult = p m = n+p+1 Pw[0] = Qw[0] Uh[:ph+1] = U[0] # Compute left end of knot vector bpts[:p+1] = Qw[:p+1] # Initialize first Bezier segment # error vector is initialized #print("Initial Uh") #print(Uh[:ph+1]) # Loop through the knot vector while b < m: # Compute knot multiplicity i = b while b < m and U[b] == U[b+1]: b += 1; mult = b-i+1 mh += mult-1 #print("Big loop b=%s < m=%s (mult=%s, mh=%s, )" % (b, m, mult, mh)) #print("a=%s;b=%s;u[a]..u[b]=%s" % (a, b, U[a:b+1])) #print("Segment bpts\n", bpts) oldr = r r = p-mult lbz = (oldr+2)//2 if oldr > 0 else 1 #print("oldr=%s; r=%s; lbz=%s" % (oldr, r, lbz)) # Insert knot U[b] r times if r > 0: # Insert knot to get Bezier segment #print("Insert knot %s %s times" % (U[b], r)) numer = U[b]-U[a] for k in range(p, mult-1, -1): alfs[k-mult-1] = numer / (U[a+k] - U[a]) #print("alfs = %s" % alfs[:p]) for j in range(1, r+1): save = r-j s = mult+j for k in range(p, s-1, -1): bpts[k] = alfs[k-s]*bpts[k] + (1.0-alfs[k-s])*bpts[k-1] Nextbpts[save] = bpts[p] #print("Nextbpts %s = %s" %(save, Nextbpts[save])) # Degree reduce Bezier segment # if debug: # print("Bezier segment degree reduction") # print("bpts =\n", bpts) # drawCtrlPts(bpts, color=magenta, marksize=8) rbpts, maxErr = BezDegreeReduce(bpts) # if debug: # print(f"Reduced ctrl points: {rbpts.shape}\n", rbpts[:p]) # print("Degree reduce error = %s"%maxErr) # drawCtrlPts(rbpts, color=cyan, marksize=8, linewidth=1, scurve=True) err[a] += maxErr if err[a] > tol: raise ValueError("Curve not degree reducible") return None # Curve not degree reducible if oldr > 0: #print("Remove knot %s %s times" % (U[a], oldr)) first = kind last = kind for k in range(oldr): i = first j = last kj = j-kind while j-i > k: alfa = (U[a]-Uh[i-1]) / (U[b]-Uh[i-1]) beta = (U[a]-Uh[j-k-1]) / (U[b]-Uh[j-k-1]) Pw[i-1] = (Pw[i-1] - (1.0-alfa)*Pw[i-2]) / alfa rbpts[kj] = (rbpts[kj] - beta*rbpts[kj+1])/(1.0-beta) i += 1 j -= 1 kj -= 1 # Compute knot removal error bounds (Br) if j-i < k: Br = length(Pw[i-2] - rbpts[kj+1]) else: delta = (U[a]-Uh[i-1]) / (U[b]-Uh[i-1]) A = delta*rbpts[kj+1] + (1.0-delta)*Pw[i-2] Br = length(Pw[i-1] - A) #print("Knot removal error = %s" % Br) # Update the error vector K = a+oldr-k q = (2*p-k+1)//2 L = K-q for ii in range(L, a+1): # These knot spans were affected err[ii] += Br if err[ii] > tol: raise ValueError("Curve not degree reducible") first -= 1 last += 1 cind = i-1 # Load knot vector and control points if a != p: # Load the knot U[a] for i in range(ph-oldr): #print("Load the knot ua=%s in Uh[%s]" % (U[a], kind)) Uh[kind] = U[a] kind += 1 for i in range(lbz, ph+1): # Load control points into Pw #print("Load control point %s from rbpts %s = %s" % (cind, i, rbpts[i])) Pw[cind] = rbpts[i] cind += 1 if b < m: # Set up for next pass thru loop for i in range(r): bpts[i] = Nextbpts[i] for i in range(r, p+1): bpts[i] = Qw[b-p+i] a = b b += 1 else: # End knot for i in range(ph+1): Uh[kind+i] = U[b] # if debug: # pause() nh = mh - ph - 1 Uh = Uh[:mh+1] Pw = Pw[:nh+1] return Pw, Uh, err
[docs]def curveUnclamp(P, U): """Unclamp a clamped curve. Input: P,U Output: P,U Note: this changes P and U inplace. Based on algorithm A12.1 of The NURBS Book. """ n = P.shape[0] - 1 m = U.shape[0] - 1 p = m - n - 1 # Unclamp at left end for i in range(p): U[p-i-1] = U[p-i] - (U[n-i+1]-U[n-i]) if i == p-1: break k = p-1 for j in range(i, -1, -1): alfa = (U[p]-U[k]) / (U[p+j+1]-U[k]) P[j] = (P[j] - alfa*P[j+1]) / (1.0-alfa) k -= 1 # Unclamp at right end for i in range(p): U[n+i+2] = U[n+i+1] + (U[p+i+1]-U[p+i]) if i == p-1: break for j in range(i, -1, -1): alfa = (U[n+1]-U[n-j]) / (U[n-j+i+2]-U[n-j]) P[n-j] = (P[n-j] - (1.0-alfa)*P[n-j-1]) / alfa return P, U
[docs]def curveGlobalInterpolationMatrix(u, p, t0, t1): """Compute the global curve interpolation matrix. Parameters ---------- u: float array (nc) The parameter values at the nc points Q to be interpolated p: int The degree of the B-spline to construct. t0: 0 | 1 1 if the tangent at the start of the curve is specified t1: 0 | 1 1 if the tangent at the end of the curve is specified Returns ------- U: float array (nU) The knot sequence, with nU = nu + p + 1, nu = nc + t0 + t1 A: float array (nu, nu) The coefficient matrix for the interpolation. The control points P can be found by solving the system of linear equations: A * P = Q. See Also -------- :func:`plugins.nurbs.globalInterpolationCurve`: the normal way to use this Notes ----- Modified algorithm A9.1 from 'The NURBS Book' p.369. """ nc = u.shape[0] nu = nc + t0 + t1 nU = nu + p + 1 m = nu + p; # Compute the knot vector U by averaging (9.8) U = np.zeros(nU) U[m-p:] = 1.0 for j in range(1-t0, nc-p+t1): for i in range(j, j+p): U[j+p+t0] += u[i] U[j+p+t0] /= p # Set up coefficient matrix A A = np.zeros((nu,nu)) A[0,0] = A[-1,-1] = 1.0 if t0: A[1,:2] = -1.0, 1.0 if t1: A[-2,-2:] = -1.0, 1.0 for i in range(1, nc-1): s = find_span(U, u[i], p, nu-1) A[t0+i, s-p:s+1] = basis_funs(U, u[i], p, s) return U, A
# TODO: merge with curveGlobalInterpolationMatrix # TODO: implement in nurbs_c
[docs]def curveGlobalInterpolationMatrix2(Q, D, u, p): """Compute the global curve interpolation matrix for all tangents given. Parameters ---------- Q: float array (nc) D: float array(nc) u: float array (nc) The parameter values at the nc points Q to be interpolated p: 2 | 3 The degree of the B-spline to construct. Returns ------- U: float array (nU) The knot sequence, with nU = 2*nc + p + 1 A: float array (2*nc, 2*nc) The coefficient matrix for the interpolation. The control points P can be found by solving the system of linear equations: A * P = Q. See Also -------- :func:`plugins.nurbs.globalInterpolationCurve`: the normal way to use this Notes ----- Modified algorithm A9.1 from 'The NURBS Book' p.369. """ nc, nd = Q.shape if D.shape != Q.shape or u.shape[0] != nc: raise ValueError("Incompatible shapes of Q, D, u") n = nc - 1 nvar = 2*nc nU = nvar + p + 1 m = nvar + p; # Knot vector U U = np.zeros(nU) U[m-p:] = 1.0 if p == 2: U[2:nU-2:2] = u U[3:nU-2:2] = (u[:-1] + u[1:]) / 2 elif p == 3: U[4] = u[1] / 2 U[-5] = (1+u[-2]) / 2 U[5:nU-5:2] = (2*u[1:-2] + u[2:-1]) / 3 U[6:nU-4:2] = (u[1:-2] + 2*u[2:-1]) / 3 else: raise ValueError("Degree should be 2 or 3") # Coefficient matrix A A = np.zeros((nvar,nvar)) A[0,0] = A[-2,-1] = 1.0 A[1,:2] = -1.0, 1.0 A[-1,-2:] = -1.0, 1.0 for i in range(1, nc-1): s = find_span(U, u[i], p, nvar-1) fd = basis_derivs(U, u[i], p, s, 1) # function values and 1st derivs A[2*i, s-p:s+1] = fd[0] A[2*i+1, s-p:s+1] = fd[1] # Right hand sides R R = np.empty((nvar, nd)) R[::2] = Q R[1::2] = D R[1] *= U[p+1] / 3 R[-1] *= (1-U[-(p+2)]) / 3 return U, A, R
[docs]def cubicSplineInterpolation(Q, t0, t1, U): """Compute the control points of a cubic spline interpolate. Parameters ---------- Q: float array (nc, 3) The nc points where the curve should pass through. t0: float array (3,) The tangent to the curve at the start point Q[0] t1: float array (3,) The tangent to the curve at the end point Q[nc-1] U: float array (nc+6,) The clamped knot vector: 3 zeros, the nc parameter values for the points, 3 ones. Returns ------- float array (nc+2, 3) The control points of the curve. With the given knots they will create a 3-rd degree NURBS curve that passes through the points Q and has derivatives t0 and t1 at its end. Based on algorithm A9.2 of 'The Nurbs Book', p. 373 """ # Initialize n = Q.shape[0] - 1 P = np.full((n+3, 3), np.nan) dd = np.full((n+1,), np.nan) # workspace P[0] = Q[0] P[1] = P[0] + U[4] / 3 * t0 P[n+2] = Q[n] P[n+1] = P[n+2] - (1-U[n+2]) / 3 * t1 abc = basis_funs(U, U[4], 3, 4) den = abc[1] P[2] =(Q[1] - abc[0]*P[1]) / den for i in range(3, n): dd[i] = abc[2]/den abc = basis_funs(U, U[i+2], 3, i+2) den = abc[1] - abc[0]*dd[i] P[i] = (Q[i-1] - abc[0]*P[i-1]) / den dd[n] = abc[2]/den abc = basis_funs(U, U[n+2], 3, n+2) den = abc[1] - abc[0]*dd[n] P[n] = (Q[n-1] - abc[2]*P[n+1] - abc[0]*P[n-1]) / den for i in range(n-1, 1, -1): P[i] = P[i] - dd[i+1]*P[i+1] return P
if __name__ == '__draw__': from pyformex.plugins.nurbs import NurbsCurve N = NurbsCurve([ [6.2, -7.3], [4.5, -6.7], [4.0, -5.2], [4.9, -3.5], [7.6, -2.5], [10.3, -3.3], [11.2, -5.0], [10.7, -6.7], [9.0, -7.5], ], degree=4, knots= [0., 0., 0., 0., 0., 1/3., 1/3., 2/3., 2/3., 1., 1., 1., 1., 1.]) clear() def drawNurbs(N, color=black): draw(N, color=color) draw(N.knotPoints(), color=color, marksize=5) X = N.coords.toCoords() draw(PolyLine(X), color=color) draw(X, color=color) drawNumbers(X, color=color) drawNurbs(N, color=red) P4, U, err = curveDegreeReduce(N.coords, N.knots, 1.0) print(P4.shape, len(U), len(err)) print(f"Maximum error {err}") N = NurbsCurve(control=P4, knots=U) drawNurbs(N, color=black) # End