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 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 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 coons_patch(bottom, right, top, left): """ Create the surface defined by the region between the 4 input curves. The input curves need to be parametrized to form a directed loop around the resulting Surface. For more information on Coons patch see: https://en.wikipedia.org/wiki/Coons_patch. :param [Curve] bottom: curve corresponding to the result parametric value v=0 :param [Curve] right: curve corresponding to the result parametric value u=1 :param [Curve] top: curve corresponding to the result parametric value v=1 (reversed: going right-left) :param [Curve] left: curve corresponding to the result parametric value u=0 (reversed: going top-bottom) :return: The enclosed surface :rtype: Surface """ # 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 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 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): """ Generate a volume by lofting a series of surfaces The resulting volume is interpolated at all input surfaces and a smooth transition between these surfaces is computed as a cubic spline interpolation in the lofting direction. In the case that insufficient surfaces are provided as input (less than 4 surfaces), then a quadratic or linear interpolation is performed. Note that the order of input surfaces matter as they will be interpolated in this particular order. Also note that the surfaces need to be parametrized in the same direction, otherwise you will encounter self-intersecting result volume as it is wrapping in on itself. :param Surfaces surfaces: A sequence of surfaces to be lofted :return: Lofted volume :rtype: Volume Examples: .. code:: python from splipy import surface_factory, volume_factory srf1 = surface_factory.disc(r=1) srf2 = 1.5 * srf1 + [0,0,1] srf3 = 1.0 * srf1 + [0,0,2] srf4 = 1.3 * srf1 + [0,0,4] vol = volume_factory.loft(srf1, srf2, srf3, srf4) # alternatively you can provide all input curves as a list all_my_surfaces = [srf1, srf2, srf3, srf4] vol = volume_factory.loft(all_my_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 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 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')