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_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 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 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_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 square(size=1, lower_left=(0,0)): """square([size=1]) Create a square with parametric origin at *(0,0)*. :param size: Size(s), either a single scalar or a tuple of scalars per axis :type size: float or (float) :return: A linear parametrized square :rtype: Surface """ result = Surface() # unit square result.scale(size) result += lower_left return result
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 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 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 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 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 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 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_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)
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 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 revolve(curve, theta=2 * pi, axis=[0,0,1]): """revolve(curve, [theta=2pi], [axis=[0,0,1]]) Revolve a surface by sweeping a curve in a rotational fashion around the *z* axis. :param Curve curve: Curve to revolve :param float theta: Angle to revolve, in radians :param vector-like axis: Axis of rotation :return: The revolved surface :rtype: Surface """ curve = curve.clone() # clone input curve, throw away input reference curve.set_dimension(3) # add z-components (if not already present) curve.force_rational() # add weight (if not already present) # align axis with the z-axis normal_theta = atan2(axis[1], axis[0]) normal_phi = atan2(sqrt(axis[0]**2 + axis[1]**2), axis[2]) curve.rotate(-normal_theta, [0,0,1]) curve.rotate(-normal_phi, [0,1,0]) circle_seg = CurveFactory.circle_segment(theta) n = len(curve) # number of control points of the curve m = len(circle_seg) # number of control points of the sweep cp = np.zeros((m * n, 4)) # loop around the circle and set control points by the traditional 9-point # circle curve with weights 1/sqrt(2), only here C0-periodic, so 8 points dt = 0 t = 0 for i in range(m): x,y,w = circle_seg[i] dt = atan2(y,x) - t t += dt curve.rotate(dt) cp[i * n:(i + 1) * n, :] = curve[:] cp[i * n:(i + 1) * n, 2] *= w cp[i * n:(i + 1) * n, 3] *= w result = Surface(curve.bases[0], circle_seg.bases[0], cp, True) # rotate it back again result.rotate(normal_phi, [0,1,0]) result.rotate(normal_theta, [0,0,1]) return result
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 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_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])
def revolve(curve, theta=2 * pi, axis=(0,0,1)): """ Revolve a surface by sweeping a curve in a rotational fashion around the *z* axis. :param Curve curve: Curve to revolve :param float theta: Angle to revolve, in radians :param array-like axis: Axis of rotation :return: The revolved surface :rtype: Surface """ curve = curve.clone() # clone input curve, throw away input reference curve.set_dimension(3) # add z-components (if not already present) curve.force_rational() # add weight (if not already present) # align axis with the z-axis normal_theta = atan2(axis[1], axis[0]) normal_phi = atan2(sqrt(axis[0]**2 + axis[1]**2), axis[2]) curve.rotate(-normal_theta, [0,0,1]) curve.rotate(-normal_phi, [0,1,0]) circle_seg = CurveFactory.circle_segment(theta) n = len(curve) # number of control points of the curve m = len(circle_seg) # number of control points of the sweep cp = np.zeros((m * n, 4)) # loop around the circle and set control points by the traditional 9-point # circle curve with weights 1/sqrt(2), only here C0-periodic, so 8 points dt = 0 t = 0 for i in range(m): x,y,w = circle_seg[i] dt = atan2(y,x) - t t += dt curve.rotate(dt) cp[i * n:(i + 1) * n, :] = curve[:] cp[i * n:(i + 1) * n, 2] *= w cp[i * n:(i + 1) * n, 3] *= w result = Surface(curve.bases[0], circle_seg.bases[0], cp, True) # rotate it back again result.rotate(normal_phi, [0,1,0]) result.rotate(normal_theta, [0,0,1]) return result
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_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 = VolumeFactory.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 = VolumeFactory.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_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 test_force_rational(self): # more or less random 3D surface with p=[3,2] and n=[4,3] controlpoints = [[0, 0, 1], [-1, 1, 1], [0, 2, 1], [1, -1, 1], [1, 0, 1], [1, 1, 1], [2, 1, 1], [2, 2, 1], [2, 3, 1], [3, 0, 1], [4, 1, 1], [3, 2, 1]] 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) evaluation_point1 = surf(0.23, .66) control_point1 = surf[0] surf.force_rational() evaluation_point2 = surf(0.23, .66) control_point2 = surf[0] # ensure that surface 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(surf.rational, True)
def ifem_solve(root, mu, i): with open('case.xinp') as f: template = Template(f.read()) with open(root / 'case.xinp', 'w') as f: f.write(template.render(geometry='square.g2', **mu)) patch = Surface() patch.set_dimension(3) with G2(str(root / 'square.g2')) as g2: g2.write(patch) try: g2.fstream.close() except: pass result = run([ '/home/eivind/repos/IFEM/Apps/Poisson/build/bin/Poisson', 'case.xinp', '-adap', '-hdf5' ], cwd=root, stdout=PIPE, stderr=PIPE) result.check_returncode() with h5py.File(root / 'case.hdf5', 'r') as h5: final = str(len(h5) - 1) group = h5[final]['Poisson-1'] patchbytes = group['basis']['1'][:].tobytes() geompatch = lr.LRSplineSurface(patchbytes) coeffs = group['fields']['u']['1'][:] solpatch = geompatch.clone() solpatch.controlpoints = coeffs.reshape(len(solpatch), -1) with open(f'poisson-mesh-single-{i}.ps', 'wb') as f: geompatch.write_postscript(f) return geompatch, solpatch
def test_swap(self): # more or less random 3D surface with p=[2,2] and n=[4,3] 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, .64, 2, 2, 2]) basis2 = BSplineBasis(3, [0, 0, 0, 1, 1, 1]) surf = Surface(basis1, basis2, controlpoints) evaluation_point1 = surf(0.23, .56) control_point1 = surf[1] # this is control point i=(1,0), when n=(4,3) surf.swap() evaluation_point2 = surf(0.56, .23) control_point2 = surf[3] # this is control point i=(0,1), when n=(3,4) # ensure that surface 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]) # check that the control points have re-ordered themselves self.assertEqual(control_point1[0], control_point2[0]) self.assertEqual(control_point1[1], control_point2[1]) self.assertEqual(control_point1[2], control_point2[2])
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 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 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 plane(self): dim = int( self.read_next_non_whitespace().strip()) center = np.array(next(self.fstream).split(), dtype=float) normal = np.array(next(self.fstream).split(), dtype=float) x_axis = np.array(next(self.fstream).split(), dtype=float) finite = next(self.fstream).strip() != '0' if finite: param_u= np.array(next(self.fstream).split(), dtype=float) param_v= np.array(next(self.fstream).split(), dtype=float) else: param_u= [-state.unlimited, +state.unlimited] param_v= [-state.unlimited, +state.unlimited] swap = next(self.fstream).strip() != '0' result = Surface() * [param_u[1]-param_u[0], param_v[1]-param_v[0]] + [param_u[0],param_v[0]] result.rotate(rotate_local_x_axis(x_axis, normal)) result = flip_and_move_plane_geometry(result,center,normal) result.reparam(param_u, param_v) if(swap): result.swap() return result
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 edge_surfaces(*surfaces): """ Create the volume defined by the region between the input surfaces. In case of six input surfaces, these must be given in the order: bottom, top, left, right, back, front. Opposing sides must be parametrized in the same directions. :param [Surface] surfaces: Two or six edge surfaces :return: The enclosed volume :rtype: Volume :raises ValueError: If the length of *surfaces* is not two or six """ if len(surfaces) == 1: # probably gives input as a list-like single variable surfaces = surfaces[0] if len(surfaces) == 2: surf1 = surfaces[0].clone() surf2 = surfaces[1].clone() Surface.make_splines_identical(surf1, surf2) (n1, n2, d) = surf1.controlpoints.shape # d = dimension + rational controlpoints = np.zeros((n1, n2, 2, d)) controlpoints[:, :, 0, :] = surf1.controlpoints controlpoints[:, :, 1, :] = surf2.controlpoints # Volume constructor orders control points in a different way, so we # create it from scratch here result = Volume(surf1.bases[0], surf1.bases[1], BSplineBasis(2), controlpoints, rational=surf1.rational, raw=True) return result elif len(surfaces) == 6: if any([surf.rational for surf in surfaces]): raise RuntimeError('edge_surfaces not supported for rational splines') # coons patch (https://en.wikipedia.org/wiki/Coons_patch) umin = surfaces[0] umax = surfaces[1] vmin = surfaces[2] vmax = surfaces[3] wmin = surfaces[4] wmax = surfaces[5] vol1 = edge_surfaces(umin,umax) vol2 = edge_surfaces(vmin,vmax) vol3 = edge_surfaces(wmin,wmax) vol4 = Volume(controlpoints=vol1.corners(order='F'), rational=vol1.rational) vol1.swap(0, 2) vol1.swap(1, 2) vol2.swap(1, 2) vol4.swap(1, 2) Volume.make_splines_identical(vol1, vol2) Volume.make_splines_identical(vol1, vol3) Volume.make_splines_identical(vol1, vol4) Volume.make_splines_identical(vol2, vol3) Volume.make_splines_identical(vol2, vol4) Volume.make_splines_identical(vol3, vol4) result = vol1.clone() result.controlpoints += vol2.controlpoints result.controlpoints += vol3.controlpoints result.controlpoints -= 2*vol4.controlpoints return result else: raise ValueError('Requires two or six input surfaces')
def loft(*surfaces): if len(surfaces) == 1: surfaces = surfaces[0] # clone input, so we don't change those references # make sure everything has the same dimension since we need to compute length surfaces = [s.clone().set_dimension(3) for s in surfaces] if len(surfaces)==2: return SurfaceFactory.edge_curves(surfaces) elif len(surfaces)==3: # can't do cubic spline interpolation, so we'll do quadratic basis3 = BSplineBasis(3) dist = basis3.greville() else: x = [s.center() for s in surfaces] # create knot vector from the euclidian length between the surfaces 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 basis3 = BSplineBasis(4, knot) n = len(surfaces) for i in range(n): for j in range(i+1,n): Surface.make_splines_identical(surfaces[i], surfaces[j]) basis1 = surfaces[0].bases[0] basis2 = surfaces[0].bases[1] m1 = basis1.num_functions() m2 = basis2.num_functions() dim = len(surfaces[0][0]) u = basis1.greville() # parametric interpolation points v = basis2.greville() w = dist # compute matrices Nu = basis1(u) Nv = basis2(v) Nw = basis3(w) Nu_inv = np.linalg.inv(Nu) Nv_inv = np.linalg.inv(Nv) Nw_inv = np.linalg.inv(Nw) # compute interpolation points in physical space x = np.zeros((m1,m2,n, dim)) for i in range(n): tmp = np.tensordot(Nv, surfaces[i].controlpoints, axes=(1,1)) x[:,:,i,:] = np.tensordot(Nu, tmp , axes=(1,1)) # solve interpolation problem cp = np.tensordot(Nw_inv, x, axes=(1,2)) cp = np.tensordot(Nv_inv, cp, axes=(1,2)) cp = np.tensordot(Nu_inv, cp, axes=(1,2)) # re-order controlpoints so they match up with Surface constructor cp = np.reshape(cp.transpose((2, 1, 0, 3)), (m1*m2*n, dim)) return Volume(basis1, basis2, basis3, cp, surfaces[0].rational)
def sphere(r=1, center=(0,0,0), type='radial'): """ Create a solid sphere :param float r: Radius :param array-like center: Local origin of the sphere :param string type: The type of parametrization ('radial' or 'square') :return: A solid ball :rtype: Volume """ if type == 'radial': shell = SurfaceFactory.sphere(r, center) midpoint = shell*0 + center return edge_surfaces(shell, midpoint) elif type == 'square': # based on the work of James E.Cobb: "Tiling the Sphere with Rational Bezier Patches" # University of Utah, July 11, 1988. UUCS-88-009 b = BSplineBasis(order=5) sr2 = sqrt(2) sr3 = sqrt(3) sr6 = sqrt(6) cp = [[ -4*(sr3-1), 4*(1-sr3), 4*(1-sr3), 4*(3-sr3) ], # row 0 [ -sr2 , sr2*(sr3-4), sr2*(sr3-4), sr2*(3*sr3-2)], [ 0 , 4./3*(1-2*sr3), 4./3*(1-2*sr3),4./3*(5-sr3) ], [ sr2 , sr2*(sr3-4), sr2*(sr3-4), sr2*(3*sr3-2)], [ 4*(sr3-1), 4*(1-sr3), 4*(1-sr3), 4*(3-sr3) ], [ -sr2*(4-sr3), -sr2, sr2*(sr3-4), sr2*(3*sr3-2)], # row 1 [ -(3*sr3-2)/2, (2-3*sr3)/2, -(sr3+6)/2, (sr3+6)/2], [ 0 , sr2*(2*sr3-7)/3, -5*sr6/3, sr2*(sr3+6)/3], [ (3*sr3-2)/2, (2-3*sr3)/2, -(sr3+6)/2, (sr3+6)/2], [ sr2*(4-sr3), -sr2, sr2*(sr3-4), sr2*(3*sr3-2)], [ -4./3*(2*sr3-1), 0, 4./3*(1-2*sr3), 4*(5-sr3)/3], # row 2 [-sr2/3*(7-2*sr3), 0, -5*sr6/3, sr2*(sr3+6)/3], [ 0 , 0, 4*(sr3-5)/3, 4*(5*sr3-1)/9], [ sr2/3*(7-2*sr3), 0, -5*sr6/3, sr2*(sr3+6)/3], [ 4./3*(2*sr3-1), 0, 4./3*(1-2*sr3), 4*(5-sr3)/3], [ -sr2*(4-sr3), sr2, sr2*(sr3-4), sr2*(3*sr3-2)], # row 3 [ -(3*sr3-2)/2, -(2-3*sr3)/2, -(sr3+6)/2, (sr3+6)/2], [ 0 ,-sr2*(2*sr3-7)/3, -5*sr6/3, sr2*(sr3+6)/3], [ (3*sr3-2)/2, -(2-3*sr3)/2, -(sr3+6)/2, (sr3+6)/2], [ sr2*(4-sr3), sr2, sr2*(sr3-4), sr2*(3*sr3-2)], [ -4*(sr3-1), -4*(1-sr3), 4*(1-sr3), 4*(3-sr3) ], # row 4 [ -sr2 , -sr2*(sr3-4), sr2*(sr3-4), sr2*(3*sr3-2)], [ 0 , -4./3*(1-2*sr3), 4./3*(1-2*sr3),4./3*(5-sr3) ], [ sr2 , -sr2*(sr3-4), sr2*(sr3-4), sr2*(3*sr3-2)], [ 4*(sr3-1), -4*(1-sr3), 4*(1-sr3), 4*(3-sr3) ]] wmin = Surface(b,b,cp, rational=True) wmax = wmin.clone().mirror([0,0,1]) vmax = wmin.clone().rotate(pi/2, [1,0,0]) vmin = vmax.clone().mirror([0,1,0]) umax = vmin.clone().rotate(pi/2, [0,0,1]) umin = umax.clone().mirror([1,0,0]) # ideally I would like to call edge_surfaces() now, but that function # does not work with rational surfaces, so we'll just manually try # and add some inner controlpoints cp = np.zeros((5,5,5,4)) cp[ :, :, 0,:] = wmin[:,:,:] cp[ :, :,-1,:] = wmax[:,:,:] cp[ :, 0, :,:] = vmin[:,:,:] cp[ :,-1, :,:] = vmax[:,:,:] cp[ 0, :, :,:] = umin[:,:,:] cp[-1, :, :,:] = umax[:,:,:] inner = np.linspace(-.5,.5, 3) Y, X, Z = np.meshgrid(inner,inner,inner) cp[1:4,1:4,1:4,0] = X cp[1:4,1:4,1:4,1] = Y cp[1:4,1:4,1:4,2] = Z cp[1:4,1:4,1:4,3] = 1 ball = Volume(b,b,b,cp,rational=True, raw=True) return r*ball + center else: raise ValueError('invalid type argument')