Example #1
0
    def insert_knot(self, u_bar, count=1):
        # "The NURBS book", 2nd edition, p.5.2, eq. 5.11
        s = sv_knotvector.find_multiplicity(self.knotvector, u_bar)
        #print(f"I: kv {len(self.knotvector)}{self.knotvector}, u_bar {u_bar} => s {s}")
        k = np.searchsorted(self.knotvector, u_bar, side='right') - 1
        p = self.degree
        u = self.knotvector
        new_knotvector = sv_knotvector.insert(self.knotvector, u_bar, count)
        N = len(self.control_points)
        control_points = self.get_homogenous_control_points()

        for r in range(1, count + 1):
            prev_control_points = control_points
            control_points = []
            for i in range(N + 1):
                #print(f"I: i {i}, k {k}, p {p}, r {r}, s {s}, k-p+r-1 {k-p+r-1}, k-s {k-s}")
                if i <= k - p + r - 1:
                    point = prev_control_points[i]
                    #print(f"P[{r},{i}] := {i}{prev_control_points[i]}")
                elif k - p + r <= i <= k - s:
                    denominator = u[i + p - r + 1] - u[i]
                    alpha = (u_bar - u[i]) / denominator
                    point = alpha * prev_control_points[i] + (
                        1.0 - alpha) * prev_control_points[i - 1]
                    #print(f"P[{r},{i}]: alpha {alpha}, pts {i}{prev_control_points[i]}, {i-1}{prev_control_points[i-1]} => {point}")
                else:
                    point = prev_control_points[i - 1]
                    #print(f"P[{r},{i}] := {i-1}{prev_control_points[i-1]}")
                control_points.append(point)
            N += 1

        control_points, weights = from_homogenous(np.array(control_points))
        curve = SvNativeNurbsCurve(self.degree, new_knotvector, control_points,
                                   weights)
        return curve
Example #2
0
 def test_from_homogenous(self):
     points = np.array([[0, 0, 1, 1], [0, 0, 4, 2], [0, 0, 9, 3]])
     result, weights = from_homogenous(points)
     expected_points = np.array([[0, 0, 1], [0, 0, 2], [0, 0, 3]])
     expected_weights = np.array([1, 2, 3])
     self.assert_numpy_arrays_equal(weights, expected_weights, precision=8)
     self.assert_numpy_arrays_equal(result, expected_points, precision=8)
Example #3
0
    def elevate_degree(self, delta=None, target=None):
        if delta is None and target is None:
            delta = 1
        if delta is not None and target is not None:
            raise Exception(
                "Of delta and target, only one parameter can be specified")
        degree = self.get_degree()
        if delta is None:
            delta = target - degree
            if delta < 0:
                raise Exception(
                    f"Curve already has degree {degree}, which is greater than target {target}"
                )
        if delta == 0:
            return self

        if self.is_bezier():
            control_points = self.get_homogenous_control_points()
            control_points = elevate_bezier_degree(degree, control_points,
                                                   delta)
            control_points, weights = from_homogenous(control_points)
            knotvector = self.get_knotvector()
            knotvector = sv_knotvector.elevate_degree(knotvector, delta)
            return SvNurbsCurve.build(self.get_nurbs_implementation(),
                                      degree + delta, knotvector,
                                      control_points, weights)
        else:
            raise UnsupportedCurveTypeException(
                "Degree elevation is not implemented for non-bezier curves yet"
            )
Example #4
0
def interpolate_nurbs_curve(cls,
                            degree,
                            points,
                            metric='DISTANCE',
                            tknots=None):
    n = len(points)
    if points.ndim != 2:
        raise Exception(
            f"Array of points was expected, but got {points.shape}: {points}")
    ndim = points.shape[1]  # 3 or 4
    if ndim not in {3, 4}:
        raise Exception(
            f"Only 3D and 4D points are supported, but ndim={ndim}")
    #points3d = points[:,:3]
    #print("pts:", points)
    if tknots is None:
        tknots = Spline.create_knots(
            points, metric=metric)  # In 3D or in 4D, in general?
    knotvector = sv_knotvector.from_tknots(degree, tknots)
    functions = SvNurbsBasisFunctions(knotvector)
    coeffs_by_row = [
        functions.function(idx, degree)(tknots) for idx in range(n)
    ]
    A = np.zeros((ndim * n, ndim * n))
    for equation_idx, t in enumerate(tknots):
        for unknown_idx in range(n):
            coeff = coeffs_by_row[unknown_idx][equation_idx]
            row = ndim * equation_idx
            col = ndim * unknown_idx
            for d in range(ndim):
                A[row + d, col + d] = coeff
    B = np.zeros((ndim * n, 1))
    for point_idx, point in enumerate(points):
        row = ndim * point_idx
        B[row:row + ndim] = point[:, np.newaxis]

    x = np.linalg.solve(A, B)

    control_points = []
    for i in range(n):
        row = i * ndim
        control = x[row:row + ndim, 0].T
        control_points.append(control)
    control_points = np.array(control_points)
    if ndim == 3:
        weights = np.ones((n, ))
    else:  # 4
        control_points, weights = from_homogenous(control_points)

    if type(cls) == type:
        return cls.build(cls.get_nurbs_implementation(), degree, knotvector,
                         control_points, weights)
    elif isinstance(cls, str):
        return SvNurbsMaths.build_curve(cls, degree, knotvector,
                                        control_points, weights)
    else:
        raise TypeError(f"Unsupported type of `cls` parameter: {type(cls)}")
Example #5
0
    def insert_knot(self, u_bar, count=1, if_possible=False):
        # "The NURBS book", 2nd edition, p.5.2, eq. 5.11
        N = len(self.control_points)
        u = self.get_knotvector()
        s = sv_knotvector.find_multiplicity(u, u_bar)
        #print(f"I: kv {len(u)}{u}, u_bar {u_bar} => s {s}")
        #k = np.searchsorted(u, u_bar, side='right')-1
        k = sv_knotvector.find_span(u, N, u_bar)
        p = self.get_degree()
        new_knotvector = sv_knotvector.insert(u, u_bar, count)
        control_points = self.get_homogenous_control_points()

        if (u_bar == u[0] or u_bar == u[-1]):
            if s + count > p + 1:
                if if_possible:
                    count = (p + 1) - s
                else:
                    raise CantInsertKnotException(
                        f"Can't insert first/last knot t={u_bar} for {count} times"
                    )
        else:
            if s + count > p:
                if if_possible:
                    count = p - s
                else:
                    raise CantInsertKnotException(
                        f"Can't insert knot t={u_bar} for {count} times")

        for r in range(1, count + 1):
            prev_control_points = control_points
            control_points = []
            for i in range(N + 1):
                #print(f"I: i {i}, k {k}, p {p}, r {r}, s {s}, k-p+r-1 {k-p+r-1}, k-s {k-s}")
                if i <= k - p + r - 1:
                    point = prev_control_points[i]
                    #print(f"P[{r},{i}] := {i}{prev_control_points[i]}")
                elif k - p + r <= i <= k - s:
                    denominator = u[i + p - r + 1] - u[i]
                    if abs(denominator) < 1e-6:
                        raise Exception(
                            f"Can't insert the knot t={u_bar} for {i}th time: u[i+p-r+1]={u[i+p-r+1]}, u[i]={u[i]}, denom={denominator}"
                        )
                    alpha = (u_bar - u[i]) / denominator
                    point = alpha * prev_control_points[i] + (
                        1.0 - alpha) * prev_control_points[i - 1]
                    #print(f"P[{r},{i}]: alpha {alpha}, pts {i}{prev_control_points[i]}, {i-1}{prev_control_points[i-1]} => {point}")
                else:
                    point = prev_control_points[i - 1]
                    #print(f"P[{r},{i}] := {i-1}{prev_control_points[i-1]}")
                control_points.append(point)
            N += 1

        control_points, weights = from_homogenous(np.array(control_points))
        curve = SvNativeNurbsCurve(self.degree, new_knotvector, control_points,
                                   weights)
        return curve
Example #6
0
 def elevate_degree(self, delta=1):
     if self.is_bezier():
         control_points = self.get_homogenous_control_points()
         degree = self.get_degree()
         control_points = elevate_bezier_degree(degree, control_points, delta)
         control_points, weights = from_homogenous(control_points)
         knotvector = self.get_knotvector()
         knotvector = sv_knotvector.elevate_degree(knotvector, delta)
         return SvNurbsCurve.build(self.get_nurbs_implementation(),
                 degree+delta, knotvector, control_points, weights)
     else:
         raise Exception("Not implemented yet!")
Example #7
0
 def to_nurbs(self, implementation=SvNurbsMaths.NATIVE):
     control_points = self.get_control_points()
     if control_points.shape[1] == 4:
         control_points, weights = from_homogenous(control_points)
     else:
         weights = None
     degree = self.get_degree()
     knotvector = sv_knotvector.generate(degree, len(control_points))
     u1, u2 = self.get_u_bounds()
     knotvector = (u2 - u1) * knotvector + u1
     nurbs = SvNurbsMaths.build_curve(implementation,
                                      degree=degree,
                                      knotvector=knotvector,
                                      control_points=control_points,
                                      weights=weights)
     #return nurbs.reparametrize(*self.get_u_bounds())
     return nurbs
Example #8
0
    def elevate_degree(self, delta=None, target=None):
        orig_delta, orig_target = delta, target
        if delta is None and target is None:
            delta = 1
        if delta is not None and target is not None:
            raise Exception(
                "Of delta and target, only one parameter can be specified")
        degree = self.get_degree()
        if delta is None:
            delta = target - degree
            if delta < 0:
                raise Exception(
                    f"Curve already has degree {degree}, which is greater than target {target}"
                )
        if delta == 0:
            return self

        if self.is_bezier():
            control_points = self.get_homogenous_control_points()
            control_points = elevate_bezier_degree(degree, control_points,
                                                   delta)
            control_points, weights = from_homogenous(control_points)
            knotvector = self.get_knotvector()
            knotvector = sv_knotvector.elevate_degree(knotvector, delta)
            return SvNurbsCurve.build(self.get_nurbs_implementation(),
                                      degree + delta, knotvector,
                                      control_points, weights)
        else:
            src_t_min, src_t_max = self.get_u_bounds()
            segments = self.to_bezier_segments(to_bezier_class=False)
            segments = [
                segment.elevate_degree(orig_delta, orig_target)
                for segment in segments
            ]
            result = segments[0]
            for segment in segments[1:]:
                result = result.concatenate(segment)
            result = result.reparametrize(src_t_min, src_t_max)
            return result
Example #9
0
        def remove_one_knot(curve):
            ctrlpts = curve.get_homogenous_control_points()
            N = len(ctrlpts)
            knotvector = curve.get_knotvector()
            orig_multiplicity = sv_knotvector.find_multiplicity(knotvector, u)
            knot_span = sv_knotvector.find_span(knotvector, N, u)

            # Initialize variables
            first = knot_span - degree
            last = knot_span - orig_multiplicity

            # Don't change input variables, prepare new ones for updating
            ctrlpts_new = deepcopy(ctrlpts)

            # Initialize temp array for storing new control points
            temp_i = np.zeros((2 * degree + 1, 4))
            temp_j = np.zeros((2 * degree + 1, 4))

            removed_count = 0
            # Loop for Eqs 5.28 & 5.29
            t = 0
            offset = first - 1  # difference in index between `temp` and ctrlpts
            temp_i[0] = ctrlpts[offset]
            temp_j[last + 1 - offset] = ctrlpts[last + 1]
            i = first
            j = last
            ii = 1
            jj = last - offset
            can_remove = False

            # Compute control points for one removal step
            while j - i > t:
                alpha_i = knot_removal_alpha_i(u, knotvector, i)
                alpha_j = knot_removal_alpha_j(u, knotvector, j)

                temp_i[ii] = (ctrlpts[i] -
                              (1.0 - alpha_i) * temp_i[ii - 1]) / alpha_i
                temp_j[jj] = (ctrlpts[j] -
                              alpha_j * temp_j[jj + 1]) / (1.0 - alpha_j)

                i += 1
                j -= 1
                ii += 1
                jj -= 1

            # Check if the knot is removable
            if j - i < t:
                dist = point_distance(temp_i[ii - 1], temp_j[jj + 1])
                if dist <= tolerance:
                    can_remove = True
                else:
                    logger.debug(f"remove_knot: stop, distance={dist}")
            else:
                alpha_i = knot_removal_alpha_i(u, knotvector, i)
                ptn = alpha_i * temp_j[ii + t + 1] + (1.0 -
                                                      alpha_i) * temp_i[ii - 1]
                dist = point_distance(ctrlpts[i], ptn)
                if dist <= tolerance:
                    can_remove = True
                else:
                    logger.debug(f"remove_knot: stop, distance={dist}")

            # Check if we can remove the knot and update new control points array
            if can_remove:
                i = first
                j = last
                while j - i > t:
                    ctrlpts_new[i] = temp_i[i - offset]
                    ctrlpts_new[j] = temp_j[j - offset]
                    i += 1
                    j -= 1
                # Update indices
                first -= 1
                last += 1
                removed_count += 1

            else:
                raise CantRemoveKnotException()

            new_kv = np.copy(curve.get_knotvector())

            if removed_count > 0:
                m = N + degree + 1
                for k in range(knot_span + 1, m):
                    new_kv[k - removed_count] = new_kv[k]
                new_kv = new_kv[:m - removed_count]
                #new_kv = np.delete(curve.get_knotvector(), np.s_[(r-t+1):(r+1)])

                # Shift control points (refer to p.183 of The NURBS Book, 2nd Edition)
                j = int((2 * knot_span - orig_multiplicity - degree) /
                        2)  # first control point out
                i = j
                for k in range(1, removed_count):
                    if k % 2 == 1:
                        i += 1
                    else:
                        j -= 1
                for k in range(i + 1, N):
                    ctrlpts_new[j] = ctrlpts_new[k]
                    j += 1

                # Slice to get the new control points
                ctrlpts_new = ctrlpts_new[0:-removed_count]

            ctrlpts_new = np.array(ctrlpts_new)
            control_points, weights = from_homogenous(ctrlpts_new)

            return curve.copy(knotvector=new_kv,
                              control_points=control_points,
                              weights=weights)
Example #10
0
def simple_loft(curves,
                degree_v=None,
                knots_u='UNIFY',
                metric='DISTANCE',
                tknots=None,
                implementation=SvNurbsSurface.NATIVE):
    """
    Loft between given NURBS curves (a.k.a skinning).

    inputs:
    * degree_v - degree of resulting surface along V parameter; by default - use the same degree as provided curves
    * knots_u - one of:
        - 'UNIFY' - unify knotvectors of given curves by inserting additional knots
        - 'AVERAGE' - average knotvectors of given curves; this will work only if all curves have the same number of control points
    * metric - metric for interpolation; most useful are 'DISTANCE' and 'CENTRIPETAL'
    * implementation - NURBS maths implementation

    output: tuple:
        * list of curves - input curves after unification
        * list of NURBS curves along V direction
        * generated NURBS surface.
    """
    if knots_u not in {'UNIFY', 'AVERAGE'}:
        raise Exception(f"Unsupported knots_u option: {knots_u}")
    curve_class = type(curves[0])
    curves = unify_curves_degree(curves)
    if knots_u == 'UNIFY':
        curves = unify_curves(curves)
    else:
        kvs = [len(curve.get_control_points()) for curve in curves]
        max_kv, min_kv = max(kvs), min(kvs)
        if max_kv != min_kv:
            raise Exception(
                f"U knotvector averaging is not applicable: Curves have different number of control points: {kvs}"
            )

    degree_u = curves[0].get_degree()
    if degree_v is None:
        degree_v = degree_u

    if degree_v > len(curves):
        raise Exception(
            f"V degree ({degree_v}) must be not greater than number of curves ({len(curves)}) minus 1"
        )

    src_points = [curve.get_homogenous_control_points() for curve in curves]
    #     lens = [len(pts) for pts in src_points]
    #     max_len, min_len = max(lens), min(lens)
    #     if max_len != min_len:
    #         raise Exception(f"Unify error: curves have different number of control points: {lens}")

    src_points = np.array(src_points)
    #print("Src:", src_points)
    src_points = np.transpose(src_points, axes=(1, 0, 2))

    v_curves = [
        interpolate_nurbs_curve(curve_class,
                                degree_v,
                                points,
                                metric=metric,
                                tknots=tknots) for points in src_points
    ]
    control_points = [
        curve.get_homogenous_control_points() for curve in v_curves
    ]
    control_points = np.array(control_points)
    #weights = [curve.get_weights() for curve in v_curves]
    #weights = np.array([curve.get_weights() for curve in curves]).T
    n, m, ndim = control_points.shape
    control_points = control_points.reshape((n * m, ndim))
    control_points, weights = from_homogenous(control_points)
    control_points = control_points.reshape((n, m, 3))
    weights = weights.reshape((n, m))

    mean_v_vector = control_points.mean(axis=0)
    tknots_v = Spline.create_knots(mean_v_vector, metric=metric)
    knotvector_v = sv_knotvector.from_tknots(degree_v, tknots_v)
    if knots_u == 'UNIFY':
        knotvector_u = curves[0].get_knotvector()
    else:
        knotvectors = np.array([curve.get_knotvector() for curve in curves])
        knotvector_u = knotvectors.mean(axis=0)

    surface = SvNurbsSurface.build(implementation, degree_u, degree_v,
                                   knotvector_u, knotvector_v, control_points,
                                   weights)
    surface.u_bounds = curves[0].get_u_bounds()
    return curves, v_curves, surface