def test_periodic_split(self): # non-uniform rational knot vector of a periodic cubic n=7 curve controlpoints = [[1, 0, 1], [1, 1, .7], [0, 1, .89], [-1, 1, 0.5], [-1, 0, 1], [-1, -.5, 1], [1, -.5, 1]] basis = BSplineBasis(4, [-3, -2, -1, 0, 1, 2, 2.5, 4, 5, 6, 7, 8, 9, 9.5], 2) crv = Curve(basis, controlpoints, rational=True) crv2 = crv.split(1) # split at knot value crv3 = crv.split(6.5) # split outside existing knot self.assertEqual(len(crv), 7) self.assertEqual(len(crv2), 10) self.assertEqual(len(crv3), 11) self.assertEqual(crv.periodic(), True) self.assertEqual(crv2.periodic(), False) self.assertEqual(crv3.periodic(), False) t = np.linspace(6.5, 8, 13) # domain where all parameter values are the same pt = crv(t) pt2 = crv2(t) pt3 = crv3(t) self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0) self.assertAlmostEqual(np.linalg.norm(pt - pt3), 0.0)
def test_derivative(self): # testing the parametrization x(t) = [.5*x^3 / ((1-x)^3+.5*x^3), 0] cp = [[0, 0, 1], [0, 0, 0], [0, 0, 0], [.5, 0, .5]] crv = Curve(BSplineBasis(4), cp, rational=True) def expect_derivative(x): return 6 * (1 - x)**2 * x**2 / (x**3 - 6 * x**2 + 6 * x - 2)**2 def expect_derivative_2(x): return -12 * x * (x**5 - 3 * x**4 + 2 * x**3 + 4 * x**2 - 6 * x + 2) / (x**3 - 6 * x**2 + 6 * x - 2)**3 # insert a few more knots to spice things up crv.insert_knot([.2, .71]) self.assertAlmostEqual( crv.derivative(0.32, 1)[0], expect_derivative(0.32)) self.assertAlmostEqual(crv.derivative(0.32, 1)[1], 0) self.assertAlmostEqual( crv.derivative(0.71, 1)[0], expect_derivative(0.71)) self.assertAlmostEqual( crv.derivative(0.22, 2)[0], expect_derivative_2(0.22)) self.assertAlmostEqual(crv.derivative(0.22, 2)[1], 0) self.assertAlmostEqual( crv.derivative(0.86, 2)[0], expect_derivative_2(0.86))
def test_torsion(self): # planar curves have zero torsion controlpoints = [[1, 0, 1], [1, 1, .7], [0, 1, .89], [-1, 1, 0.5], [-1, 0, 1], [-1, -.5, 1], [1, -.5, 1]] basis = BSplineBasis(4, [-3, -2, -1, 0, 1, 2, 2.5, 4, 5, 6, 7, 8, 9, 9.5], 2) crv = Curve(basis, controlpoints, rational=True) self.assertAlmostEqual(crv.torsion(.3), 0.0) # test multiple evaluation points t = np.linspace(0, 1, 10) k = crv.torsion(t) self.assertEqual(len(k.shape), 1) # is vector self.assertEqual(k.shape[0], 10) # length 10 vector self.assertTrue(np.allclose(k, 0.0)) # zero torsion for planar curves # test helix: [a*cos(t), a*sin(t), b*t] t = np.linspace(0, 6 * pi, 300) a = 3.0 b = 2.0 x = np.array([a * np.cos(t), a * np.sin(t), b * t]) crv = cf.cubic_curve(x.T, t=t) t = np.linspace(0, 6 * pi, 10) k = crv.torsion(t) # this is a helix approximation, hence atol=1e-3 self.assertTrue(np.allclose(k, b / (a**2 + b**2), atol=1e-3)) # helix have const. torsion
def test_surface_loft(self): crv1 = Curve(BSplineBasis(3, range(11), 1), [[1,-1], [1,0], [1,1], [-1,1], [-1,0], [-1,-1]]) crv2 = cf.circle(2) + (0,0,1) crv3 = Curve(BSplineBasis(4, range(11), 2), [[1,-1,2], [1,1,2], [-1,1,2], [-1,-1,2]]) crv4 = cf.circle(2) + (0,0,3) surf = sf.loft(crv1, crv2, crv3, crv4) crv1.set_dimension(3) # for convenience when evaluating t = np.linspace( 0, 1, 13) u = np.linspace(crv1.start(0), crv1.end(0), 13) pt = crv1(u) pt2 = surf(t,0).reshape(13,3) self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0) u = np.linspace(crv2.start(0), crv2.end(0), 13) pt = crv2(u) pt2 = surf(t,1).reshape(13,3) self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0) u = np.linspace(crv3.start(0), crv3.end(0), 13) pt = crv3(u) pt2 = surf(t,2).reshape(13,3) self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0) u = np.linspace(crv4.start(0), crv4.end(0), 13) pt = crv4(u) pt2 = surf(t,3).reshape(13,3) self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)
def poisson_patch(bottom, right, top, left): from nutils import version if version != '3.0': raise ImportError('Outdated nutils version detected, only v3.0 supported. Upgrade by \"pip install --upgrade nutils\"') from nutils import mesh, function as fn from nutils import _, log # error test input if left.rational or right.rational or top.rational or bottom.rational: raise RuntimeError('poisson_patch not supported for rational splines') # these are given as a oriented loop, so make all run in positive parametric direction top.reverse() left.reverse() # in order to add spline surfaces, they need identical parametrization Curve.make_splines_identical(top, bottom) Curve.make_splines_identical(left, right) # create computational (nutils) mesh p1 = bottom.order(0) p2 = left.order(0) n1 = len(bottom) n2 = len(left) dim= left.dimension k1 = bottom.knots(0) k2 = left.knots(0) m1 = [bottom.order(0) - bottom.continuity(k) - 1 for k in k1] m2 = [left.order(0) - left.continuity(k) - 1 for k in k2] domain, geom = mesh.rectilinear([k1, k2]) basis = domain.basis('spline', [p1-1, p2-1], knotmultiplicities=[m1,m2]) # assemble system matrix grad = basis.grad(geom) outer = fn.outer(grad,grad) integrand = outer.sum(-1) matrix = domain.integrate(integrand, geometry=geom, ischeme='gauss'+str(max(p1,p2)+1)) # initialize variables controlpoints = np.zeros((n1,n2,dim)) rhs = np.zeros((n1*n2)) constraints = np.array([[np.nan]*n2]*n1) # treat all dimensions independently for d in range(dim): # add boundary conditions constraints[ 0, :] = left[ :,d] constraints[-1, :] = right[ :,d] constraints[ :, 0] = bottom[:,d] constraints[ :,-1] = top[ :,d] # solve system lhs = matrix.solve(rhs, constrain=np.ndarray.flatten(constraints), solver='cg', tol=state.controlpoint_absolute_tolerance) # wrap results into splipy datastructures controlpoints[:,:,d] = np.reshape(lhs, (n1,n2), order='C') return Surface(bottom.bases[0], left.bases[0], controlpoints, bottom.rational, raw=True)
def test_center(self): # create the geometric mapping x(t) = t, y(t) = t^3, t=[0,1] cp = [[0, 0], [1.0 / 3, 0], [2.0 / 3, 0], [1.0, 1]] basis = BSplineBasis(4) crv = Curve(basis, cp) center = crv.center() self.assertAlmostEqual(center[0], .5) self.assertAlmostEqual(center[1], .25)
def test_center(self): # create the geometric mapping x(t) = t, y(t) = t^3, t=[0,1] cp = [[0, 0], [1.0/3, 0], [2.0/3, 0], [1.0, 1]] basis = BSplineBasis(4) crv = Curve(basis, cp) center = crv.center() self.assertAlmostEqual(center[0], .5) self.assertAlmostEqual(center[1], .25)
def test_raise_order(self): # non-uniform knot vector of a squiggly quadratic n=5 curve in 3D controlpoints = [[0, 0, 0], [1, 1, 1], [2, -1, 0], [3, 0, -1], [0, 0, -5]] crv = Curve(BSplineBasis(3, [0, 0, 0, .3, .4, 1, 1, 1]), controlpoints) evaluation_point1 = crv(0.37) crv.raise_order(2) evaluation_point2 = crv(0.37) # ensure that curve has not chcanged, by comparing evaluation of it self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2]) # ensure that curve has the right order self.assertEqual(crv.order(0), 5) # check integer type for argument with self.assertRaises(TypeError): crv.raise_order(0.5) # check logic error for negative argument with self.assertRaises(Exception): crv.raise_order(-1)
def loft(*curves): if len(curves) == 1: curves = curves[0] # clone input, so we don't change those references # make sure everything has the same dimension since we need to compute length curves = [c.clone().set_dimension(3) for c in curves] if len(curves)==2: return edge_curves(curves) elif len(curves)==3: # can't do cubic spline interpolation, so we'll do quadratic basis2 = BSplineBasis(3) dist = basis2.greville() else: x = [c.center() for c in curves] # create knot vector from the euclidian length between the curves dist = [0] for (x1,x0) in zip(x[1:],x[:-1]): dist.append(dist[-1] + np.linalg.norm(x1-x0)) # using "free" boundary condition by setting N'''(u) continuous at second to last and second knot knot = [dist[0]]*4 + dist[2:-2] + [dist[-1]]*4 basis2 = BSplineBasis(4, knot) n = len(curves) for i in range(n): for j in range(i+1,n): Curve.make_splines_identical(curves[i], curves[j]) basis1 = curves[0].bases[0] m = basis1.num_functions() u = basis1.greville() # parametric interpolation points v = dist # parametric interpolation points # compute matrices Nu = basis1(u) Nv = basis2(v) Nu_inv = np.linalg.inv(Nu) Nv_inv = np.linalg.inv(Nv) # compute interpolation points in physical space x = np.zeros((m,n, curves[0][0].size)) for i in range(n): x[:,i,:] = Nu * curves[i].controlpoints # solve interpolation problem cp = np.tensordot(Nv_inv, x, axes=(1,1)) cp = np.tensordot(Nu_inv, cp, axes=(1,1)) # re-order controlpoints so they match up with Surface constructor cp = cp.transpose((1, 0, 2)) cp = cp.reshape(n*m, cp.shape[2]) return Surface(basis1, basis2, cp, curves[0].rational)
def edge_curves(*curves): """edge_curves(curves...) Create the surface defined by the region between the input curves. In case of four input curves, these must be given in an ordered directional closed loop around the resulting surface. :param [Curve] curves: Two or four edge curves :return: The enclosed surface :rtype: Surface :raises ValueError: If the length of *curves* is not two or four """ if len(curves) == 1: # probably gives input as a list-like single variable curves = curves[0] if len(curves) == 2: crv1 = curves[0].clone() crv2 = curves[1].clone() Curve.make_splines_identical(crv1, crv2) (n, d) = crv1.controlpoints.shape # d = dimension + rational controlpoints = np.zeros((2 * n, d)) controlpoints[:n, :] = crv1.controlpoints controlpoints[n:, :] = crv2.controlpoints linear = BSplineBasis(2) return Surface(crv1.bases[0], linear, controlpoints, crv1.rational) elif len(curves) == 4: # coons patch (https://en.wikipedia.org/wiki/Coons_patch) bottom = curves[0] right = curves[1] top = curves[2].clone() left = curves[3].clone() # gonna change these two, so make copies top.reverse() left.reverse() # create linear interpolation between opposing sides s1 = edge_curves(bottom, top) s2 = edge_curves(left, right) s2.swap() # create (linear,linear) corner parametrization linear = BSplineBasis(2) rat = s1.rational # using control-points from top/bottom, so need to know if these are rational s3 = Surface(linear, linear, [bottom[0], bottom[-1], top[0], top[-1]], rat) # in order to add spline surfaces, they need identical parametrization Surface.make_splines_identical(s1, s2) Surface.make_splines_identical(s1, s3) Surface.make_splines_identical(s2, s3) result = s1 result.controlpoints += s2.controlpoints result.controlpoints -= s3.controlpoints return result else: raise ValueError('Requires two or four input curves')
def test_flip_parametrization(self): # non-uniform knot vector of a squiggly quadratic n=4 curve controlpoints = [[0, 0, 0], [1, 1, 0], [2, -1, 0], [3, 0, 0]] crv = Curve(BSplineBasis(3, [0, 0, 0, .3, 1, 1, 1]), controlpoints) p1 = crv(0.23) crv.reverse() p2 = crv(0.77) self.assertAlmostEqual(p1[0], p2[0]) self.assertAlmostEqual(p1[1], p2[1]) self.assertAlmostEqual(p1[2], p2[2])
def test_make_periodic_reconstruct(self): orig = Curve( BSplineBasis(4, [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7], 2), [[1, 1], [2, 2], [3, 3], [4, 4]], rational=True, ) recons = orig.split(0).make_periodic(2) self.assertAlmostEqual( np.linalg.norm(orig.controlpoints - recons.controlpoints), 0.0) self.assertAlmostEqual( np.linalg.norm(orig.bases[0].knots - recons.bases[0].knots), 0.0)
def test_constructor(self): # test 3D constructor crv = Curve(controlpoints=[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]) val = crv(0.5) self.assertEqual(val[0], 0.5) self.assertEqual(crv.dimension, 3) # test 2D constructor crv2 = Curve() val = crv(0.5) self.assertEqual(val[0], 0.5) self.assertEqual(crv2.dimension, 2)
def test_surface_loft(self): crv1 = Curve(BSplineBasis(3, range(11), 1), [[1,-1], [1,0], [1,1], [-1,1], [-1,0], [-1,-1]]) crv2 = CurveFactory.circle(2) + (0,0,1) crv3 = Curve(BSplineBasis(4, range(11), 2), [[1,-1,2], [1,1,2], [-1,1,2], [-1,-1,2]]) crv4 = CurveFactory.circle(2) + (0,0,3) surf = SurfaceFactory.loft(crv1, crv2, crv3, crv4) crv1.set_dimension(3) # for convenience when evaluating t = np.linspace( 0, 1, 13) u = np.linspace(crv1.start(0), crv1.end(0), 13) pt = crv1(u) pt2 = surf(t,0).reshape(13,3) self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0) u = np.linspace(crv2.start(0), crv2.end(0), 13) pt = crv2(u) pt2 = surf(t,1).reshape(13,3) self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0) u = np.linspace(crv3.start(0), crv3.end(0), 13) pt = crv3(u) pt2 = surf(t,2).reshape(13,3) self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0) u = np.linspace(crv4.start(0), crv4.end(0), 13) pt = crv4(u) pt2 = surf(t,3).reshape(13,3) self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)
def test_edge_curves(self): # create an arrow-like 2D geometry with the pointy end at (-1,1) towards up and left # mixes rational and non-rational curves with different parametrization spaces c1 = CurveFactory.circle_segment(pi / 2) c2 = Curve(BSplineBasis(2, [0, 0, 1, 2, 2]), [[0, 1], [-1, 1], [-1, 0]]) c3 = CurveFactory.circle_segment(pi / 2) c3.rotate(pi) c4 = Curve(BSplineBasis(2), [[0, -1], [1, 0]]) surf = SurfaceFactory.edge_curves(c1, c2, c3, c4) # srf spits out parametric space (0,1)^2, so we sync these up to input curves c3.reverse() c4.reverse() c1.reparam() c2.reparam() c3.reparam() c4.reparam() for u in np.linspace(0, 1, 7): self.assertAlmostEqual(surf(u, 0)[0], c1(u)[0]) # x-coord, bottom crv self.assertAlmostEqual(surf(u, 0)[1], c1(u)[1]) # y-coord, bottom crv for u in np.linspace(0, 1, 7): self.assertAlmostEqual(surf(u, 1)[0], c3(u)[0]) # x-coord, top crv self.assertAlmostEqual(surf(u, 1)[1], c3(u)[1]) # y-coord, top crv for v in np.linspace(0, 1, 7): self.assertAlmostEqual(surf(0, v)[0], c4(v)[0]) # x-coord, left crv self.assertAlmostEqual(surf(0, v)[1], c4(v)[1]) # y-coord, left crv for v in np.linspace(0, 1, 7): self.assertAlmostEqual(surf(1, v)[0], c2(v)[0]) # x-coord, right crv self.assertAlmostEqual(surf(1, v)[1], c2(v)[1]) # y-coord, right crv
def polygon(*points, **keywords): """ Create a linear interpolation between input points. :param [array-like] points: The points to interpolate :param bool relative: If controlpoints are interpreted as relative to the previous one :param [float] t: specify parametric interpolation points (the knot vector) :return: Linear curve through the input points :rtype: Curve """ if len(points) == 1: points = points[0] knot = keywords.get('t', []) if len(knot) == 0: # establish knot vector based on eucledian length between points knot = [0, 0] prevPt = points[0] for pt in points[1:]: dist = 0 for (x0, x1) in zip(prevPt, pt): # loop over (x,y) and maybe z-coordinate dist += (x1 - x0)**2 knot.append(knot[-1] + sqrt(dist)) prevPt = pt knot.append(knot[-1]) else: # use knot vector given as input argument knot = [knot[0]] + list(knot) + [knot[-1]] relative = keywords.get('relative', False) if relative: points = list(points) for i in range(1, len(points)): points[i] = [x0 + x1 for (x0,x1) in zip(points[i-1], points[i])] return Curve(BSplineBasis(2, knot), points)
def const_par_curve(self, knot, direction): """ Get a Curve representation of the parametric line of some constant knot value. :param float knot: The constant knot value to sample the surface :param int direction: The parametric direction for the constant value :return: curve on this surface :rtype: Curve """ direction = check_direction(direction, 2) # clone basis since we need to augment this by knot insertion b = self.bases[direction].clone() # compute mapping matrix C which is the knotinsertion operator mult = min(b.continuity(knot), b.order - 1) C = np.identity(self.shape[direction]) for i in range(mult): C = b.insert_knot(knot) @ C # at this point we have a C0 basis, find the right interpolating index i = max(bisect_left(b.knots, knot) - 1, 0) # compute the controlpoints and return Curve cp = np.tensordot(C[i, :], self.controlpoints, axes=(0, direction)) return Curve(self.bases[1 - direction], cp, self.rational)
def n_gon(n=5, r=1, center=(0,0,0), normal=(0,0,1)): """ Create a regular polygon of *n* equal sides centered at the origin. :param int n: Number of sides and vertices :param float r: Radius :param array-like center: local origin :param array-like normal: local normal :return: A linear, periodic, 2D curve :rtype: Curve :raises ValueError: If radius is not positive :raises ValueError: If *n* < 3 """ if r <= 0: raise ValueError('radius needs to be positive') if n < 3: raise ValueError('regular polygons need at least 3 sides') cp = [] dt = 2 * pi / n knot = [-1] for i in range(n): cp.append([r * cos(i * dt), r * sin(i * dt)]) knot.append(i) knot += [n, n+1] basis = BSplineBasis(2, knot, 0) result = Curve(basis, cp) return flip_and_move_plane_geometry(result, center, normal)
def bezier(pts, quadratic=False, relative=False): """ Generate a cubic or quadratic bezier curve from a set of control points :param [array-like] pts: list of control-points. In addition to a starting point we need three points per bezier interval for cubic splines and two points for quadratic splines :param bool quadratic: True if a quadratic spline is to be returned, False if a cubic spline is to be returned :param bool relative: If controlpoints are interpreted as relative to the previous one :return: Bezier curve :rtype: Curve """ if quadratic: p = 3 else: p = 4 # compute number of intervals n = int((len(pts)-1)/(p-1)) # generate uniform knot vector of repeated integers knot = list(range(n+1)) * (p-1) + [0, n] knot.sort() if relative: pts = copy.deepcopy(pts) for i in range(1, len(pts)): pts[i] = [x0 + x1 for (x0,x1) in zip(pts[i-1], pts[i])] return Curve(BSplineBasis(p, knot), pts)
def read(self): lines = self.lines() version = next(lines).split() assert version[0] == 'C' assert version[3] == '0' # No support for rational SPL yet pardim = int(version[1]) physdim = int(version[2]) orders = [int(k) for k in islice(lines, pardim)] ncoeffs = [int(k) for k in islice(lines, pardim)] totcoeffs = int(np.prod(ncoeffs)) nknots = [a + b for a, b in zip(orders, ncoeffs)] next(lines) # Skip spline accuracy knots = [[float(k) for k in islice(lines, nkts)] for nkts in nknots] bases = [BSplineBasis(p, kts, -1) for p, kts in zip(orders, knots)] cpts = np.array([float(k) for k in islice(lines, totcoeffs * physdim)]) cpts = cpts.reshape(physdim, *(ncoeffs[::-1])).transpose() if pardim == 1: patch = Curve(*bases, controlpoints=cpts, raw=True) elif pardim == 2: patch = Surface(*bases, controlpoints=cpts, raw=True) elif pardim == 3: patch = Volume(*bases, controlpoints=cpts, raw=True) else: patch = SplineObject(bases, controlpoints=cpts, raw=True) return [patch]
def test_edge_curves_elasticity(self): # create an arrow-like 2D geometry with the pointy end at (-1,1) towards up and left # rebuild to avoid rational representations c1 = CurveFactory.circle_segment(pi / 2).rebuild(3,11) c2 = Curve(BSplineBasis(2, [0, 0, 1, 2, 2]), [[0, 1], [-1, 1], [-1, 0]]) c3 = CurveFactory.circle_segment(pi / 2).rebuild(3,11) c3.rotate(pi) c4 = Curve(BSplineBasis(2), [[0, -1], [1, 0]]).rebuild(3,10) c4 = c4.rebuild(4,11) surf = SurfaceFactory.edge_curves([c1, c2, c3, c4], type='elasticity') # check right dimensions of output self.assertEqual(surf.shape[0], 11) # 11 controlpoints in the circle segment self.assertEqual(surf.shape[1], 13) # 11 controlpoints in c4, +2 for C0-knot in c1 self.assertEqual(surf.order(0), 3) self.assertEqual(surf.order(1), 4) # check that c1 edge conforms to surface edge u = np.linspace(0,1,7) c1.reparam() pts_surf = surf(u,0.0) pts_c1 = c1(u) for (xs,xc) in zip(pts_surf[:,0,:], pts_c1): self.assertTrue(np.allclose(xs, xc)) # check that c2 edge conforms to surface edge v = np.linspace(0,1,7) c2.reparam() pts_surf = surf(1.0,v) pts_c2 = c2(v) for (xs,xc) in zip(pts_surf[:,0,:], pts_c2): self.assertTrue(np.allclose(xs, xc))
def get_spline(spline, n, p, rational=False): basis = BSplineBasis(p, [0]*(p-1) + list(range(n-p+2)) + [n-p+1]*(p-1)) if spline == 'curve': return Curve(basis, rational=rational) elif spline == 'surface': return Surface(basis, basis, rational=rational) elif spline == 'volume': return Volume(basis, basis, basis, rational=rational) return None
def test_edge_curves(self): # create an arrow-like 2D geometry with the pointy end at (-1,1) towards up and left # mixes rational and non-rational curves with different parametrization spaces c1 = CurveFactory.circle_segment(pi / 2) c2 = Curve(BSplineBasis(2, [0, 0, 1, 2, 2]), [[0, 1], [-1, 1], [-1, 0]]) c3 = CurveFactory.circle_segment(pi / 2) c3.rotate(pi) c4 = Curve(BSplineBasis(2), [[0, -1], [1, 0]]) surf = SurfaceFactory.edge_curves(c1, c2, c3, c4) # srf spits out parametric space (0,1)^2, so we sync these up to input curves c3.reverse() c4.reverse() c1.reparam() c2.reparam() c3.reparam() c4.reparam() for u in np.linspace(0, 1, 7): self.assertAlmostEqual(surf(u, 0)[0], c1(u)[0]) # x-coord, bottom crv self.assertAlmostEqual(surf(u, 0)[1], c1(u)[1]) # y-coord, bottom crv for u in np.linspace(0, 1, 7): self.assertAlmostEqual(surf(u, 1)[0], c3(u)[0]) # x-coord, top crv self.assertAlmostEqual(surf(u, 1)[1], c3(u)[1]) # y-coord, top crv for v in np.linspace(0, 1, 7): self.assertAlmostEqual(surf(0, v)[0], c4(v)[0]) # x-coord, left crv self.assertAlmostEqual(surf(0, v)[1], c4(v)[1]) # y-coord, left crv for v in np.linspace(0, 1, 7): self.assertAlmostEqual(surf(1, v)[0], c2(v)[0]) # x-coord, right crv self.assertAlmostEqual(surf(1, v)[1], c2(v)[1]) # y-coord, right crv # add a case where opposing sites have mis-matching rationality crvs = Surface().edges() # returned in order umin, umax, vmin, vmax crvs[0].force_rational() crvs[1].reverse() crvs[2].reverse() # input curves should be clockwise oriented closed loop srf = SurfaceFactory.edge_curves(crvs[0], crvs[3], crvs[1], crvs[2]) crvs[1].reverse() u = np.linspace(0,1,7) self.assertTrue(np.allclose(srf(u,0).reshape((7,2)), crvs[0](u))) self.assertTrue(np.allclose(srf(u,1).reshape((7,2)), crvs[1](u))) # test self-organizing curve ordering when they are not sequential srf = SurfaceFactory.edge_curves(crvs[0], crvs[2].reverse(), crvs[3], crvs[1]) u = np.linspace(0,1,7) self.assertTrue(np.allclose(srf(u,0).reshape((7,2)), crvs[0](u))) self.assertTrue(np.allclose(srf(u,1).reshape((7,2)), crvs[1](u))) # test error handling with self.assertRaises(ValueError): srf = SurfaceFactory.edge_curves(crvs + (Curve(),)) # 5 input curves
def test_force_rational(self): # non-uniform knot vector of a squiggly quadratic n=4 curve controlpoints = [[0, 0, 0], [1, 1, 0], [2, -1, 0], [3, 0, 0]] crv = Curve(BSplineBasis(3, [0, 0, 0, .3, 1, 1, 1]), controlpoints) evaluation_point1 = crv(0.23) control_point1 = crv[0] crv.force_rational() evaluation_point2 = crv(0.23) control_point2 = crv[0] # ensure that curve has not chcanged, by comparing evaluation of it self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2]) # ensure that we include rational weights of 1 self.assertEqual(len(control_point1), 3) self.assertEqual(len(control_point2), 4) self.assertEqual(control_point2[3], 1) self.assertEqual(crv.rational, True)
def test_volume_loft(self): crv1 = Curve(BSplineBasis(3, range(11), 1), [[1, -1], [1, 0], [1, 1], [-1, 1], [-1, 0], [-1, -1]]) crv2 = CurveFactory.circle(2) + (0, 0, 1) crv3 = Curve(BSplineBasis(4, range(11), 2), [[1, -1, 2], [1, 1, 2], [-1, 1, 2], [-1, -1, 2]]) crv4 = CurveFactory.circle(2) + (0, 0, 3) surf = [] for c in [crv1, crv2, crv3, crv4]: c2 = c.clone() c2.project('z') surf.append(SurfaceFactory.edge_curves(c, c2)) vol = VolumeFactory.loft(surf) surf[0].set_dimension(3) # for convenience when evaluating t = np.linspace(0, 1, 9) s = np.linspace(0, 1, 9) u = np.linspace(surf[0].start(0), surf[0].end(0), 9) v = np.linspace(surf[0].start(1), surf[0].end(1), 9) pt = surf[0](u, v) pt2 = vol(s, t, 0).reshape(9, 9, 3) self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0) u = np.linspace(surf[1].start(0), surf[1].end(0), 9) u = np.linspace(surf[1].start(1), surf[1].end(1), 9) pt = surf[1](u, v) pt2 = vol(s, t, 1).reshape(9, 9, 3) self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0) u = np.linspace(surf[2].start(0), surf[2].end(0), 9) v = np.linspace(surf[2].start(1), surf[2].end(1), 9) pt = surf[2](u, v) pt2 = vol(s, t, 2).reshape(9, 9, 3) self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0) u = np.linspace(surf[3].start(0), surf[3].end(0), 9) v = np.linspace(surf[3].start(1), surf[3].end(1), 9) pt = surf[3](u, v) pt2 = vol(s, t, 3).reshape(9, 9, 3) self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0)
def test_thicken(self): c = Curve() # 2D curve from (0,0) to (1,0) s = sf.thicken(c, .5) # extend to y=[-.5, .5] self.assertTupleEqual(s.order(), (2, 2)) self.assertTupleEqual(s.start(), (0, 0)) self.assertTupleEqual(s.end(), (1, 1)) self.assertTupleEqual(s.bounding_box()[0], (0.0, 1.0)) self.assertTupleEqual(s.bounding_box()[1], (-.5, .5)) # test a case with vanishing velocity. x'(t)=0, y'(t)=0 for t=0 c = Curve(BSplineBasis(3), [[0, 0], [0, 0], [1, 0]]) # x(t)=t^2, y(t)=0 s = sf.thicken(c, .5) self.assertTupleEqual(s.order(), (3, 2)) self.assertTupleEqual(s.start(), (0, 0)) self.assertTupleEqual(s.end(), (1, 1)) self.assertTupleEqual(s.bounding_box()[0], (0.0, 1.0)) self.assertTupleEqual(s.bounding_box()[1], (-.5, .5)) def myThickness(t): return t**2 c = Curve(BSplineBasis(3)) s = sf.thicken(c, myThickness) self.assertTupleEqual(s.order(), (3, 2)) self.assertTupleEqual(s.start(), (0, 0)) self.assertTupleEqual(s.end(), (1, 1)) self.assertTupleEqual(s.bounding_box()[0], (0.0, 1.0)) self.assertTupleEqual(s.bounding_box()[1], (-1.0, 1.0)) # test 3D geometry c = Curve() c.set_dimension(3) s = sf.thicken(c, 1) # cylinder along x-axis with h=1, r=1 for u in np.linspace(s.start(0), s.end(0), 5): for v in np.linspace(s.start(1), s.end(1), 5): x = s(u, v) self.assertAlmostEqual(x[1]**2 + x[2]**2, 1.0**2) # distance to x-axis self.assertAlmostEqual(x[0], u) # x coordinate should be linear
def line(a, b, relative=False): """ Create a line between two points. :param array-like a: Start point :param array-like b: End point :param bool relative: Whether *b* is relative to *a* :return: Linear curve from *a* to *b* :rtype: Curve """ if relative: b = tuple(ai + bi for ai, bi in zip(a, b)) return Curve(controlpoints=[a, b])
def test_append(self): crv = Curve(BSplineBasis(3), [[0,0], [1,0], [0,1]]) crv2 = Curve(BSplineBasis(4), [[0,1,0], [0,1,1], [0,2,1], [0,2,2]]) crv2.insert_knot(0.5) crv3 = crv.clone() crv3.append(crv2) expected_knot = [0,0,0,0,1,1,1,1.5,2,2,2,2] self.assertEqual(crv3.order(direction=0), 4) self.assertEqual(crv3.rational, False) self.assertEqual(np.linalg.norm(crv3.knots(0, True)-expected_knot), 0.0) t = np.linspace(0,1,11) pt = np.zeros((11,3)) pt[:,:-1] = crv(t) pt2 = crv3(t) self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0) pt = crv2(t) pt2 = crv3(t+1.0) self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)
def test_periodic_split(self): # non-uniform rational knot vector of a periodic cubic n=7 curve controlpoints = [[1, 0, 1], [1, 1, .7], [0, 1, .89], [-1, 1, 0.5], [-1, 0, 1], [-1,-.5,1], [1, -.5, 1]] basis = BSplineBasis(4, [-3, -2, -1, 0, 1, 2, 2.5, 4, 5, 6, 7, 8, 9, 9.5], 2) crv = Curve(basis, controlpoints, rational=True) crv2 = crv.split(1) # split at knot value crv3 = crv.split(6.5) # split outside existing knot self.assertEqual(len(crv), 7) self.assertEqual(len(crv2), 10) self.assertEqual(len(crv3), 11) self.assertEqual(crv.periodic(), True) self.assertEqual(crv2.periodic(), False) self.assertEqual(crv3.periodic(), False) t = np.linspace(6.5, 8, 13) # domain where all parameter values are the same pt = crv( t) pt2 = crv2(t) pt3 = crv3(t) self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0) self.assertAlmostEqual(np.linalg.norm(pt-pt3), 0.0)
def circle_segment(theta, r=1, center=(0, 0, 0), normal=(0, 0, 1), xaxis=(1, 0, 0)): """ Create a circle segment starting parallel to the rotated x-axis. :param float theta: Angle in radians :param float r: Radius :param array-like center: circle segment center :param array-like normal: normal vector to the plane that contains circle :param array-like xaxis: direction of the parametric start point t=0 :return: A quadratic rational curve :rtype: Curve :raises ValueError: If radius is not positive :raises ValueError: If theta is not in the range *[-2pi, 2pi]* """ # error test input if abs(theta) > 2 * pi: raise ValueError('theta needs to be in range [-2pi,2pi]') if r <= 0: raise ValueError('radius needs to be positive') if theta == 2 * pi: return circle(r, center, normal) # build knot vector knot_spans = int(ceil(abs(theta) / (2 * pi / 3))) knot = [0] for i in range(knot_spans + 1): knot += [i] * 2 knot += [knot_spans] # knot vector [0,0,0,1,1,2,2,..,n,n,n] knot = np.array(knot) / float( knot[-1]) * theta # set parametric space to [0,theta] n = (knot_spans - 1) * 2 + 3 # number of control points needed cp = [] t = 0 # current angle dt = float(theta) / knot_spans / 2 # angle step # build control points for i in range(n): w = 1 - (i % 2) * (1 - cos(dt) ) # weights = 1 and cos(dt) every other i x = r * cos(t) y = r * sin(t) cp += [[x, y, w]] t += dt if theta < 0: cp.reverse() result = Curve(BSplineBasis(3, np.flip(knot, 0)), cp, True) else: result = Curve(BSplineBasis(3, knot), cp, True) result.rotate(rotate_local_x_axis(xaxis, normal)) return flip_and_move_plane_geometry(result, center, normal)
def test_evaluate(self): # create the mapping # x(t) = 2t + 1 # y(t) = 2t(1-t) # z(t) = 0 controlpoints = [[1, 0, 0], [2, 1, 0], [3, 0, 0]] crv = Curve(BSplineBasis(3), controlpoints) # startpoint evaluation val = crv(0.0) self.assertAlmostEqual(val[0], 1.0) self.assertAlmostEqual(val[1], 0.0) self.assertAlmostEqual(val[2], 0.0) # inner evaluation val = crv(0.4) self.assertAlmostEqual(val[0], 1.8) self.assertAlmostEqual(val[1], 0.48) self.assertAlmostEqual(val[2], 0.0) # endpoint evaluation val = crv(1.0) self.assertAlmostEqual(val[0], 3.0) self.assertAlmostEqual(val[1], 0.0) self.assertAlmostEqual(val[2], 0.0) # test evaluation at multiple points val = crv([0.0, 0.4, 0.8, 1.0]) self.assertEqual(len(val.shape), 2) # return matrix self.assertEqual(val.shape[0], 4) # 4 evaluation points self.assertEqual(val.shape[1], 3) # (x,y,z) results self.assertAlmostEqual(val[0, 0], 1.0) self.assertAlmostEqual(val[0, 1], 0.0) self.assertAlmostEqual(val[0, 2], 0.0) # startpt evaluation self.assertAlmostEqual(val[1, 0], 1.8) self.assertAlmostEqual(val[1, 1], 0.48) self.assertAlmostEqual(val[1, 2], 0.0) # inner evaluation self.assertAlmostEqual(val[3, 0], 3.0) self.assertAlmostEqual(val[3, 1], 0.0) self.assertAlmostEqual(val[3, 2], 0.0) # endpt evaluation # test errors and exceptions with self.assertRaises(ValueError): val = crv(-10) # evalaute outside parametric domain with self.assertRaises(ValueError): val = crv(+10) # evalaute outside parametric domain
def test_curvature(self): # linear curves have zero curvature crv = Curve() self.assertAlmostEqual(crv.curvature(.3), 0.0) # test multiple evaluation points t = np.linspace(0, 1, 10) k = crv.curvature(t) self.assertTrue(np.allclose(k, 0.0)) # test circle crv = cf.circle(r=3) + [1, 1] t = np.linspace(0, 2 * pi, 10) k = crv.curvature(t) self.assertTrue(np.allclose(k, 1.0 / 3.0)) # circles: k = 1/r # test 3D (np.cross has different behaviour in 2D/3D) crv.set_dimension(3) k = crv.curvature(t) self.assertTrue(np.allclose(k, 1.0 / 3.0)) # circles: k = 1/r
def circle(r=1, center=(0,0,0), normal=(0,0,1), type='p2C0', xaxis=(1,0,0)): """ Create a circle. :param float r: Radius :param array-like center: local origin :param array-like normal: local normal :param string type: The type of parametrization ('p2C0' or 'p4C1') :param array-like xaxis: direction of sem, i.e. parametric start point t=0 :return: A periodic, quadratic rational curve :rtype: Curve :raises ValueError: If radius is not positive """ if r <= 0: raise ValueError('radius needs to be positive') if type == 'p2C0' or type == 'C0p2': w = 1.0 / sqrt(2) controlpoints = [[1, 0, 1], [w, w, w], [0, 1, 1], [-w, w, w], [-1, 0, 1], [-w, -w, w], [0, -1, 1], [w, -w, w]] knot = np.array([-1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5]) / 4.0 * 2 * pi result = Curve(BSplineBasis(3, knot, 0), controlpoints, True) elif type.lower() == 'p4c1' or type.lower() == 'c1p4': w = 2*sqrt(2)/3 a = 1.0/2/sqrt(2) b = 1.0/6 * (4*sqrt(2)-1) controlpoints = [[ 1,-a, 1], [ 1, a, 1], [ b, b, w], [ a, 1, 1], [-a, 1, 1], [-b, b, w], [-1, a, 1], [-1,-a, 1], [-b,-b, w], [-a,-1, 1], [ a,-1, 1], [ b,-b, w]] knot = np.array([ -1, -1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5]) / 4.0 * 2 * pi result = Curve(BSplineBasis(5, knot, 1), controlpoints, True) else: raise ValueError('Unkown type: %s' %(type)) result *= r result.rotate(rotate_local_x_axis(xaxis, normal)) return flip_and_move_plane_geometry(result, center, normal)
def least_square_fit(x, basis, t): """ Perform a least-square fit of a point cloud onto a spline basis :param matrix-like x: Matrix *X[i,j]* of interpolation points *xi* with components *j*. The number of points must be equal to or larger than the number of basis functions in *basis* :param BSplineBasis basis: Basis on which to interpolate :param array-like t: parametric values at evaluation points :return: Approximated curve :rtype: Curve """ # evaluate all basis functions at evaluation points N = basis.evaluate(t) # solve interpolation problem controlpoints, _, _, _ = np.linalg.lstsq(N, x, rcond=None) return Curve(basis, controlpoints)
def read_curve(self, ncrv): knots = [0] for i in ncrv.Knots: knots.append(i) knots[0] = knots[1] knots.append(knots[len(knots) - 1]) basis = BSplineBasis(ncrv.Order, knots, -1) cpts = [] cpts = np.ndarray((len(ncrv.Points), ncrv.Dimension + ncrv.IsRational)) for u in range(0, len(ncrv.Points)): cpts[u, 0] = ncrv.Points[u].X cpts[u, 1] = ncrv.Points[u].Y if ncrv.Dimension > 2: cpts[u, 2] = ncrv.Points[u].Z if ncrv.IsRational: cpts[u, 3] = ncrv.Points[u].W return Curve(basis, cpts, ncrv.IsRational)
def camber(M, P, order=5): """ Create the NACA centerline used for wing profiles. This is given as an exact quadratic piecewise polynomial y(x), see http://airfoiltools.com/airfoil/naca4digit. The method will produce one of two representations: For order<5 it will create x(t)=t and for order>4 it will create x(t) as qudratic in t and stretched towards the endpointspoints creating a more optimized parametrization. :param M: Max camber height (y) given as percentage 0% to 9% of length :type M: Int 0<M<10 :param P: Max camber position (x) given as percentage 0% to 90% of length :type P: Int 0<P<10 :return : Exact centerline representation :rtype : Curve """ # parametrized by x=t or x="t^2" if order>4 M = M / 100.0 P = P / 10.0 basis = BSplineBasis(order) # basis.insert_knot([P]*(order-2)) # insert a C1-knot for i in range(order - 2): basis.insert_knot(P) t = basis.greville() n = len(t) x = np.zeros((n, 2)) for i in range(n): if t[i] <= P: if order > 4: x[i, 0] = t[i]**2 / P else: x[i, 0] = t[i] x[i, 1] = M / P / P * (2 * P * x[i, 0] - x[i, 0] * x[i, 0]) else: if order > 4: x[i, 0] = (t[i]**2 - 2 * t[i] + P) / (P - 1) else: x[i, 0] = t[i] x[i, 1] = M / (1 - P) / (1 - P) * (1 - 2 * P + 2 * P * x[i, 0] - x[i, 0] * x[i, 0]) N = basis.evaluate(t) controlpoints = np.linalg.solve(N, x) return Curve(basis, controlpoints)
def test_derivative(self): # testing the parametrization x(t) = [.5*x^3 / ((1-x)^3+.5*x^3), 0] cp = [[0,0,1], [0,0,0], [0,0,0], [.5,0,.5]] crv = Curve(BSplineBasis(4), cp, rational=True) def expect_derivative(x): return 6*(1-x)**2*x**2/(x**3 - 6*x**2 + 6*x - 2)**2 def expect_derivative_2(x): return -12*x*(x**5 - 3*x**4 + 2*x**3 + 4*x**2 - 6*x + 2)/(x**3 - 6*x**2 + 6*x - 2)**3 # insert a few more knots to spice things up crv.insert_knot([.2, .71]) self.assertAlmostEqual(crv.tangent(0.32)[0], expect_derivative(0.32)) self.assertAlmostEqual(crv.tangent(0.32)[1], 0) self.assertAlmostEqual(crv.tangent(0.71)[0], expect_derivative(0.71)) self.assertAlmostEqual(crv.derivative(0.22, 2)[0], expect_derivative_2(0.22)) self.assertAlmostEqual(crv.derivative(0.22, 2)[1], 0) self.assertAlmostEqual(crv.derivative(0.86, 2)[0], expect_derivative_2(0.86))
def interpolate(x, basis, t=None): """ Perform general spline interpolation on a provided basis. :param matrix-like x: Matrix *X[i,j]* of interpolation points *xi* with components *j* :param BSplineBasis basis: Basis on which to interpolate :param array-like t: parametric values at interpolation points; defaults to Greville points if not provided :return: Interpolated curve :rtype: Curve """ # wrap x into a numpy matrix x = np.matrix(x) # evaluate all basis functions in the interpolation points if t is None: t = basis.greville() N = basis.evaluate(t, sparse=True) # solve interpolation problem controlpoints = splinalg.spsolve(N, x) return Curve(basis, controlpoints)
def test_thicken(self): c = Curve() # 2D curve from (0,0) to (1,0) s = SurfaceFactory.thicken(c, .5) # extend to y=[-.5, .5] self.assertTupleEqual(s.order(), (2,2)) self.assertTupleEqual(s.start(), (0,0)) self.assertTupleEqual(s.end(), (1,1)) self.assertTupleEqual(s.bounding_box()[0], (0.0,1.0)) self.assertTupleEqual(s.bounding_box()[1], (-.5, .5)) # test a case with vanishing velocity. x'(t)=0, y'(t)=0 for t=0 c = Curve(BSplineBasis(3), [[0,0],[0,0],[1,0]]) # x(t)=t^2, y(t)=0 s = SurfaceFactory.thicken(c, .5) self.assertTupleEqual(s.order(), (3,2)) self.assertTupleEqual(s.start(), (0,0)) self.assertTupleEqual(s.end(), (1,1)) self.assertTupleEqual(s.bounding_box()[0], (0.0,1.0)) self.assertTupleEqual(s.bounding_box()[1], (-.5, .5)) def myThickness(t): return t**2 c = Curve(BSplineBasis(3)) s = SurfaceFactory.thicken(c, myThickness) self.assertTupleEqual(s.order(), (3,2)) self.assertTupleEqual(s.start(), (0,0)) self.assertTupleEqual(s.end(), (1,1)) self.assertTupleEqual(s.bounding_box()[0], ( 0.0, 1.0)) self.assertTupleEqual(s.bounding_box()[1], (-1.0, 1.0)) # test 3D geometry c = Curve() c.set_dimension(3) s = SurfaceFactory.thicken(c, 1) # cylinder along x-axis with h=1, r=1 for u in np.linspace(s.start(0), s.end(0), 5): for v in np.linspace(s.start(1), s.end(1), 5): x = s(u, v) self.assertAlmostEqual(x[1]**2+x[2]**2, 1.0**2) # distance to x-axis self.assertAlmostEqual(x[0], u) # x coordinate should be linear
def curves_from_path(self, path): # see https://www.w3schools.com/graphics/svg_path.asp for documentation # and also https://www.w3.org/TR/SVG/paths.html # figure out the largest polynomial order of this path if re.search('[cCsS]', path): order = 4 elif re.search('[qQtTaA]', path): order = 3 else: order = 2 last_curve = None result = [] # each 'piece' is an operator (M,C,Q,L etc) and accomponying list of argument points for piece in re.findall('[a-zA-Z][^a-zA-Z]*', path): # if not single-letter command (i.e. 'z') if len(piece)>1: # points is a (string-)list of (x,y)-coordinates for the given operator points = re.findall('-?\d+\.?\d*', piece[1:]) if piece[0].lower() != 'a' and piece[0].lower() != 'v' and piece[0].lower() != 'h': # convert string-list to a list of numpy arrays (of size 2) np_pts = np.reshape(np.array(points).astype('float'), (int(len(points)/2),2)) if piece[0] == 'm' or piece[0] == 'M': # I really hope it always start with a move command (think it does) startpoint = np_pts[0] if len(np_pts) > 1: if piece[0] == 'M': knot = [0] + list(range(len(np_pts))) + [len(np_pts)-1] curve_piece = Curve(BSplineBasis(2, knot), np_pts) elif piece[0] == 'm': knot = [0] + list(range(len(np_pts))) + [len(np_pts)-1] controlpoints = [startpoint] for cp in np_pts[1:]: controlpoints.append(cp + controlpoints[-1]) curve_piece = Curve(BSplineBasis(2, knot), controlpoints) else: continue elif piece[0] == 'c': # cubic spline, relatively positioned controlpoints = [startpoint] knot = list(range(int(len(np_pts)/3)+1)) * 3 knot += [knot[0], knot[-1]] knot.sort() for cp in np_pts: startpoint = controlpoints[int((len(controlpoints)-1)/3)*3] controlpoints.append(cp + startpoint) curve_piece = Curve(BSplineBasis(4, knot), controlpoints) elif piece[0] == 'C': # cubic spline, absolute position controlpoints = [startpoint] knot = list(range(int(len(np_pts)/3)+1)) * 3 knot += [knot[0], knot[-1]] knot.sort() for cp in np_pts: controlpoints.append(cp) curve_piece = Curve(BSplineBasis(4, knot), controlpoints) elif piece[0] == 's': # smooth cubic spline, relative position controlpoints = [startpoint] knot = list(range(int(len(np_pts)/2)+1)) * 3 knot += [knot[0], knot[-1]] knot.sort() x0 = np.array(last_curve[-1]) xn1 = np.array(last_curve[-2]) controlpoints.append(2*x0 -xn1) startpoint = controlpoints[-1] for i, cp in enumerate(np_pts): if i % 2 == 0 and i>0: startpoint = controlpoints[-1] controlpoints.append(2*controlpoints[-1] - controlpoints[-2]) controlpoints.append(cp + startpoint) curve_piece = Curve(BSplineBasis(4, knot), controlpoints) elif piece[0] == 'S': # smooth cubic spline, absolute position controlpoints = [startpoint] knot = list(range(int(len(np_pts)/2)+1)) * 3 knot += [knot[0], knot[-1]] knot.sort() x0 = np.array(last_curve[-1]) xn1 = np.array(last_curve[-2]) controlpoints.append(2*x0 -xn1) for i,cp in enumerate(np_pts): if i % 2 == 0 and i>0: controlpoints.append(2*controlpoints[-1] - controlpoints[-2]) controlpoints.append(cp) curve_piece = Curve(BSplineBasis(4, knot), controlpoints) elif piece[0] == 'q': # quadratic spline, relatively positioned controlpoints = [startpoint] knot = list(range(int(len(np_pts)/2)+1)) * 2 knot += [knot[0], knot[-1]] knot.sort() for cp in np_pts: startpoint = controlpoints[int((len(controlpoints)-1)/2)*2] controlpoints.append(cp + startpoint) curve_piece = Curve(BSplineBasis(3, knot), controlpoints) elif piece[0] == 'Q': # quadratic spline, absolute position controlpoints = [startpoint] knot = list(range(len(np_pts)/2+1)) * 2 knot += [knot[0], knot[-1]] knot.sort() for cp in np_pts: controlpoints.append(cp) curve_piece = Curve(BSplineBasis(3, knot), controlpoints) elif piece[0] == 'l': # linear spline, relatively positioned controlpoints = [startpoint] knot = list(range(len(np_pts)+1)) knot += [knot[0], knot[-1]] knot.sort() for cp in np_pts: startpoint = controlpoints[-1] controlpoints.append(cp + startpoint) curve_piece = Curve(BSplineBasis(2, knot), controlpoints) elif piece[0] == 'L': # linear spline, absolute position controlpoints = [startpoint] knot = list(range(len(np_pts)+1)) knot += [knot[0], knot[-1]] knot.sort() for cp in np_pts: controlpoints.append(cp) curve_piece = Curve(BSplineBasis(2, knot), controlpoints) elif piece[0] == 'h': # horizontal piece, relatively positioned np_pts = np.array(points).astype('float') controlpoints = [startpoint] knot = list(range(len(np_pts)+1)) knot += [knot[0], knot[-1]] knot.sort() for cp in np_pts: startpoint = controlpoints[-1] controlpoints.append(np.array([cp, 0]) + startpoint) curve_piece = Curve(BSplineBasis(2, knot), controlpoints) elif piece[0] == 'H': # horizontal piece, absolute position np_pts = np.array(points).astype('float') controlpoints = [startpoint] knot = list(range(len(np_pts)+1)) knot += [knot[0], knot[-1]] knot.sort() for cp in np_pts: controlpoints.append([cp, startpoint[1]]) curve_piece = Curve(BSplineBasis(2, knot), controlpoints) elif piece[0] == 'v': # vertical piece, relatively positioned np_pts = np.array(points).astype('float') controlpoints = [startpoint] knot = list(range(len(np_pts)+1)) knot += [knot[0], knot[-1]] knot.sort() for cp in np_pts: startpoint = controlpoints[-1] controlpoints.append(np.array([0, cp]) + startpoint) curve_piece = Curve(BSplineBasis(2, knot), controlpoints) elif piece[0] == 'V': # vertical piece, absolute position np_pts = np.array(points).astype('float') controlpoints = [startpoint] knot = list(range(len(np_pts)+1)) knot += [knot[0], knot[-1]] knot.sort() for cp in np_pts: controlpoints.append([startpoint[0], cp]) curve_piece = Curve(BSplineBasis(2, knot), controlpoints) elif piece[0] == 'A' or piece[0] == 'a': np_pts = np.reshape(np.array(points).astype('float'), (int(len(points)))) rx = float(points[0]) ry = float(points[1]) x_axis_rotation = float(points[2]) large_arc_flag = (points[3] != '0') sweep_flag = (points[4] != '0') xend = np.array([float(points[5]), float(points[6]) ]) if piece[0] == 'a': xend += startpoint R = np.array([[ np.cos(x_axis_rotation), np.sin(x_axis_rotation)], [-np.sin(x_axis_rotation), np.cos(x_axis_rotation)]]) xp = np.linalg.solve(R, (startpoint - xend)/2) if sweep_flag == large_arc_flag: cprime = -(np.sqrt(abs(rx**2*ry**2 - rx**2*xp[1]**2 - ry**2*xp[0]**2) / (rx**2*xp[1]**2 + ry**2*xp[0]**2)) * np.array([rx*xp[1]/ry, -ry*xp[0]/rx])) else: cprime = +(np.sqrt(abs(rx**2*ry**2 - rx**2*xp[1]**2 - ry**2*xp[0]**2) / (rx**2*xp[1]**2 + ry**2*xp[0]**2)) * np.array([rx*xp[1]/ry, -ry*xp[0]/rx])) center = np.linalg.solve(R.T, cprime) + (startpoint+xend)/2 def arccos(vec1, vec2): return (np.sign(vec1[0]*vec2[1] - vec1[1]*vec2[0]) * np.arccos(vec1.dot(vec2)/np.linalg.norm(vec1)/np.linalg.norm(vec2))) tmp1 = np.divide( xp - cprime, [rx,ry]) tmp2 = np.divide(-xp - cprime, [rx,ry]) theta1 = arccos(np.array([1,0]), tmp1) delta_t= arccos(tmp1, tmp2) % (2*np.pi) if not sweep_flag and delta_t > 0: delta_t -= 2*np.pi elif sweep_flag and delta_t < 0: delta_t += 2*np.pi curve_piece = (curve_factory.circle_segment(delta_t)*[rx,ry]).rotate(theta1) + center # curve_piece = curve_factory.circle_segment(delta_t) elif piece[0] == 'z' or piece[0] == 'Z': # periodic curve # curve_piece = Curve(BSplineBasis(2), [startpoint, last_curve[0]]) # curve_piece.reparam([0, curve_piece.length()]) # last_curve.append(curve_piece).make_periodic(0) last_curve.make_periodic(0) result.append(last_curve) last_curve = None continue else: raise RuntimeError('Unknown path parameter:' + piece) if(curve_piece.length()>state.controlpoint_absolute_tolerance): curve_piece.reparam([0, curve_piece.length()]) if last_curve is None: last_curve = curve_piece else: last_curve.append(curve_piece) startpoint = last_curve[-1,:2] # disregard rational weight (if any) if last_curve is not None: result.append(last_curve) return result
def test_length(self): crv = Curve() self.assertAlmostEqual(crv.length(), 1.0) crv = Curve(BSplineBasis(2, [-1,-1,1,2,3,3]), [[0,0,0], [1,0,0], [1,0,3],[1,10,3]]) self.assertAlmostEqual(crv.length(), 14.0)
def test_split(self): # non-uniform knot vector of a squiggly quadratic n=4 curve controlpoints = [[0, 0, 1], [1, 1, 0], [2, -1, 0], [3, 0, 0]] crv = Curve(BSplineBasis(3, [0, 0, 0, .7, 1, 1, 1]), controlpoints) # get some info on the initial curve evaluation_point1 = crv(0.50) evaluation_point2 = crv(0.70) evaluation_point3 = crv(0.33) # split curves away from knot new_curves_050 = crv.split(0.50) self.assertEqual(len(new_curves_050), 2) self.assertEqual( len(new_curves_050[0].knots(0, with_multiplicities=True)), 6) # open knot vector [0,0,0,.5,.5,.5] self.assertEqual( len(new_curves_050[1].knots(0, with_multiplicities=True)), 7) # open knot vector [.5,.5,.5,.7,1,1,1] # split curves at existing knot new_curves_070 = crv.split(0.70) self.assertEqual(len(new_curves_070), 2) self.assertEqual( len(new_curves_070[0].knots(0, with_multiplicities=True)), 6) # open knot vector [0,0,0,.7,.7,.7] self.assertEqual( len(new_curves_070[1].knots(0, with_multiplicities=True)), 6) # open knot vector [.7,.7,.7,1,1,1] # split curves multiple points new_curves_all = crv.split([0.50, 0.70]) self.assertEqual(len(new_curves_all), 3) self.assertEqual( len(new_curves_all[0].knots(0, with_multiplicities=True)), 6) # open knot vector [0,0,0,.5,.5,.5] self.assertEqual( len(new_curves_all[1].knots(0, with_multiplicities=True)), 6) # open knot vector [.5,.5,.5,.7,.7,.7] self.assertEqual( len(new_curves_all[2].knots(0, with_multiplicities=True)), 6) # open knot vector [.7,.7,.7,1,1,1] # compare all curves which exist at parametric point 0.5 for c in new_curves_050 + [new_curves_070[0]] + new_curves_all[0:2]: new_curve_evaluation = c(0.50) self.assertAlmostEqual(evaluation_point1[0], new_curve_evaluation[0]) self.assertAlmostEqual(evaluation_point1[1], new_curve_evaluation[1]) self.assertAlmostEqual(evaluation_point1[2], new_curve_evaluation[2]) # compare all curves which exist at parametric point 0.33 for c in [new_curves_050[0]] + [new_curves_070[0]] + [new_curves_all[0]]: new_curve_evaluation = c(0.33) self.assertAlmostEqual(evaluation_point3[0], new_curve_evaluation[0]) self.assertAlmostEqual(evaluation_point3[1], new_curve_evaluation[1]) self.assertAlmostEqual(evaluation_point3[2], new_curve_evaluation[2]) # compare all curves which exist at parametric point 0.7 for c in [new_curves_050[1]] + new_curves_070 + new_curves_all[1:3]: new_curve_evaluation = c(0.70) self.assertAlmostEqual(evaluation_point2[0], new_curve_evaluation[0]) self.assertAlmostEqual(evaluation_point2[1], new_curve_evaluation[1]) self.assertAlmostEqual(evaluation_point2[2], new_curve_evaluation[2]) # test errors and exceptions with self.assertRaises(TypeError): crv.split(.1, .2, .3) # too many arguments with self.assertRaises(Exception): crv.split(-0.2) # GoTools returns error on outside-domain errors with self.assertRaises(Exception): crv.split(1.4) # GoTools returns error on outside-domain errors
def test_reparam(self): # non-uniform knot vector of a squiggly quadratic n=4 curve controlpoints = [[0, 0, 0], [1, 1, 0], [2, -1, 0], [3, 0, 0]] crv = Curve(BSplineBasis(3, [0, 0, 0, 1.32, 3, 3, 3]), controlpoints) # get some info on the initial curve knots1 = crv.knots(0) evaluation_point1 = crv(1.20) self.assertEqual(knots1[0], 0) self.assertEqual(knots1[-1], 3) # reparametrize crv.reparam((6.0, 9.0)) # get some info on the reparametrized curve knots2 = crv.knots(0) evaluation_point2 = crv(7.20) self.assertEqual(knots2[0], 6) self.assertEqual(knots2[-1], 9) # ensure that curve has not chcanged, by comparing evaluation of it self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2]) # normalize, i.e. set domain to [0,1] crv.reparam() # get some info on the normalized curve knots3 = crv.knots(0) evaluation_point3 = crv(0.40) self.assertEqual(knots3[0], 0) self.assertEqual(knots3[-1], 1) # ensure that curve has not chcanged, by comparing evaluation of it self.assertAlmostEqual(evaluation_point1[0], evaluation_point3[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point3[1]) self.assertAlmostEqual(evaluation_point1[2], evaluation_point3[2]) # test errors and exceptions with self.assertRaises(ValueError): crv.reparam((9, 3)) with self.assertRaises(TypeError): crv.reparam(("one", "two"))
def test_insert_knot(self): # non-uniform knot vector of a squiggly quadratic n=5 curve in 3D controlpoints = [[0, 0, 0], [1, 1, 1], [2, -1, 0], [3, 0, -1], [0, 0, -5]] crv = Curve(BSplineBasis(3, [0, 0, 0, .3, .4, 1, 1, 1]), controlpoints) evaluation_point1 = crv(0.37) crv.insert_knot(.2) crv.insert_knot(.3) crv.insert_knot(.9) evaluation_point2 = crv(0.37) # ensure that curve has not chcanged, by comparing evaluation of it self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2]) self.assertEqual(len(crv.knots(0, with_multiplicities=True)), 11) # test knot insertion on single knot span crv = Curve(BSplineBasis(5), [[0, 0, 0], [1, 1, 1], [2, -1, 0], [3, 0, -1], [0, 0, -5]]) evaluation_point1 = crv(0.27) crv.insert_knot(.2) evaluation_point2 = crv(0.27) self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2]) # test knot insertion on first of two-knot span crv = Curve( BSplineBasis(3, [0, 0, 0, .5, 1, 1, 1]), [[0, 0, 0], [2, -1, 0], [3, 0, -1], [0, 0, -5] ]) evaluation_point1 = crv(0.27) crv.insert_knot(.2) evaluation_point2 = crv(0.27) self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2]) # test knot insertion on last of two-knot span crv = Curve( BSplineBasis(3, [0, 0, 0, .5, 1, 1, 1]), [[0, 0, 0], [2, -1, 0], [3, 0, -1], [0, 0, -5] ]) evaluation_point1 = crv(0.27) crv.insert_knot(.9) evaluation_point2 = crv(0.27) self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2]) # test knot insertion down to C0 basis crv = Curve(BSplineBasis(3), [[0, 0, 0], [2, -1, 0], [0, 0, -5]]) evaluation_point1 = crv(0.27) crv.insert_knot(.4) crv.insert_knot(.4) evaluation_point2 = crv(0.27) self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2]) # test rational curves, here a perfect circle represented as n=9, p=2-curve s = 1.0 / sqrt(2) controlpoints = [[1, 0, 1], [s, s, s], [0, 1, 1], [-s, s, s], [-1, 0, 1], [-s, -s, s], [0, -1, 1], [s, -s, s], [1, 0, 1]] crv = Curve(BSplineBasis(3, [-1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5]), controlpoints, True) evaluation_point1 = crv(0.37) crv.insert_knot(.2) crv.insert_knot(.3) crv.insert_knot(.9) evaluation_point2 = crv(0.37) self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) self.assertEqual(len(crv.knots(0, with_multiplicities=True)), 15) # test errors and exceptions with self.assertRaises(TypeError): crv.insert_knot(1, 2, 3) # too many arguments with self.assertRaises(ValueError): crv.insert_knot(1, 2) # direction=2 is illegal for curves with self.assertRaises(TypeError): crv.insert_knot() # too few arguments with self.assertRaises(ValueError): crv.insert_knot(-0.2) # Outside-domain error with self.assertRaises(ValueError): crv.insert_knot(4.4) # Outside-domain error