class Srf: '''Construct a NURB surface structure, and check the format. The NURB surface is represented by a 4 dimensional b-spline. INPUT: cntrl - Control points, homogeneous coordinates (wx,wy,wz,w) For a surface [dim,nu,nv] matrix where nu is along the u direction and nv is along the v direction. dim is the dimension valid options are: 2 .... (x,y) 2D cartesian coordinates 3 .... (x,y,z) 3D cartesian coordinates 4 .... (wx,wy,wz,w) 4D homogeneous coordinates uknots - Knot sequence along the parametric u direction. vknots - Knot sequence along the paramteric v direction. NOTES: Its assumed that the input knot sequences span the interval [0.0,1.0] and are clamped to the control points at the end by a knot multiplicity equal to the spline order.''' def __init__(self, cntrl, uknots, vknots): self._bezier = None cntrl = numerix.asarray(cntrl, numerix.Float) (dim, nu, nv) = cntrl.shape if dim < 2 or dim > 4: raise NURBSError, 'Illegal control point format' elif dim < 4: self.cntrl = numerix.zeros((4, nu, nv), numerix.Float) self.cntrl[0:dim, :, :] = cntrl self.cntrl[-1, :, :] = numerix.ones((nu, nv), numerix.Float) else: self.cntrl = cntrl # Force the u knot sequence to be a vector in ascending order # and normalise between [0.0,1.0] uknots = numerix.sort(numerix.asarray(uknots, numerix.Float)) nku = uknots.shape[0] uknots = (uknots - uknots[0]) / (uknots[-1] - uknots[0]) if uknots[0] == uknots[-1]: raise NURBSError, 'Illegal uknots sequence' self.uknots = uknots # Force the v knot sequence to be a vector in ascending order # and normalise between [0.0,1.0] vknots = -numerix.sort(-numerix.asarray(vknots, numerix.Float)) nkv = vknots.shape[0] vknots = (vknots - vknots[0]) / (vknots[-1] - vknots[0]) if vknots[0] == vknots[-1]: raise NURBSError, 'Illegal vknots sequence' self.vknots = vknots # Spline Degree self.degree = [nku - nu - 1, nkv - nv - 1] if self.degree[0] < 0 or self.degree[1] < 0: raise NURBSError, 'NURBS order must be a positive integer' def trans(self, mat): "Apply the 4D transform matrix to the NURB control points." for v in range(self.cntrl.shape[2]): self.cntrl[:, :, v] = numerix.dot(mat, self.cntrl[:, :, v]) def swapuv(self): "Swap u and v parameters." self.cntrl = numerix.transpose(self.cntrl, (0, 2, 1)) temp = self.uknots[:] self.uknots = self.vknots[:] self.vknots = temp udegree, vdegree = self.degree self.degree[0] = vdegree self.degree[1] = udegree def reverse(self): "Reverse evaluation directions." coefs = self.cntrl[:, :, ::-1] self.cntrl = coefs[:, ::-1, :] self.uknots = 1. - self.uknots[::-1] self.vknots = 1. - self.vknots[::-1] def extractV(self, u): "Extract curve in v-direction at parameter u." if numerix.any(u < 0.) or numerix.any(u > 1.): raise NURBSError, 'Out of parameter range [0,1]' if u == 0.: cntrl = self.cntrl[:, 0, :] knots = self.vknots[:] elif u == 1.: cntrl = self.cntrl[:, -1, :] knots = self.vknots[:] else: uknots = numerix.repeat( numerix.asarray([u], numerix.Float), [self.degree[1] * (self.cntrl.shape[2] + 1)]) coefs = numerix.transpose(self.cntrl, (0, 2, 1)) coefs = numerix.resize( coefs, (4 * self.cntrl.shape[2], self.cntrl.shape[1])) coefs, knots = bspkntins(self.degree[0], coefs, self.uknots, uknots) coefs = numerix.resize(coefs, (4, self.cntrl.shape[2], coefs.shape[1])) cntrl = numerix.transpose(coefs, (0, 2, 1)) i = 0 j = knots[0] for k in knots[1:]: if k == u: break elif k != j: i += 1 j = k return Crv.Crv(cntrl[:, i, :], self.vknots[:]) def extractU(self, v): "Extract curve in u-direction at parameter v." if numerix.any(v < 0.) or numerix.any(v > 1.): raise NURBSError, 'Out of parameter range [0,1]' if v == 0.: cntrl = self.cntrl[:, :, 0] knots = self.uknots[:] elif v == 1.: cntrl = self.cntrl[:, :, -1] knots = self.uknots[:] else: vknots = numerix.repeat( numerix.asarray([v], numerix.Float), [self.degree[0] * (self.cntrl.shape[1] + 1)]) coefs = numerix.resize( self.cntrl, (4 * self.cntrl.shape[1], self.cntrl.shape[2])) coefs, knots = bspkntins(self.degree[1], coefs, self.vknots, vknots) cntrl = numerix.resize(coefs, (4, self.cntrl.shape[1], coefs.shape[1])) i = 0 j = knots[0] for k in knots[1:]: if k == v: break elif k != j: i += 1 j = k return Crv.Crv(cntrl[:, :, i], self.uknots[:]) def kntins(self, uknots, vknots=None): """Insert new knots into the surface uknots - knots to be inserted along u direction vknots - knots to be inserted along v direction NOTE: No knot multiplicity will be increased beyond the order of the spline""" if len(vknots): # Force the v knot sequence to be a vector in ascending order vknots = numerix.sort(numerix.asarray(vknots, numerix.Float)) if numerix.any(vknots < 0.) or numerix.any(vknots > 1.): raise NURBSError, 'Illegal vknots sequence' coefs = numerix.resize( self.cntrl, (4 * self.cntrl.shape[1], self.cntrl.shape[2])) coefs, self.vknots = bspkntins(self.degree[1], coefs, self.vknots, vknots) self.cntrl = numerix.resize( coefs, (4, self.cntrl.shape[1], coefs.shape[1])) if len(uknots): # Force the u knot sequence to be a vector in ascending order uknots = numerix.sort(numerix.asarray(uknots, numerix.Float)) if numerix.any(uknots < 0.) or numerix.any(uknots > 1.): raise NURBSError, 'Illegal uknots sequence' coefs = numerix.transpose(self.cntrl, (0, 2, 1)) coefs = numerix.resize( coefs, (4 * self.cntrl.shape[2], self.cntrl.shape[1])) coefs, self.uknots = bspkntins(self.degree[0], coefs, self.uknots, uknots) coefs = numerix.resize(coefs, (4, self.cntrl.shape[2], coefs.shape[1])) self.cntrl = numerix.transpose(coefs, (0, 2, 1)) def degelev(self, utimes, vtimes=None): """Degree elevate the surface. utimes - degree elevate utimes along u direction. vtimes - degree elevate vtimes along v direction.""" if vtimes: if vtimes < 0: raise NURBSError, 'Degree must be positive' coefs = numerix.resize( self.cntrl, (4 * self.cntrl.shape[1], self.cntrl.shape[2])) coefs, vknots, nh = bspdegelev(self.degree[1], coefs, self.vknots, vtimes) coefs = coefs[:, :nh + 1] self.vknots = vknots[:nh + self.degree[1] + vtimes + 2] self.degree[1] += vtimes self.cntrl = numerix.resize( coefs, (4, self.cntrl.shape[1], coefs.shape[1])) if utimes: if utimes < 0: raise NURBSError, 'Degree must be positive' coefs = numerix.transpose(self.cntrl, (0, 2, 1)) coefs = numerix.resize( coefs, (4 * self.cntrl.shape[2], self.cntrl.shape[1])) coefs, uknots, nh = bspdegelev(self.degree[0], coefs, self.uknots, utimes) coefs = coefs[:, :nh + 1] self.uknots = uknots[:nh + self.degree[0] + utimes + 2] self.degree[0] += utimes coefs = numerix.resize(coefs, (4, self.cntrl.shape[2], coefs.shape[1])) self.cntrl = numerix.transpose(coefs, (0, 2, 1)) def bezier(self, update=None): "Decompose surface to bezier patches and return overlaping control points." if update or not self._bezier: cntrl = numerix.resize( self.cntrl, (4 * self.cntrl.shape[1], self.cntrl.shape[2])) cntrl = bspbezdecom(self.degree[1], cntrl, self.vknots) cntrl = numerix.resize(cntrl, (4, self.cntrl.shape[1], cntrl.shape[1])) temp1 = cntrl.shape[1] temp2 = cntrl.shape[2] cntrl = numerix.transpose(cntrl, (0, 2, 1)) cntrl = numerix.resize(cntrl, (4 * temp2, temp1)) cntrl = bspbezdecom(self.degree[0], cntrl, self.uknots) cntrl = numerix.resize(cntrl, (4, temp2, cntrl.shape[1])) self._bezier = numerix.transpose(cntrl, (0, 2, 1)) return self._bezier def bounds(self): "Return the bounding box for the surface." w = self.cntrl[3, :, :] cx = numerix.sort(numerix.ravel(self.cntrl[0, :, :] / w)) cy = numerix.sort(numerix.ravel(self.cntrl[1, :, :] / w)) cz = numerix.sort(numerix.ravel(self.cntrl[2, :, :] / w)) return numerix.asarray([cx[0], cy[0], cz[0], cx[-1], cy[-1], cz[-1]], numerix.Float) def pnt3D(self, ut, vt=None): """Evaluate parametric point[s] and return 3D cartesian coordinate[s] If only ut is given then we will evaluate at scattered points. ut(0,:) represents the u direction. ut(1,:) represents the v direction. If both parameters are given then we will evaluate over a [u,v] grid.""" val = self.pnt4D(ut, vt) if len(val.shape) < 3: return val[0:3, :] / numerix.resize(val[-1, :], (3, val.shape[1])) else: #FIX! return val[0:3, :, :] / numerix.resize( val[-1, :, :], (3, val.shape[1], val.shape[2])) def pnt4D(self, ut, vt=None): """Evaluate parametric point[s] and return 4D homogeneous coordinates. If only ut is given then we will evaluate at scattered points. ut(0,:) represents the u direction. ut(1,:) represents the v direction. If both parameters are given then we will evaluate over a [u,v] grid.""" ut = numerix.asarray(ut, numerix.Float) if numerix.any(ut < 0.) or numerix.any(ut > 1.): raise NURBSError, 'NURBS curve parameter out of range [0,1]' if vt: #FIX! # Evaluate over a [u,v] grid vt = numerix.asarray(vt, numerix.Float) if numerix.any(vt < 0.) or numerix.any(vt > 1.): raise NURBSError, 'NURBS curve parameter out of range [0,1]' val = numerix.resize( self.cntrl, (4 * self.cntrl.shape[1], self.cntrl.shape[2])) val = bspeval(self.degree[1], val, self.vknots, vt) val = numerix.resize(val, (4, self.cntrl.shape[1], vt.shape[0])) val = numerix.transpose(val, (0, 2, 1)) val = numerix.resize(self.cntrl, (4 * vt.shape[0], self.cntrl.shape[1])) val = bspeval(self.degree[0], val, self.uknots, ut) val = numerix.resize(val, (4, vt.shape[0], ut.shape[0])) return numerix.transpose(val, (0, 2, 1)) # Evaluate at scattered points nt = ut.shape[1] uval = numerix.resize(self.cntrl, (4 * self.cntrl.shape[1], self.cntrl.shape[2])) uval = bspeval(self.degree[1], uval, self.vknots, ut[1, :]) uval = numerix.resize(uval, (4, self.cntrl.shape[1], nt)) val = numerix.zeros((4, nt), numerix.Float) for v in range(nt): val[:, v] = bspeval( self.degree[0], numerix.resize(uval[:, :, v], (4, self.cntrl.shape[1])), self.uknots, (ut[0, v], ))[:, 0] return val def plot(self, n=50, iso=8): """A simple plotting function based on dislin for debugging purpose. n = number of subdivisions. iso = number of iso line to plot in each dir. TODO: plot ctrl poins and knots.""" try: import dislin except ImportError, value: print 'dislin plotting library not available' return maxminx = numerix.sort( numerix.ravel(self.cntrl[0, :, :]) / numerix.ravel(self.cntrl[3, :, :])) minx = maxminx[0] maxx = maxminx[-1] if minx == maxx: minx -= 1. maxx += 1. maxminy = numerix.sort( numerix.ravel(self.cntrl[1, :, :]) / numerix.ravel(self.cntrl[3, :, :])) miny = maxminy[0] maxy = maxminy[-1] if miny == maxy: miny -= 1. maxy += 1. maxminz = numerix.sort( numerix.ravel(self.cntrl[2, :, :]) / numerix.ravel(self.cntrl[3, :, :])) minz = maxminz[0] maxz = maxminz[-1] if minz == maxz: minz -= 1. maxz += 1. dislin.metafl('cons') dislin.disini() dislin.hwfont() dislin.pagera() dislin.name('X-axis', 'X') dislin.name('Y-axis', 'Y') dislin.name('Z-axis', 'Z') dislin.graf3d(minx, maxx, 0, abs((maxx - minx) / 4.), miny, maxy, 0, abs((maxy - miny) / 4.), minz, maxz, 0, abs((maxz - minz) / 4.)) dislin.color('yellow') pnts0 = self.pnt3D([ numerix.arange(n + 1, typecode=numerix.Float) / n, numerix.zeros(n + 1, numerix.Float) ]) pnts1 = self.pnt3D([ numerix.arange(n + 1, typecode=numerix.Float) / n, numerix.ones(n + 1, numerix.Float) ]) pnts2 = self.pnt3D([ numerix.zeros(n + 1, numerix.Float), numerix.arange(n + 1, typecode=numerix.Float) / n ]) pnts3 = self.pnt3D([ numerix.ones(n + 1, numerix.Float), numerix.arange(n + 1, typecode=numerix.Float) / n ]) dislin.curv3d(pnts0[0, :], pnts0[1, :], pnts0[2, :], n + 1) dislin.curv3d(pnts1[0, :], pnts1[1, :], pnts1[2, :], n + 1) dislin.curv3d(pnts2[0, :], pnts2[1, :], pnts2[2, :], n + 1) dislin.curv3d(pnts3[0, :], pnts3[1, :], pnts3[2, :], n + 1) dislin.color('red') step = 1. / iso for uv in numerix.arange(step, 1., step): pnts = self.pnt3D([ numerix.arange(n + 1, typecode=numerix.Float) / n, numerix.zeros(n + 1, numerix.Float) + uv ]) dislin.curv3d(pnts[0, :], pnts[1, :], pnts[2, :], n + 1) pnts = self.pnt3D([ numerix.zeros(n + 1, numerix.Float) + uv, numerix.arange(n + 1, typecode=numerix.Float) / n ]) dislin.curv3d(pnts[0, :], pnts[1, :], pnts[2, :], n + 1) dislin.disfin()
#!/usr/bin/env python import dislin ctit = 'Symbols' dislin.setpag('da4p') dislin.metafl('cons') dislin.disini() dislin.pagera() dislin.complx() dislin.paghdr('H. Michels (', ')', 2, 0) dislin.height(60) nl = dislin.nlmess(ctit) dislin.messag(ctit, (2100 - nl) / 2, 200) dislin.height(50) dislin.hsymbl(120) ny = 150 for i in range(0, 22): if (i % 4) == 0: ny = ny + 400 nxp = 550 else: nxp = nxp + 350 nl = dislin.nlnumb(i, -1) dislin.number(i, -1, nxp - nl / 2, ny + 150)
class Crv: '''Construct a NURB curve and check the format. The NURB curve is represented by a 4 dimensional b-spline. INPUT: cntrl - Control points, homogeneous coordinates (wx,wy,wz,w) [dim,nu] matrix dim is the dimension valid options are: 2 .... (x,y) 2D cartesian coordinates 3 .... (x,y,z) 3D cartesian coordinates 4 .... (wx,wy,wz,w) 4D homogeneous coordinates uknots - Knot sequence along the parametric u direction. NOTES: Its assumed that the input knot sequences span the interval [0.0,1.0] and are clamped to the control points at the end by a knot multiplicity equal to the spline order.''' def __init__(self, cntrl, uknots): self._bezier = None # Force the u knot sequence to be a vector in ascending order # and normalise between [0.0,1.0] uknots = np.sort(np.asarray(uknots, np.float)) nku = uknots.shape[0] uknots = (uknots - uknots[0])/(uknots[-1] - uknots[0]) if uknots[0] == uknots[-1]: raise NURBSError, 'Illegal uknots sequence' self.uknots = uknots cntrl = np.asarray(cntrl, np.float) (dim, nu) = cntrl.shape if dim < 2 or dim > 4: raise NURBSError, 'Illegal control point format' elif dim < 4: self.cntrl = np.zeros((4, nu), np.float) self.cntrl[0:dim,:] = cntrl self.cntrl[-1,:] = np.ones((nu,)) else: self.cntrl = cntrl # Spline degree self.degree = nku - nu - 1 if self.degree < 0: raise NURBSError, 'NURBS order must be a positive integer' def trans(self, mat): "Apply the 4D transform matrix to the NURB control points." self.cntrl = np.dot(mat, self.cntrl) def reverse(self): "Reverse evaluation direction" self.cntrl = self.cntrl[:,::-1] self.uknots = 1 - self.uknots[::-1] def kntins(self, uknots): """Insert new knots into the curve NOTE: No knot multiplicity will be increased beyond the order of the spline""" if len(uknots): uknots = np.sort(np.asarray(uknots, np.float)) if np.less(uknots, 0.) or np.greater(uknots, 1.): raise NURBSError, 'NURBS curve parameter out of range [0,1]' self.cntrl, self.uknots = bspkntins(self.degree, self.cntrl, self.uknots, uknots) def degelev(self, degree): "Degree elevate the curve" if degree < 0: raise NURBSError, 'degree must be a positive number' if degree > 0: cntrl, uknots, nh = bspdegelev(self.degree, self.cntrl, self.uknots, degree) self.cntrl = cntrl[:,:nh + 1] self.uknots = uknots[:nh + self.degree + degree + 2] self.degree += degree def bezier(self, update = None): "Decompose curve to bezier segments and return overlaping control points" if update or not self._bezier: self._bezier = bspbezdecom(self.degree, self.cntrl, self.uknots) return self._bezier def bounds(self): "Return the boundingbox for the curve" ww = np.resize(self.cntrl[-1,:], (3, self.cntrl.shape[1])) cntrl = np.sort(self.cntrl[0:3,:]/ww) return np.asarray([cntrl[0,0], cntrl[1,0], cntrl[2,0], cntrl[0,-1], cntrl[1,-1], cntrl[2,-1]], np.float) def pnt3D(self, ut): "Evaluate parametric point[s] and return 3D cartesian coordinate[s]" val = self.pnt4D(ut) return val[0:3,:]/np.resize(val[-1,:], (3, val.shape[1])) def pnt4D(self, ut): "Evaluate parametric point[s] and return 4D homogeneous coordinates" ut = np.asarray(ut, np.float) if np.less(ut, 0.) or np.greater(ut, 1.): raise NURBSError, 'NURBS curve parameter out of range [0,1]' return bspeval(self.degree, self.cntrl, self.uknots, ut) def plot(self, n = 25): """A simple plotting function for debugging purpose n = number of subdivisions. Depends on the dislin plotting library.""" try: import dislin except ImportError, value: print 'dislin plotting library not available' return pnts = self.pnt3D(np.arange(n + 1, typecode = np.float)/n) knots = self.pnt3D(self.uknots) maxminx = np.sort(self.cntrl[0,:]/self.cntrl[3,:]) minx = maxminx[0] maxx = maxminx[-1] if minx == maxx: minx -= 1. maxx += 1. maxminy = np.sort(self.cntrl[1,:]/self.cntrl[3,:]) miny = maxminy[0] maxy = maxminy[-1] if miny == maxy: miny -= 1. maxy += 1. maxminz = np.sort(self.cntrl[2,:]/self.cntrl[3,:]) minz = maxminz[0] maxz = maxminz[-1] if minz == maxz: minz -= 1. maxz += 1. dislin.metafl('cons') dislin.disini() dislin.hwfont() dislin.pagera() dislin.name('X-axis', 'X') dislin.name('Y-axis', 'Y') dislin.name('Z-axis', 'Z') dislin.graf3d(minx, maxx, 0 , abs((maxx-minx)/4.), miny, maxy, 0 , abs((maxy-miny)/4.), minz, maxz, 0 , abs((maxz-minz)/4.)) dislin.color('yellow') dislin.curv3d(pnts[0,:], pnts[1,:], pnts[2,:], n+1) dislin.color('red') dislin.dashm() dislin.curv3d(self.cntrl[0,:]/self.cntrl[3,:], self.cntrl[1,:]/self.cntrl[3,:], self.cntrl[2,:]/self.cntrl[3,:], self.cntrl.shape[1]) dislin.color('white') dislin.incmrk(-1) dislin.marker(8) dislin.curv3d(knots[0,:], knots[1,:], knots[2,:], knots.shape[1]) dislin.disfin()