def test_insert_knot(self): # more or less random 2D surface with p=[3,2] and n=[4,3] controlpoints = [[0, 0], [-1, 1], [0, 2], [1, -1], [1, 0], [1, 1], [2, 1], [2, 2], [2, 3], [3, 0], [4, 1], [3, 2]] basis1 = BSplineBasis(4, [0, 0, 0, 0, 2, 2, 2, 2]) basis2 = BSplineBasis(3, [0, 0, 0, 1, 1, 1]) surf = Surface(basis1, basis2, controlpoints) # pick some evaluation point (could be anything) evaluation_point1 = surf(0.23, 0.37) surf.insert_knot(.22, 0) surf.insert_knot(.5, 0) surf.insert_knot(.7, 0) surf.insert_knot(.1, 1) surf.insert_knot(1.0 / 3, 1) knot1, knot2 = surf.knots(with_multiplicities=True) self.assertEqual(len(knot1), 11) # 8 to start with, 3 new ones self.assertEqual(len(knot2), 8) # 6 to start with, 2 new ones evaluation_point2 = surf(0.23, 0.37) # evaluation before and after InsertKnot should remain unchanged self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) # test a rational 2D surface controlpoints = [[0, 0, 1], [-1, 1, .96], [0, 2, 1], [1, -1, 1], [1, 0, .8], [1, 1, 1], [2, 1, .89], [2, 2, .9], [2, 3, 1], [3, 0, 1], [4, 1, 1], [3, 2, 1]] basis1 = BSplineBasis(3, [0, 0, 0, .4, 1, 1, 1]) basis2 = BSplineBasis(3, [0, 0, 0, 1, 1, 1]) surf = Surface(basis1, basis2, controlpoints, True) evaluation_point1 = surf(0.23, 0.37) surf.insert_knot(.22, 0) surf.insert_knot(.5, 0) surf.insert_knot(.7, 0) surf.insert_knot(.1, 1) surf.insert_knot(1.0 / 3, 1) knot1, knot2 = surf.knots(with_multiplicities=True) self.assertEqual(len(knot1), 10) # 7 to start with, 3 new ones self.assertEqual(len(knot2), 8) # 6 to start with, 2 new ones evaluation_point2 = surf(0.23, 0.37) # evaluation before and after InsertKnot should remain unchanged self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) # test errors and exceptions with self.assertRaises(TypeError): surf.insert_knot(1, 2, 3) # too many arguments with self.assertRaises(ValueError): surf.insert_knot("tree-fiddy", .5) # wrong argument type with self.assertRaises(ValueError): surf.insert_knot(0, -0.2) # Outside-domain error with self.assertRaises(ValueError): surf.insert_knot(1, 1.4) # Outside-domain error
def test_make_identical(self): basis1 = BSplineBasis(4, [-1, -1, 0, 0, 1, 2, 9, 9, 10, 10, 11, 12], periodic=1) basis2 = BSplineBasis(2, [0, 0, .25, 1, 1]) cp = [[0, 0, 0, 1], [1, 0, 0, 1.1], [2, 0, 0, 1], [0, 1, 0, .7], [1, 1, 0, .8], [2, 1, 0, 1], [0, 2, 0, 1], [1, 2, 0, 1.2], [2, 2, 0, 1]] surf1 = Surface(BSplineBasis(3), BSplineBasis(3), cp, True) # rational 3D surf2 = Surface(basis1, basis2) # periodic 2D surf1.insert_knot([0.25, .5, .75], direction='v') Surface.make_splines_identical(surf1, surf2) for s in (surf1, surf2): self.assertEqual(s.periodic(0), False) self.assertEqual(s.periodic(1), False) self.assertEqual(s.dimension, 3) self.assertEqual(s.rational, True) self.assertEqual(s.order(), (4, 3)) self.assertAlmostEqual(len(s.knots(0, True)), 12) self.assertAlmostEqual(len(s.knots(1, True)), 10) self.assertEqual(s.bases[1].continuity(.25), 0) self.assertEqual(s.bases[1].continuity(.75), 1) self.assertEqual(s.bases[0].continuity(.2), 2) self.assertEqual(s.bases[0].continuity(.9), 1)
def test_normal(self): surf = sf.sphere(1) surf.swap() u = np.linspace(surf.start(0) + 1e-3, surf.end(0) - 1e-3, 9) v = np.linspace(surf.start(1) + 1e-3, surf.end(1) - 1e-3, 9) xpts = surf(u, v, tensor=False) npts = surf.normal(u, v, tensor=False) self.assertEqual(npts.shape, (9, 3)) # check that the normal is pointing out of the unit ball on a 9x9 evaluation grid for (x, n) in zip(xpts, npts): self.assertAlmostEqual(n[0], x[0]) self.assertAlmostEqual(n[1], x[1]) self.assertAlmostEqual(n[2], x[2]) xpts = surf(u, v) npts = surf.normal(u, v) self.assertEqual(npts.shape, (9, 9, 3)) # check that the normal is pointing out of the unit ball on a 9x9 evaluation grid for (i, j) in zip(xpts, npts): for (x, n) in zip(i, j): self.assertAlmostEqual(n[0], x[0]) self.assertAlmostEqual(n[1], x[1]) self.assertAlmostEqual(n[2], x[2]) # check single value input n = surf.normal(0, 0) self.assertEqual(len(n), 3) # test 2D surface s = Surface() n = s.normal(.5, .5) self.assertEqual(len(n), 3) self.assertAlmostEqual(n[0], 0.0) self.assertAlmostEqual(n[1], 0.0) self.assertAlmostEqual(n[2], 1.0) n = s.normal([.25, .5], [.1, .2, .3, .4, .5, .6, .7, .8, .9]) self.assertEqual(n.shape[0], 2) self.assertEqual(n.shape[1], 9) self.assertEqual(n.shape[2], 3) self.assertAlmostEqual(n[1, 4, 0], 0.0) self.assertAlmostEqual(n[1, 4, 1], 0.0) self.assertAlmostEqual(n[1, 4, 2], 1.0) n = s.normal([.25, .5, .75], [.3, .5, .9], tensor=False) self.assertEqual(n.shape, (3, 3)) for i in range(3): for j in range(2): self.assertAlmostEqual(n[i, j], 0.0) self.assertAlmostEqual(n[i, 2], 1.0) # test errors s = Surface(BSplineBasis(3), BSplineBasis(3), [[0]] * 9) # 1D-surface with self.assertRaises(RuntimeError): s.normal(.5, .5)
def test_edge_surfaces(self): # test 3D surface vs 2D rational surface # more or less random 3D surface with p=[2,2] and n=[3,4] controlpoints = [[0, 0, 1], [-1, 1, 1], [0, 2, 1], [1, -1, 1], [1, 0, .5], [1, 1, 1], [2, 1, 1], [2, 2, .5], [2, 3, 1], [3, 0, 1], [4, 1, 1], [3, 2, 1]] basis1 = BSplineBasis(3, [0, 0, 0, 1, 1, 1]) basis2 = BSplineBasis(3, [0, 0, 0, .64, 2, 2, 2]) top = Surface(basis1, basis2, controlpoints) # more or less random 2D rational surface with p=[1,2] and n=[3,4] controlpoints = [[0, 0, 1], [-1, 1, .96], [0, 2, 1], [1, -1, 1], [1, 0, .8], [1, 1, 1], [2, 1, .89], [2, 2, .9], [2, 3, 1], [3, 0, 1], [4, 1, 1], [3, 2, 1]] basis1 = BSplineBasis(3, [0, 0, 0, 1, 1, 1]) basis2 = BSplineBasis(2, [0, 0, .4, .44, 1, 1]) bottom = Surface(basis1, basis2, controlpoints, True) vol = vf.edge_surfaces(bottom, top) # set parametric domain to [0,1]^2 for easier comparison top.reparam() bottom.reparam() # verify on 7x7x2 evaluation grid for u in np.linspace(0, 1, 7): for v in np.linspace(0, 1, 7): for w in np.linspace(0, 1, 2): # rational basis, not linear in w-direction self.assertAlmostEqual( vol(u, v, w)[0], bottom(u, v)[0] * (1 - w) + top(u, v)[0] * w) # x-coordinate self.assertAlmostEqual( vol(u, v, w)[1], bottom(u, v)[1] * (1 - w) + top(u, v)[1] * w) # y-coordinate self.assertAlmostEqual( vol(u, v, w)[2], 0 * (1 - w) + top(u, v)[2] * w) # z-coordinate # test 3D surface vs 2D surface controlpoints = [[0, 0], [-1, 1], [0, 2], [1, -1], [1, 0], [1, 1], [2, 1], [2, 2], [2, 3], [3, 0], [4, 1], [3, 2]] basis1 = BSplineBasis(3, [0, 0, 0, 1, 1, 1]) basis2 = BSplineBasis(2, [0, 0, .4, .44, 1, 1]) bottom = Surface(basis1, basis2, controlpoints) # non-rational! vol = vf.edge_surfaces(bottom, top) # also non-rational! # verify on 5x5x7 evaluation grid for u in np.linspace(0, 1, 5): for v in np.linspace(0, 1, 5): for w in np.linspace(0, 1, 7): # include inner evaluation points self.assertAlmostEqual( vol(u, v, w)[0], bottom(u, v)[0] * (1 - w) + top(u, v)[0] * w) # x-coordinate self.assertAlmostEqual( vol(u, v, w)[1], bottom(u, v)[1] * (1 - w) + top(u, v)[1] * w) # y-coordinate self.assertAlmostEqual( vol(u, v, w)[2], 0 * (1 - w) + top(u, v)[2] * w) # z-coordinate
def test_revolve(self): # square torus square = Surface() + (1, 0) square.rotate( pi / 2, (1, 0, 0)) # in xz-plane with corners at (1,0),(2,0),(2,1),(1,1) vol = VolumeFactory.revolve(square) vol.reparam() # set parametric space to (0,1)^3 u = np.linspace(0, 1, 7) v = np.linspace(0, 1, 7) w = np.linspace(0, 1, 7) x = vol.evaluate(u, v, w) V, U, W = np.meshgrid(v, u, w) R = np.sqrt(x[:, :, :, 0]**2 + x[:, :, :, 1]**2) self.assertEqual(np.allclose(R, U + 1), True) self.assertEqual(np.allclose(x[:, :, :, 2], V), True) self.assertAlmostEqual(vol.volume(), 2 * pi * 1.5, places=3) # test incomplete reolve vol = VolumeFactory.revolve(square, theta=pi / 3) vol.reparam() # set parametric space to (0,1)^3 u = np.linspace(0, 1, 7) v = np.linspace(0, 1, 7) w = np.linspace(0, 1, 7) x = vol.evaluate(u, v, w) V, U, W = np.meshgrid(v, u, w) R = np.sqrt(x[:, :, :, 0]**2 + x[:, :, :, 1]**2) self.assertEqual(np.allclose(R, U + 1), True) self.assertEqual(np.allclose(x[:, :, :, 2], V), True) self.assertAlmostEqual(vol.volume(), 2 * pi * 1.5 / 6, places=3) self.assertTrue(np.all(x >= 0)) # completely contained in first octant # test axis revolve vol = VolumeFactory.revolve(Surface() + (1, 1), theta=pi / 3, axis=(1, 0, 0)) vol.reparam() # set parametric space to (0,1)^3 u = np.linspace(0, 1, 7) v = np.linspace(0, 1, 7) w = np.linspace(0, 1, 7) x = vol.evaluate(u, v, w) V, U, W = np.meshgrid(v, u, w) R = np.sqrt(x[:, :, :, 1]**2 + x[:, :, :, 2]**2) self.assertEqual(np.allclose(R, V + 1), True) self.assertEqual(np.allclose(x[:, :, :, 0], U + 1), True) self.assertTrue(np.all(x >= 0)) # completely contained in first octant
def test_const_par_crv(self): # more or less random 2D surface with p=[2,2] and n=[4,3] controlpoints = [[0, 0], [-1, 1], [0, 2], [1, -1], [1, 0], [1, 1], [2, 1], [2, 2], [2, 3], [3, 0], [4, 1], [3, 2]] basis1 = BSplineBasis(3, [0, 0, 0, .4, 1, 1, 1]) basis2 = BSplineBasis(3, [0, 0, 0, 1, 1, 1]) surf = Surface(basis1, basis2, controlpoints) surf.refine(1) # try general knot in u-direction crv = surf.const_par_curve(0.3, 'u') v = np.linspace(0, 1, 13) self.assertTrue(np.allclose(surf(0.3, v), crv(v))) # try existing knot in u-direction crv = surf.const_par_curve(0.4, 'u') v = np.linspace(0, 1, 13) self.assertTrue(np.allclose(surf(0.4, v), crv(v))) # try general knot in v-direction crv = surf.const_par_curve(0.3, 'v') u = np.linspace(0, 1, 13) self.assertTrue(np.allclose(surf(u, 0.3).reshape(13, 2), crv(u))) # try start-point crv = surf.const_par_curve(0.0, 'v') u = np.linspace(0, 1, 13) self.assertTrue(np.allclose(surf(u, 0.0).reshape(13, 2), crv(u))) # try end-point crv = surf.const_par_curve(1.0, 'v') u = np.linspace(0, 1, 13) self.assertTrue(np.allclose(surf(u, 1.0).reshape(13, 2), crv(u)))
def disc(r=1, center=(0, 0, 0), normal=(0, 0, 1), type='radial', xaxis=(1, 0, 0)): """ Create a circular disc. The *type* parameter distinguishes between different parametrizations. :param float r: Radius :param array-like center: local origin :param array-like normal: local normal :param string type: The type of parametrization ('radial' or 'square') :param array-like xaxis: direction of sem, i.e. parametric start point v=0 :return: The disc :rtype: Surface """ if type == 'radial': c1 = CurveFactory.circle(r, center=center, normal=normal, xaxis=xaxis) c2 = flip_and_move_plane_geometry(c1 * 0, center, normal) result = edge_curves(c2, c1) result.swap() result.reparam((0, r), (0, 2 * pi)) return result elif type == 'square': w = 1 / sqrt(2) cp = [[-r * w, -r * w, 1], [0, -r, w], [r * w, -r * w, 1], [-r, 0, w], [0, 0, 1], [r, 0, w], [-r * w, r * w, 1], [0, r, w], [r * w, r * w, 1]] basis1 = BSplineBasis(3) basis2 = BSplineBasis(3) result = Surface(basis1, basis2, cp, True) return flip_and_move_plane_geometry(result, center, normal) else: raise ValueError('invalid type argument')
def interpolate(x, bases, u=None): """ Interpolate a surface on a set of regular gridded interpolation points `x`. The points can be either a matrix (in which case the first index is interpreted as a flat row-first index of the interpolation grid) or a 3D tensor. In both cases the last index is the physical coordinates. :param numpy.ndarray x: Grid of interpolation points :param [BSplineBasis] bases: The basis to interpolate on :param [array-like] u: Parametric interpolation points, defaults to Greville points of the basis :return: Interpolated surface :rtype: Surface """ surf_shape = [b.num_functions() for b in bases] dim = x.shape[-1] if len(x.shape) == 2: x = x.reshape(surf_shape + [dim]) if u is None: u = [b.greville() for b in bases] N_all = [b(t) for b, t in zip(bases, u)] N_all.reverse() cp = x for N in N_all: cp = np.tensordot(np.linalg.inv(N), cp, axes=(1, 1)) return Surface(bases[0], bases[1], cp.transpose(1, 0, 2).reshape((np.prod(surf_shape), dim)))
def least_square_fit(x, bases, u): """ Perform a least-square fit of a point cloud `x` onto a spline basis. The points can be either a matrix (in which case the first index is interpreted as a flat row-first index of the interpolation grid) or a 3D tensor. In both cases the last index is the physical coordinates. There must be at least as many points as basis functions. :param numpy.ndarray x: Grid of evaluation points :param [BSplineBasis] bases: Basis on which to interpolate :param [array-like] u: Parametric values at evaluation points :return: Approximated surface :rtype: Surface """ surf_shape = [b.num_functions() for b in bases] dim = x.shape[-1] if len(x.shape) == 2: x = x.reshape(surf_shape + [dim]) N_all = [b(t) for b, t in zip(bases, u)] N_all.reverse() cp = x for N in N_all: cp = np.tensordot(N.T, cp, axes=(1, 1)) for N in N_all: cp = np.tensordot(np.linalg.inv(N.T @ N), cp, axes=(1, 1)) return Surface(bases[0], bases[1], cp.transpose(1, 0, 2).reshape((np.prod(surf_shape), dim)))
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 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 teapot(): """ Generate the Utah teapot as 32 cubic bezier patches. This teapot has a rim, but no bottom. It is also self-intersecting making it unsuitable for perfect-match multipatch modeling. The data is picked from http://www.holmes3d.net/graphics/teapot/ :return: The utah teapot :rtype: List of Surface """ path = join(dirname(realpath(__file__)), 'templates', 'teapot.bpt') with open(path) as f: results = [] numb_patches = int(f.readline()) for i in range(numb_patches): p = np.fromstring(f.readline(), dtype=np.uint8, count=2, sep=' ') basis1 = BSplineBasis(p[0] + 1) basis2 = BSplineBasis(p[1] + 1) ncp = basis1.num_functions() * basis2.num_functions() cp = [ np.fromstring(f.readline(), dtype=np.float, count=3, sep=' ') for j in range(ncp) ] results.append(Surface(basis1, basis2, cp)) return results
def coons_patch(bottom, right, top, left): # coons patch (https://en.wikipedia.org/wiki/Coons_patch) 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 if rat: bottom = bottom.clone().force_rational() # don't mess with the input curve, make clone top.force_rational() # this is already a clone 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
def read_surface(self, nsrf): knotsu = [0] for i in nsrf.KnotsU: knotsu.append(i) knotsu.append(knotsu[len(knotsu) - 1]) knotsu[0] = knotsu[1] knotsv = [0] for i in nsrf.KnotsV: knotsv.append(i) knotsv.append(knotsv[len(knotsv) - 1]) knotsv[0] = knotsv[1] basisu = BSplineBasis(nsrf.OrderU, knotsu, -1) basisv = BSplineBasis(nsrf.OrderV, knotsv, -1) cpts = [] cpts = np.ndarray( (nsrf.Points.CountU * nsrf.Points.CountV, 3 + nsrf.IsRational)) for v in range(0, nsrf.Points.CountV): for u in range(0, nsrf.Points.CountU): cpts[u + v * nsrf.Points.CountU, 0] = nsrf.Points[u, v].X cpts[u + v * nsrf.Points.CountU, 1] = nsrf.Points[u, v].Y cpts[u + v * nsrf.Points.CountU, 2] = nsrf.Points[u, v].Z if nsrf.IsRational: cpts[u + v * nsrf.Points.CountU, 3] = nsrf.Points[u, v].W return Surface(basisu, basisv, cpts, nsrf.IsRational)
def test_derivative(self): # knot vector [t_1, t_2, ... t_{n+p+1}] # polynomial degree p (order-1) # n basis functions N_i(t), for i=1...n # the power basis {1,t,t^2,t^3,...} can be expressed as: # 1 = sum N_i(t) # t = sum ts_i * N_i(t) # t^2 = sum t2s_i * N_i(t) # ts_i = sum_{j=i+1}^{i+p} t_j / p # t2s_i = sum_{j=i+1}^{i+p-1} sum_{k=j+1}^{i+p} t_j*t_k / (p 2) # (p 2) = binomial coefficient # creating the mapping: # x(u,v) = u^2*v + u(1-v) # y(u,v) = v controlpoints = [[0, 0], [1.0 / 4, 0], [3.0 / 4, 0], [.75, 0], [0, 1], [0, 1], [.5, 1], [1, 1]] basis1 = BSplineBasis(3, [0, 0, 0, .5, 1, 1, 1]) basis2 = BSplineBasis(2, [0, 0, 1, 1]) surf = Surface(basis1, basis2, controlpoints) # call evaluation at a 5x4 grid of points val = surf.derivative([0, .2, .5, .6, 1], [0, .2, .4, 1], d=(1, 0)) self.assertEqual(len(val.shape), 3) # result should be wrapped in 3-index tensor self.assertEqual(val.shape[0], 5) # 5 evaluation points in u-direction self.assertEqual(val.shape[1], 4) # 4 evaluation points in v-direction self.assertEqual(val.shape[2], 2) # 2 coordinates (x,y) self.assertAlmostEqual(surf.derivative(.2, .2, d=(1, 0))[0], .88) # dx/du=2uv+(1-v) self.assertAlmostEqual(surf.derivative(.2, .2, d=(1, 0))[1], 0) # dy/du=0 self.assertAlmostEqual(surf.derivative(.2, .2, d=(0, 1))[0], -.16) # dx/dv=u^2-u self.assertAlmostEqual(surf.derivative(.2, .2, d=(0, 1))[1], 1) # dy/dv=1 self.assertAlmostEqual(surf.derivative(.2, .2, d=(1, 1))[0], -.60) # d2x/dudv=2u-1 self.assertAlmostEqual(surf.derivative(.2, .2, d=(2, 0))[0], 0.40) # d2x/dudu=2v self.assertAlmostEqual(surf.derivative(.2, .2, d=(3, 0))[0], 0.00) # d3x/du3=0 self.assertAlmostEqual(surf.derivative(.2, .2, d=(0, 2))[0], 0.00) # d2y/dv2=0 # test errors and exceptions with self.assertRaises(ValueError): val = surf.derivative(-10, .5) # evalaute outside parametric domain with self.assertRaises(ValueError): val = surf.derivative(+10, .3) # evalaute outside parametric domain with self.assertRaises(ValueError): val = surf.derivative(.5, -10) # evalaute outside parametric domain with self.assertRaises(ValueError): val = surf.derivative(.5, +10) # evalaute outside parametric domain
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_multiplicity(self): surf = Surface() surf.refine(1) surf.raise_order(1) surf.refine(1) self.assertTrue( np.allclose(multiplicities(surf), [[3, 1, 2, 1, 3], [3, 1, 2, 1, 3]]))
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_raise_order(self): # more or less random 2D surface with p=[2,2] and n=[4,3] controlpoints = [[0, 0], [-1, 1], [0, 2], [1, -1], [1, 0], [1, 1], [2, 1], [2, 2], [2, 3], [3, 0], [4, 1], [3, 2]] basis1 = BSplineBasis(3, [0, 0, 0, .4, 1, 1, 1]) basis2 = BSplineBasis(3, [0, 0, 0, 1, 1, 1]) surf = Surface(basis1, basis2, controlpoints) self.assertEqual(surf.order()[0], 3) self.assertEqual(surf.order()[1], 3) evaluation_point1 = surf( 0.23, 0.37) # pick some evaluation point (could be anything) surf.raise_order(1, 2) self.assertEqual(surf.order()[0], 4) self.assertEqual(surf.order()[1], 5) evaluation_point2 = surf(0.23, 0.37) # evaluation before and after RaiseOrder should remain unchanged self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1]) # test a rational 2D surface controlpoints = [[0, 0, 1], [-1, 1, .96], [0, 2, 1], [1, -1, 1], [1, 0, .8], [1, 1, 1], [2, 1, .89], [2, 2, .9], [2, 3, 1], [3, 0, 1], [4, 1, 1], [3, 2, 1]] basis1 = BSplineBasis(3, [0, 0, 0, .4, 1, 1, 1]) basis2 = BSplineBasis(3, [0, 0, 0, 1, 1, 1]) surf = Surface(basis1, basis2, controlpoints, True) self.assertEqual(surf.order()[0], 3) self.assertEqual(surf.order()[1], 3) evaluation_point1 = surf(0.23, 0.37) surf.raise_order(1, 2) self.assertEqual(surf.order()[0], 4) self.assertEqual(surf.order()[1], 5) evaluation_point2 = surf(0.23, 0.37) # evaluation before and after RaiseOrder should remain unchanged self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0]) self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1])
def test_repr(self): major, minor, patch = np.version.version.split('.') if int(major) <= 1 and int(minor) <= 13: self.assertEqual( repr(Surface()), 'p=2, [ 0. 0. 1. 1.]\n' 'p=2, [ 0. 0. 1. 1.]\n' '[ 0. 0.]\n' '[ 1. 0.]\n' '[ 0. 1.]\n' '[ 1. 1.]\n')
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 test_evaluate(self): # knot vector [t_1, t_2, ... t_{n+p+1}] # polynomial degree p (order-1) # n basis functions N_i(t), for i=1...n # the power basis {1,t,t^2,t^3,...} can be expressed as: # 1 = sum N_i(t) # t = sum ts_i * N_i(t) # t^2 = sum t2s_i * N_i(t) # ts_i = sum_{j=i+1}^{i+p} t_j / p # t2s_i = sum_{j=i+1}^{i+p-1} sum_{k=j+1}^{i+p} t_j*t_k / (p 2) # (p 2) = binomial coefficient # creating the mapping: # x(u,v) = u^2*v + u(1-v) # y(u,v) = v controlpoints = [[0, 0], [1.0 / 4, 0], [3.0 / 4, 0], [.75, 0], [0, 1], [0, 1], [.5, 1], [1, 1]] basis1 = BSplineBasis(3, [0, 0, 0, .5, 1, 1, 1]) basis2 = BSplineBasis(2, [0, 0, 1, 1]) surf = Surface(basis1, basis2, controlpoints) # call evaluation at a 5x4 grid of points val = surf([0, .2, .5, .6, 1], [0, .2, .4, 1]) self.assertEqual(len(val.shape), 3) # result should be wrapped in 3-index tensor self.assertEqual(val.shape[0], 5) # 5 evaluation points in u-direction self.assertEqual(val.shape[1], 4) # 4 evaluation points in v-direction self.assertEqual(val.shape[2], 2) # 2 coordinates (x,y) # check evaluation at (0,0) self.assertAlmostEqual(val[0][0][0], 0.0) self.assertAlmostEqual(val[0][0][1], 0.0) # check evaluation at (.2,0) self.assertAlmostEqual(val[1][0][0], 0.2) self.assertAlmostEqual(val[1][0][1], 0.0) # check evaluation at (.2,.2) self.assertAlmostEqual(val[1][1][0], 0.168) self.assertAlmostEqual(val[1][1][1], 0.2) # check evaluation at (.5,.4) self.assertAlmostEqual(val[2][2][0], 0.4) self.assertAlmostEqual(val[2][2][1], 0.4) # check evaluation at (.6,1) self.assertAlmostEqual(val[3][3][0], 0.36) self.assertAlmostEqual(val[3][3][1], 1) # test errors and exceptions with self.assertRaises(ValueError): val = surf(-10, .5) # evalaute outside parametric domain with self.assertRaises(ValueError): val = surf(+10, .3) # evalaute outside parametric domain with self.assertRaises(ValueError): val = surf(.5, -10) # evalaute outside parametric domain with self.assertRaises(ValueError): val = surf(.5, +10) # evalaute outside parametric domain
def test_edges(self): (umin, umax, vmin, vmax) = Surface().edges() # check controlpoints self.assertAlmostEqual(umin[0, 0], 0) self.assertAlmostEqual(umin[0, 1], 0) self.assertAlmostEqual(umin[1, 0], 0) self.assertAlmostEqual(umin[1, 1], 1) self.assertAlmostEqual(umax[0, 0], 1) self.assertAlmostEqual(umax[0, 1], 0) self.assertAlmostEqual(umax[1, 0], 1) self.assertAlmostEqual(umax[1, 1], 1) self.assertAlmostEqual(vmin[0, 0], 0) self.assertAlmostEqual(vmin[0, 1], 0) self.assertAlmostEqual(vmin[1, 0], 1) self.assertAlmostEqual(vmin[1, 1], 0) # check a slightly more general surface cp = [[0, 0], [.5, -.5], [1, 0], [-.6, 1], [1, 1], [2, 1.4], [0, 2], [.8, 3], [2, 2.4]] surf = Surface(BSplineBasis(3), BSplineBasis(3), cp) edg = surf.edges() u = np.linspace(0, 1, 9) v = np.linspace(0, 1, 9) pt = surf(0, v) pt2 = edg[0](v) self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0) pt = surf(1, v) pt2 = edg[1](v) self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0) pt = surf(u, 0).reshape(9, 2) pt2 = edg[2](u) self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0) pt = surf(u, 1).reshape(9, 2) pt2 = edg[3](u) self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0)
def square(size=1, lower_left=(0, 0)): """ Create a square with parametric origin at *(0,0)*. :param float size: Size(s), either a single scalar or a tuple of scalars per axis :param array-like lower_left: local origin, the lower left corner of the square :return: A linear parametrized square :rtype: Surface """ result = Surface() # unit square result.scale(size) result += lower_left return result
def test_3d_self_connection(self): square = Surface() + [1, 0] square = square.rotate(np.pi / 2, (1, 0, 0)) vol = volume_factory.revolve(square) vol = vol.split(vol.knots('w')[0], direction='w') # break periodicity model = SplineModel(3, 3) model.add(vol, raise_on_twins=False) writer = IFEMWriter(model) expected = [IFEMConnection(1, 1, 5, 6, 0)] for connection, want in zip(writer.connections(), expected): self.assertEqual(connection, want)
def test_reverse(self): basis1 = BSplineBasis(4, [2, 2, 2, 2, 3, 6, 7, 7, 7, 7]) basis2 = BSplineBasis(3, [-3, -3, -3, 20, 30, 31, 31, 31]) surf = Surface(basis1, basis2) surf2 = Surface(basis1, basis2) surf3 = Surface(basis1, basis2) surf2.reverse('v') surf3.reverse('u') for i in range(6): # loop over surf forward, and surf2 backward (in 'v'-direction) for (cp1, cp2) in zip(surf[i, :, :], surf2[i, ::-1, :]): self.assertAlmostEqual(cp1[0], cp2[0]) self.assertAlmostEqual(cp1[1], cp2[1]) for j in range(5): # loop over surf forward, and surf3 backward (in 'u'-direction) for (cp1, cp2) in zip(surf[:, j, :], surf3[::-1, j, :]): self.assertAlmostEqual(cp1[0], cp2[0]) self.assertAlmostEqual(cp1[1], cp2[1])
def test_constructor(self): # test 3D constructor cp = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]] surf = Surface(controlpoints=cp) val = surf(0.5, 0.5) self.assertEqual(val[0], 0.5) self.assertEqual(len(surf[0]), 3) # test 2D constructor cp = [[0, 0], [1, 0], [0, 1], [1, 1]] surf2 = Surface(controlpoints=cp) val = surf2(0.5, 0.5) self.assertEqual(val[0], 0.5) self.assertEqual(len(surf2[0]), 2) # test rational 2D constructor cp = [[0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]] surf3 = Surface(controlpoints=cp, rational=True) val = surf3(0.5, 0.5) self.assertEqual(val[0], 0.5) self.assertEqual(len(surf3[0]), 3) # test rational 3D constructor cp = [[0, 0, 0, 1], [1, 0, 0, 1], [0, 1, 0, 1], [1, 1, 0, 1]] surf4 = Surface(controlpoints=cp, rational=True) val = surf4(0.5, 0.5) self.assertEqual(val[0], 0.5) self.assertEqual(len(surf4[0]), 4) # test constructor with single basis b = BSplineBasis(4) surf = Surface(b, b) surf.insert_knot(.3, 'u') # change one, but not the other self.assertEqual(len(surf.knots('u')), 3) self.assertEqual(len(surf.knots('v')), 2) # TODO: Include a default constructor specifying nothing, or just polynomial degrees, or just knot vectors. # This should create identity mappings # test errors and exceptions controlpoints = [[0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]] with self.assertRaises(ValueError): basis1 = BSplineBasis(2, [1, 1, 0, 0]) basis2 = BSplineBasis(2, [0, 0, 1, 1]) surf = Surface(basis1, basis2, controlpoints) # illegal knot vector with self.assertRaises(ValueError): basis1 = BSplineBasis(2, [0, 0, .5, 1, 1]) basis2 = BSplineBasis(2, [0, 0, 1, 1]) surf = Surface(basis1, basis2, controlpoints) # too few controlpoints
def test_reparam(self): # identity mapping, control points generated from knot vector basis1 = BSplineBasis(4, [2, 2, 2, 2, 3, 6, 7, 7, 7, 7]) basis2 = BSplineBasis(3, [-3, -3, -3, 20, 30, 31, 31, 31]) surf = Surface(basis1, basis2) self.assertAlmostEqual(surf.start(0), 2) self.assertAlmostEqual(surf.end(0), 7) self.assertAlmostEqual(surf.start(1), -3) self.assertAlmostEqual(surf.end(1), 31) surf.reparam((4, 10), (0, 9)) self.assertAlmostEqual(surf.start(0), 4) self.assertAlmostEqual(surf.end(0), 10) self.assertAlmostEqual(surf.start(1), 0) self.assertAlmostEqual(surf.end(1), 9) surf.reparam((5, 11), direction=0) self.assertAlmostEqual(surf.start(0), 5) self.assertAlmostEqual(surf.end(0), 11) self.assertAlmostEqual(surf.start(1), 0) self.assertAlmostEqual(surf.end(1), 9) surf.reparam((5, 11), direction='v') self.assertAlmostEqual(surf.start(0), 5) self.assertAlmostEqual(surf.end(0), 11) self.assertAlmostEqual(surf.start(1), 5) self.assertAlmostEqual(surf.end(1), 11) surf.reparam((-9, 9)) self.assertAlmostEqual(surf.start(0), -9) self.assertAlmostEqual(surf.end(0), 9) self.assertAlmostEqual(surf.start(1), 0) self.assertAlmostEqual(surf.end(1), 1) surf.reparam() self.assertAlmostEqual(surf.start(0), 0) self.assertAlmostEqual(surf.end(0), 1) self.assertAlmostEqual(surf.start(1), 0) self.assertAlmostEqual(surf.end(1), 1) surf.reparam((4, 10), (0, 9)) surf.reparam(direction=1) self.assertAlmostEqual(surf.start(0), 4) self.assertAlmostEqual(surf.end(0), 10) self.assertAlmostEqual(surf.start(1), 0) self.assertAlmostEqual(surf.end(1), 1)
def extrude(curve, amount): """ Extrude a curve by sweeping it to a given height. :param Curve curve: Curve to extrude :param array-like amount: 3-component vector of sweeping amount and direction :return: The extruded curve :rtype: Surface """ curve = curve.clone() # clone input curve, throw away input reference curve.set_dimension(3) # add z-components (if not already present) n = len(curve) # number of control points of the curve cp = np.zeros((2 * n, curve.dimension + curve.rational)) cp[:n, :] = curve.controlpoints # the first control points form the bottom curve += amount cp[n:, :] = curve.controlpoints # the last control points form the top return Surface(curve.bases[0], BSplineBasis(2), cp, curve.rational)
def test_split(self): # test a rational 2D surface controlpoints = [[0, 0, 1], [-1, 1, .96], [0, 2, 1], [1, -1, 1], [1, 0, .8], [1, 1, 1], [2, 1, .89], [2, 2, .9], [2, 3, 1], [3, 0, 1], [4, 1, 1], [3, 2, 1]] basis1 = BSplineBasis(3, [1, 1, 1, 1.4, 5, 5, 5]) basis2 = BSplineBasis(3, [2, 2, 2, 7, 7, 7]) surf = Surface(basis1, basis2, controlpoints, True) split_u_surf = surf.split([1.1, 1.6, 4], 0) split_v_surf = surf.split(3.1, 1) self.assertEqual(len(split_u_surf), 4) self.assertEqual(len(split_v_surf), 2) # check that the u-vector is properly split self.assertAlmostEqual(split_u_surf[0].start()[0], 1.0) self.assertAlmostEqual(split_u_surf[0].end()[0], 1.1) self.assertAlmostEqual(split_u_surf[1].start()[0], 1.1) self.assertAlmostEqual(split_u_surf[1].end()[0], 1.6) self.assertAlmostEqual(split_u_surf[2].start()[0], 1.6) self.assertAlmostEqual(split_u_surf[2].end()[0], 4.0) self.assertAlmostEqual(split_u_surf[3].start()[0], 4.0) self.assertAlmostEqual(split_u_surf[3].end()[0], 5.0) # check that the v-vectors remain unchanged self.assertAlmostEqual(split_u_surf[2].start()[1], 2.0) self.assertAlmostEqual(split_u_surf[2].end()[1], 7.0) # check that the v-vector is properly split self.assertAlmostEqual(split_v_surf[0].start()[1], 2.0) self.assertAlmostEqual(split_v_surf[0].end()[1], 3.1) self.assertAlmostEqual(split_v_surf[1].start()[1], 3.1) self.assertAlmostEqual(split_v_surf[1].end()[1], 7.0) # check that the u-vector remain unchanged self.assertAlmostEqual(split_v_surf[1].start()[0], 1.0) self.assertAlmostEqual(split_v_surf[1].end()[0], 5.0) # check that evaluations remain unchanged pt1 = surf(3.23, 2.12) self.assertAlmostEqual(split_u_surf[2].evaluate(3.23, 2.12)[0], pt1[0]) self.assertAlmostEqual(split_u_surf[2].evaluate(3.23, 2.12)[1], pt1[1]) self.assertAlmostEqual(split_v_surf[0].evaluate(3.23, 2.12)[0], pt1[0]) self.assertAlmostEqual(split_v_surf[0].evaluate(3.23, 2.12)[1], pt1[1])