Example #1
0
    def test_periodic_split(self):
        # non-uniform rational knot vector of a periodic cubic n=7 curve
        controlpoints = [[1, 0, 1], [1, 1, .7], [0, 1, .89], [-1, 1, 0.5],
                         [-1, 0, 1], [-1, -.5, 1], [1, -.5, 1]]
        basis = BSplineBasis(4,
                             [-3, -2, -1, 0, 1, 2, 2.5, 4, 5, 6, 7, 8, 9, 9.5],
                             2)
        crv = Curve(basis, controlpoints, rational=True)
        crv2 = crv.split(1)  # split at knot value
        crv3 = crv.split(6.5)  # split outside existing knot

        self.assertEqual(len(crv), 7)
        self.assertEqual(len(crv2), 10)
        self.assertEqual(len(crv3), 11)

        self.assertEqual(crv.periodic(), True)
        self.assertEqual(crv2.periodic(), False)
        self.assertEqual(crv3.periodic(), False)

        t = np.linspace(6.5, 8,
                        13)  # domain where all parameter values are the same
        pt = crv(t)
        pt2 = crv2(t)
        pt3 = crv3(t)

        self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0)
        self.assertAlmostEqual(np.linalg.norm(pt - pt3), 0.0)
Example #2
0
    def test_derivative(self):
        # testing the parametrization x(t) = [.5*x^3 / ((1-x)^3+.5*x^3), 0]
        cp = [[0, 0, 1], [0, 0, 0], [0, 0, 0], [.5, 0, .5]]
        crv = Curve(BSplineBasis(4), cp, rational=True)

        def expect_derivative(x):
            return 6 * (1 - x)**2 * x**2 / (x**3 - 6 * x**2 + 6 * x - 2)**2

        def expect_derivative_2(x):
            return -12 * x * (x**5 - 3 * x**4 + 2 * x**3 + 4 * x**2 - 6 * x +
                              2) / (x**3 - 6 * x**2 + 6 * x - 2)**3

        # insert a few more knots to spice things up
        crv.insert_knot([.2, .71])

        self.assertAlmostEqual(
            crv.derivative(0.32, 1)[0], expect_derivative(0.32))
        self.assertAlmostEqual(crv.derivative(0.32, 1)[1], 0)
        self.assertAlmostEqual(
            crv.derivative(0.71, 1)[0], expect_derivative(0.71))

        self.assertAlmostEqual(
            crv.derivative(0.22, 2)[0], expect_derivative_2(0.22))
        self.assertAlmostEqual(crv.derivative(0.22, 2)[1], 0)
        self.assertAlmostEqual(
            crv.derivative(0.86, 2)[0], expect_derivative_2(0.86))
Example #3
0
    def test_torsion(self):
        # planar curves have zero torsion
        controlpoints = [[1, 0, 1], [1, 1, .7], [0, 1, .89], [-1, 1, 0.5],
                         [-1, 0, 1], [-1, -.5, 1], [1, -.5, 1]]
        basis = BSplineBasis(4,
                             [-3, -2, -1, 0, 1, 2, 2.5, 4, 5, 6, 7, 8, 9, 9.5],
                             2)
        crv = Curve(basis, controlpoints, rational=True)
        self.assertAlmostEqual(crv.torsion(.3), 0.0)

        # test multiple evaluation points
        t = np.linspace(0, 1, 10)
        k = crv.torsion(t)
        self.assertEqual(len(k.shape), 1)  # is vector
        self.assertEqual(k.shape[0], 10)  # length 10 vector
        self.assertTrue(np.allclose(k, 0.0))  # zero torsion for planar curves

        # test helix: [a*cos(t), a*sin(t), b*t]
        t = np.linspace(0, 6 * pi, 300)
        a = 3.0
        b = 2.0
        x = np.array([a * np.cos(t), a * np.sin(t), b * t])
        crv = cf.cubic_curve(x.T, t=t)
        t = np.linspace(0, 6 * pi, 10)
        k = crv.torsion(t)

        # this is a helix approximation, hence atol=1e-3
        self.assertTrue(np.allclose(k, b / (a**2 + b**2),
                                    atol=1e-3))  # helix have const. torsion
Example #4
0
    def test_surface_loft(self):
        crv1 = Curve(BSplineBasis(3, range(11), 1), [[1,-1], [1,0], [1,1], [-1,1], [-1,0], [-1,-1]])
        crv2 = cf.circle(2) + (0,0,1)
        crv3 = Curve(BSplineBasis(4, range(11), 2), [[1,-1,2], [1,1,2], [-1,1,2], [-1,-1,2]])
        crv4 = cf.circle(2) + (0,0,3)
        surf = sf.loft(crv1, crv2, crv3, crv4)

        crv1.set_dimension(3) # for convenience when evaluating
        t = np.linspace( 0, 1, 13)

        u = np.linspace(crv1.start(0), crv1.end(0), 13)
        pt  = crv1(u)
        pt2 = surf(t,0).reshape(13,3)
        self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)

        u = np.linspace(crv2.start(0), crv2.end(0), 13)
        pt  = crv2(u)
        pt2 = surf(t,1).reshape(13,3)
        self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)

        u = np.linspace(crv3.start(0), crv3.end(0), 13)
        pt  = crv3(u)
        pt2 = surf(t,2).reshape(13,3)
        self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)

        u = np.linspace(crv4.start(0), crv4.end(0), 13)
        pt  = crv4(u)
        pt2 = surf(t,3).reshape(13,3)
        self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)
Example #5
0
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)
Example #6
0
    def test_center(self):
        # create the geometric mapping x(t) = t, y(t) = t^3,  t=[0,1]
        cp = [[0, 0], [1.0 / 3, 0], [2.0 / 3, 0], [1.0, 1]]
        basis = BSplineBasis(4)
        crv = Curve(basis, cp)
        center = crv.center()

        self.assertAlmostEqual(center[0], .5)
        self.assertAlmostEqual(center[1], .25)
Example #7
0
    def test_center(self):
        # create the geometric mapping x(t) = t, y(t) = t^3,  t=[0,1]
        cp = [[0, 0], [1.0/3, 0], [2.0/3, 0], [1.0, 1]]
        basis = BSplineBasis(4)
        crv = Curve(basis, cp)
        center = crv.center()

        self.assertAlmostEqual(center[0], .5)
        self.assertAlmostEqual(center[1], .25)
Example #8
0
    def test_raise_order(self):
        # non-uniform knot vector of a squiggly quadratic n=5 curve in 3D
        controlpoints = [[0, 0, 0], [1, 1, 1], [2, -1, 0], [3, 0, -1],
                         [0, 0, -5]]
        crv = Curve(BSplineBasis(3, [0, 0, 0, .3, .4, 1, 1, 1]), controlpoints)

        evaluation_point1 = crv(0.37)
        crv.raise_order(2)
        evaluation_point2 = crv(0.37)

        # ensure that curve has not chcanged, by comparing evaluation of it
        self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0])
        self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1])
        self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2])

        # ensure that curve has the right order
        self.assertEqual(crv.order(0), 5)

        # check integer type for argument
        with self.assertRaises(TypeError):
            crv.raise_order(0.5)

        # check logic error for negative argument
        with self.assertRaises(Exception):
            crv.raise_order(-1)
Example #9
0
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)
Example #10
0
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)
Example #11
0
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')
Example #12
0
    def test_flip_parametrization(self):
        # non-uniform knot vector of a squiggly quadratic n=4 curve
        controlpoints = [[0, 0, 0], [1, 1, 0], [2, -1, 0], [3, 0, 0]]
        crv = Curve(BSplineBasis(3, [0, 0, 0, .3, 1, 1, 1]), controlpoints)

        p1 = crv(0.23)
        crv.reverse()
        p2 = crv(0.77)
        self.assertAlmostEqual(p1[0], p2[0])
        self.assertAlmostEqual(p1[1], p2[1])
        self.assertAlmostEqual(p1[2], p2[2])
Example #13
0
    def test_flip_parametrization(self):
        # non-uniform knot vector of a squiggly quadratic n=4 curve
        controlpoints = [[0, 0, 0], [1, 1, 0], [2, -1, 0], [3, 0, 0]]
        crv = Curve(BSplineBasis(3, [0, 0, 0, .3, 1, 1, 1]), controlpoints)

        p1 = crv(0.23)
        crv.reverse()
        p2 = crv(0.77)
        self.assertAlmostEqual(p1[0], p2[0])
        self.assertAlmostEqual(p1[1], p2[1])
        self.assertAlmostEqual(p1[2], p2[2])
Example #14
0
    def test_make_periodic_reconstruct(self):
        orig = Curve(
            BSplineBasis(4, [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7], 2),
            [[1, 1], [2, 2], [3, 3], [4, 4]],
            rational=True,
        )
        recons = orig.split(0).make_periodic(2)

        self.assertAlmostEqual(
            np.linalg.norm(orig.controlpoints - recons.controlpoints), 0.0)
        self.assertAlmostEqual(
            np.linalg.norm(orig.bases[0].knots - recons.bases[0].knots), 0.0)
Example #15
0
    def test_constructor(self):
        # test 3D constructor
        crv = Curve(controlpoints=[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]])
        val = crv(0.5)
        self.assertEqual(val[0], 0.5)
        self.assertEqual(crv.dimension, 3)

        # test 2D constructor
        crv2 = Curve()
        val = crv(0.5)
        self.assertEqual(val[0], 0.5)
        self.assertEqual(crv2.dimension, 2)
Example #16
0
    def test_surface_loft(self):
        crv1 = Curve(BSplineBasis(3, range(11), 1), [[1,-1], [1,0], [1,1], [-1,1], [-1,0], [-1,-1]])
        crv2 = CurveFactory.circle(2) + (0,0,1)
        crv3 = Curve(BSplineBasis(4, range(11), 2), [[1,-1,2], [1,1,2], [-1,1,2], [-1,-1,2]])
        crv4 = CurveFactory.circle(2) + (0,0,3)
        surf = SurfaceFactory.loft(crv1, crv2, crv3, crv4)

        crv1.set_dimension(3) # for convenience when evaluating
        t = np.linspace( 0, 1, 13)

        u = np.linspace(crv1.start(0), crv1.end(0), 13)
        pt  = crv1(u)
        pt2 = surf(t,0).reshape(13,3)
        self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)

        u = np.linspace(crv2.start(0), crv2.end(0), 13)
        pt  = crv2(u)
        pt2 = surf(t,1).reshape(13,3)
        self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)

        u = np.linspace(crv3.start(0), crv3.end(0), 13)
        pt  = crv3(u)
        pt2 = surf(t,2).reshape(13,3)
        self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)

        u = np.linspace(crv4.start(0), crv4.end(0), 13)
        pt  = crv4(u)
        pt2 = surf(t,3).reshape(13,3)
        self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)
Example #17
0
    def test_edge_curves(self):
        # create an arrow-like 2D geometry with the pointy end at (-1,1) towards up and left
        # mixes rational and non-rational curves with different parametrization spaces
        c1 = CurveFactory.circle_segment(pi / 2)
        c2 = Curve(BSplineBasis(2, [0, 0, 1, 2, 2]), [[0, 1], [-1, 1], [-1, 0]])
        c3 = CurveFactory.circle_segment(pi / 2)
        c3.rotate(pi)
        c4 = Curve(BSplineBasis(2), [[0, -1], [1, 0]])

        surf = SurfaceFactory.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
Example #18
0
def polygon(*points, **keywords):
    """  Create a linear interpolation between input points.

    :param [array-like] points: The points to interpolate
    :param bool relative: If controlpoints are interpreted as relative to the
        previous one
    :param [float] t: specify parametric interpolation points (the knot vector)
    :return: Linear curve through the input points
    :rtype: Curve
    """
    if len(points) == 1:
        points = points[0]

    knot = keywords.get('t', [])
    if len(knot) == 0: # establish knot vector based on eucledian length between points
        knot = [0, 0]
        prevPt = points[0]
        for pt in points[1:]:
            dist = 0
            for (x0, x1) in zip(prevPt, pt):  # loop over (x,y) and maybe z-coordinate
                dist += (x1 - x0)**2
            knot.append(knot[-1] + sqrt(dist))
            prevPt = pt
        knot.append(knot[-1])
    else: # use knot vector given as input argument
        knot = [knot[0]] + list(knot) + [knot[-1]]

    relative = keywords.get('relative', False)
    if relative:
        points = list(points)
        for i in range(1, len(points)):
            points[i] = [x0 + x1 for (x0,x1) in zip(points[i-1], points[i])]

    return Curve(BSplineBasis(2, knot), points)
Example #19
0
    def const_par_curve(self, knot, direction):
        """  Get a Curve representation of the parametric line of some constant
        knot value.
        :param float knot: The constant knot value to sample the surface
        :param int direction: The parametric direction for the constant value
        :return: curve on this surface
        :rtype: Curve
        """
        direction = check_direction(direction, 2)

        # clone basis since we need to augment this by knot insertion
        b = self.bases[direction].clone()

        # compute mapping matrix C which is the knotinsertion operator
        mult = min(b.continuity(knot), b.order - 1)
        C = np.identity(self.shape[direction])
        for i in range(mult):
            C = b.insert_knot(knot) @ C

        # at this point we have a C0 basis, find the right interpolating index
        i = max(bisect_left(b.knots, knot) - 1, 0)

        # compute the controlpoints and return Curve
        cp = np.tensordot(C[i, :], self.controlpoints, axes=(0, direction))
        return Curve(self.bases[1 - direction], cp, self.rational)
Example #20
0
def n_gon(n=5, r=1, center=(0,0,0), normal=(0,0,1)):
    """  Create a regular polygon of *n* equal sides centered at the origin.

    :param int n: Number of sides and vertices
    :param float r: Radius
    :param array-like center: local origin
    :param array-like normal: local normal
    :return: A linear, periodic, 2D curve
    :rtype: Curve
    :raises ValueError: If radius is not positive
    :raises ValueError: If *n* < 3
    """
    if r <= 0:
        raise ValueError('radius needs to be positive')
    if n < 3:
        raise ValueError('regular polygons need at least 3 sides')

    cp = []
    dt = 2 * pi / n
    knot = [-1]
    for i in range(n):
        cp.append([r * cos(i * dt), r * sin(i * dt)])
        knot.append(i)
    knot += [n, n+1]
    basis = BSplineBasis(2, knot, 0)

    result =  Curve(basis, cp)
    return flip_and_move_plane_geometry(result, center, normal)
Example #21
0
def bezier(pts, quadratic=False, relative=False):
    """  Generate a cubic or quadratic bezier curve from a set of control points

    :param [array-like] pts: list of control-points. In addition to a starting
        point we need three points per bezier interval for cubic splines and
        two points for quadratic splines
    :param bool quadratic: True if a quadratic spline is to be returned, False
        if a cubic spline is to be returned
    :param bool relative: If controlpoints are interpreted as relative to the
        previous one
    :return: Bezier curve
    :rtype: Curve

    """
    if quadratic:
        p = 3
    else:
        p = 4
    # compute number of intervals
    n = int((len(pts)-1)/(p-1))
    # generate uniform knot vector of repeated integers
    knot = list(range(n+1)) * (p-1) + [0, n]
    knot.sort()
    if relative:
        pts = copy.deepcopy(pts)
        for i in range(1, len(pts)):
            pts[i] = [x0 + x1 for (x0,x1) in zip(pts[i-1], pts[i])]
    return Curve(BSplineBasis(p, knot), pts)
Example #22
0
    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]
Example #23
0
    def test_edge_curves_elasticity(self):
        # create an arrow-like 2D geometry with the pointy end at (-1,1) towards up and left
        # rebuild to avoid rational representations
        c1 = CurveFactory.circle_segment(pi / 2).rebuild(3,11)
        c2 = Curve(BSplineBasis(2, [0, 0, 1, 2, 2]), [[0, 1], [-1, 1], [-1, 0]])
        c3 = CurveFactory.circle_segment(pi / 2).rebuild(3,11)
        c3.rotate(pi)
        c4 = Curve(BSplineBasis(2), [[0, -1], [1, 0]]).rebuild(3,10)
        c4 = c4.rebuild(4,11)

        surf = SurfaceFactory.edge_curves([c1, c2, c3, c4], type='elasticity')

        # check right dimensions of output
        self.assertEqual(surf.shape[0], 11) # 11 controlpoints in the circle segment
        self.assertEqual(surf.shape[1], 13) # 11 controlpoints in c4, +2 for C0-knot in c1
        self.assertEqual(surf.order(0), 3)
        self.assertEqual(surf.order(1), 4)

        # check that c1 edge conforms to surface edge
        u = np.linspace(0,1,7)
        c1.reparam()
        pts_surf = surf(u,0.0)
        pts_c1   = c1(u)
        for (xs,xc) in zip(pts_surf[:,0,:], pts_c1):
            self.assertTrue(np.allclose(xs, xc))

        # check that c2 edge conforms to surface edge
        v = np.linspace(0,1,7)
        c2.reparam()
        pts_surf = surf(1.0,v)
        pts_c2   = c2(v)
        for (xs,xc) in zip(pts_surf[:,0,:], pts_c2):
            self.assertTrue(np.allclose(xs, xc))
Example #24
0
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_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 = CurveFactory.circle_segment(pi / 2)
        c2 = Curve(BSplineBasis(2, [0, 0, 1, 2, 2]), [[0, 1], [-1, 1], [-1, 0]])
        c3 = CurveFactory.circle_segment(pi / 2)
        c3.rotate(pi)
        c4 = Curve(BSplineBasis(2), [[0, -1], [1, 0]])

        surf = SurfaceFactory.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 = SurfaceFactory.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 = SurfaceFactory.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 = SurfaceFactory.edge_curves(crvs + (Curve(),)) # 5 input curves
Example #26
0
    def test_force_rational(self):
        # non-uniform knot vector of a squiggly quadratic n=4 curve
        controlpoints = [[0, 0, 0], [1, 1, 0], [2, -1, 0], [3, 0, 0]]
        crv = Curve(BSplineBasis(3, [0, 0, 0, .3, 1, 1, 1]), controlpoints)

        evaluation_point1 = crv(0.23)
        control_point1 = crv[0]
        crv.force_rational()
        evaluation_point2 = crv(0.23)
        control_point2 = crv[0]
        # ensure that curve has not chcanged, by comparing evaluation of it
        self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0])
        self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1])
        self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2])
        # ensure that we include rational weights of 1
        self.assertEqual(len(control_point1), 3)
        self.assertEqual(len(control_point2), 4)
        self.assertEqual(control_point2[3], 1)
        self.assertEqual(crv.rational, True)
Example #27
0
    def test_volume_loft(self):
        crv1 = Curve(BSplineBasis(3, range(11), 1),
                     [[1, -1], [1, 0], [1, 1], [-1, 1], [-1, 0], [-1, -1]])
        crv2 = CurveFactory.circle(2) + (0, 0, 1)
        crv3 = Curve(BSplineBasis(4, range(11), 2),
                     [[1, -1, 2], [1, 1, 2], [-1, 1, 2], [-1, -1, 2]])
        crv4 = CurveFactory.circle(2) + (0, 0, 3)

        surf = []
        for c in [crv1, crv2, crv3, crv4]:
            c2 = c.clone()
            c2.project('z')
            surf.append(SurfaceFactory.edge_curves(c, c2))

        vol = VolumeFactory.loft(surf)

        surf[0].set_dimension(3)  # for convenience when evaluating
        t = np.linspace(0, 1, 9)
        s = np.linspace(0, 1, 9)

        u = np.linspace(surf[0].start(0), surf[0].end(0), 9)
        v = np.linspace(surf[0].start(1), surf[0].end(1), 9)
        pt = surf[0](u, v)
        pt2 = vol(s, t, 0).reshape(9, 9, 3)
        self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0)

        u = np.linspace(surf[1].start(0), surf[1].end(0), 9)
        u = np.linspace(surf[1].start(1), surf[1].end(1), 9)
        pt = surf[1](u, v)
        pt2 = vol(s, t, 1).reshape(9, 9, 3)
        self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0)

        u = np.linspace(surf[2].start(0), surf[2].end(0), 9)
        v = np.linspace(surf[2].start(1), surf[2].end(1), 9)
        pt = surf[2](u, v)
        pt2 = vol(s, t, 2).reshape(9, 9, 3)
        self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0)

        u = np.linspace(surf[3].start(0), surf[3].end(0), 9)
        v = np.linspace(surf[3].start(1), surf[3].end(1), 9)
        pt = surf[3](u, v)
        pt2 = vol(s, t, 3).reshape(9, 9, 3)
        self.assertAlmostEqual(np.linalg.norm(pt - pt2), 0.0)
Example #28
0
    def test_force_rational(self):
        # non-uniform knot vector of a squiggly quadratic n=4 curve
        controlpoints = [[0, 0, 0], [1, 1, 0], [2, -1, 0], [3, 0, 0]]
        crv = Curve(BSplineBasis(3, [0, 0, 0, .3, 1, 1, 1]), controlpoints)

        evaluation_point1 = crv(0.23)
        control_point1 = crv[0]
        crv.force_rational()
        evaluation_point2 = crv(0.23)
        control_point2 = crv[0]
        # ensure that curve has not chcanged, by comparing evaluation of it
        self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0])
        self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1])
        self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2])
        # ensure that we include rational weights of 1
        self.assertEqual(len(control_point1), 3)
        self.assertEqual(len(control_point2), 4)
        self.assertEqual(control_point2[3], 1)
        self.assertEqual(crv.rational, True)
Example #29
0
    def test_thicken(self):
        c = Curve()  # 2D curve from (0,0) to (1,0)
        s = sf.thicken(c, .5)  # extend to y=[-.5, .5]
        self.assertTupleEqual(s.order(), (2, 2))
        self.assertTupleEqual(s.start(), (0, 0))
        self.assertTupleEqual(s.end(), (1, 1))
        self.assertTupleEqual(s.bounding_box()[0], (0.0, 1.0))
        self.assertTupleEqual(s.bounding_box()[1], (-.5, .5))

        # test a case with vanishing velocity. x'(t)=0, y'(t)=0 for t=0
        c = Curve(BSplineBasis(3),
                  [[0, 0], [0, 0], [1, 0]])  # x(t)=t^2, y(t)=0
        s = sf.thicken(c, .5)
        self.assertTupleEqual(s.order(), (3, 2))
        self.assertTupleEqual(s.start(), (0, 0))
        self.assertTupleEqual(s.end(), (1, 1))
        self.assertTupleEqual(s.bounding_box()[0], (0.0, 1.0))
        self.assertTupleEqual(s.bounding_box()[1], (-.5, .5))

        def myThickness(t):
            return t**2

        c = Curve(BSplineBasis(3))
        s = sf.thicken(c, myThickness)
        self.assertTupleEqual(s.order(), (3, 2))
        self.assertTupleEqual(s.start(), (0, 0))
        self.assertTupleEqual(s.end(), (1, 1))
        self.assertTupleEqual(s.bounding_box()[0], (0.0, 1.0))
        self.assertTupleEqual(s.bounding_box()[1], (-1.0, 1.0))

        # test 3D geometry
        c = Curve()
        c.set_dimension(3)
        s = sf.thicken(c, 1)  # cylinder along x-axis with h=1, r=1
        for u in np.linspace(s.start(0), s.end(0), 5):
            for v in np.linspace(s.start(1), s.end(1), 5):
                x = s(u, v)
                self.assertAlmostEqual(x[1]**2 + x[2]**2,
                                       1.0**2)  # distance to x-axis
                self.assertAlmostEqual(x[0],
                                       u)  # x coordinate should be linear
Example #30
0
def line(a, b, relative=False):
    """  Create a line between two points.

    :param array-like a: Start point
    :param array-like b: End point
    :param bool relative: Whether *b* is relative to *a*
    :return: Linear curve from *a* to *b*
    :rtype: Curve
    """
    if relative:
        b = tuple(ai + bi for ai, bi in zip(a, b))
    return Curve(controlpoints=[a, b])
Example #31
0
    def test_append(self):
        crv  = Curve(BSplineBasis(3), [[0,0], [1,0], [0,1]])
        crv2 = Curve(BSplineBasis(4), [[0,1,0], [0,1,1], [0,2,1], [0,2,2]])
        crv2.insert_knot(0.5)

        crv3 = crv.clone()
        crv3.append(crv2)
 
        expected_knot = [0,0,0,0,1,1,1,1.5,2,2,2,2]
        self.assertEqual(crv3.order(direction=0),  4)
        self.assertEqual(crv3.rational, False)
        self.assertEqual(np.linalg.norm(crv3.knots(0, True)-expected_knot), 0.0)
 
        t = np.linspace(0,1,11)
        pt        = np.zeros((11,3))
        pt[:,:-1] = crv(t)
        pt2       = crv3(t)
        self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)
        pt  = crv2(t)
        pt2 = crv3(t+1.0)
        self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)
Example #32
0
    def test_periodic_split(self):
        # non-uniform rational knot vector of a periodic cubic n=7 curve
        controlpoints = [[1, 0, 1], [1, 1, .7], [0, 1, .89], [-1, 1, 0.5], [-1, 0, 1], [-1,-.5,1], [1, -.5, 1]]
        basis = BSplineBasis(4, [-3, -2, -1, 0, 1, 2, 2.5, 4, 5, 6, 7, 8, 9, 9.5], 2)
        crv = Curve(basis, controlpoints, rational=True)
        crv2 = crv.split(1)   # split at knot value
        crv3 = crv.split(6.5) # split outside existing knot

        self.assertEqual(len(crv),   7)
        self.assertEqual(len(crv2), 10)
        self.assertEqual(len(crv3), 11)

        self.assertEqual(crv.periodic(),  True)
        self.assertEqual(crv2.periodic(), False)
        self.assertEqual(crv3.periodic(), False)

        t = np.linspace(6.5, 8, 13) # domain where all parameter values are the same
        pt  = crv( t)
        pt2 = crv2(t)
        pt3 = crv3(t)

        self.assertAlmostEqual(np.linalg.norm(pt-pt2), 0.0)
        self.assertAlmostEqual(np.linalg.norm(pt-pt3), 0.0)
Example #33
0
def circle_segment(theta,
                   r=1,
                   center=(0, 0, 0),
                   normal=(0, 0, 1),
                   xaxis=(1, 0, 0)):
    """  Create a circle segment starting parallel to the rotated x-axis.

    :param float theta: Angle in radians
    :param float r: Radius
    :param array-like center: circle segment center
    :param array-like normal: normal vector to the plane that contains circle
    :param array-like xaxis: direction of the parametric start point t=0
    :return: A quadratic rational curve
    :rtype: Curve
    :raises ValueError: If radius is not positive
    :raises ValueError: If theta is not in the range *[-2pi, 2pi]*
    """
    # error test input
    if abs(theta) > 2 * pi:
        raise ValueError('theta needs to be in range [-2pi,2pi]')
    if r <= 0:
        raise ValueError('radius needs to be positive')
    if theta == 2 * pi:
        return circle(r, center, normal)

    # build knot vector
    knot_spans = int(ceil(abs(theta) / (2 * pi / 3)))
    knot = [0]
    for i in range(knot_spans + 1):
        knot += [i] * 2
    knot += [knot_spans]  # knot vector [0,0,0,1,1,2,2,..,n,n,n]
    knot = np.array(knot) / float(
        knot[-1]) * theta  # set parametric space to [0,theta]

    n = (knot_spans - 1) * 2 + 3  # number of control points needed
    cp = []
    t = 0  # current angle
    dt = float(theta) / knot_spans / 2  # angle step

    # build control points
    for i in range(n):
        w = 1 - (i % 2) * (1 - cos(dt)
                           )  # weights = 1 and cos(dt) every other i
        x = r * cos(t)
        y = r * sin(t)
        cp += [[x, y, w]]
        t += dt

    if theta < 0:
        cp.reverse()
        result = Curve(BSplineBasis(3, np.flip(knot, 0)), cp, True)
    else:
        result = Curve(BSplineBasis(3, knot), cp, True)
    result.rotate(rotate_local_x_axis(xaxis, normal))
    return flip_and_move_plane_geometry(result, center, normal)
Example #34
0
    def test_evaluate(self):
        # create the mapping
        # x(t) = 2t + 1
        # y(t) = 2t(1-t)
        # z(t) = 0
        controlpoints = [[1, 0, 0], [2, 1, 0], [3, 0, 0]]
        crv = Curve(BSplineBasis(3), controlpoints)

        # startpoint evaluation
        val = crv(0.0)
        self.assertAlmostEqual(val[0], 1.0)
        self.assertAlmostEqual(val[1], 0.0)
        self.assertAlmostEqual(val[2], 0.0)

        # inner evaluation
        val = crv(0.4)
        self.assertAlmostEqual(val[0], 1.8)
        self.assertAlmostEqual(val[1], 0.48)
        self.assertAlmostEqual(val[2], 0.0)

        # endpoint evaluation
        val = crv(1.0)
        self.assertAlmostEqual(val[0], 3.0)
        self.assertAlmostEqual(val[1], 0.0)
        self.assertAlmostEqual(val[2], 0.0)

        # test evaluation at multiple points
        val = crv([0.0, 0.4, 0.8, 1.0])
        self.assertEqual(len(val.shape), 2)  # return matrix
        self.assertEqual(val.shape[0], 4)  # 4 evaluation points
        self.assertEqual(val.shape[1], 3)  # (x,y,z) results
        self.assertAlmostEqual(val[0, 0], 1.0)
        self.assertAlmostEqual(val[0, 1], 0.0)
        self.assertAlmostEqual(val[0, 2], 0.0)  # startpt evaluation
        self.assertAlmostEqual(val[1, 0], 1.8)
        self.assertAlmostEqual(val[1, 1], 0.48)
        self.assertAlmostEqual(val[1, 2], 0.0)  # inner evaluation
        self.assertAlmostEqual(val[3, 0], 3.0)
        self.assertAlmostEqual(val[3, 1], 0.0)
        self.assertAlmostEqual(val[3, 2], 0.0)  # endpt evaluation

        # test errors and exceptions
        with self.assertRaises(ValueError):
            val = crv(-10)  # evalaute outside parametric domain
        with self.assertRaises(ValueError):
            val = crv(+10)  # evalaute outside parametric domain
Example #35
0
    def test_curvature(self):
        # linear curves have zero curvature
        crv = Curve()
        self.assertAlmostEqual(crv.curvature(.3), 0.0)
        # test multiple evaluation points
        t = np.linspace(0, 1, 10)
        k = crv.curvature(t)
        self.assertTrue(np.allclose(k, 0.0))

        # test circle
        crv = cf.circle(r=3) + [1, 1]
        t = np.linspace(0, 2 * pi, 10)
        k = crv.curvature(t)
        self.assertTrue(np.allclose(k, 1.0 / 3.0))  # circles: k = 1/r

        # test 3D (np.cross has different behaviour in 2D/3D)
        crv.set_dimension(3)
        k = crv.curvature(t)
        self.assertTrue(np.allclose(k, 1.0 / 3.0))  # circles: k = 1/r
Example #36
0
def circle(r=1, center=(0,0,0), normal=(0,0,1), type='p2C0', xaxis=(1,0,0)):
    """  Create a circle.

    :param float r: Radius
    :param array-like center: local origin
    :param array-like normal: local normal
    :param string type: The type of parametrization ('p2C0' or 'p4C1')
    :param array-like xaxis: direction of sem, i.e. parametric start point t=0
    :return: A periodic, quadratic rational curve
    :rtype: Curve
    :raises ValueError: If radius is not positive
    """
    if r <= 0:
        raise ValueError('radius needs to be positive')

    if type == 'p2C0' or type == 'C0p2':
        w = 1.0 / sqrt(2)
        controlpoints = [[1, 0, 1],
                         [w, w, w],
                         [0, 1, 1],
                         [-w, w, w],
                         [-1, 0, 1],
                         [-w, -w, w],
                         [0, -1, 1],
                         [w, -w, w]]
        knot = np.array([-1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5]) / 4.0 * 2 * pi

        result = Curve(BSplineBasis(3, knot, 0), controlpoints, True)
    elif type.lower() == 'p4c1' or type.lower() == 'c1p4':
        w = 2*sqrt(2)/3
        a = 1.0/2/sqrt(2)
        b = 1.0/6 * (4*sqrt(2)-1)
        controlpoints = [[ 1,-a, 1],
                         [ 1, a, 1],
                         [ b, b, w],
                         [ a, 1, 1],
                         [-a, 1, 1],
                         [-b, b, w],
                         [-1, a, 1],
                         [-1,-a, 1],
                         [-b,-b, w],
                         [-a,-1, 1],
                         [ a,-1, 1],
                         [ b,-b, w]]
        knot = np.array([ -1, -1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5]) / 4.0 * 2 * pi
        result = Curve(BSplineBasis(5, knot, 1), controlpoints, True)
    else:
        raise ValueError('Unkown type: %s' %(type))

    result *= r
    result.rotate(rotate_local_x_axis(xaxis, normal))
    return flip_and_move_plane_geometry(result, center, normal)
Example #37
0
def least_square_fit(x, basis, t):
    """  Perform a least-square fit of a point cloud onto a spline basis

    :param matrix-like x: Matrix *X[i,j]* of interpolation points *xi* with
        components *j*. The number of points must be equal to or larger than
        the number of basis functions in *basis*
    :param BSplineBasis basis: Basis on which to interpolate
    :param array-like t: parametric values at evaluation points
    :return: Approximated curve
    :rtype: Curve
    """

    # evaluate all basis functions at evaluation points
    N = basis.evaluate(t)

    # solve interpolation problem
    controlpoints, _, _, _ = np.linalg.lstsq(N, x, rcond=None)

    return Curve(basis, controlpoints)
Example #38
0
    def read_curve(self, ncrv):
        knots = [0]
        for i in ncrv.Knots:
            knots.append(i)
        knots[0] = knots[1]
        knots.append(knots[len(knots) - 1])
        basis = BSplineBasis(ncrv.Order, knots, -1)
        cpts = []

        cpts = np.ndarray((len(ncrv.Points), ncrv.Dimension + ncrv.IsRational))
        for u in range(0, len(ncrv.Points)):
            cpts[u, 0] = ncrv.Points[u].X
            cpts[u, 1] = ncrv.Points[u].Y
            if ncrv.Dimension > 2:
                cpts[u, 2] = ncrv.Points[u].Z
            if ncrv.IsRational:
                cpts[u, 3] = ncrv.Points[u].W

        return Curve(basis, cpts, ncrv.IsRational)
Example #39
0
def camber(M, P, order=5):
    """ Create the NACA centerline used for wing profiles. This is given as
    an exact quadratic piecewise polynomial y(x),
    see http://airfoiltools.com/airfoil/naca4digit. The method will produce
    one of two representations: For order<5 it will create x(t)=t and
    for order>4 it will create x(t) as qudratic in t and stretched towards the
    endpointspoints creating a more optimized parametrization.
    :param M: Max camber height (y) given as percentage 0% to 9% of length
    :type  M: Int 0<M<10
    :param P: Max camber position (x) given as percentage 0% to 90% of length
    :type  P: Int 0<P<10
    :return : Exact centerline representation
    :rtype  : Curve
    """
    # parametrized by x=t or x="t^2" if order>4
    M = M / 100.0
    P = P / 10.0
    basis = BSplineBasis(order)
    # basis.insert_knot([P]*(order-2)) # insert a C1-knot
    for i in range(order - 2):
        basis.insert_knot(P)

    t = basis.greville()
    n = len(t)
    x = np.zeros((n, 2))
    for i in range(n):
        if t[i] <= P:
            if order > 4:
                x[i, 0] = t[i]**2 / P
            else:
                x[i, 0] = t[i]
            x[i, 1] = M / P / P * (2 * P * x[i, 0] - x[i, 0] * x[i, 0])
        else:
            if order > 4:
                x[i, 0] = (t[i]**2 - 2 * t[i] + P) / (P - 1)
            else:
                x[i, 0] = t[i]
            x[i, 1] = M / (1 - P) / (1 - P) * (1 - 2 * P + 2 * P * x[i, 0] - x[i, 0] * x[i, 0])
    N = basis.evaluate(t)
    controlpoints = np.linalg.solve(N, x)
    return Curve(basis, controlpoints)
Example #40
0
    def test_derivative(self):
        # testing the parametrization x(t) = [.5*x^3 / ((1-x)^3+.5*x^3), 0]
        cp = [[0,0,1], [0,0,0], [0,0,0], [.5,0,.5]]
        crv = Curve(BSplineBasis(4), cp, rational=True)
        def expect_derivative(x):
            return 6*(1-x)**2*x**2/(x**3 - 6*x**2 + 6*x - 2)**2
        def expect_derivative_2(x):
            return -12*x*(x**5 - 3*x**4 + 2*x**3 + 4*x**2 - 6*x + 2)/(x**3 - 6*x**2 + 6*x - 2)**3

        # insert a few more knots to spice things up
        crv.insert_knot([.2, .71])

        self.assertAlmostEqual(crv.tangent(0.32)[0], expect_derivative(0.32))
        self.assertAlmostEqual(crv.tangent(0.32)[1], 0)
        self.assertAlmostEqual(crv.tangent(0.71)[0], expect_derivative(0.71))

        self.assertAlmostEqual(crv.derivative(0.22, 2)[0], expect_derivative_2(0.22))
        self.assertAlmostEqual(crv.derivative(0.22, 2)[1], 0)
        self.assertAlmostEqual(crv.derivative(0.86, 2)[0], expect_derivative_2(0.86))
Example #41
0
    def test_raise_order(self):
        # non-uniform knot vector of a squiggly quadratic n=5 curve in 3D
        controlpoints = [[0, 0, 0], [1, 1, 1], [2, -1, 0], [3, 0, -1], [0, 0, -5]]
        crv = Curve(BSplineBasis(3, [0, 0, 0, .3, .4, 1, 1, 1]), controlpoints)

        evaluation_point1 = crv(0.37)
        crv.raise_order(2)
        evaluation_point2 = crv(0.37)

        # ensure that curve has not chcanged, by comparing evaluation of it
        self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0])
        self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1])
        self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2])

        # ensure that curve has the right order
        self.assertEqual(crv.order(0), 5)

        # check integer type for argument
        with self.assertRaises(TypeError):
            crv.raise_order(0.5)

        # check logic error for negative argument 
        with self.assertRaises(Exception):
            crv.raise_order(-1)
Example #42
0
def interpolate(x, basis, t=None):
    """  Perform general spline interpolation on a provided basis.

    :param matrix-like x: Matrix *X[i,j]* of interpolation points *xi* with
        components *j*
    :param BSplineBasis basis: Basis on which to interpolate
    :param array-like t: parametric values at interpolation points; defaults to
        Greville points if not provided
    :return: Interpolated curve
    :rtype: Curve
    """
    # wrap x into a numpy matrix
    x = np.matrix(x)

    # evaluate all basis functions in the interpolation points
    if t is None:
        t = basis.greville()
    N = basis.evaluate(t, sparse=True)

    # solve interpolation problem
    controlpoints = splinalg.spsolve(N, x)

    return Curve(basis, controlpoints)
    def test_thicken(self):
        c = Curve()                       # 2D curve from (0,0) to (1,0)
        s = SurfaceFactory.thicken(c, .5) # extend to y=[-.5, .5]
        self.assertTupleEqual(s.order(), (2,2))
        self.assertTupleEqual(s.start(), (0,0))
        self.assertTupleEqual(s.end(),   (1,1))
        self.assertTupleEqual(s.bounding_box()[0], (0.0,1.0))
        self.assertTupleEqual(s.bounding_box()[1], (-.5, .5))

        # test a case with vanishing velocity. x'(t)=0, y'(t)=0 for t=0
        c = Curve(BSplineBasis(3), [[0,0],[0,0],[1,0]]) # x(t)=t^2, y(t)=0
        s = SurfaceFactory.thicken(c, .5)
        self.assertTupleEqual(s.order(), (3,2))
        self.assertTupleEqual(s.start(), (0,0))
        self.assertTupleEqual(s.end(),   (1,1))
        self.assertTupleEqual(s.bounding_box()[0], (0.0,1.0))
        self.assertTupleEqual(s.bounding_box()[1], (-.5, .5))

        def myThickness(t):
            return t**2
        c = Curve(BSplineBasis(3))
        s = SurfaceFactory.thicken(c, myThickness)
        self.assertTupleEqual(s.order(), (3,2))
        self.assertTupleEqual(s.start(), (0,0))
        self.assertTupleEqual(s.end(),   (1,1))
        self.assertTupleEqual(s.bounding_box()[0], ( 0.0, 1.0))
        self.assertTupleEqual(s.bounding_box()[1], (-1.0, 1.0))

        # test 3D geometry
        c = Curve()
        c.set_dimension(3)
        s = SurfaceFactory.thicken(c, 1) # cylinder along x-axis with h=1, r=1
        for u in np.linspace(s.start(0), s.end(0), 5):
            for v in np.linspace(s.start(1), s.end(1), 5):
                x = s(u, v)
                self.assertAlmostEqual(x[1]**2+x[2]**2, 1.0**2) # distance to x-axis
                self.assertAlmostEqual(x[0], u)                 # x coordinate should be linear
Example #44
0
    def curves_from_path(self, path):
        # see https://www.w3schools.com/graphics/svg_path.asp for documentation
        # and also https://www.w3.org/TR/SVG/paths.html

        # figure out the largest polynomial order of this path
        if re.search('[cCsS]', path):
            order = 4
        elif re.search('[qQtTaA]', path):
            order = 3
        else:
            order = 2
        last_curve = None
        result = []

        # each 'piece' is an operator (M,C,Q,L etc) and accomponying list of argument points
        for piece in re.findall('[a-zA-Z][^a-zA-Z]*', path):

            # if not single-letter command (i.e. 'z')
            if len(piece)>1:
                # points is a (string-)list of (x,y)-coordinates for the given operator
                points = re.findall('-?\d+\.?\d*', piece[1:])

                if piece[0].lower() != 'a' and piece[0].lower() != 'v' and piece[0].lower() != 'h':
                    # convert string-list to a list of numpy arrays (of size 2)
                    np_pts = np.reshape(np.array(points).astype('float'), (int(len(points)/2),2))

            if piece[0] == 'm' or piece[0] == 'M':
                # I really hope it always start with a move command (think it does)
                startpoint = np_pts[0]
                if len(np_pts) > 1:
                    if piece[0] == 'M':
                        knot = [0] + list(range(len(np_pts))) + [len(np_pts)-1]
                        curve_piece = Curve(BSplineBasis(2, knot), np_pts)
                    elif piece[0] == 'm':
                        knot = [0] + list(range(len(np_pts))) + [len(np_pts)-1]
                        controlpoints = [startpoint]
                        for cp in np_pts[1:]:
                            controlpoints.append(cp + controlpoints[-1])
                        curve_piece = Curve(BSplineBasis(2, knot), controlpoints)
                else:
                    continue
            elif piece[0] == 'c':
                # cubic spline, relatively positioned
                controlpoints = [startpoint]
                knot = list(range(int(len(np_pts)/3)+1)) * 3
                knot += [knot[0], knot[-1]]
                knot.sort()
                for cp in np_pts:
                    startpoint = controlpoints[int((len(controlpoints)-1)/3)*3]
                    controlpoints.append(cp + startpoint)
                curve_piece = Curve(BSplineBasis(4, knot), controlpoints)
            elif piece[0] == 'C':
                # cubic spline, absolute position
                controlpoints = [startpoint]
                knot = list(range(int(len(np_pts)/3)+1)) * 3
                knot += [knot[0], knot[-1]]
                knot.sort()
                for cp in np_pts:
                    controlpoints.append(cp)
                curve_piece = Curve(BSplineBasis(4, knot), controlpoints)
            elif piece[0] == 's':
                # smooth cubic spline, relative position
                controlpoints = [startpoint]
                knot = list(range(int(len(np_pts)/2)+1)) * 3
                knot += [knot[0], knot[-1]]
                knot.sort()
                x0  = np.array(last_curve[-1])
                xn1 = np.array(last_curve[-2])
                controlpoints.append(2*x0 -xn1)
                startpoint = controlpoints[-1]
                for i, cp in enumerate(np_pts):
                    if i % 2 == 0 and i>0:
                        startpoint = controlpoints[-1]
                        controlpoints.append(2*controlpoints[-1] - controlpoints[-2])
                    controlpoints.append(cp + startpoint)
                curve_piece = Curve(BSplineBasis(4, knot), controlpoints)
            elif piece[0] == 'S':
                # smooth cubic spline, absolute position
                controlpoints = [startpoint]
                knot = list(range(int(len(np_pts)/2)+1)) * 3
                knot += [knot[0], knot[-1]]
                knot.sort()
                x0  = np.array(last_curve[-1])
                xn1 = np.array(last_curve[-2])
                controlpoints.append(2*x0 -xn1)
                for i,cp in enumerate(np_pts):
                    if i % 2 == 0 and i>0:
                        controlpoints.append(2*controlpoints[-1] - controlpoints[-2])
                    controlpoints.append(cp)
                curve_piece = Curve(BSplineBasis(4, knot), controlpoints)
            elif piece[0] == 'q':
                # quadratic spline, relatively positioned
                controlpoints = [startpoint]
                knot = list(range(int(len(np_pts)/2)+1)) * 2
                knot += [knot[0], knot[-1]]
                knot.sort()
                for cp in np_pts:
                    startpoint = controlpoints[int((len(controlpoints)-1)/2)*2]
                    controlpoints.append(cp + startpoint)
                curve_piece = Curve(BSplineBasis(3, knot), controlpoints)
            elif piece[0] == 'Q':
                # quadratic spline, absolute position
                controlpoints = [startpoint]
                knot = list(range(len(np_pts)/2+1)) * 2
                knot += [knot[0], knot[-1]]
                knot.sort()
                for cp in np_pts:
                    controlpoints.append(cp)
                curve_piece = Curve(BSplineBasis(3, knot), controlpoints)
            elif piece[0] == 'l':
                # linear spline, relatively positioned
                controlpoints = [startpoint]
                knot = list(range(len(np_pts)+1))
                knot += [knot[0], knot[-1]]
                knot.sort()
                for cp in np_pts:
                    startpoint = controlpoints[-1]
                    controlpoints.append(cp + startpoint)
                curve_piece = Curve(BSplineBasis(2, knot), controlpoints)
            elif piece[0] == 'L':
                # linear spline, absolute position
                controlpoints = [startpoint]
                knot = list(range(len(np_pts)+1))
                knot += [knot[0], knot[-1]]
                knot.sort()
                for cp in np_pts:
                    controlpoints.append(cp)
                curve_piece = Curve(BSplineBasis(2, knot), controlpoints)
            elif piece[0] == 'h':
                # horizontal piece, relatively positioned
                np_pts = np.array(points).astype('float')
                controlpoints = [startpoint]
                knot = list(range(len(np_pts)+1))
                knot += [knot[0], knot[-1]]
                knot.sort()
                for cp in np_pts:
                    startpoint = controlpoints[-1]
                    controlpoints.append(np.array([cp, 0]) + startpoint)
                curve_piece = Curve(BSplineBasis(2, knot), controlpoints)
            elif piece[0] == 'H':
                # horizontal piece, absolute position
                np_pts = np.array(points).astype('float')
                controlpoints = [startpoint]
                knot = list(range(len(np_pts)+1))
                knot += [knot[0], knot[-1]]
                knot.sort()
                for cp in np_pts:
                    controlpoints.append([cp, startpoint[1]])
                curve_piece = Curve(BSplineBasis(2, knot), controlpoints)
            elif piece[0] == 'v':
                # vertical piece, relatively positioned
                np_pts = np.array(points).astype('float')
                controlpoints = [startpoint]
                knot = list(range(len(np_pts)+1))
                knot += [knot[0], knot[-1]]
                knot.sort()
                for cp in np_pts:
                    startpoint = controlpoints[-1]
                    controlpoints.append(np.array([0, cp]) + startpoint)
                curve_piece = Curve(BSplineBasis(2, knot), controlpoints)
            elif piece[0] == 'V':
                # vertical piece, absolute position
                np_pts = np.array(points).astype('float')
                controlpoints = [startpoint]
                knot = list(range(len(np_pts)+1))
                knot += [knot[0], knot[-1]]
                knot.sort()
                for cp in np_pts:
                    controlpoints.append([startpoint[0], cp])
                curve_piece = Curve(BSplineBasis(2, knot), controlpoints)
            elif piece[0] == 'A' or piece[0] == 'a':
                np_pts = np.reshape(np.array(points).astype('float'), (int(len(points))))
                rx              = float(points[0])
                ry              = float(points[1])
                x_axis_rotation = float(points[2])
                large_arc_flag  = (points[3] != '0')
                sweep_flag      = (points[4] != '0')
                xend            = np.array([float(points[5]), float(points[6]) ])
                if piece[0] == 'a':
                    xend += startpoint

                R = np.array([[ np.cos(x_axis_rotation), np.sin(x_axis_rotation)],
                              [-np.sin(x_axis_rotation), np.cos(x_axis_rotation)]])
                xp = np.linalg.solve(R, (startpoint - xend)/2)
                if sweep_flag == large_arc_flag:
                    cprime = -(np.sqrt(abs(rx**2*ry**2 - rx**2*xp[1]**2 - ry**2*xp[0]**2) /
                                       (rx**2*xp[1]**2 + ry**2*xp[0]**2)) *
                                       np.array([rx*xp[1]/ry, -ry*xp[0]/rx]))
                else:
                    cprime = +(np.sqrt(abs(rx**2*ry**2 - rx**2*xp[1]**2 - ry**2*xp[0]**2) /
                                       (rx**2*xp[1]**2 + ry**2*xp[0]**2)) *
                                       np.array([rx*xp[1]/ry, -ry*xp[0]/rx]))
                center = np.linalg.solve(R.T, cprime) + (startpoint+xend)/2
                def arccos(vec1, vec2):
                    return (np.sign(vec1[0]*vec2[1] - vec1[1]*vec2[0]) *
                            np.arccos(vec1.dot(vec2)/np.linalg.norm(vec1)/np.linalg.norm(vec2)))
                tmp1 = np.divide( xp - cprime, [rx,ry])
                tmp2 = np.divide(-xp - cprime, [rx,ry])
                theta1 = arccos(np.array([1,0]), tmp1)
                delta_t= arccos(tmp1, tmp2) % (2*np.pi)
                if not sweep_flag and delta_t > 0:
                    delta_t -= 2*np.pi
                elif sweep_flag and delta_t < 0:
                    delta_t += 2*np.pi
                curve_piece = (curve_factory.circle_segment(delta_t)*[rx,ry]).rotate(theta1) + center
                # curve_piece = curve_factory.circle_segment(delta_t)

            elif piece[0] == 'z' or piece[0] == 'Z':
                # periodic curve
                # curve_piece = Curve(BSplineBasis(2), [startpoint, last_curve[0]])
                # curve_piece.reparam([0, curve_piece.length()])
                # last_curve.append(curve_piece).make_periodic(0)
                last_curve.make_periodic(0)
                result.append(last_curve)
                last_curve = None
                continue
            else:
                raise RuntimeError('Unknown path parameter:' + piece)

            if(curve_piece.length()>state.controlpoint_absolute_tolerance):
                curve_piece.reparam([0, curve_piece.length()])
                if last_curve is None:
                    last_curve = curve_piece
                else:
                    last_curve.append(curve_piece)
            startpoint = last_curve[-1,:2] # disregard rational weight (if any)

        if last_curve is not None:
            result.append(last_curve)
        return result
Example #45
0
 def test_length(self):
     crv = Curve()
     self.assertAlmostEqual(crv.length(), 1.0)
     crv = Curve(BSplineBasis(2, [-1,-1,1,2,3,3]), [[0,0,0], [1,0,0], [1,0,3],[1,10,3]])
     self.assertAlmostEqual(crv.length(), 14.0)
Example #46
0
    def test_split(self):
        # non-uniform knot vector of a squiggly quadratic n=4 curve
        controlpoints = [[0, 0, 1], [1, 1, 0], [2, -1, 0], [3, 0, 0]]
        crv = Curve(BSplineBasis(3, [0, 0, 0, .7, 1, 1, 1]), controlpoints)

        # get some info on the initial curve
        evaluation_point1 = crv(0.50)
        evaluation_point2 = crv(0.70)
        evaluation_point3 = crv(0.33)

        # split curves away from knot
        new_curves_050 = crv.split(0.50)
        self.assertEqual(len(new_curves_050), 2)
        self.assertEqual(
            len(new_curves_050[0].knots(0, with_multiplicities=True)), 6)  # open knot vector [0,0,0,.5,.5,.5]
        self.assertEqual(
            len(new_curves_050[1].knots(0, with_multiplicities=True)), 7)  # open knot vector [.5,.5,.5,.7,1,1,1]

        # split curves at existing knot
        new_curves_070 = crv.split(0.70)
        self.assertEqual(len(new_curves_070), 2)
        self.assertEqual(
            len(new_curves_070[0].knots(0, with_multiplicities=True)), 6)  # open knot vector [0,0,0,.7,.7,.7]
        self.assertEqual(
            len(new_curves_070[1].knots(0, with_multiplicities=True)), 6)  # open knot vector [.7,.7,.7,1,1,1]

        # split curves multiple points
        new_curves_all = crv.split([0.50, 0.70])
        self.assertEqual(len(new_curves_all), 3)
        self.assertEqual(
            len(new_curves_all[0].knots(0, with_multiplicities=True)), 6)  # open knot vector [0,0,0,.5,.5,.5]
        self.assertEqual(
            len(new_curves_all[1].knots(0, with_multiplicities=True)), 6)  # open knot vector [.5,.5,.5,.7,.7,.7]
        self.assertEqual(
            len(new_curves_all[2].knots(0, with_multiplicities=True)), 6)  # open knot vector [.7,.7,.7,1,1,1]

        # compare all curves which exist at parametric point 0.5
        for c in new_curves_050 + [new_curves_070[0]] + new_curves_all[0:2]:
            new_curve_evaluation = c(0.50)
            self.assertAlmostEqual(evaluation_point1[0], new_curve_evaluation[0])
            self.assertAlmostEqual(evaluation_point1[1], new_curve_evaluation[1])
            self.assertAlmostEqual(evaluation_point1[2], new_curve_evaluation[2])

            # compare all curves which exist at parametric point 0.33
        for c in [new_curves_050[0]] + [new_curves_070[0]] + [new_curves_all[0]]:
            new_curve_evaluation = c(0.33)
            self.assertAlmostEqual(evaluation_point3[0], new_curve_evaluation[0])
            self.assertAlmostEqual(evaluation_point3[1], new_curve_evaluation[1])
            self.assertAlmostEqual(evaluation_point3[2], new_curve_evaluation[2])

        # compare all curves which exist at parametric point 0.7
        for c in [new_curves_050[1]] + new_curves_070 + new_curves_all[1:3]:
            new_curve_evaluation = c(0.70)
            self.assertAlmostEqual(evaluation_point2[0], new_curve_evaluation[0])
            self.assertAlmostEqual(evaluation_point2[1], new_curve_evaluation[1])
            self.assertAlmostEqual(evaluation_point2[2], new_curve_evaluation[2])

            # test errors and exceptions
        with self.assertRaises(TypeError):
            crv.split(.1, .2, .3)  # too many arguments
        with self.assertRaises(Exception):
            crv.split(-0.2)  # GoTools returns error on outside-domain errors
        with self.assertRaises(Exception):
            crv.split(1.4)  # GoTools returns error on outside-domain errors
Example #47
0
    def test_reparam(self):
        # non-uniform knot vector of a squiggly quadratic n=4 curve
        controlpoints = [[0, 0, 0], [1, 1, 0], [2, -1, 0], [3, 0, 0]]
        crv = Curve(BSplineBasis(3, [0, 0, 0, 1.32, 3, 3, 3]), controlpoints)

        # get some info on the initial curve
        knots1 = crv.knots(0)
        evaluation_point1 = crv(1.20)
        self.assertEqual(knots1[0], 0)
        self.assertEqual(knots1[-1], 3)

        # reparametrize
        crv.reparam((6.0, 9.0))

        # get some info on the reparametrized curve
        knots2 = crv.knots(0)
        evaluation_point2 = crv(7.20)
        self.assertEqual(knots2[0], 6)
        self.assertEqual(knots2[-1], 9)

        # ensure that curve has not chcanged, by comparing evaluation of it
        self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0])
        self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1])
        self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2])

        # normalize, i.e. set domain to [0,1]
        crv.reparam()

        # get some info on the normalized curve
        knots3 = crv.knots(0)
        evaluation_point3 = crv(0.40)
        self.assertEqual(knots3[0], 0)
        self.assertEqual(knots3[-1], 1)

        # ensure that curve has not chcanged, by comparing evaluation of it
        self.assertAlmostEqual(evaluation_point1[0], evaluation_point3[0])
        self.assertAlmostEqual(evaluation_point1[1], evaluation_point3[1])
        self.assertAlmostEqual(evaluation_point1[2], evaluation_point3[2])

        # test errors and exceptions
        with self.assertRaises(ValueError):
            crv.reparam((9, 3))
        with self.assertRaises(TypeError):
            crv.reparam(("one", "two"))
Example #48
0
    def test_insert_knot(self):
        # non-uniform knot vector of a squiggly quadratic n=5 curve in 3D
        controlpoints = [[0, 0, 0], [1, 1, 1], [2, -1, 0], [3, 0, -1], [0, 0, -5]]
        crv = Curve(BSplineBasis(3, [0, 0, 0, .3, .4, 1, 1, 1]), controlpoints)

        evaluation_point1 = crv(0.37)
        crv.insert_knot(.2)
        crv.insert_knot(.3)
        crv.insert_knot(.9)
        evaluation_point2 = crv(0.37)

        # ensure that curve has not chcanged, by comparing evaluation of it
        self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0])
        self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1])
        self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2])
        self.assertEqual(len(crv.knots(0, with_multiplicities=True)), 11)

        # test knot insertion on single knot span
        crv = Curve(BSplineBasis(5), [[0, 0, 0], [1, 1, 1], [2, -1, 0], [3, 0, -1], [0, 0, -5]])
        evaluation_point1 = crv(0.27)
        crv.insert_knot(.2)
        evaluation_point2 = crv(0.27)
        self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0])
        self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1])
        self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2])

        # test knot insertion on first of two-knot span
        crv = Curve(
            BSplineBasis(3, [0, 0, 0, .5, 1, 1, 1]), [[0, 0, 0], [2, -1, 0], [3, 0, -1], [0, 0, -5]
                                                      ])
        evaluation_point1 = crv(0.27)
        crv.insert_knot(.2)
        evaluation_point2 = crv(0.27)
        self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0])
        self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1])
        self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2])

        # test knot insertion on last of two-knot span
        crv = Curve(
            BSplineBasis(3, [0, 0, 0, .5, 1, 1, 1]), [[0, 0, 0], [2, -1, 0], [3, 0, -1], [0, 0, -5]
                                                      ])
        evaluation_point1 = crv(0.27)
        crv.insert_knot(.9)
        evaluation_point2 = crv(0.27)
        self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0])
        self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1])
        self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2])

        # test knot insertion down to C0 basis
        crv = Curve(BSplineBasis(3), [[0, 0, 0], [2, -1, 0], [0, 0, -5]])
        evaluation_point1 = crv(0.27)
        crv.insert_knot(.4)
        crv.insert_knot(.4)
        evaluation_point2 = crv(0.27)
        self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0])
        self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1])
        self.assertAlmostEqual(evaluation_point1[2], evaluation_point2[2])

        # test rational curves, here a perfect circle represented as n=9, p=2-curve
        s = 1.0 / sqrt(2)
        controlpoints = [[1, 0, 1], [s, s, s], [0, 1, 1], [-s, s, s], [-1, 0, 1], [-s, -s, s],
                         [0, -1, 1], [s, -s, s], [1, 0, 1]]
        crv = Curve(BSplineBasis(3, [-1, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5]), controlpoints, True)
        evaluation_point1 = crv(0.37)
        crv.insert_knot(.2)
        crv.insert_knot(.3)
        crv.insert_knot(.9)
        evaluation_point2 = crv(0.37)
        self.assertAlmostEqual(evaluation_point1[0], evaluation_point2[0])
        self.assertAlmostEqual(evaluation_point1[1], evaluation_point2[1])
        self.assertEqual(len(crv.knots(0, with_multiplicities=True)), 15)

        # test errors and exceptions
        with self.assertRaises(TypeError):
            crv.insert_knot(1, 2, 3)  # too many arguments
        with self.assertRaises(ValueError):
            crv.insert_knot(1, 2)  # direction=2 is illegal for curves
        with self.assertRaises(TypeError):
            crv.insert_knot()  # too few arguments
        with self.assertRaises(ValueError):
            crv.insert_knot(-0.2)  # Outside-domain error
        with self.assertRaises(ValueError):
            crv.insert_knot(4.4)  # Outside-domain error