def test_curve_interpolation(self): basis = BSplineBasis(4, [0, 0, 0, 0, .3, .9, 1, 1, 1, 1]) t = np.array(basis.greville()) # create the mapping (x,y,z)=(t^2, 1-t, t^3+2*t) x_pts = np.zeros((len(t), 3)) x_pts[:, 0] = t * t x_pts[:, 1] = 1 - t x_pts[:, 2] = t * t * t + 2 * t crv = CurveFactory.interpolate(x_pts, basis) self.assertEqual(crv.order(0), 4) self.assertAlmostEqual(crv(.4)[0], .4**2) # x=t^2 self.assertAlmostEqual(crv(.4)[1], 1 - .4) # y=1-t self.assertAlmostEqual(crv(.4)[2], .4**3 + 2 * .4) # z=t^3+2t self.assertAlmostEqual(crv(.5)[0], .5**2) # x=t^2 self.assertAlmostEqual(crv(.5)[1], 1 - .5) # y=1-t self.assertAlmostEqual(crv(.5)[2], .5**3 + 2 * .5) # z=t^3+2t
def test_curve_interpolation(self): basis = BSplineBasis(4, [0, 0, 0, 0, .3, .9, 1, 1, 1, 1]) t = np.array(basis.greville()) # create the mapping (x,y,z)=(t^2, 1-t, t^3+2*t) x_pts = np.zeros((len(t), 3)) x_pts[:, 0] = t * t x_pts[:, 1] = 1 - t x_pts[:, 2] = t * t * t + 2 * t crv = cf.interpolate(x_pts, basis) self.assertEqual(crv.order(0), 4) self.assertAlmostEqual(crv(.4)[0], .4**2) # x=t^2 self.assertAlmostEqual(crv(.4)[1], 1 - .4) # y=1-t self.assertAlmostEqual(crv(.4)[2], .4**3 + 2 * .4) # z=t^3+2t self.assertAlmostEqual(crv(.5)[0], .5**2) # x=t^2 self.assertAlmostEqual(crv(.5)[1], 1 - .5) # y=1-t self.assertAlmostEqual(crv(.5)[2], .5**3 + 2 * .5) # z=t^3+2t
def test_lower_order(self): basis = BSplineBasis(4, [0,0,0,0,.2, .3, .3, .6, .9, 1,1,1,1]) interp_pts = basis.greville() x = [[t*(1-t), t**2] for t in interp_pts] crv = CurveFactory.interpolate(x, basis) # function in space, exact representation kept crv2 = crv.lower_order(1) # still in space, crv2 is *also* exact t = np.linspace(0,1, 13) self.assertTrue(np.allclose( crv(t), crv2(t)) ) self.assertEqual(crv.order(0), 4) self.assertEqual(crv2.order(0), 3) self.assertEqual(crv.continuity(0.3), 1) self.assertEqual(crv.continuity(0.6), 2) self.assertEqual(crv2.continuity(0.3), 1) self.assertEqual(crv2.continuity(0.6), 1)
def test_lower_order(self): basis = BSplineBasis(4, [0, 0, 0, 0, .2, .3, .3, .6, .9, 1, 1, 1, 1]) interp_pts = basis.greville() x = [[t * (1 - t), t**2] for t in interp_pts] crv = cf.interpolate( x, basis) # function in space, exact representation kept crv2 = crv.lower_order(1) # still in space, crv2 is *also* exact t = np.linspace(0, 1, 13) self.assertTrue(np.allclose(crv(t), crv2(t))) self.assertEqual(crv.order(0), 4) self.assertEqual(crv2.order(0), 3) self.assertEqual(crv.continuity(0.3), 1) self.assertEqual(crv.continuity(0.6), 2) self.assertEqual(crv2.continuity(0.3), 1) self.assertEqual(crv2.continuity(0.6), 1)
def thicken(curve, amount): """ Generate a surface by adding thickness to a curve. - For 2D curves this will generate a 2D planar surface with the curve through the center. - For 3D curves this will generate a surface "tube" which is open at both ends (that is, for a straight line it will generate a cylinder shell). The resulting surface is an approximation generated by interpolating at the Greville points. It will use the same discretization as the input curve. It does not check for self-intersecting geometries. :param Curve curve: The generating curve :param amount: The amount of thickness, either constant or variable (if variable, the function must accept parameters named *x*, *y*, *z* and/or *t*) :return: Surrounding surface :rtype: Surface """ # NOTES: There are several pitfalls with this function # * self intersection: # could be handled more gracefully, but is here ignored # * choice of discretization: # the offset curve is computed from the velocity (tangent) which is of # one continuity less than the original curve. In particular C1 # quadratic curves will get very noticeable C0-kinks in them. Currently # this is completely ignored and we keep the high continuity of the # original curve. # * width given by function input # could produce wild behaviour. Original discretization might not # produce a satisfactory result curve = curve.clone() # clone input curve, throw away input reference t = curve.bases[0].greville() if curve.dimension == 2: # linear parametrization across domain n = len(curve) left_points = np.zeros((n, 2)) right_points = np.zeros((n, 2)) linear = BSplineBasis(2) x = curve.evaluate(t) # curve at interpolation points v = curve.derivative(t) # velocity at interpolation points l = np.sqrt(v[:, 0]**2 + v[:, 1]**2) # normalizing factor for velocity for i in range(n): if l[i] < 1e-13: # in case of zero velocity, use neighbour instead if i > 0: v[i, :] = v[i - 1, :] else: v[i, :] = v[i + 1, :] else: v[i, :] /= l[i] if inspect.isfunction(amount): arg_names = inspect.signature(amount).parameters argc = len(arg_names) argv = [0] * argc for i in range(n): # build up the list of arguments (in case not all of (x,y,t) are specified) for j, name in enumerate(arg_names): if name == 'x': argv[j] = x[i, 0] elif name == 'y': argv[j] = x[i, 1] elif name == 'z': argv[j] = 0.0 elif name == 't': argv[j] = t[i] # figure out the distane at this particular point dist = amount(*argv) # store interpolation points right_points[i, 0] = x[i, 0] - v[i, 1] * dist # x at bottom right_points[i, 1] = x[i, 1] + v[i, 0] * dist # y at bottom left_points[i, 0] = x[i, 0] + v[i, 1] * dist # x at top left_points[i, 1] = x[i, 1] - v[i, 0] * dist # y at top else: right_points[:, 0] = x[:, 0] - v[:, 1] * amount # x at bottom right_points[:, 1] = x[:, 1] + v[:, 0] * amount # y at bottom left_points[:, 0] = x[:, 0] + v[:, 1] * amount # x at top left_points[:, 1] = x[:, 1] - v[:, 0] * amount # y at top # perform interpolation on each side right = CurveFactory.interpolate(right_points, curve.bases[0]) left = CurveFactory.interpolate(left_points, curve.bases[0]) return edge_curves(right, left) else: # dimension=3, we will create a surrounding tube return sweep(curve, CurveFactory.circle(r=amount))
def thicken(curve, amount): """Generate a surface by adding thickness to a curve. - For 2D curves this will generate a 2D planar surface with the curve through the center. - For 3D curves this will generate a surface "tube" which is open at both ends (that is, for a straight line it will generate a cylinder shell). The resulting surface is an approximation generated by interpolating at the Greville points. It will use the same discretization as the input curve. It does not check for self-intersecting geometries. :param Curve curve: The generating curve :param amount: The amount of thickness, either constant or variable (if variable, the function must accept parameters named *x*, *y*, *z* and/or *t*) :return: Surrounding surface :rtype: Surface """ # NOTES: There are several pitfalls with this function # * self intersection: # could be handled more gracefully, but is here ignored # * choice of discretization: # the offset curve is computed from the velocity (tangent) which is of # one continuity less than the original curve. In particular C1 # quadratic curves will get very noticable C0-kinks in them. Currently # this is completely ignored and we keep the high continuity of the # original curve. # * width given by function input # could produce wild behaviour. Original discretization might not # produce a satisfactory result # * 3D tube geometries: # unimplemented as of now. Would like to see the three points above # resolved before this is implemented. Rough idea is to compute the # acceleration and binormal vectors to the curve and sketch out a # circle in the plane defined by these two vectors curve = curve.clone() # clone input curve, throw away input reference t = curve.bases[0].greville() if curve.dimension == 2: # linear parametrization across domain n = len(curve) left_points = np.matrix(np.zeros((n, 2))) right_points = np.matrix(np.zeros((n, 2))) linear = BSplineBasis(2) x = np.matrix(curve.evaluate(t)) # curve at interpolation points v = curve.tangent(t) # velocity at interpolation points l = np.sqrt(v[:, 0]**2 + v[:, 1]**2) # normalizing factor for velocity v[:, 0] = v[:, 0] / l v[:, 1] = v[:, 1] / l for i in range(n): if l[i] < 1e-13: # in case of zero velocity, use neighbour instead if i>0: v[i,:] = v[i-1,:] else: v[i,:] = v[i+1,:] v = np.matrix(v) if inspect.isfunction(amount): arg_names = inspect.getargspec(amount).args argc = len(arg_names) argv = [0] * argc for i in range(n): # build up the list of arguments (in case not all of (x,y,t) are specified) for j in range(argc): if arg_names[j] == 'x': argv[j] = x[i, 0] elif arg_names[j] == 'y': argv[j] = x[i, 1] elif arg_names[j] == 'z': argv[j] = 0.0 elif arg_names[j] == 't': argv[j] = t[i] # figure out the distane at this particular point dist = amount(*argv) # store interpolation points right_points[i, 0] = x[i, 0] - v[i, 1] * dist # x at bottom right_points[i, 1] = x[i, 1] + v[i, 0] * dist # y at bottom left_points[ i, 0] = x[i, 0] + v[i, 1] * dist # x at top left_points[ i, 1] = x[i, 1] - v[i, 0] * dist # y at top else: right_points[:, 0] = x[:, 0] - v[:, 1] * amount # x at bottom right_points[:, 1] = x[:, 1] + v[:, 0] * amount # y at bottom left_points[ :, 0] = x[:, 0] + v[:, 1] * amount # x at top left_points[ :, 1] = x[:, 1] - v[:, 0] * amount # y at top # perform interpolation on each side right = CurveFactory.interpolate(right_points, curve.bases[0]) left = CurveFactory.interpolate(left_points, curve.bases[0]) return edge_curves(right, left) else: # dimension=3, we will create a surrounding tube raise NotImplementedError('Currently only 2D supported. See comments in source code')