def test_make_periodic(self): my_cps = np.array([[0, -1], [1, 0], [0, 1], [-1, 0], [0, -1]], dtype=float) crv = Curve(BSplineBasis(2, [0, 0, 1, 2, 3, 4, 4]), my_cps, rational=False) crv = crv.make_periodic(0) cps = [[0, -1], [1, 0], [0, 1], [-1, 0]] self.assertAlmostEqual(np.linalg.norm(crv.controlpoints - cps), 0.0) crv = Curve(BSplineBasis(2, [0, 0, 1, 2, 3, 4, 4]), my_cps, rational=False) crv = crv.make_periodic(0) cps = [[0, -1], [1, 0], [0, 1], [-1, 0]] self.assertAlmostEqual(np.linalg.norm(crv.controlpoints - cps), 0.0) crv = Curve(BSplineBasis(3, [0, 0, 0, 1, 2, 3, 3, 3]), my_cps, rational=False) crv = crv.make_periodic(0) cps = [[0, -1], [1, 0], [0, 1], [-1, 0]] self.assertAlmostEqual(np.linalg.norm(crv.controlpoints - cps), 0.0) crv = Curve(BSplineBasis(3, [0, 0, 0, 1, 2, 3, 3, 3]), my_cps, rational=False) crv = crv.make_periodic(1) cps = [[-1, 0], [1, 0], [0, 1]] self.assertAlmostEqual(np.linalg.norm(crv.controlpoints - cps), 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 = cf.circle_segment(pi / 2) c2 = Curve(BSplineBasis(2, [0, 0, 1, 2, 2]), [[0, 1], [-1, 1], [-1, 0]]) c3 = cf.circle_segment(pi / 2) c3.rotate(pi) c4 = Curve(BSplineBasis(2), [[0, -1], [1, 0]]) surf = sf.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 = sf.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 = sf.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 = sf.edge_curves(crvs + (Curve(), )) # 5 input curves
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 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 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_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 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 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 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_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_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_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 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 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 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 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 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_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_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_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_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 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 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.assertAlmostEqual( 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_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_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 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 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 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)