Module sverchok.utils.surface.algorithms

Expand source code
# This file is part of project Sverchok. It's copyrighted by the contributors
# recorded in the version control history of the file, available from
# its original location https://github.com/nortikin/sverchok/commit/master
#  
# SPDX-License-Identifier: GPL3
# License-Filename: LICENSE

from math import pi, cos, sin
from collections import defaultdict

from mathutils import Matrix, Vector

from sverchok.utils.math import (
        ZERO, FRENET, HOUSEHOLDER, TRACK, DIFF, TRACK_NORMAL,
        np_dot
    )
from sverchok.utils.geom import (
        LineEquation, CircleEquation3D, PlaneEquation,
        rotate_vector_around_vector, rotate_vector_around_vector_np,
        autorotate_householder, autorotate_track, autorotate_diff,
        center, linear_approximation
    )
from sverchok.utils.curve.core import UnsupportedCurveTypeException
from sverchok.utils.curve.primitives import SvCircle
from sverchok.utils.curve import knotvector as sv_knotvector
from sverchok.utils.curve.algorithms import (
            SvNormalTrack, curve_frame_on_surface_array,
            MathutilsRotationCalculator, DifferentialRotationCalculator,
            reparametrize_curve
        )
from sverchok.utils.surface.core import SvSurface, UnsupportedSurfaceTypeException
from sverchok.utils.surface.nurbs import SvNurbsSurface
from sverchok.utils.surface.data import *
from sverchok.utils.nurbs_common import SvNurbsBasisFunctions, SvNurbsMaths
from sverchok.utils.sv_logging import sv_logger


class SvInterpolatingSurface(SvSurface):
    __description__ = "Interpolating"

    def __init__(self, u_bounds, v_bounds, u_spline_constructor, v_splines, reparametrize_v_splines=True):
        if reparametrize_v_splines:
            self.v_splines = [reparametrize_curve(spline) for spline in v_splines]
        else:
            for spline in v_splines:
                m,M = spline.get_u_bounds()
                if m != 0.0 or M != 1.0:
                    raise Exception("one of splines has to be reparametrized")
            self.v_splines = v_splines
        self.u_spline_constructor = u_spline_constructor
        self.u_bounds = u_bounds
        self.v_bounds = v_bounds

        # Caches
        # v -> Spline
        self._u_splines = {}
        # (u,v) -> vertex
        self._eval_cache = {}
        # (u,v) -> normal
        self._normal_cache = {}

    @property
    def u_size(self):
        return self.u_bounds[1] - self.u_bounds[0]
        #v = 0.0
        #verts = [spline.evaluate(v) for spline in self.v_splines]
        #return self.get_u_spline(v, verts).u_size

    @property
    def v_size(self):
        return self.v_bounds[1] - self.v_bounds[0]
        #return self.v_splines[0].v_size

    def get_u_spline(self, v, vertices):
        """Get a spline along U direction for specified value of V coordinate"""
        spline = self._u_splines.get(v, None)
        if spline is not None:
            return spline
        else:
            spline = self.u_spline_constructor(vertices)
            self._u_splines[v] = spline
            return spline

    def _evaluate(self, u, v):
        spline_vertices = []
        for spline in self.v_splines:
            point = spline.evaluate(v)
            spline_vertices.append(point)
        #spline_vertices = [spline.evaluate(v) for spline in self.v_splines]
        u_spline = self.get_u_spline(v, spline_vertices)
        result = u_spline.evaluate(u)
        return result

    def evaluate(self, u, v):
        result = self._eval_cache.get((u,v), None)
        if result is not None:
            return result
        else:
            result = self._evaluate(u, v)
            self._eval_cache[(u,v)] = result
            return result

#     def evaluate_array(self, us, vs):
#         # FIXME: To be optimized!
#         normals = [self._evaluate(u, v) for u,v in zip(us, vs)]
#         return np.array(normals)

    def evaluate_array(self, us, vs):
        result = np.empty((len(us), 3))
        v_to_u = defaultdict(list)
        v_to_i = defaultdict(list)
        for i, (u, v) in enumerate(zip(us, vs)):
            v_to_u[v].append(u)
            v_to_i[v].append(i)

        # here we rely on fact that in Python 3.7+ dicts are ordered.
        all_vs = np.array(list(v_to_u.keys()))
        v_spline_points = np.array([spline.evaluate_array(all_vs) for spline in self.v_splines])

        for v_idx, (v, us_by_v) in enumerate(v_to_u.items()):
            is_by_v = v_to_i[v]
            spline_vertices = []
            for spline_idx, spline in enumerate(self.v_splines):
                point = v_spline_points[spline_idx,v_idx]
                #point = spline.evaluate(v)
                spline_vertices.append(point)
            u_spline = self.get_u_spline(v, spline_vertices)
            points = u_spline.evaluate_array(np.array(us_by_v))
            idxs = np.array(is_by_v)[np.newaxis].T
            np.put_along_axis(result, idxs, points, axis=0)
        return result

    def _normal(self, u, v):
        h = 0.001
        point = self.evaluate(u, v)
        # we know this exists because it was filled in evaluate()
        u_spline = self._u_splines[v]
        u_tangent = u_spline.tangent(u)
        point_v = self.evaluate(u, v+h)
        dv = (point_v - point)/h
        n = np.cross(u_tangent, dv)
        norm = np.linalg.norm(n)
        if norm != 0:
            n = n / norm
        return n

    def normal(self, u, v):
        result = self._normal_cache.get((u,v), None)
        if result is not None:
            return result
        else:
            result = self._normal(u, v)
            self._normal_cache[(u,v)] = result
            return result

#     def normal_array(self, us, vs):
#         # FIXME: To be optimized!
#         normals = [self._normal(u, v) for u,v in zip(us, vs)]
#         return np.array(normals)

    def normal_array(self, us, vs):
        h = 0.001
        result = np.empty((len(us), 3))
        v_to_u = defaultdict(list)
        v_to_i = defaultdict(list)
        for i, (u, v) in enumerate(zip(us, vs)):
            v_to_u[v].append(u)
            v_to_i[v].append(i)
        for v, us_by_v in v_to_u.items():
            us_by_v = np.array(us_by_v)
            is_by_v = v_to_i[v]
            spline_vertices = []
            spline_vertices_h = []
            for v_spline in self.v_splines:
                v_min, v_max = v_spline.get_u_bounds()
                vx = (v_max - v_min) * v + v_min
                if vx +h <= v_max:
                    point = v_spline.evaluate(vx)
                    point_h = v_spline.evaluate(vx + h)
                else:
                    point = v_spline.evaluate(vx - h)
                    point_h = v_spline.evaluate(vx)
                spline_vertices.append(point)
                spline_vertices_h.append(point_h)
            if v+h <= v_max:
                u_spline = self.get_u_spline(v, spline_vertices)
                u_spline_h = self.get_u_spline(v+h, spline_vertices_h)
            else:
                u_spline = self.get_u_spline(v-h, spline_vertices)
                u_spline_h = self.get_u_spline(v, spline_vertices_h)
            u_min, u_max = 0.0, 1.0

            good_us = us_by_v + h < u_max
            bad_us = np.logical_not(good_us)

            good_points = np.broadcast_to(good_us[np.newaxis].T, (len(us_by_v), 3)).flatten()
            bad_points = np.logical_not(good_points)
            points = np.empty((len(us_by_v), 3))
            points[good_us] = u_spline.evaluate_array(us_by_v[good_us])
            points[bad_us] = u_spline.evaluate_array(us_by_v[bad_us] - h)
            points_u_h = np.empty((len(us_by_v), 3))
            points_u_h[good_us] = u_spline.evaluate_array(us_by_v[good_us] + h)
            points_u_h[bad_us] = u_spline.evaluate_array(us_by_v[bad_us])
            points_v_h = u_spline_h.evaluate_array(us_by_v)

            dvs = (points_v_h - points) / h
            dus = (points_u_h - points) / h
            normals = np.cross(dus, dvs)
            norms = np.linalg.norm(normals, axis=1, keepdims=True)
            normals = normals / norms

            idxs = np.array(is_by_v)[np.newaxis].T
            np.put_along_axis(result, idxs, normals, axis=0)
        return result

PROJECT = 'project'
COPROJECT = 'coproject'

def _dot(vs1, vs2):
    return (vs1 * vs2).sum(axis=1)[np.newaxis].T

class SvDeformedByFieldSurface(SvSurface):
    def __init__(self, surface, field, coefficient=1.0, by_normal=None):
        self.surface = surface
        self.field = field
        self.coefficient = coefficient
        self.by_normal = by_normal
        self.normal_delta = 0.001
        self.__description__ = "{}({})".format(field, surface)

    def get_coord_mode(self):
        return self.surface.get_coord_mode()

    def get_u_min(self):
        return self.surface.get_u_min()

    def get_u_max(self):
        return self.surface.get_u_max()

    def get_v_min(self):
        return self.surface.get_v_min()

    def get_v_max(self):
        return self.surface.get_v_max()

    @property
    def u_size(self):
        return self.surface.u_size

    @property
    def v_size(self):
        return self.surface.v_size

    @property
    def has_input_matrix(self):
        return self.surface.has_input_matrix

    def get_input_matrix(self):
        return self.surface.get_input_matrix()

    def evaluate(self, u, v):
        p = self.surface.evaluate(u, v)
        vec = self.field.evaluate(p[0], p[1], p[2])
        if self.by_normal == PROJECT:
            normal = self.surface.normal(u, v)
            vec = np.dot(vec, normal) * normal / np.dot(normal, normal)
        elif self.by_normal == COPROJECT:
            normal = self.surface.normal(u, v)
            projection = np.dot(vec, normal) * normal / np.dot(normal, normal)
            vec = vec - projection
        return p + self.coefficient * vec

    def evaluate_array(self, us, vs):
        ps = self.surface.evaluate_array(us, vs)
        xs, ys, zs = ps[:,0], ps[:,1], ps[:,2]
        vxs, vys, vzs = self.field.evaluate_grid(xs, ys, zs)
        vecs = np.stack((vxs, vys, vzs)).T
        if self.by_normal == PROJECT:
            normals = self.surface.normal_array(us, vs)
            vecs = _dot(vecs, normals) * normals / _dot(normals, normals)
        elif self.by_normal == COPROJECT:
            normals = self.surface.normal_array(us, vs)
            projections = _dot(vecs, normals) * normals / _dot(normals, normals)
            vecs = vecs - projections
        return ps + self.coefficient * vecs

    def normal(self, u, v):
        h = self.normal_delta
        p = self.evaluate(u, v)
        p_u = self.evaluate(u+h, v)
        p_v = self.evaluate(u, v+h)
        du = (p_u - p) / h
        dv = (p_v - p) / h
        normal = np.cross(du, dv)
        n = np.linalg.norm(normal)
        normal = normal / n
        return normal

    def normal_array(self, us, vs):
        surf_vertices = self.evaluate_array(us, vs)
        u_plus = self.evaluate_array(us + self.normal_delta, vs)
        v_plus = self.evaluate_array(us, vs + self.normal_delta)
        du = u_plus - surf_vertices
        dv = v_plus - surf_vertices
        #self.info("Du: %s", du)
        #self.info("Dv: %s", dv)
        normal = np.cross(du, dv)
        norm = np.linalg.norm(normal, axis=1)[np.newaxis].T
        #if norm != 0:
        normal = normal / norm
        #self.info("Normals: %s", normal)
        return normal

class SvRevolutionSurface(SvSurface):
    __description__ = "Revolution"

    def __init__(self, curve, point, direction, global_origin=True):
        self.curve = curve
        self.point = point
        self.direction = direction
        self.global_origin = global_origin
        self.normal_delta = 0.001
        self.v_bounds = (0.0, 2*pi)

    @classmethod
    def build(cls, curve, point, direction, v_min=0, v_max=2*pi, global_origin=True):
        if hasattr(curve, 'make_revolution_surface'):
            try:
                return curve.make_revolution_surface(point, direction, v_min, v_max, global_origin)
            except UnsupportedCurveTypeException:
                pass
        surface = SvRevolutionSurface(curve, point, direction, global_origin)
        surface.v_bounds = (v_min, v_max)
        return surface

    def evaluate(self, u, v):
        point_on_curve = self.curve.evaluate(u)
        dv = point_on_curve - self.point
        result = np.array(rotate_vector_around_vector(dv, self.direction, v))
        if not self.global_origin:
            result = result + self.point
        return result

    def evaluate_array(self, us, vs):
        points_on_curve = self.curve.evaluate_array(us)
        dvs = points_on_curve - self.point
        result = rotate_vector_around_vector_np(dvs, self.direction, vs)
        if not self.global_origin:
            result = result + self.point
        return result

    def get_u_min(self):
        return self.curve.get_u_bounds()[0]

    def get_u_max(self):
        return self.curve.get_u_bounds()[1]

    def get_v_min(self):
        return self.v_bounds[0]

    def get_v_max(self):
        return self.v_bounds[1]

class SvExtrudeCurveVectorSurface(SvSurface):
    def __init__(self, curve, vector):
        self.curve = curve
        self.vector = np.array(vector)
        self.normal_delta = 0.001
        self.__description__ = "Extrusion of {}".format(curve)

    @classmethod
    def build(cls, curve, vector):
        if hasattr(curve, 'extrude_along_vector'):
            try:
                return curve.extrude_along_vector(vector)
            except UnsupportedCurveTypeException:
                pass
        return SvExtrudeCurveVectorSurface(curve, vector)

    def evaluate(self, u, v):
        point_on_curve = self.curve.evaluate(u)
        return point_on_curve + v * self.vector

    def evaluate_array(self, us, vs):
        points_on_curve = self.curve.evaluate_array(us)
        return points_on_curve + vs[np.newaxis].T * self.vector

    def get_u_min(self):
        return self.curve.get_u_bounds()[0]

    def get_u_max(self):
        return self.curve.get_u_bounds()[1]

    def get_v_min(self):
        return 0.0

    def get_v_max(self):
        return 1.0

    @property
    def u_size(self):
        m,M = self.curve.get_u_bounds()
        return M - m

    @property
    def v_size(self):
        return 1.0

class SvExtrudeCurvePointSurface(SvSurface):
    def __init__(self, curve, point):
        self.curve = curve
        self.point = point
        self.normal_delta = 0.001
        self.__description__ = "Extrusion of {}".format(curve)

    @staticmethod
    def build(curve, point):
        if hasattr(curve, 'extrude_to_point'):
            try:
                return curve.extrude_to_point(point)
            except UnsupportedCurveTypeException:
                pass
        return SvExtrudeCurvePointSurface(curve, point)

    def evaluate(self, u, v):
        point_on_curve = self.curve.evaluate(u)
        return (1.0 - v) * point_on_curve + v * self.point

    def evaluate_array(self, us, vs):
        points_on_curve = self.curve.evaluate_array(us)
        vs = vs[np.newaxis].T
        return (1.0 - vs) * points_on_curve + vs * self.point

    def get_u_min(self):
        return self.curve.get_u_bounds()[0]

    def get_u_max(self):
        return self.curve.get_u_bounds()[1]

    def get_v_min(self):
        return 0.0

    def get_v_max(self):
        return 1.0

    @property
    def u_size(self):
        m,M = self.curve.get_u_bounds()
        return M - m

    @property
    def v_size(self):
        return 1.0

PROFILE = 'profile'
EXTRUSION = 'extrusion'

class SvExtrudeCurveCurveSurface(SvSurface):
    def __init__(self, u_curve, v_curve, origin = PROFILE):
        self.u_curve = u_curve
        self.v_curve = v_curve
        self.origin = origin
        self.normal_delta = 0.001
        self.__description__ = "Extrusion of {}".format(u_curve)

    def evaluate(self, u, v):
        u_point = self.u_curve.evaluate(u)
        u_min, u_max = self.u_curve.get_u_bounds()
        v_min, v_max = self.v_curve.get_u_bounds()
        v0 = self.v_curve.evaluate(v_min)
        v_point = self.v_curve.evaluate(v)
        if self.origin == EXTRUSION:
            result = u_point + v_point
        else:
            result = u_point + (v_point - v0)
        return result

    def evaluate_array(self, us, vs):
        u_points = self.u_curve.evaluate_array(us)
        u_min, u_max = self.u_curve.get_u_bounds()
        v_min, v_max = self.v_curve.get_u_bounds()
        v0 = self.v_curve.evaluate(v_min)
        v_points = self.v_curve.evaluate_array(vs)
        if self.origin == EXTRUSION:
            result = u_points + v_points
        else:
            result = u_points + (v_points - v0)
        return result

    def get_u_min(self):
        return self.u_curve.get_u_bounds()[0]

    def get_u_max(self):
        return self.u_curve.get_u_bounds()[1]

    def get_v_min(self):
        return self.v_curve.get_u_bounds()[0]

    def get_v_max(self):
        return self.v_curve.get_u_bounds()[1]

    @property
    def u_size(self):
        m,M = self.u_curve.get_u_bounds()
        return M - m

    @property
    def v_size(self):
        m,M = self.v_curve.get_u_bounds()
        return M - m

class SvExtrudeCurveFrenetSurface(SvSurface):
    def __init__(self, profile, extrusion, origin = PROFILE):
        self.profile = profile
        self.extrusion = extrusion
        self.origin = origin
        self.normal_delta = 0.001
        self.__description__ = "Extrusion of {}".format(profile)

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def evaluate_array(self, us, vs):
        profile_points = self.profile.evaluate_array(us)
        u_min, u_max = self.profile.get_u_bounds()
        v_min, v_max = self.extrusion.get_u_bounds()
        profile_vectors = profile_points
        profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
        extrusion_start = self.extrusion.evaluate(v_min)
        extrusion_points = self.extrusion.evaluate_array(vs)
        extrusion_vectors = extrusion_points - extrusion_start
        frenet, _ , _ = self.extrusion.frame_array(vs)
        profile_vectors = (frenet @ profile_vectors)[:,:,0]
        result = extrusion_vectors + profile_vectors
        if self.origin == EXTRUSION:
            result = result + self.extrusion.evaluate(v_min)
        return result

    def get_u_min(self):
        return self.profile.get_u_bounds()[0]

    def get_u_max(self):
        return self.profile.get_u_bounds()[1]

    def get_v_min(self):
        return self.extrusion.get_u_bounds()[0]

    def get_v_max(self):
        return self.extrusion.get_u_bounds()[1]

    @property
    def u_size(self):
        m,M = self.profile.get_u_bounds()
        return M - m

    @property
    def v_size(self):
        m,M = self.extrusion.get_u_bounds()
        return M - m

class SvExtrudeCurveZeroTwistSurface(SvSurface):
    def __init__(self, profile, extrusion, resolution, origin = PROFILE):
        self.profile = profile
        self.extrusion = extrusion
        self.origin = origin
        self.normal_delta = 0.001
        self.extrusion.pre_calc_torsion_integral(resolution)
        self.__description__ = "Extrusion of {}".format(profile)

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def evaluate_array(self, us, vs):
        profile_points = self.profile.evaluate_array(us)
        u_min, u_max = self.profile.get_u_bounds()
        v_min, v_max = self.extrusion.get_u_bounds()
        profile_vectors = profile_points
        profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
        extrusion_start = self.extrusion.evaluate(v_min)
        extrusion_points = self.extrusion.evaluate_array(vs)
        extrusion_vectors = extrusion_points - extrusion_start

        frenet, _ , _ = self.extrusion.frame_array(vs)

        angles = - self.extrusion.torsion_integral(vs)
        n = len(us)
        zeros = np.zeros((n,))
        ones = np.ones((n,))
        row1 = np.stack((np.cos(angles), np.sin(angles), zeros)).T # (n, 3)
        row2 = np.stack((-np.sin(angles), np.cos(angles), zeros)).T # (n, 3)
        row3 = np.stack((zeros, zeros, ones)).T # (n, 3)
        rotation_matrices = np.dstack((row1, row2, row3))

        profile_vectors = (frenet @ rotation_matrices @ profile_vectors)[:,:,0]
        result = extrusion_vectors + profile_vectors
        if self.origin == EXTRUSION:
            result = result + self.extrusion.evaluate(v_min)
        return result

    def get_u_min(self):
        return self.profile.get_u_bounds()[0]

    def get_u_max(self):
        return self.profile.get_u_bounds()[1]

    def get_v_min(self):
        return self.extrusion.get_u_bounds()[0]

    def get_v_max(self):
        return self.extrusion.get_u_bounds()[1]

class SvExtrudeCurveTrackNormalSurface(SvSurface):
    def __init__(self, profile, extrusion, resolution, origin = PROFILE):
        self.profile = profile
        self.extrusion = extrusion
        self.origin = origin
        self.normal_delta = 0.001
        self.tracker = SvNormalTrack(extrusion, resolution)
        self.__description__ = "Extrusion of {}".format(profile)

    def get_u_min(self):
        return self.profile.get_u_bounds()[0]

    def get_u_max(self):
        return self.profile.get_u_bounds()[1]

    def get_v_min(self):
        return self.extrusion.get_u_bounds()[0]

    def get_v_max(self):
        return self.extrusion.get_u_bounds()[1]

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def evaluate_array(self, us, vs):
        profile_vectors = self.profile.evaluate_array(us)
        u_min, u_max = self.profile.get_u_bounds()
        v_min, v_max = self.extrusion.get_u_bounds()
        profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
        extrusion_start = self.extrusion.evaluate(v_min)
        extrusion_points = self.extrusion.evaluate_array(vs)
        extrusion_vectors = extrusion_points - extrusion_start

        matrices = self.tracker.evaluate_array(vs)
        profile_vectors = (matrices @ profile_vectors)[:,:,0]
        result = extrusion_vectors + profile_vectors
        if self.origin == EXTRUSION:
            result = result + extrusion_start
        return result

class SvExtrudeCurveMathutilsSurface(SvSurface):
    def __init__(self, profile, extrusion, algorithm, orient_axis='Z', up_axis='X', origin = PROFILE):
        self.profile = profile
        self.extrusion = extrusion
        self.algorithm = algorithm
        self.orient_axis = orient_axis
        self.up_axis = up_axis
        self.origin = origin
        self.normal_delta = 0.001
        self.__description__ = "Extrusion of {}".format(profile)

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def get_matrix(self, tangent):
        x = Vector((1.0, 0.0, 0.0))
        y = Vector((0.0, 1.0, 0.0))
        z = Vector((0.0, 0.0, 1.0))

        if self.orient_axis == 'X':
            ax1, ax2, ax3 = x, y, z
        elif self.orient_axis == 'Y':
            ax1, ax2, ax3 = y, x, z
        else:
            ax1, ax2, ax3 = z, x, y

        if self.algorithm == 'householder':
            rot = autorotate_householder(ax1, tangent).inverted()
        elif self.algorithm == 'track':
            rot = autorotate_track(self.orient_axis, tangent, self.up_axis)
        elif self.algorithm == 'diff':
            rot = autorotate_diff(tangent, ax1)
        else:
            raise Exception("Unsupported algorithm")

        return rot

    def get_matrices(self, vs):
        tangents = self.extrusion.tangent_array(vs)
        matrices = []
        for tangent in tangents:
            matrix = self.get_matrix(Vector(tangent)).to_3x3()
            matrices.append(matrix)
        return np.array(matrices)

    def evaluate_array(self, us, vs):
        profile_points = self.profile.evaluate_array(us)
        u_min, u_max = self.profile.get_u_bounds()
        v_min, v_max = self.extrusion.get_u_bounds()
        profile_vectors = profile_points
        profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
        extrusion_start = self.extrusion.evaluate(v_min)
        extrusion_points = self.extrusion.evaluate_array(vs)
        extrusion_vectors = extrusion_points - extrusion_start

        matrices = self.get_matrices(vs)

        profile_vectors = (matrices @ profile_vectors)[:,:,0]
        result = extrusion_vectors + profile_vectors
        if self.origin == EXTRUSION:
            result = result + self.extrusion.evaluate(v_min)
        return result

    def get_u_min(self):
        return self.profile.get_u_bounds()[0]

    def get_u_max(self):
        return self.profile.get_u_bounds()[1]

    def get_v_min(self):
        return self.extrusion.get_u_bounds()[0]

    def get_v_max(self):
        return self.extrusion.get_u_bounds()[1]

class SvExtrudeCurveNormalDirSurface(SvSurface):
    def __init__(self, profile, extrusion, plane_normal, origin = PROFILE):
        self.profile = profile
        self.extrusion = extrusion
        self.origin = origin
        self.plane_normal = np.array(plane_normal)
        self.normal_delta = 0.001
        self.__description__ = "Extrusion of {}".format(profile)

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def evaluate_array(self, us, vs):
        profile_points = self.profile.evaluate_array(us)
        u_min, u_max = self.profile.get_u_bounds()
        v_min, v_max = self.extrusion.get_u_bounds()
        profile_vectors = profile_points
        profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
        extrusion_start = self.extrusion.evaluate(v_min)
        extrusion_points = self.extrusion.evaluate_array(vs)
        extrusion_vectors = extrusion_points - extrusion_start
        matrices, _ , _ = self.extrusion.frame_by_plane_array(vs, self.plane_normal)
        profile_vectors = (matrices @ profile_vectors)[:,:,0]
        result = extrusion_vectors + profile_vectors
        if self.origin == EXTRUSION:
            result = result + self.extrusion.evaluate(v_min)
        return result

    def get_u_min(self):
        return self.profile.get_u_bounds()[0]

    def get_u_max(self):
        return self.profile.get_u_bounds()[1]

    def get_v_min(self):
        return self.extrusion.get_u_bounds()[0]

    def get_v_max(self):
        return self.extrusion.get_u_bounds()[1]

    @property
    def u_size(self):
        m,M = self.profile.get_u_bounds()
        return M - m

    @property
    def v_size(self):
        m,M = self.extrusion.get_u_bounds()
        return M - m

class SvConstPipeSurface(SvSurface):
    __description__ = "Pipe"

    def __init__(self, curve, radius, algorithm = FRENET, resolution=50):
        self.curve = curve
        self.radius = radius
        self.circle = SvCircle(Matrix(), radius)
        self.algorithm = algorithm
        self.normal_delta = 0.001
        self.u_bounds = self.circle.get_u_bounds()
        if algorithm in {FRENET, ZERO, TRACK_NORMAL}:
            self.calculator = DifferentialRotationCalculator(curve, algorithm, resolution)

    def get_u_min(self):
        return self.u_bounds[0]

    def get_u_max(self):
        return self.u_bounds[1]

    def get_v_min(self):
        return self.curve.get_u_bounds()[0]

    def get_v_max(self):
        return self.curve.get_u_bounds()[1]

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def get_matrix(self, tangent):
        return MathutilsRotationCalculator.get_matrix(tangent, scale=1.0,
                axis=2,
                algorithm = self.algorithm,
                scale_all=False)

    def get_matrices(self, ts):
        if self.algorithm in {FRENET, ZERO, TRACK_NORMAL}:
            return self.calculator.get_matrices(ts)
        elif self.algorithm in {HOUSEHOLDER, TRACK, DIFF}:
            tangents = self.curve.tangent_array(ts)
            matrices = np.vectorize(lambda t : self.get_matrix(t), signature='(3)->(3,3)')(tangents)
            return matrices
        else:
            raise Exception("Unsupported algorithm")

    def evaluate_array(self, us, vs):
        profile_vectors = self.circle.evaluate_array(us)
        u_min, u_max = self.circle.get_u_bounds()
        v_min, v_max = self.curve.get_u_bounds()
        profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
        extrusion_start = self.curve.evaluate(v_min)
        extrusion_points = self.curve.evaluate_array(vs)
        extrusion_vectors = extrusion_points - extrusion_start

        matrices = self.get_matrices(vs)

        profile_vectors = (matrices @ profile_vectors)[:,:,0]
        result = extrusion_vectors + profile_vectors
        result = result + extrusion_start
        return result

class SvCurveLerpSurface(SvSurface):
    __description__ = "Ruled"

    def __init__(self, curve1, curve2):
        self.curve1 = curve1
        self.curve2 = curve2
        self.normal_delta = 0.001
        self.v_bounds = (0.0, 1.0)
        self.u_bounds = (0.0, 1.0)
        self.c1_min, self.c1_max = curve1.get_u_bounds()
        self.c2_min, self.c2_max = curve2.get_u_bounds()

    @classmethod
    def build(cls, curve1, curve2, vmin=0.0, vmax=1.0):
        if hasattr(curve1, 'make_ruled_surface'):
            try:
                return curve1.make_ruled_surface(curve2, vmin, vmax)
            except TypeError as e:
                # make_ruled_surface method can raise TypeError in case
                # it can't work with given curve2.
                # In this case we must use generic method.
                sv_logger.debug("Can't make a native ruled surface: %s", e)
                pass

        # generic method
        surface = SvCurveLerpSurface(curve1, curve2)
        surface.v_bounds = (vmin, vmax)
        return surface

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def evaluate_array(self, us, vs):
        us1 = (self.c1_max - self.c1_min) * us + self.c1_min
        us2 = (self.c2_max - self.c2_min) * us + self.c2_min
        c1_points = self.curve1.evaluate_array(us1)
        c2_points = self.curve2.evaluate_array(us2)
        vs = vs[np.newaxis].T
        points = (1.0 - vs)*c1_points + vs*c2_points
        return points

    def get_u_min(self):
        return self.u_bounds[0]

    def get_u_max(self):
        return self.u_bounds[1]

    def get_v_min(self):
        return self.v_bounds[0]

    def get_v_max(self):
        return self.v_bounds[1]

    @property
    def u_size(self):
        return self.u_bounds[1] - self.u_bounds[0]

    @property
    def v_size(self):
        return self.v_bounds[1] - self.v_bounds[0]

class SvSurfaceLerpSurface(SvSurface):
    __description__ = "Lerp"

    def __init__(self, surface1, surface2, coefficient):
        self.surface1 = surface1
        self.surface2 = surface2
        self.coefficient = coefficient
        self.normal_delta = 0.001
        self.v_bounds = (0.0, 1.0)
        self.u_bounds = (0.0, 1.0)
        self.s1_u_min, self.s1_u_max = surface1.get_u_min(), surface1.get_u_max()
        self.s1_v_min, self.s1_v_max = surface1.get_v_min(), surface1.get_v_max()
        self.s2_u_min, self.s2_u_max = surface2.get_u_min(), surface2.get_u_max()
        self.s2_v_min, self.s2_v_max = surface2.get_v_min(), surface2.get_v_max()

    def get_u_min(self):
        return self.u_bounds[0]

    def get_u_max(self):
        return self.u_bounds[1]

    def get_v_min(self):
        return self.v_bounds[0]

    def get_v_max(self):
        return self.v_bounds[1]

    @property
    def u_size(self):
        return self.u_bounds[1] - self.u_bounds[0]

    @property
    def v_size(self):
        return self.v_bounds[1] - self.v_bounds[0]

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]
    
    def evaluate_array(self, us, vs):
        us1 = (self.s1_u_max - self.s1_u_min) * us + self.s1_u_min
        us2 = (self.s2_u_max - self.s2_u_min) * us + self.s2_u_min
        vs1 = (self.s1_v_max - self.s1_v_min) * vs + self.s1_v_min
        vs2 = (self.s2_v_max - self.s2_v_min) * vs + self.s2_v_min
        s1_points = self.surface1.evaluate_array(us1, vs1)
        s2_points = self.surface2.evaluate_array(us2, vs2)
        k = self.coefficient
        points = (1.0 - k) * s1_points + k * s2_points
        return points

class SvTaperSweepSurface(SvSurface):
    __description__ = "Taper & Sweep"

    UNIT = 'UNIT'
    PROFILE = 'PROFILE'
    TAPER = 'TAPER'

    def __init__(self, profile, taper, point, direction, scale_base=UNIT):
        self.profile = profile
        self.taper = taper
        self.direction = direction
        self.point = point
        self.line = LineEquation.from_direction_and_point(direction, point)
        self.scale_base = scale_base
        self.normal_delta = 0.001

    def get_u_min(self):
        return self.profile.get_u_bounds()[0]

    def get_u_max(self):
        return self.profile.get_u_bounds()[1]

    def get_v_min(self):
        return self.taper.get_u_bounds()[0]

    def get_v_max(self):
        return self.taper.get_u_bounds()[1]

    def _get_profile_scale(self):
        profile_u_min = self.profile.get_u_bounds()[0]
        profile_start = self.profile.evaluate(profile_u_min)
        profile_start_projection = self.line.projection_of_point(profile_start)
        dp = np.linalg.norm(profile_start - profile_start_projection)
        return dp

    def evaluate(self, u, v):
        taper_point = self.taper.evaluate(v)
        taper_projection = np.array( self.line.projection_of_point(taper_point) )
        scale = np.linalg.norm(taper_projection - taper_point)
        if self.scale_base == SvTaperSweepSurface.TAPER:
            dp = self._get_profile_scale()
            scale /= dp
        elif self.scale_base == SvTaperSweepSurface.PROFILE:
            taper_t_min = self.taper.get_u_bounds()[0]
            taper_start = self.taper.evaluate(taper_t_min)
            taper_start_projection = np.array(self.line.projection_of_point(taper_start))
            scale0 = np.linalg.norm(taper_start - taper_start_projection)
            scale /= scale0

        profile_point = self.profile.evaluate(u)
        return profile_point * scale + taper_projection

    def evaluate_array(self, us, vs):
        taper_points = self.taper.evaluate_array(vs)
        taper_projections = self.line.projection_of_points(taper_points)
        scale = np.linalg.norm(taper_projections - taper_points, axis=1, keepdims=True)

        if self.scale_base == SvTaperSweepSurface.TAPER:
            dp = self._get_profile_scale()
            scale /= dp
        elif self.scale_base == SvTaperSweepSurface.PROFILE:
            scale0 = scale[0]
            scale /= scale0

        profile_points = self.profile.evaluate_array(us)
        return profile_points * scale + taper_projections

class SvBlendSurface(SvSurface):
    def __init__(self, surface1, surface2, curve1, curve2, bulge1, bulge2):
        self.surface1 = surface1
        self.surface2 = surface2
        self.curve1 = curve1
        self.curve2 = curve2
        self.bulge1 = bulge1
        self.bulge2 = bulge2
        self.u_bounds = (0.0, 1.0)
        self.v_bounds = (0.0, 1.0)

    def get_u_min(self):
        return self.u_bounds[0]

    def get_u_max(self):
        return self.u_bounds[1]

    def get_v_min(self):
        return self.v_bounds[0]

    def get_v_max(self):
        return self.v_bounds[1]

    def evaluate_array(self, us, vs):
        c1_min, c1_max = self.curve1.get_u_bounds()
        c2_min, c2_max = self.curve2.get_u_bounds()
        c1_us = (c1_max - c1_min) * us + c1_min
        c2_us = (c2_max - c2_min) * us + c2_min

        _, c1_points, _, _, c1_binormals = curve_frame_on_surface_array(self.surface1, self.curve1, c1_us)
        _, c2_points, _, _, c2_binormals = curve_frame_on_surface_array(self.surface2, self.curve2, c2_us)
        c1_binormals = self.bulge1 * c1_binormals
        c2_binormals = self.bulge2 * c2_binormals

        # See also sverchok.utils.curve.bezier.SvCubicBezierCurve.
        # Here we have re-implementation of the same algorithm
        # which works with arrays of control points
        p0s = c1_points                 # (n, 3)
        p1s = c1_points + c1_binormals
        p2s = c2_points + c2_binormals
        p3s = c2_points

        c0 = (1 - vs)**3      # (n,)
        c1 = 3*vs*(1-vs)**2
        c2 = 3*vs**2*(1-vs)
        c3 = vs**3

        # (n,1)
        c0, c1, c2, c3 = c0[:,np.newaxis], c1[:,np.newaxis], c2[:,np.newaxis], c3[:,np.newaxis]

        return c0*p0s + c1*p1s + c2*p2s + c3*p3s

class SvConcatSurface(SvSurface):
    def __init__(self, direction, surfaces):
        self.direction = direction
        self.surfaces = self._unify(surfaces)

        p1 = self._get_p_min(surfaces[0])
        boundaries = [p1]
        boundaries.extend([self._get_p_delta(s) for s in surfaces])
        self.boundaries = np.array(boundaries).cumsum()

    def _unify(self, surfaces):
        if self.direction == 'U':
            min_vs = [s.get_v_min() for s in surfaces]
            max_vs = [s.get_v_max() for s in surfaces]

            if min(min_vs) != max(min_vs) or min(max_vs) != max(max_vs):
                surfaces = [SvReparametrizedSurface.build(s, s.get_u_min(), s.get_u_max(), 0.0, 1.0) for s in surfaces]
            return surfaces
        
        else:
            min_us = [s.get_u_min() for s in surfaces]
            max_us = [s.get_u_max() for s in surfaces]

            if min(min_us) != max(min_us) or min(max_us) != max(max_us):
                surfaces = [SvReparametrizedSurface.build(s, 0.0, 1.0, s.get_v_min(), s.get_v_max()) for s in surfaces]
            return surfaces

    def _get_p_max(self, surface):
        if self.direction == 'U':
            return surface.get_u_max()
        else:
            return surface.get_v_max()

    def _get_p_delta(self, surface):
        if self.direction == 'U':
            return surface.get_u_max() - surface.get_u_min()
        else:
            return surface.get_v_max() - surface.get_v_min()

    def _get_p_min(self, surface):
        if self.direction == 'U':
            return surface.get_u_min()
        else:
            return surface.get_v_min()

    def get_u_min(self):
        if self.direction == 'U':
            return self.boundaries[0]
        else:
            return self.surfaces[0].get_u_min()

    def get_u_max(self):
        if self.direction == 'U':
            return self.boundaries[-1]
        else:
            return self.surfaces[0].get_u_max()

    def get_v_min(self):
        if self.direction == 'U':
            return self.surfaces[0].get_v_min()
        else:
            return self.boundaries[0]

    def get_v_max(self):
        if self.direction == 'U':
            return self.surfaces[0].get_v_max()
        else:
            return self.boundaries[-1]

    def evaluate(self, u, v):
        if self.direction == 'U':
            u_idx = self.boundaries.searchsorted(u, side='right')-1
            if u_idx >= len(self.surfaces):
                v_idx = len(self.surfaces)-1
                du = self._get_p_delta(self.surfaces[-1])
            else:
                du = u - self.boundaries[u_idx]
            subsurf = self.surfaces[u_idx]
            return subsurf.evaluate(subsurf.get_u_min()+du, v)
        else:
            v_idx = self.boundaries.searchsorted(v, side='right')-1
            if v_idx >= len(self.surfaces):
                v_idx = len(self.surfaces)-1
                dv = self._get_p_delta(self.surfaces[-1])
            else:
                dv = v - self.boundaries[v_idx]
            subsurf = self.surfaces[v_idx]
            return subsurf.evaluate(u, subsurf.get_v_min()+dv)

    def evaluate_array(self, us, vs):
        # TODO: numpy implementation
        return np.vectorize(self.evaluate, signature='(),()->(3)')(us, vs)

def concatenate_surfaces(direction, surfaces):
    if all(hasattr(s, 'concatenate') for s in surfaces):
        try:
            result = surfaces[0]
            for s in surfaces[1:]:
                result = result.concatenate(direction, s)
            return result
        except UnsupportedSurfaceTypeException as e:
            sv_logger.debug("Can't concatenate surfaces natively: %s", e)
    
    return SvConcatSurface(direction, surfaces)

def nurbs_revolution_surface(curve, origin, axis, v_min=0, v_max=2*pi, global_origin=True):
    my_control_points = curve.get_control_points()
    my_weights = curve.get_weights()
    control_points = []
    weights = []

    any_circle = SvCircle(Matrix(), 1)
    any_circle.u_bounds = (v_min, v_max)
    any_circle = any_circle.to_nurbs()
    # all circles with given (v_min, v_max)
    # actually always have the same knotvector
    # and the same number of control points
    n = len(any_circle.get_control_points())
    circle_knotvector = any_circle.get_knotvector()
    circle_weights = any_circle.get_weights()

    # TODO: vectorize with numpy? Or better let it so for better readability?
    for my_control_point, my_weight in zip(my_control_points, my_weights):
        eq = CircleEquation3D.from_axis_point(origin, axis, my_control_point)
        if abs(eq.radius) < 1e-8:
            parallel_points = np.empty((n, 3))
            parallel_points[:] = np.array(eq.center) #[np.newaxis].T
        else:
            circle = SvCircle.from_equation(eq)
            circle.u_bounds = (v_min, v_max)
            nurbs_circle = circle.to_nurbs()
            parallel_points = nurbs_circle.get_control_points()
        parallel_weights = circle_weights * my_weight
        control_points.append(parallel_points)
        weights.append(parallel_weights)
    control_points = np.array(control_points)
    if global_origin:
        control_points = control_points - origin

    weights = np.array(weights)
    degree_u = curve.get_degree()
    degree_v = 2 # circle

    return SvNurbsSurface.build(curve.get_nurbs_implementation(),
            degree_u, degree_v,
            curve.get_knotvector(), circle_knotvector,
            control_points, weights)

def round_knotvectors(surface, accuracy):
    knotvector_u = surface.get_knotvector_u()
    knotvector_v = surface.get_knotvector_v()

    knotvector_u = np.round(knotvector_u, accuracy)
    knotvector_v = np.round(knotvector_v, accuracy)

    result = surface.copy(knotvector_u = knotvector_u, knotvector_v = knotvector_v)

    tolerance = 10**(-accuracy)

#     print(f"KV_U: {knotvector_u}")
#     print(f"KV_V: {knotvector_v}")
#     degree = surface.get_degree_u()
#     ms = sv_knotvector.to_multiplicity(knotvector_u, tolerance)
#     n = len(ms)
#     for idx, (u, count) in enumerate(ms):
#         if idx == 0 or idx == n-1:
#             max_allowed = degree+1
#         else:
#             max_allowed = degree
#         print(f"U={u}: max.allowed {max_allowed}, actual {count}")
#         diff = count - max_allowed
# 
#         if diff > 0:
#             print(f"Remove U={u} x {diff}")
#             result = result.remove_knot(SvNurbsSurface.U, u, diff)
# 
#     degree = surface.get_degree_v()
#     ms = sv_knotvector.to_multiplicity(knotvector_v, tolerance)
#     n = len(ms)
#     for idx, (v, count) in enumerate(ms):
#         if idx == 0 or idx == n-1:
#             max_allowed = degree+1
#         else:
#             max_allowed = degree
#         print(f"V={v}: max.allowed {max_allowed}, actual {count}")
#         diff = count - max_allowed
# 
#         if diff > 0:
#             print(f"Remove V={v} x {diff}")
#             result = result.remove_knot(SvNurbsSurface.V, v, diff)

    return result

def unify_nurbs_surfaces(surfaces, knots_method = 'UNIFY', knotvector_accuracy=6):
    # Unify surface degrees

    degrees_u = [surface.get_degree_u() for surface in surfaces]
    degrees_v = [surface.get_degree_v() for surface in surfaces]

    degree_u = max(degrees_u)
    degree_v = max(degrees_v)
    #print(f"Elevate everything to {degree_u}x{degree_v}")

    surfaces = [surface.elevate_degree(SvNurbsSurface.U, target=degree_u) for surface in surfaces]
    surfaces = [surface.elevate_degree(SvNurbsSurface.V, target=degree_v) for surface in surfaces]

    # Unify surface knotvectors

    knotvector_tolerance = 10**(-knotvector_accuracy)

    if knots_method == 'UNIFY':

        surfaces = [round_knotvectors(s, knotvector_accuracy) for s in surfaces]
        for i, surface in enumerate(surfaces):
            #print(f"S #{i} KV_U: {surface.get_knotvector_u()}")
            #print(f"S #{i} KV_V: {surface.get_knotvector_v()}")
            kv_err = sv_knotvector.check_multiplicity(surface.get_degree_u(), surface.get_knotvector_u(), tolerance=knotvector_tolerance)
            if kv_err is not None:
                raise Exception(f"Surface #{i}: invalid U knotvector: {kv_err}")

            kv_err = sv_knotvector.check_multiplicity(surface.get_degree_v(), surface.get_knotvector_v(), tolerance=knotvector_tolerance)
            if kv_err is not None:
                raise Exception(f"Surface #{i}: invalid V knotvector: {kv_err}")

        dst_knots_u = defaultdict(int)
        dst_knots_v = defaultdict(int)
        for surface in surfaces:
            m_u = sv_knotvector.to_multiplicity(surface.get_knotvector_u(), tolerance=knotvector_tolerance)
            m_v = sv_knotvector.to_multiplicity(surface.get_knotvector_v(), tolerance=knotvector_tolerance)

            for u, count in m_u:
                u = round(u, knotvector_accuracy)
                dst_knots_u[u] = max(dst_knots_u[u], count)

            for v, count in m_v:
                v = round(v, knotvector_accuracy)
                dst_knots_v[v] = max(dst_knots_v[v], count)

        result = []
        for surface in surfaces:
            diffs_u = []
            kv_u = np.round(surface.get_knotvector_u(), knotvector_accuracy)
            ms_u = dict(sv_knotvector.to_multiplicity(kv_u, tolerance=knotvector_tolerance))
            for dst_u, dst_multiplicity in dst_knots_u.items():
                src_multiplicity = ms_u.get(dst_u, 0)
                diff = dst_multiplicity - src_multiplicity
                diffs_u.append((dst_u, diff))

            for u, diff in diffs_u:
                if diff > 0:
                    #print(f"S: Insert U = {u} x {diff}")
                    surface = surface.insert_knot(SvNurbsSurface.U, u, diff)

            diffs_v = []
            kv_v = np.round(surface.get_knotvector_v(), knotvector_accuracy)
            ms_v = dict(sv_knotvector.to_multiplicity(kv_v, tolerance=knotvector_tolerance))
            for dst_v, dst_multiplicity in dst_knots_v.items():
                src_multiplicity = ms_v.get(dst_v, 0)
                diff = dst_multiplicity - src_multiplicity
                diffs_v.append((dst_v, diff))

            for v, diff in diffs_v:
                if diff > 0:
                    #print(f"S: Insert V = {v} x {diff}")
                    surface = surface.insert_knot(SvNurbsSurface.V, v, diff)

            result.append(surface)

        return result

    elif knots_method == 'AVERAGE':
        kvs = [len(surface.get_control_points()) for surface in surfaces]
        max_kv, min_kv = max(kvs), min(kvs)
        if max_kv != min_kv:
            raise Exception(f"U knotvector averaging is not applicable: Surfaces have different number of control points: {kvs}")

        kvs = [len(surface.get_control_points()[0]) for surface in surfaces]
        max_kv, min_kv = max(kvs), min(kvs)
        if max_kv != min_kv:
            raise Exception(f"V knotvector averaging is not applicable: Surfaces have different number of control points: {kvs}")


        knotvectors = np.array([surface.get_knotvector_u() for surface in surfaces])
        knotvector_u = knotvectors.mean(axis=0)

        knotvectors = np.array([surface.get_knotvector_v() for surface in surfaces])
        knotvector_u = knotvectors.mean(axis=0)

        result = []
        for surface in surfaces:
            surface = SvNurbsSurface.build(surface.get_nurbs_implementation(),
                    surface.get_degree_u(), surface.get_degree_v(),
                    knotvector_u, knotvector_v,
                    surface.get_control_points(),
                    surface.get_weights())
            result.append(surface)
        return result
    else:
        raise Exception('Unsupported knotvector unification method')

def remove_excessive_knots(surface, direction, tolerance=1e-6):
    if direction not in {'U', 'V', 'UV'}:
        raise Exception("Unsupported direction")

    if direction in {'U', 'UV'}:
        kv = surface.get_knotvector_u()
        for u in sv_knotvector.get_internal_knots(kv):
            surface = surface.remove_knot('U', u, count='ALL', tolerance=tolerance, if_possible=True)

    if direction in {'V', 'UV'}:
        kv = surface.get_knotvector_v()
        for v in sv_knotvector.get_internal_knots(kv):
            surface = surface.remove_knot('V', v, count='ALL', tolerance=tolerance, if_possible=True)

    return surface

def build_nurbs_sphere(center, radius):
    """
    Generate NURBS Surface representing a sphere.
    Sphere is defined here as a surface of revolution of
    half a circle.
    """
    vectorx = np.array([0.0, 0.0, radius])
    axis = np.array([0.0, 0.0, 1.0])
    normal = np.array([1.0, 0.0, 0.0])
    matrix = SvCircle.calc_matrix(normal, vectorx)
    matrix = Matrix.Translation(center) @ Matrix(matrix).to_4x4()
    arc = SvCircle(matrix=matrix, radius=radius, normal=normal, vectorx=vectorx)
    arc.u_bounds = (0.0, pi)
    return nurbs_revolution_surface(arc.to_nurbs(), center, axis, 0, 2*pi, global_origin=True)

def deform_nurbs_surface(src_surface, uknots, vknots, points):
    """
    Move some control points of a NURBS surface so that at
    given parameter values it passes through the given points.
    NB: rational surfaces are not supported yet.
    Parameters:
    * src_surface - SvNurbsSurface instance
    * uknots, vknots - np.array of shape (n,): U and V coordinates
        of points to be moved
    * points: np.array of shape (n,3): desired locations of surface points.
    Output:
    * SvNurbsSurface instance.
    """
    n = len(points)
    if len(uknots) != n or len(vknots) != n:
        raise Exception("Number of points, uknots and vknots must be equal")
    if src_surface.is_rational():
        raise UnsupportedSurfaceTypeException("Rational surfaces are not supported yet")

    ndim = 3
    knotvector_u = src_surface.get_knotvector_u()
    knotvector_v = src_surface.get_knotvector_v()
    basis_u = SvNurbsBasisFunctions(knotvector_u)
    basis_v = SvNurbsBasisFunctions(knotvector_v)
    degree_u = src_surface.get_degree_u()
    degree_v = src_surface.get_degree_v()
    ncpts_u, ncpts_v,_ = src_surface.get_control_points().shape
    nsu = np.array([basis_u.derivative(i, degree_u, 0)(uknots) for i in range(ncpts_u)])
    nsv = np.array([basis_v.derivative(i, degree_v, 0)(vknots) for i in range(ncpts_v)])
    
    nsu_t = np.transpose(nsu[np.newaxis], axes=(1,0,2)) # (ncpts_u, 1, n)
    nsv_t = nsv[np.newaxis] # (1, ncpts_v, n)
    ns_t = nsu_t * nsv_t # (ncpts_u, ncpts_v, n)
    denominator = ns_t.sum(axis=0).sum(axis=0)
    
    n_equations = n*ndim
    n_unknowns = ncpts_u * ncpts_v * ndim
    #print(f"Eqs: {n_equations}, Unk: {n_unknowns}")
    
    A = np.zeros((n_equations, n_unknowns))
    for u_idx in range(ncpts_u):
        for v_idx in range(ncpts_v):
            cpt_idx = ncpts_v * u_idx + v_idx
            for pt_idx in range(n):
                alpha = nsu[u_idx][pt_idx] * nsv[v_idx][pt_idx] / denominator[pt_idx]
                for dim_idx in range(ndim):
                    A[ndim*pt_idx + dim_idx, ndim*cpt_idx + dim_idx] = alpha
                    
    src_points = src_surface.evaluate_array(uknots, vknots)
    
    B = np.zeros((n_equations,1))
    for pt_idx, point in enumerate(points):
        B[pt_idx*3:pt_idx*3+3,0] = point[np.newaxis] - src_points[pt_idx][np.newaxis]

    if n_equations == n_unknowns:
        print("Well-determined", n_equations)
        A1 = np.linalg.inv(A)
        X = (A1 @ B).T
    elif n_equations < n_unknowns:
        print("Underdetermined", n_equations, n_unknowns)
        A1 = np.linalg.pinv(A)
        X = (A1 @ B).T
    else: # n_equations > n_unknowns
        print("Overdetermined", n_equations, n_unknowns)
        X, residues, rank, singval = np.linalg.lstsq(A, B)
    d_cpts = X.reshape((ncpts_u, ncpts_v, ndim))
    cpts = src_surface.get_control_points()
    
    surface = SvNurbsSurface.build(src_surface.get_nurbs_implementation(),
                degree_u, degree_v, knotvector_u, knotvector_v,
                cpts + d_cpts)
    return surface

def make_planar_surface(origin, u_axis, v_axis, degree_u, degree_v, ncpts_u, ncpts_v, size_u, size_v, implementation = SvNurbsSurface.NATIVE):
    """
    Generate squa planar NURBS surface.
    Parameters:
    * origin - point at the plane, which will have UV coordinates (0.5, 0.5). np.array of shape (3,).
    * u_axis, v_axis - vectors which will define directions of U and V parameter axes. np.array of shape (3,).
    * degree_u, degree_v - degrees of the surface along U and V parameters.
    * ncpts_u, ncpts_v - number of control points of the surface along U and V.
    * size_u, size_v - size of the surface along U and V, measured in lengths of u_axis and v_axis.
    Return value: an instance of SvNurbsSurface.
    """
    us = np.linspace(-size_u/2.0, size_u/2.0, num=ncpts_u)
    vs = np.linspace(-size_v/2.0, size_v/2.0, num=ncpts_v)
    cpts = [[origin + u*u_axis + v*v_axis for u in list(vs)] for v in list(us)]
    cpts = np.array(cpts)
    knotvector_u = sv_knotvector.generate(degree_u, ncpts_u)
    knotvector_v = sv_knotvector.generate(degree_v, ncpts_v)
    return SvNurbsSurface.build(implementation, 
                degree_u, degree_v,
                knotvector_u, knotvector_v,
                cpts)

def nurbs_surface_from_points(points, degree_u, degree_v, num_cpts_u, num_cpts_v, normal = None, implementation = SvNurbsSurface.NATIVE):
    """
    Generate a NURBS surface which passes either through or near the specified points.
    Parameters:
    * points - points to draw a surface through. np.array of shape (n,3).
    * degree_u, degree_v - degrees of the surface along U and V directions.
    * num_cpts_u, num_cpts_v - number of surface's control points along U and V.

    If total number of control points (num_cpts_u * num_cpts_v) is equal to the
    number of points specified, then the system will be well-determined, so
    this will do interpolation (although depending on location of points, it
    may fail).
    If total number of control points is less than the number of points
    specified, then the system will be overdetermined, so this will do
    approximation.
    If total number of control points is more than the number of points
    specified, then the system will be underdetermined, i.e. there are many
    surfaces passing through these points. In this case, the method will select
    the surface which has all it's control points as close to origin (0.0, 0.0,
    0.0) as possible.

    Return values:
    * SvNurbsSurface instance
    * uv_points - coordinates of points provided in UV space of the surface.
        np.array of shape (n, 3).
    """

    def calc_y_axis(plane, x_axis):
        normal = np.array(plane.normal)
        y_axis = np.cross(x_axis, normal)
        y_axis /= np.linalg.norm(y_axis)
        return y_axis

    if normal is None:
        linear = linear_approximation(points)
        origin = linear.center
        plane = linear.most_similar_plane()
    else:
        origin = center(points)
        plane = PlaneEquation.from_normal_and_point(normal, origin)

    start = points[0]
    start_projection = np.asarray(plane.projection_of_point(start))
    x_axis = start_projection - origin
    x_axis /= np.linalg.norm(x_axis)
    y_axis = calc_y_axis(plane, x_axis)
    distances = np.linalg.norm(points - origin, axis=1)
    max_distance = distances.max()
    
    planar_surface = make_planar_surface(origin,
                    x_axis, y_axis,
                    degree_u, degree_v,
                    num_cpts_u, num_cpts_v,
                    max_distance*2, max_distance*2,
                    implementation = implementation)
    
    us = np_dot(points, x_axis)
    vs = np_dot(points, y_axis)
    us_min, us_max = us.min(), us.max()
    vs_min, vs_max = vs.min(), vs.max()
    us = (us - us_min) / (us_max - us_min)
    vs = (vs - vs_min) / (vs_max - vs_min)
    surface = deform_nurbs_surface(planar_surface, us, vs, points)
    uv_points = np.array([[u,v, 0.0] for u,v in zip(us,vs)])
    return surface, uv_points

def nurbs_surface_from_curve(curve, samples, degree_u, degree_v, num_cpts_u, num_cpts_v, implementation = None):
    """
    Generate a NURBS surface which passes through the points of specified curve.
    See also documentation of nurbs_surface_from_points method.
    Parameters:
    * curve - SvNurbsCurve instance.
    * samples - the number of points on the curve, through which the surface should be build.
    * degree_u, degree_v - degrees of the surface along U and V directions.
    * num_cpts_u, num_cpts_v - number of surface's control points along U and V.
    Return value:
    * SvNurbsSurface instance
    * SvNurbsCurve instance: a curve in surface's UV space, which passes
        through projections of specified points to the surface.
    """

    if implementation is None:
        implementation = curve.get_nurbs_implementation()

    t_min, t_max = curve.get_u_bounds()
    ts = np.linspace(t_min, t_max, num=samples)
    points = curve.evaluate_array(ts)
    surface = nurbs_surface_from_points(points, degree_u, degere_v, num_cpts_u, num_cpts_v, implementation = implementation)
    trim_curve = SvNurbsMaths.interpolate_curve(implementation, curve.get_degree(), uv_points)
    return surface, trim_curve

Functions

def build_nurbs_sphere(center, radius)

Generate NURBS Surface representing a sphere. Sphere is defined here as a surface of revolution of half a circle.

Expand source code
def build_nurbs_sphere(center, radius):
    """
    Generate NURBS Surface representing a sphere.
    Sphere is defined here as a surface of revolution of
    half a circle.
    """
    vectorx = np.array([0.0, 0.0, radius])
    axis = np.array([0.0, 0.0, 1.0])
    normal = np.array([1.0, 0.0, 0.0])
    matrix = SvCircle.calc_matrix(normal, vectorx)
    matrix = Matrix.Translation(center) @ Matrix(matrix).to_4x4()
    arc = SvCircle(matrix=matrix, radius=radius, normal=normal, vectorx=vectorx)
    arc.u_bounds = (0.0, pi)
    return nurbs_revolution_surface(arc.to_nurbs(), center, axis, 0, 2*pi, global_origin=True)
def concatenate_surfaces(direction, surfaces)
Expand source code
def concatenate_surfaces(direction, surfaces):
    if all(hasattr(s, 'concatenate') for s in surfaces):
        try:
            result = surfaces[0]
            for s in surfaces[1:]:
                result = result.concatenate(direction, s)
            return result
        except UnsupportedSurfaceTypeException as e:
            sv_logger.debug("Can't concatenate surfaces natively: %s", e)
    
    return SvConcatSurface(direction, surfaces)
def deform_nurbs_surface(src_surface, uknots, vknots, points)

Move some control points of a NURBS surface so that at given parameter values it passes through the given points. NB: rational surfaces are not supported yet. Parameters: * src_surface - SvNurbsSurface instance * uknots, vknots - np.array of shape (n,): U and V coordinates of points to be moved * points: np.array of shape (n,3): desired locations of surface points. Output: * SvNurbsSurface instance.

Expand source code
def deform_nurbs_surface(src_surface, uknots, vknots, points):
    """
    Move some control points of a NURBS surface so that at
    given parameter values it passes through the given points.
    NB: rational surfaces are not supported yet.
    Parameters:
    * src_surface - SvNurbsSurface instance
    * uknots, vknots - np.array of shape (n,): U and V coordinates
        of points to be moved
    * points: np.array of shape (n,3): desired locations of surface points.
    Output:
    * SvNurbsSurface instance.
    """
    n = len(points)
    if len(uknots) != n or len(vknots) != n:
        raise Exception("Number of points, uknots and vknots must be equal")
    if src_surface.is_rational():
        raise UnsupportedSurfaceTypeException("Rational surfaces are not supported yet")

    ndim = 3
    knotvector_u = src_surface.get_knotvector_u()
    knotvector_v = src_surface.get_knotvector_v()
    basis_u = SvNurbsBasisFunctions(knotvector_u)
    basis_v = SvNurbsBasisFunctions(knotvector_v)
    degree_u = src_surface.get_degree_u()
    degree_v = src_surface.get_degree_v()
    ncpts_u, ncpts_v,_ = src_surface.get_control_points().shape
    nsu = np.array([basis_u.derivative(i, degree_u, 0)(uknots) for i in range(ncpts_u)])
    nsv = np.array([basis_v.derivative(i, degree_v, 0)(vknots) for i in range(ncpts_v)])
    
    nsu_t = np.transpose(nsu[np.newaxis], axes=(1,0,2)) # (ncpts_u, 1, n)
    nsv_t = nsv[np.newaxis] # (1, ncpts_v, n)
    ns_t = nsu_t * nsv_t # (ncpts_u, ncpts_v, n)
    denominator = ns_t.sum(axis=0).sum(axis=0)
    
    n_equations = n*ndim
    n_unknowns = ncpts_u * ncpts_v * ndim
    #print(f"Eqs: {n_equations}, Unk: {n_unknowns}")
    
    A = np.zeros((n_equations, n_unknowns))
    for u_idx in range(ncpts_u):
        for v_idx in range(ncpts_v):
            cpt_idx = ncpts_v * u_idx + v_idx
            for pt_idx in range(n):
                alpha = nsu[u_idx][pt_idx] * nsv[v_idx][pt_idx] / denominator[pt_idx]
                for dim_idx in range(ndim):
                    A[ndim*pt_idx + dim_idx, ndim*cpt_idx + dim_idx] = alpha
                    
    src_points = src_surface.evaluate_array(uknots, vknots)
    
    B = np.zeros((n_equations,1))
    for pt_idx, point in enumerate(points):
        B[pt_idx*3:pt_idx*3+3,0] = point[np.newaxis] - src_points[pt_idx][np.newaxis]

    if n_equations == n_unknowns:
        print("Well-determined", n_equations)
        A1 = np.linalg.inv(A)
        X = (A1 @ B).T
    elif n_equations < n_unknowns:
        print("Underdetermined", n_equations, n_unknowns)
        A1 = np.linalg.pinv(A)
        X = (A1 @ B).T
    else: # n_equations > n_unknowns
        print("Overdetermined", n_equations, n_unknowns)
        X, residues, rank, singval = np.linalg.lstsq(A, B)
    d_cpts = X.reshape((ncpts_u, ncpts_v, ndim))
    cpts = src_surface.get_control_points()
    
    surface = SvNurbsSurface.build(src_surface.get_nurbs_implementation(),
                degree_u, degree_v, knotvector_u, knotvector_v,
                cpts + d_cpts)
    return surface
def make_planar_surface(origin, u_axis, v_axis, degree_u, degree_v, ncpts_u, ncpts_v, size_u, size_v, implementation='NATIVE')

Generate squa planar NURBS surface. Parameters: * origin - point at the plane, which will have UV coordinates (0.5, 0.5). np.array of shape (3,). * u_axis, v_axis - vectors which will define directions of U and V parameter axes. np.array of shape (3,). * degree_u, degree_v - degrees of the surface along U and V parameters. * ncpts_u, ncpts_v - number of control points of the surface along U and V. * size_u, size_v - size of the surface along U and V, measured in lengths of u_axis and v_axis. Return value: an instance of SvNurbsSurface.

Expand source code
def make_planar_surface(origin, u_axis, v_axis, degree_u, degree_v, ncpts_u, ncpts_v, size_u, size_v, implementation = SvNurbsSurface.NATIVE):
    """
    Generate squa planar NURBS surface.
    Parameters:
    * origin - point at the plane, which will have UV coordinates (0.5, 0.5). np.array of shape (3,).
    * u_axis, v_axis - vectors which will define directions of U and V parameter axes. np.array of shape (3,).
    * degree_u, degree_v - degrees of the surface along U and V parameters.
    * ncpts_u, ncpts_v - number of control points of the surface along U and V.
    * size_u, size_v - size of the surface along U and V, measured in lengths of u_axis and v_axis.
    Return value: an instance of SvNurbsSurface.
    """
    us = np.linspace(-size_u/2.0, size_u/2.0, num=ncpts_u)
    vs = np.linspace(-size_v/2.0, size_v/2.0, num=ncpts_v)
    cpts = [[origin + u*u_axis + v*v_axis for u in list(vs)] for v in list(us)]
    cpts = np.array(cpts)
    knotvector_u = sv_knotvector.generate(degree_u, ncpts_u)
    knotvector_v = sv_knotvector.generate(degree_v, ncpts_v)
    return SvNurbsSurface.build(implementation, 
                degree_u, degree_v,
                knotvector_u, knotvector_v,
                cpts)
def nurbs_revolution_surface(curve, origin, axis, v_min=0, v_max=6.283185307179586, global_origin=True)
Expand source code
def nurbs_revolution_surface(curve, origin, axis, v_min=0, v_max=2*pi, global_origin=True):
    my_control_points = curve.get_control_points()
    my_weights = curve.get_weights()
    control_points = []
    weights = []

    any_circle = SvCircle(Matrix(), 1)
    any_circle.u_bounds = (v_min, v_max)
    any_circle = any_circle.to_nurbs()
    # all circles with given (v_min, v_max)
    # actually always have the same knotvector
    # and the same number of control points
    n = len(any_circle.get_control_points())
    circle_knotvector = any_circle.get_knotvector()
    circle_weights = any_circle.get_weights()

    # TODO: vectorize with numpy? Or better let it so for better readability?
    for my_control_point, my_weight in zip(my_control_points, my_weights):
        eq = CircleEquation3D.from_axis_point(origin, axis, my_control_point)
        if abs(eq.radius) < 1e-8:
            parallel_points = np.empty((n, 3))
            parallel_points[:] = np.array(eq.center) #[np.newaxis].T
        else:
            circle = SvCircle.from_equation(eq)
            circle.u_bounds = (v_min, v_max)
            nurbs_circle = circle.to_nurbs()
            parallel_points = nurbs_circle.get_control_points()
        parallel_weights = circle_weights * my_weight
        control_points.append(parallel_points)
        weights.append(parallel_weights)
    control_points = np.array(control_points)
    if global_origin:
        control_points = control_points - origin

    weights = np.array(weights)
    degree_u = curve.get_degree()
    degree_v = 2 # circle

    return SvNurbsSurface.build(curve.get_nurbs_implementation(),
            degree_u, degree_v,
            curve.get_knotvector(), circle_knotvector,
            control_points, weights)
def nurbs_surface_from_curve(curve, samples, degree_u, degree_v, num_cpts_u, num_cpts_v, implementation=None)

Generate a NURBS surface which passes through the points of specified curve. See also documentation of nurbs_surface_from_points method. Parameters: * curve - SvNurbsCurve instance. * samples - the number of points on the curve, through which the surface should be build. * degree_u, degree_v - degrees of the surface along U and V directions. * num_cpts_u, num_cpts_v - number of surface's control points along U and V. Return value: * SvNurbsSurface instance * SvNurbsCurve instance: a curve in surface's UV space, which passes through projections of specified points to the surface.

Expand source code
def nurbs_surface_from_curve(curve, samples, degree_u, degree_v, num_cpts_u, num_cpts_v, implementation = None):
    """
    Generate a NURBS surface which passes through the points of specified curve.
    See also documentation of nurbs_surface_from_points method.
    Parameters:
    * curve - SvNurbsCurve instance.
    * samples - the number of points on the curve, through which the surface should be build.
    * degree_u, degree_v - degrees of the surface along U and V directions.
    * num_cpts_u, num_cpts_v - number of surface's control points along U and V.
    Return value:
    * SvNurbsSurface instance
    * SvNurbsCurve instance: a curve in surface's UV space, which passes
        through projections of specified points to the surface.
    """

    if implementation is None:
        implementation = curve.get_nurbs_implementation()

    t_min, t_max = curve.get_u_bounds()
    ts = np.linspace(t_min, t_max, num=samples)
    points = curve.evaluate_array(ts)
    surface = nurbs_surface_from_points(points, degree_u, degere_v, num_cpts_u, num_cpts_v, implementation = implementation)
    trim_curve = SvNurbsMaths.interpolate_curve(implementation, curve.get_degree(), uv_points)
    return surface, trim_curve
def nurbs_surface_from_points(points, degree_u, degree_v, num_cpts_u, num_cpts_v, normal=None, implementation='NATIVE')

Generate a NURBS surface which passes either through or near the specified points. Parameters: * points - points to draw a surface through. np.array of shape (n,3). * degree_u, degree_v - degrees of the surface along U and V directions. * num_cpts_u, num_cpts_v - number of surface's control points along U and V.

If total number of control points (num_cpts_u * num_cpts_v) is equal to the number of points specified, then the system will be well-determined, so this will do interpolation (although depending on location of points, it may fail). If total number of control points is less than the number of points specified, then the system will be overdetermined, so this will do approximation. If total number of control points is more than the number of points specified, then the system will be underdetermined, i.e. there are many surfaces passing through these points. In this case, the method will select the surface which has all it's control points as close to origin (0.0, 0.0, 0.0) as possible.

Return values: * SvNurbsSurface instance * uv_points - coordinates of points provided in UV space of the surface. np.array of shape (n, 3).

Expand source code
def nurbs_surface_from_points(points, degree_u, degree_v, num_cpts_u, num_cpts_v, normal = None, implementation = SvNurbsSurface.NATIVE):
    """
    Generate a NURBS surface which passes either through or near the specified points.
    Parameters:
    * points - points to draw a surface through. np.array of shape (n,3).
    * degree_u, degree_v - degrees of the surface along U and V directions.
    * num_cpts_u, num_cpts_v - number of surface's control points along U and V.

    If total number of control points (num_cpts_u * num_cpts_v) is equal to the
    number of points specified, then the system will be well-determined, so
    this will do interpolation (although depending on location of points, it
    may fail).
    If total number of control points is less than the number of points
    specified, then the system will be overdetermined, so this will do
    approximation.
    If total number of control points is more than the number of points
    specified, then the system will be underdetermined, i.e. there are many
    surfaces passing through these points. In this case, the method will select
    the surface which has all it's control points as close to origin (0.0, 0.0,
    0.0) as possible.

    Return values:
    * SvNurbsSurface instance
    * uv_points - coordinates of points provided in UV space of the surface.
        np.array of shape (n, 3).
    """

    def calc_y_axis(plane, x_axis):
        normal = np.array(plane.normal)
        y_axis = np.cross(x_axis, normal)
        y_axis /= np.linalg.norm(y_axis)
        return y_axis

    if normal is None:
        linear = linear_approximation(points)
        origin = linear.center
        plane = linear.most_similar_plane()
    else:
        origin = center(points)
        plane = PlaneEquation.from_normal_and_point(normal, origin)

    start = points[0]
    start_projection = np.asarray(plane.projection_of_point(start))
    x_axis = start_projection - origin
    x_axis /= np.linalg.norm(x_axis)
    y_axis = calc_y_axis(plane, x_axis)
    distances = np.linalg.norm(points - origin, axis=1)
    max_distance = distances.max()
    
    planar_surface = make_planar_surface(origin,
                    x_axis, y_axis,
                    degree_u, degree_v,
                    num_cpts_u, num_cpts_v,
                    max_distance*2, max_distance*2,
                    implementation = implementation)
    
    us = np_dot(points, x_axis)
    vs = np_dot(points, y_axis)
    us_min, us_max = us.min(), us.max()
    vs_min, vs_max = vs.min(), vs.max()
    us = (us - us_min) / (us_max - us_min)
    vs = (vs - vs_min) / (vs_max - vs_min)
    surface = deform_nurbs_surface(planar_surface, us, vs, points)
    uv_points = np.array([[u,v, 0.0] for u,v in zip(us,vs)])
    return surface, uv_points
def remove_excessive_knots(surface, direction, tolerance=1e-06)
Expand source code
def remove_excessive_knots(surface, direction, tolerance=1e-6):
    if direction not in {'U', 'V', 'UV'}:
        raise Exception("Unsupported direction")

    if direction in {'U', 'UV'}:
        kv = surface.get_knotvector_u()
        for u in sv_knotvector.get_internal_knots(kv):
            surface = surface.remove_knot('U', u, count='ALL', tolerance=tolerance, if_possible=True)

    if direction in {'V', 'UV'}:
        kv = surface.get_knotvector_v()
        for v in sv_knotvector.get_internal_knots(kv):
            surface = surface.remove_knot('V', v, count='ALL', tolerance=tolerance, if_possible=True)

    return surface
def round_knotvectors(surface, accuracy)
Expand source code
def round_knotvectors(surface, accuracy):
    knotvector_u = surface.get_knotvector_u()
    knotvector_v = surface.get_knotvector_v()

    knotvector_u = np.round(knotvector_u, accuracy)
    knotvector_v = np.round(knotvector_v, accuracy)

    result = surface.copy(knotvector_u = knotvector_u, knotvector_v = knotvector_v)

    tolerance = 10**(-accuracy)

#     print(f"KV_U: {knotvector_u}")
#     print(f"KV_V: {knotvector_v}")
#     degree = surface.get_degree_u()
#     ms = sv_knotvector.to_multiplicity(knotvector_u, tolerance)
#     n = len(ms)
#     for idx, (u, count) in enumerate(ms):
#         if idx == 0 or idx == n-1:
#             max_allowed = degree+1
#         else:
#             max_allowed = degree
#         print(f"U={u}: max.allowed {max_allowed}, actual {count}")
#         diff = count - max_allowed
# 
#         if diff > 0:
#             print(f"Remove U={u} x {diff}")
#             result = result.remove_knot(SvNurbsSurface.U, u, diff)
# 
#     degree = surface.get_degree_v()
#     ms = sv_knotvector.to_multiplicity(knotvector_v, tolerance)
#     n = len(ms)
#     for idx, (v, count) in enumerate(ms):
#         if idx == 0 or idx == n-1:
#             max_allowed = degree+1
#         else:
#             max_allowed = degree
#         print(f"V={v}: max.allowed {max_allowed}, actual {count}")
#         diff = count - max_allowed
# 
#         if diff > 0:
#             print(f"Remove V={v} x {diff}")
#             result = result.remove_knot(SvNurbsSurface.V, v, diff)

    return result
def unify_nurbs_surfaces(surfaces, knots_method='UNIFY', knotvector_accuracy=6)
Expand source code
def unify_nurbs_surfaces(surfaces, knots_method = 'UNIFY', knotvector_accuracy=6):
    # Unify surface degrees

    degrees_u = [surface.get_degree_u() for surface in surfaces]
    degrees_v = [surface.get_degree_v() for surface in surfaces]

    degree_u = max(degrees_u)
    degree_v = max(degrees_v)
    #print(f"Elevate everything to {degree_u}x{degree_v}")

    surfaces = [surface.elevate_degree(SvNurbsSurface.U, target=degree_u) for surface in surfaces]
    surfaces = [surface.elevate_degree(SvNurbsSurface.V, target=degree_v) for surface in surfaces]

    # Unify surface knotvectors

    knotvector_tolerance = 10**(-knotvector_accuracy)

    if knots_method == 'UNIFY':

        surfaces = [round_knotvectors(s, knotvector_accuracy) for s in surfaces]
        for i, surface in enumerate(surfaces):
            #print(f"S #{i} KV_U: {surface.get_knotvector_u()}")
            #print(f"S #{i} KV_V: {surface.get_knotvector_v()}")
            kv_err = sv_knotvector.check_multiplicity(surface.get_degree_u(), surface.get_knotvector_u(), tolerance=knotvector_tolerance)
            if kv_err is not None:
                raise Exception(f"Surface #{i}: invalid U knotvector: {kv_err}")

            kv_err = sv_knotvector.check_multiplicity(surface.get_degree_v(), surface.get_knotvector_v(), tolerance=knotvector_tolerance)
            if kv_err is not None:
                raise Exception(f"Surface #{i}: invalid V knotvector: {kv_err}")

        dst_knots_u = defaultdict(int)
        dst_knots_v = defaultdict(int)
        for surface in surfaces:
            m_u = sv_knotvector.to_multiplicity(surface.get_knotvector_u(), tolerance=knotvector_tolerance)
            m_v = sv_knotvector.to_multiplicity(surface.get_knotvector_v(), tolerance=knotvector_tolerance)

            for u, count in m_u:
                u = round(u, knotvector_accuracy)
                dst_knots_u[u] = max(dst_knots_u[u], count)

            for v, count in m_v:
                v = round(v, knotvector_accuracy)
                dst_knots_v[v] = max(dst_knots_v[v], count)

        result = []
        for surface in surfaces:
            diffs_u = []
            kv_u = np.round(surface.get_knotvector_u(), knotvector_accuracy)
            ms_u = dict(sv_knotvector.to_multiplicity(kv_u, tolerance=knotvector_tolerance))
            for dst_u, dst_multiplicity in dst_knots_u.items():
                src_multiplicity = ms_u.get(dst_u, 0)
                diff = dst_multiplicity - src_multiplicity
                diffs_u.append((dst_u, diff))

            for u, diff in diffs_u:
                if diff > 0:
                    #print(f"S: Insert U = {u} x {diff}")
                    surface = surface.insert_knot(SvNurbsSurface.U, u, diff)

            diffs_v = []
            kv_v = np.round(surface.get_knotvector_v(), knotvector_accuracy)
            ms_v = dict(sv_knotvector.to_multiplicity(kv_v, tolerance=knotvector_tolerance))
            for dst_v, dst_multiplicity in dst_knots_v.items():
                src_multiplicity = ms_v.get(dst_v, 0)
                diff = dst_multiplicity - src_multiplicity
                diffs_v.append((dst_v, diff))

            for v, diff in diffs_v:
                if diff > 0:
                    #print(f"S: Insert V = {v} x {diff}")
                    surface = surface.insert_knot(SvNurbsSurface.V, v, diff)

            result.append(surface)

        return result

    elif knots_method == 'AVERAGE':
        kvs = [len(surface.get_control_points()) for surface in surfaces]
        max_kv, min_kv = max(kvs), min(kvs)
        if max_kv != min_kv:
            raise Exception(f"U knotvector averaging is not applicable: Surfaces have different number of control points: {kvs}")

        kvs = [len(surface.get_control_points()[0]) for surface in surfaces]
        max_kv, min_kv = max(kvs), min(kvs)
        if max_kv != min_kv:
            raise Exception(f"V knotvector averaging is not applicable: Surfaces have different number of control points: {kvs}")


        knotvectors = np.array([surface.get_knotvector_u() for surface in surfaces])
        knotvector_u = knotvectors.mean(axis=0)

        knotvectors = np.array([surface.get_knotvector_v() for surface in surfaces])
        knotvector_u = knotvectors.mean(axis=0)

        result = []
        for surface in surfaces:
            surface = SvNurbsSurface.build(surface.get_nurbs_implementation(),
                    surface.get_degree_u(), surface.get_degree_v(),
                    knotvector_u, knotvector_v,
                    surface.get_control_points(),
                    surface.get_weights())
            result.append(surface)
        return result
    else:
        raise Exception('Unsupported knotvector unification method')

Classes

class SvBlendSurface (surface1, surface2, curve1, curve2, bulge1, bulge2)
Expand source code
class SvBlendSurface(SvSurface):
    def __init__(self, surface1, surface2, curve1, curve2, bulge1, bulge2):
        self.surface1 = surface1
        self.surface2 = surface2
        self.curve1 = curve1
        self.curve2 = curve2
        self.bulge1 = bulge1
        self.bulge2 = bulge2
        self.u_bounds = (0.0, 1.0)
        self.v_bounds = (0.0, 1.0)

    def get_u_min(self):
        return self.u_bounds[0]

    def get_u_max(self):
        return self.u_bounds[1]

    def get_v_min(self):
        return self.v_bounds[0]

    def get_v_max(self):
        return self.v_bounds[1]

    def evaluate_array(self, us, vs):
        c1_min, c1_max = self.curve1.get_u_bounds()
        c2_min, c2_max = self.curve2.get_u_bounds()
        c1_us = (c1_max - c1_min) * us + c1_min
        c2_us = (c2_max - c2_min) * us + c2_min

        _, c1_points, _, _, c1_binormals = curve_frame_on_surface_array(self.surface1, self.curve1, c1_us)
        _, c2_points, _, _, c2_binormals = curve_frame_on_surface_array(self.surface2, self.curve2, c2_us)
        c1_binormals = self.bulge1 * c1_binormals
        c2_binormals = self.bulge2 * c2_binormals

        # See also sverchok.utils.curve.bezier.SvCubicBezierCurve.
        # Here we have re-implementation of the same algorithm
        # which works with arrays of control points
        p0s = c1_points                 # (n, 3)
        p1s = c1_points + c1_binormals
        p2s = c2_points + c2_binormals
        p3s = c2_points

        c0 = (1 - vs)**3      # (n,)
        c1 = 3*vs*(1-vs)**2
        c2 = 3*vs**2*(1-vs)
        c3 = vs**3

        # (n,1)
        c0, c1, c2, c3 = c0[:,np.newaxis], c1[:,np.newaxis], c2[:,np.newaxis], c3[:,np.newaxis]

        return c0*p0s + c1*p1s + c2*p2s + c3*p3s

Ancestors

Methods

def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    c1_min, c1_max = self.curve1.get_u_bounds()
    c2_min, c2_max = self.curve2.get_u_bounds()
    c1_us = (c1_max - c1_min) * us + c1_min
    c2_us = (c2_max - c2_min) * us + c2_min

    _, c1_points, _, _, c1_binormals = curve_frame_on_surface_array(self.surface1, self.curve1, c1_us)
    _, c2_points, _, _, c2_binormals = curve_frame_on_surface_array(self.surface2, self.curve2, c2_us)
    c1_binormals = self.bulge1 * c1_binormals
    c2_binormals = self.bulge2 * c2_binormals

    # See also sverchok.utils.curve.bezier.SvCubicBezierCurve.
    # Here we have re-implementation of the same algorithm
    # which works with arrays of control points
    p0s = c1_points                 # (n, 3)
    p1s = c1_points + c1_binormals
    p2s = c2_points + c2_binormals
    p3s = c2_points

    c0 = (1 - vs)**3      # (n,)
    c1 = 3*vs*(1-vs)**2
    c2 = 3*vs**2*(1-vs)
    c3 = vs**3

    # (n,1)
    c0, c1, c2, c3 = c0[:,np.newaxis], c1[:,np.newaxis], c2[:,np.newaxis], c3[:,np.newaxis]

    return c0*p0s + c1*p1s + c2*p2s + c3*p3s
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.u_bounds[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.u_bounds[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.v_bounds[1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.v_bounds[0]
class SvConcatSurface (direction, surfaces)
Expand source code
class SvConcatSurface(SvSurface):
    def __init__(self, direction, surfaces):
        self.direction = direction
        self.surfaces = self._unify(surfaces)

        p1 = self._get_p_min(surfaces[0])
        boundaries = [p1]
        boundaries.extend([self._get_p_delta(s) for s in surfaces])
        self.boundaries = np.array(boundaries).cumsum()

    def _unify(self, surfaces):
        if self.direction == 'U':
            min_vs = [s.get_v_min() for s in surfaces]
            max_vs = [s.get_v_max() for s in surfaces]

            if min(min_vs) != max(min_vs) or min(max_vs) != max(max_vs):
                surfaces = [SvReparametrizedSurface.build(s, s.get_u_min(), s.get_u_max(), 0.0, 1.0) for s in surfaces]
            return surfaces
        
        else:
            min_us = [s.get_u_min() for s in surfaces]
            max_us = [s.get_u_max() for s in surfaces]

            if min(min_us) != max(min_us) or min(max_us) != max(max_us):
                surfaces = [SvReparametrizedSurface.build(s, 0.0, 1.0, s.get_v_min(), s.get_v_max()) for s in surfaces]
            return surfaces

    def _get_p_max(self, surface):
        if self.direction == 'U':
            return surface.get_u_max()
        else:
            return surface.get_v_max()

    def _get_p_delta(self, surface):
        if self.direction == 'U':
            return surface.get_u_max() - surface.get_u_min()
        else:
            return surface.get_v_max() - surface.get_v_min()

    def _get_p_min(self, surface):
        if self.direction == 'U':
            return surface.get_u_min()
        else:
            return surface.get_v_min()

    def get_u_min(self):
        if self.direction == 'U':
            return self.boundaries[0]
        else:
            return self.surfaces[0].get_u_min()

    def get_u_max(self):
        if self.direction == 'U':
            return self.boundaries[-1]
        else:
            return self.surfaces[0].get_u_max()

    def get_v_min(self):
        if self.direction == 'U':
            return self.surfaces[0].get_v_min()
        else:
            return self.boundaries[0]

    def get_v_max(self):
        if self.direction == 'U':
            return self.surfaces[0].get_v_max()
        else:
            return self.boundaries[-1]

    def evaluate(self, u, v):
        if self.direction == 'U':
            u_idx = self.boundaries.searchsorted(u, side='right')-1
            if u_idx >= len(self.surfaces):
                v_idx = len(self.surfaces)-1
                du = self._get_p_delta(self.surfaces[-1])
            else:
                du = u - self.boundaries[u_idx]
            subsurf = self.surfaces[u_idx]
            return subsurf.evaluate(subsurf.get_u_min()+du, v)
        else:
            v_idx = self.boundaries.searchsorted(v, side='right')-1
            if v_idx >= len(self.surfaces):
                v_idx = len(self.surfaces)-1
                dv = self._get_p_delta(self.surfaces[-1])
            else:
                dv = v - self.boundaries[v_idx]
            subsurf = self.surfaces[v_idx]
            return subsurf.evaluate(u, subsurf.get_v_min()+dv)

    def evaluate_array(self, us, vs):
        # TODO: numpy implementation
        return np.vectorize(self.evaluate, signature='(),()->(3)')(us, vs)

Ancestors

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    if self.direction == 'U':
        u_idx = self.boundaries.searchsorted(u, side='right')-1
        if u_idx >= len(self.surfaces):
            v_idx = len(self.surfaces)-1
            du = self._get_p_delta(self.surfaces[-1])
        else:
            du = u - self.boundaries[u_idx]
        subsurf = self.surfaces[u_idx]
        return subsurf.evaluate(subsurf.get_u_min()+du, v)
    else:
        v_idx = self.boundaries.searchsorted(v, side='right')-1
        if v_idx >= len(self.surfaces):
            v_idx = len(self.surfaces)-1
            dv = self._get_p_delta(self.surfaces[-1])
        else:
            dv = v - self.boundaries[v_idx]
        subsurf = self.surfaces[v_idx]
        return subsurf.evaluate(u, subsurf.get_v_min()+dv)
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    # TODO: numpy implementation
    return np.vectorize(self.evaluate, signature='(),()->(3)')(us, vs)
def get_u_max(self)
Expand source code
def get_u_max(self):
    if self.direction == 'U':
        return self.boundaries[-1]
    else:
        return self.surfaces[0].get_u_max()
def get_u_min(self)
Expand source code
def get_u_min(self):
    if self.direction == 'U':
        return self.boundaries[0]
    else:
        return self.surfaces[0].get_u_min()
def get_v_max(self)
Expand source code
def get_v_max(self):
    if self.direction == 'U':
        return self.surfaces[0].get_v_max()
    else:
        return self.boundaries[-1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    if self.direction == 'U':
        return self.surfaces[0].get_v_min()
    else:
        return self.boundaries[0]
class SvConstPipeSurface (curve, radius, algorithm='FRENET', resolution=50)
Expand source code
class SvConstPipeSurface(SvSurface):
    __description__ = "Pipe"

    def __init__(self, curve, radius, algorithm = FRENET, resolution=50):
        self.curve = curve
        self.radius = radius
        self.circle = SvCircle(Matrix(), radius)
        self.algorithm = algorithm
        self.normal_delta = 0.001
        self.u_bounds = self.circle.get_u_bounds()
        if algorithm in {FRENET, ZERO, TRACK_NORMAL}:
            self.calculator = DifferentialRotationCalculator(curve, algorithm, resolution)

    def get_u_min(self):
        return self.u_bounds[0]

    def get_u_max(self):
        return self.u_bounds[1]

    def get_v_min(self):
        return self.curve.get_u_bounds()[0]

    def get_v_max(self):
        return self.curve.get_u_bounds()[1]

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def get_matrix(self, tangent):
        return MathutilsRotationCalculator.get_matrix(tangent, scale=1.0,
                axis=2,
                algorithm = self.algorithm,
                scale_all=False)

    def get_matrices(self, ts):
        if self.algorithm in {FRENET, ZERO, TRACK_NORMAL}:
            return self.calculator.get_matrices(ts)
        elif self.algorithm in {HOUSEHOLDER, TRACK, DIFF}:
            tangents = self.curve.tangent_array(ts)
            matrices = np.vectorize(lambda t : self.get_matrix(t), signature='(3)->(3,3)')(tangents)
            return matrices
        else:
            raise Exception("Unsupported algorithm")

    def evaluate_array(self, us, vs):
        profile_vectors = self.circle.evaluate_array(us)
        u_min, u_max = self.circle.get_u_bounds()
        v_min, v_max = self.curve.get_u_bounds()
        profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
        extrusion_start = self.curve.evaluate(v_min)
        extrusion_points = self.curve.evaluate_array(vs)
        extrusion_vectors = extrusion_points - extrusion_start

        matrices = self.get_matrices(vs)

        profile_vectors = (matrices @ profile_vectors)[:,:,0]
        result = extrusion_vectors + profile_vectors
        result = result + extrusion_start
        return result

Ancestors

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    return self.evaluate_array(np.array([u]), np.array([v]))[0]
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    profile_vectors = self.circle.evaluate_array(us)
    u_min, u_max = self.circle.get_u_bounds()
    v_min, v_max = self.curve.get_u_bounds()
    profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
    extrusion_start = self.curve.evaluate(v_min)
    extrusion_points = self.curve.evaluate_array(vs)
    extrusion_vectors = extrusion_points - extrusion_start

    matrices = self.get_matrices(vs)

    profile_vectors = (matrices @ profile_vectors)[:,:,0]
    result = extrusion_vectors + profile_vectors
    result = result + extrusion_start
    return result
def get_matrices(self, ts)
Expand source code
def get_matrices(self, ts):
    if self.algorithm in {FRENET, ZERO, TRACK_NORMAL}:
        return self.calculator.get_matrices(ts)
    elif self.algorithm in {HOUSEHOLDER, TRACK, DIFF}:
        tangents = self.curve.tangent_array(ts)
        matrices = np.vectorize(lambda t : self.get_matrix(t), signature='(3)->(3,3)')(tangents)
        return matrices
    else:
        raise Exception("Unsupported algorithm")
def get_matrix(self, tangent)
Expand source code
def get_matrix(self, tangent):
    return MathutilsRotationCalculator.get_matrix(tangent, scale=1.0,
            axis=2,
            algorithm = self.algorithm,
            scale_all=False)
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.u_bounds[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.u_bounds[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.curve.get_u_bounds()[1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.curve.get_u_bounds()[0]
class SvCurveLerpSurface (curve1, curve2)
Expand source code
class SvCurveLerpSurface(SvSurface):
    __description__ = "Ruled"

    def __init__(self, curve1, curve2):
        self.curve1 = curve1
        self.curve2 = curve2
        self.normal_delta = 0.001
        self.v_bounds = (0.0, 1.0)
        self.u_bounds = (0.0, 1.0)
        self.c1_min, self.c1_max = curve1.get_u_bounds()
        self.c2_min, self.c2_max = curve2.get_u_bounds()

    @classmethod
    def build(cls, curve1, curve2, vmin=0.0, vmax=1.0):
        if hasattr(curve1, 'make_ruled_surface'):
            try:
                return curve1.make_ruled_surface(curve2, vmin, vmax)
            except TypeError as e:
                # make_ruled_surface method can raise TypeError in case
                # it can't work with given curve2.
                # In this case we must use generic method.
                sv_logger.debug("Can't make a native ruled surface: %s", e)
                pass

        # generic method
        surface = SvCurveLerpSurface(curve1, curve2)
        surface.v_bounds = (vmin, vmax)
        return surface

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def evaluate_array(self, us, vs):
        us1 = (self.c1_max - self.c1_min) * us + self.c1_min
        us2 = (self.c2_max - self.c2_min) * us + self.c2_min
        c1_points = self.curve1.evaluate_array(us1)
        c2_points = self.curve2.evaluate_array(us2)
        vs = vs[np.newaxis].T
        points = (1.0 - vs)*c1_points + vs*c2_points
        return points

    def get_u_min(self):
        return self.u_bounds[0]

    def get_u_max(self):
        return self.u_bounds[1]

    def get_v_min(self):
        return self.v_bounds[0]

    def get_v_max(self):
        return self.v_bounds[1]

    @property
    def u_size(self):
        return self.u_bounds[1] - self.u_bounds[0]

    @property
    def v_size(self):
        return self.v_bounds[1] - self.v_bounds[0]

Ancestors

Static methods

def build(curve1, curve2, vmin=0.0, vmax=1.0)
Expand source code
@classmethod
def build(cls, curve1, curve2, vmin=0.0, vmax=1.0):
    if hasattr(curve1, 'make_ruled_surface'):
        try:
            return curve1.make_ruled_surface(curve2, vmin, vmax)
        except TypeError as e:
            # make_ruled_surface method can raise TypeError in case
            # it can't work with given curve2.
            # In this case we must use generic method.
            sv_logger.debug("Can't make a native ruled surface: %s", e)
            pass

    # generic method
    surface = SvCurveLerpSurface(curve1, curve2)
    surface.v_bounds = (vmin, vmax)
    return surface

Instance variables

var u_size
Expand source code
@property
def u_size(self):
    return self.u_bounds[1] - self.u_bounds[0]
var v_size
Expand source code
@property
def v_size(self):
    return self.v_bounds[1] - self.v_bounds[0]

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    return self.evaluate_array(np.array([u]), np.array([v]))[0]
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    us1 = (self.c1_max - self.c1_min) * us + self.c1_min
    us2 = (self.c2_max - self.c2_min) * us + self.c2_min
    c1_points = self.curve1.evaluate_array(us1)
    c2_points = self.curve2.evaluate_array(us2)
    vs = vs[np.newaxis].T
    points = (1.0 - vs)*c1_points + vs*c2_points
    return points
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.u_bounds[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.u_bounds[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.v_bounds[1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.v_bounds[0]
class SvDeformedByFieldSurface (surface, field, coefficient=1.0, by_normal=None)
Expand source code
class SvDeformedByFieldSurface(SvSurface):
    def __init__(self, surface, field, coefficient=1.0, by_normal=None):
        self.surface = surface
        self.field = field
        self.coefficient = coefficient
        self.by_normal = by_normal
        self.normal_delta = 0.001
        self.__description__ = "{}({})".format(field, surface)

    def get_coord_mode(self):
        return self.surface.get_coord_mode()

    def get_u_min(self):
        return self.surface.get_u_min()

    def get_u_max(self):
        return self.surface.get_u_max()

    def get_v_min(self):
        return self.surface.get_v_min()

    def get_v_max(self):
        return self.surface.get_v_max()

    @property
    def u_size(self):
        return self.surface.u_size

    @property
    def v_size(self):
        return self.surface.v_size

    @property
    def has_input_matrix(self):
        return self.surface.has_input_matrix

    def get_input_matrix(self):
        return self.surface.get_input_matrix()

    def evaluate(self, u, v):
        p = self.surface.evaluate(u, v)
        vec = self.field.evaluate(p[0], p[1], p[2])
        if self.by_normal == PROJECT:
            normal = self.surface.normal(u, v)
            vec = np.dot(vec, normal) * normal / np.dot(normal, normal)
        elif self.by_normal == COPROJECT:
            normal = self.surface.normal(u, v)
            projection = np.dot(vec, normal) * normal / np.dot(normal, normal)
            vec = vec - projection
        return p + self.coefficient * vec

    def evaluate_array(self, us, vs):
        ps = self.surface.evaluate_array(us, vs)
        xs, ys, zs = ps[:,0], ps[:,1], ps[:,2]
        vxs, vys, vzs = self.field.evaluate_grid(xs, ys, zs)
        vecs = np.stack((vxs, vys, vzs)).T
        if self.by_normal == PROJECT:
            normals = self.surface.normal_array(us, vs)
            vecs = _dot(vecs, normals) * normals / _dot(normals, normals)
        elif self.by_normal == COPROJECT:
            normals = self.surface.normal_array(us, vs)
            projections = _dot(vecs, normals) * normals / _dot(normals, normals)
            vecs = vecs - projections
        return ps + self.coefficient * vecs

    def normal(self, u, v):
        h = self.normal_delta
        p = self.evaluate(u, v)
        p_u = self.evaluate(u+h, v)
        p_v = self.evaluate(u, v+h)
        du = (p_u - p) / h
        dv = (p_v - p) / h
        normal = np.cross(du, dv)
        n = np.linalg.norm(normal)
        normal = normal / n
        return normal

    def normal_array(self, us, vs):
        surf_vertices = self.evaluate_array(us, vs)
        u_plus = self.evaluate_array(us + self.normal_delta, vs)
        v_plus = self.evaluate_array(us, vs + self.normal_delta)
        du = u_plus - surf_vertices
        dv = v_plus - surf_vertices
        #self.info("Du: %s", du)
        #self.info("Dv: %s", dv)
        normal = np.cross(du, dv)
        norm = np.linalg.norm(normal, axis=1)[np.newaxis].T
        #if norm != 0:
        normal = normal / norm
        #self.info("Normals: %s", normal)
        return normal

Ancestors

Instance variables

var has_input_matrix
Expand source code
@property
def has_input_matrix(self):
    return self.surface.has_input_matrix
var u_size
Expand source code
@property
def u_size(self):
    return self.surface.u_size
var v_size
Expand source code
@property
def v_size(self):
    return self.surface.v_size

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    p = self.surface.evaluate(u, v)
    vec = self.field.evaluate(p[0], p[1], p[2])
    if self.by_normal == PROJECT:
        normal = self.surface.normal(u, v)
        vec = np.dot(vec, normal) * normal / np.dot(normal, normal)
    elif self.by_normal == COPROJECT:
        normal = self.surface.normal(u, v)
        projection = np.dot(vec, normal) * normal / np.dot(normal, normal)
        vec = vec - projection
    return p + self.coefficient * vec
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    ps = self.surface.evaluate_array(us, vs)
    xs, ys, zs = ps[:,0], ps[:,1], ps[:,2]
    vxs, vys, vzs = self.field.evaluate_grid(xs, ys, zs)
    vecs = np.stack((vxs, vys, vzs)).T
    if self.by_normal == PROJECT:
        normals = self.surface.normal_array(us, vs)
        vecs = _dot(vecs, normals) * normals / _dot(normals, normals)
    elif self.by_normal == COPROJECT:
        normals = self.surface.normal_array(us, vs)
        projections = _dot(vecs, normals) * normals / _dot(normals, normals)
        vecs = vecs - projections
    return ps + self.coefficient * vecs
def get_coord_mode(self)
Expand source code
def get_coord_mode(self):
    return self.surface.get_coord_mode()
def get_input_matrix(self)
Expand source code
def get_input_matrix(self):
    return self.surface.get_input_matrix()
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.surface.get_u_max()
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.surface.get_u_min()
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.surface.get_v_max()
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.surface.get_v_min()
def normal(self, u, v)
Expand source code
def normal(self, u, v):
    h = self.normal_delta
    p = self.evaluate(u, v)
    p_u = self.evaluate(u+h, v)
    p_v = self.evaluate(u, v+h)
    du = (p_u - p) / h
    dv = (p_v - p) / h
    normal = np.cross(du, dv)
    n = np.linalg.norm(normal)
    normal = normal / n
    return normal
def normal_array(self, us, vs)
Expand source code
def normal_array(self, us, vs):
    surf_vertices = self.evaluate_array(us, vs)
    u_plus = self.evaluate_array(us + self.normal_delta, vs)
    v_plus = self.evaluate_array(us, vs + self.normal_delta)
    du = u_plus - surf_vertices
    dv = v_plus - surf_vertices
    #self.info("Du: %s", du)
    #self.info("Dv: %s", dv)
    normal = np.cross(du, dv)
    norm = np.linalg.norm(normal, axis=1)[np.newaxis].T
    #if norm != 0:
    normal = normal / norm
    #self.info("Normals: %s", normal)
    return normal
class SvExtrudeCurveCurveSurface (u_curve, v_curve, origin='profile')
Expand source code
class SvExtrudeCurveCurveSurface(SvSurface):
    def __init__(self, u_curve, v_curve, origin = PROFILE):
        self.u_curve = u_curve
        self.v_curve = v_curve
        self.origin = origin
        self.normal_delta = 0.001
        self.__description__ = "Extrusion of {}".format(u_curve)

    def evaluate(self, u, v):
        u_point = self.u_curve.evaluate(u)
        u_min, u_max = self.u_curve.get_u_bounds()
        v_min, v_max = self.v_curve.get_u_bounds()
        v0 = self.v_curve.evaluate(v_min)
        v_point = self.v_curve.evaluate(v)
        if self.origin == EXTRUSION:
            result = u_point + v_point
        else:
            result = u_point + (v_point - v0)
        return result

    def evaluate_array(self, us, vs):
        u_points = self.u_curve.evaluate_array(us)
        u_min, u_max = self.u_curve.get_u_bounds()
        v_min, v_max = self.v_curve.get_u_bounds()
        v0 = self.v_curve.evaluate(v_min)
        v_points = self.v_curve.evaluate_array(vs)
        if self.origin == EXTRUSION:
            result = u_points + v_points
        else:
            result = u_points + (v_points - v0)
        return result

    def get_u_min(self):
        return self.u_curve.get_u_bounds()[0]

    def get_u_max(self):
        return self.u_curve.get_u_bounds()[1]

    def get_v_min(self):
        return self.v_curve.get_u_bounds()[0]

    def get_v_max(self):
        return self.v_curve.get_u_bounds()[1]

    @property
    def u_size(self):
        m,M = self.u_curve.get_u_bounds()
        return M - m

    @property
    def v_size(self):
        m,M = self.v_curve.get_u_bounds()
        return M - m

Ancestors

Instance variables

var u_size
Expand source code
@property
def u_size(self):
    m,M = self.u_curve.get_u_bounds()
    return M - m
var v_size
Expand source code
@property
def v_size(self):
    m,M = self.v_curve.get_u_bounds()
    return M - m

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    u_point = self.u_curve.evaluate(u)
    u_min, u_max = self.u_curve.get_u_bounds()
    v_min, v_max = self.v_curve.get_u_bounds()
    v0 = self.v_curve.evaluate(v_min)
    v_point = self.v_curve.evaluate(v)
    if self.origin == EXTRUSION:
        result = u_point + v_point
    else:
        result = u_point + (v_point - v0)
    return result
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    u_points = self.u_curve.evaluate_array(us)
    u_min, u_max = self.u_curve.get_u_bounds()
    v_min, v_max = self.v_curve.get_u_bounds()
    v0 = self.v_curve.evaluate(v_min)
    v_points = self.v_curve.evaluate_array(vs)
    if self.origin == EXTRUSION:
        result = u_points + v_points
    else:
        result = u_points + (v_points - v0)
    return result
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.u_curve.get_u_bounds()[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.u_curve.get_u_bounds()[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.v_curve.get_u_bounds()[1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.v_curve.get_u_bounds()[0]
class SvExtrudeCurveFrenetSurface (profile, extrusion, origin='profile')
Expand source code
class SvExtrudeCurveFrenetSurface(SvSurface):
    def __init__(self, profile, extrusion, origin = PROFILE):
        self.profile = profile
        self.extrusion = extrusion
        self.origin = origin
        self.normal_delta = 0.001
        self.__description__ = "Extrusion of {}".format(profile)

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def evaluate_array(self, us, vs):
        profile_points = self.profile.evaluate_array(us)
        u_min, u_max = self.profile.get_u_bounds()
        v_min, v_max = self.extrusion.get_u_bounds()
        profile_vectors = profile_points
        profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
        extrusion_start = self.extrusion.evaluate(v_min)
        extrusion_points = self.extrusion.evaluate_array(vs)
        extrusion_vectors = extrusion_points - extrusion_start
        frenet, _ , _ = self.extrusion.frame_array(vs)
        profile_vectors = (frenet @ profile_vectors)[:,:,0]
        result = extrusion_vectors + profile_vectors
        if self.origin == EXTRUSION:
            result = result + self.extrusion.evaluate(v_min)
        return result

    def get_u_min(self):
        return self.profile.get_u_bounds()[0]

    def get_u_max(self):
        return self.profile.get_u_bounds()[1]

    def get_v_min(self):
        return self.extrusion.get_u_bounds()[0]

    def get_v_max(self):
        return self.extrusion.get_u_bounds()[1]

    @property
    def u_size(self):
        m,M = self.profile.get_u_bounds()
        return M - m

    @property
    def v_size(self):
        m,M = self.extrusion.get_u_bounds()
        return M - m

Ancestors

Instance variables

var u_size
Expand source code
@property
def u_size(self):
    m,M = self.profile.get_u_bounds()
    return M - m
var v_size
Expand source code
@property
def v_size(self):
    m,M = self.extrusion.get_u_bounds()
    return M - m

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    return self.evaluate_array(np.array([u]), np.array([v]))[0]
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    profile_points = self.profile.evaluate_array(us)
    u_min, u_max = self.profile.get_u_bounds()
    v_min, v_max = self.extrusion.get_u_bounds()
    profile_vectors = profile_points
    profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
    extrusion_start = self.extrusion.evaluate(v_min)
    extrusion_points = self.extrusion.evaluate_array(vs)
    extrusion_vectors = extrusion_points - extrusion_start
    frenet, _ , _ = self.extrusion.frame_array(vs)
    profile_vectors = (frenet @ profile_vectors)[:,:,0]
    result = extrusion_vectors + profile_vectors
    if self.origin == EXTRUSION:
        result = result + self.extrusion.evaluate(v_min)
    return result
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.profile.get_u_bounds()[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.profile.get_u_bounds()[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.extrusion.get_u_bounds()[1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.extrusion.get_u_bounds()[0]
class SvExtrudeCurveMathutilsSurface (profile, extrusion, algorithm, orient_axis='Z', up_axis='X', origin='profile')
Expand source code
class SvExtrudeCurveMathutilsSurface(SvSurface):
    def __init__(self, profile, extrusion, algorithm, orient_axis='Z', up_axis='X', origin = PROFILE):
        self.profile = profile
        self.extrusion = extrusion
        self.algorithm = algorithm
        self.orient_axis = orient_axis
        self.up_axis = up_axis
        self.origin = origin
        self.normal_delta = 0.001
        self.__description__ = "Extrusion of {}".format(profile)

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def get_matrix(self, tangent):
        x = Vector((1.0, 0.0, 0.0))
        y = Vector((0.0, 1.0, 0.0))
        z = Vector((0.0, 0.0, 1.0))

        if self.orient_axis == 'X':
            ax1, ax2, ax3 = x, y, z
        elif self.orient_axis == 'Y':
            ax1, ax2, ax3 = y, x, z
        else:
            ax1, ax2, ax3 = z, x, y

        if self.algorithm == 'householder':
            rot = autorotate_householder(ax1, tangent).inverted()
        elif self.algorithm == 'track':
            rot = autorotate_track(self.orient_axis, tangent, self.up_axis)
        elif self.algorithm == 'diff':
            rot = autorotate_diff(tangent, ax1)
        else:
            raise Exception("Unsupported algorithm")

        return rot

    def get_matrices(self, vs):
        tangents = self.extrusion.tangent_array(vs)
        matrices = []
        for tangent in tangents:
            matrix = self.get_matrix(Vector(tangent)).to_3x3()
            matrices.append(matrix)
        return np.array(matrices)

    def evaluate_array(self, us, vs):
        profile_points = self.profile.evaluate_array(us)
        u_min, u_max = self.profile.get_u_bounds()
        v_min, v_max = self.extrusion.get_u_bounds()
        profile_vectors = profile_points
        profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
        extrusion_start = self.extrusion.evaluate(v_min)
        extrusion_points = self.extrusion.evaluate_array(vs)
        extrusion_vectors = extrusion_points - extrusion_start

        matrices = self.get_matrices(vs)

        profile_vectors = (matrices @ profile_vectors)[:,:,0]
        result = extrusion_vectors + profile_vectors
        if self.origin == EXTRUSION:
            result = result + self.extrusion.evaluate(v_min)
        return result

    def get_u_min(self):
        return self.profile.get_u_bounds()[0]

    def get_u_max(self):
        return self.profile.get_u_bounds()[1]

    def get_v_min(self):
        return self.extrusion.get_u_bounds()[0]

    def get_v_max(self):
        return self.extrusion.get_u_bounds()[1]

Ancestors

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    return self.evaluate_array(np.array([u]), np.array([v]))[0]
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    profile_points = self.profile.evaluate_array(us)
    u_min, u_max = self.profile.get_u_bounds()
    v_min, v_max = self.extrusion.get_u_bounds()
    profile_vectors = profile_points
    profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
    extrusion_start = self.extrusion.evaluate(v_min)
    extrusion_points = self.extrusion.evaluate_array(vs)
    extrusion_vectors = extrusion_points - extrusion_start

    matrices = self.get_matrices(vs)

    profile_vectors = (matrices @ profile_vectors)[:,:,0]
    result = extrusion_vectors + profile_vectors
    if self.origin == EXTRUSION:
        result = result + self.extrusion.evaluate(v_min)
    return result
def get_matrices(self, vs)
Expand source code
def get_matrices(self, vs):
    tangents = self.extrusion.tangent_array(vs)
    matrices = []
    for tangent in tangents:
        matrix = self.get_matrix(Vector(tangent)).to_3x3()
        matrices.append(matrix)
    return np.array(matrices)
def get_matrix(self, tangent)
Expand source code
def get_matrix(self, tangent):
    x = Vector((1.0, 0.0, 0.0))
    y = Vector((0.0, 1.0, 0.0))
    z = Vector((0.0, 0.0, 1.0))

    if self.orient_axis == 'X':
        ax1, ax2, ax3 = x, y, z
    elif self.orient_axis == 'Y':
        ax1, ax2, ax3 = y, x, z
    else:
        ax1, ax2, ax3 = z, x, y

    if self.algorithm == 'householder':
        rot = autorotate_householder(ax1, tangent).inverted()
    elif self.algorithm == 'track':
        rot = autorotate_track(self.orient_axis, tangent, self.up_axis)
    elif self.algorithm == 'diff':
        rot = autorotate_diff(tangent, ax1)
    else:
        raise Exception("Unsupported algorithm")

    return rot
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.profile.get_u_bounds()[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.profile.get_u_bounds()[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.extrusion.get_u_bounds()[1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.extrusion.get_u_bounds()[0]
class SvExtrudeCurveNormalDirSurface (profile, extrusion, plane_normal, origin='profile')
Expand source code
class SvExtrudeCurveNormalDirSurface(SvSurface):
    def __init__(self, profile, extrusion, plane_normal, origin = PROFILE):
        self.profile = profile
        self.extrusion = extrusion
        self.origin = origin
        self.plane_normal = np.array(plane_normal)
        self.normal_delta = 0.001
        self.__description__ = "Extrusion of {}".format(profile)

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def evaluate_array(self, us, vs):
        profile_points = self.profile.evaluate_array(us)
        u_min, u_max = self.profile.get_u_bounds()
        v_min, v_max = self.extrusion.get_u_bounds()
        profile_vectors = profile_points
        profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
        extrusion_start = self.extrusion.evaluate(v_min)
        extrusion_points = self.extrusion.evaluate_array(vs)
        extrusion_vectors = extrusion_points - extrusion_start
        matrices, _ , _ = self.extrusion.frame_by_plane_array(vs, self.plane_normal)
        profile_vectors = (matrices @ profile_vectors)[:,:,0]
        result = extrusion_vectors + profile_vectors
        if self.origin == EXTRUSION:
            result = result + self.extrusion.evaluate(v_min)
        return result

    def get_u_min(self):
        return self.profile.get_u_bounds()[0]

    def get_u_max(self):
        return self.profile.get_u_bounds()[1]

    def get_v_min(self):
        return self.extrusion.get_u_bounds()[0]

    def get_v_max(self):
        return self.extrusion.get_u_bounds()[1]

    @property
    def u_size(self):
        m,M = self.profile.get_u_bounds()
        return M - m

    @property
    def v_size(self):
        m,M = self.extrusion.get_u_bounds()
        return M - m

Ancestors

Instance variables

var u_size
Expand source code
@property
def u_size(self):
    m,M = self.profile.get_u_bounds()
    return M - m
var v_size
Expand source code
@property
def v_size(self):
    m,M = self.extrusion.get_u_bounds()
    return M - m

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    return self.evaluate_array(np.array([u]), np.array([v]))[0]
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    profile_points = self.profile.evaluate_array(us)
    u_min, u_max = self.profile.get_u_bounds()
    v_min, v_max = self.extrusion.get_u_bounds()
    profile_vectors = profile_points
    profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
    extrusion_start = self.extrusion.evaluate(v_min)
    extrusion_points = self.extrusion.evaluate_array(vs)
    extrusion_vectors = extrusion_points - extrusion_start
    matrices, _ , _ = self.extrusion.frame_by_plane_array(vs, self.plane_normal)
    profile_vectors = (matrices @ profile_vectors)[:,:,0]
    result = extrusion_vectors + profile_vectors
    if self.origin == EXTRUSION:
        result = result + self.extrusion.evaluate(v_min)
    return result
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.profile.get_u_bounds()[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.profile.get_u_bounds()[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.extrusion.get_u_bounds()[1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.extrusion.get_u_bounds()[0]
class SvExtrudeCurvePointSurface (curve, point)
Expand source code
class SvExtrudeCurvePointSurface(SvSurface):
    def __init__(self, curve, point):
        self.curve = curve
        self.point = point
        self.normal_delta = 0.001
        self.__description__ = "Extrusion of {}".format(curve)

    @staticmethod
    def build(curve, point):
        if hasattr(curve, 'extrude_to_point'):
            try:
                return curve.extrude_to_point(point)
            except UnsupportedCurveTypeException:
                pass
        return SvExtrudeCurvePointSurface(curve, point)

    def evaluate(self, u, v):
        point_on_curve = self.curve.evaluate(u)
        return (1.0 - v) * point_on_curve + v * self.point

    def evaluate_array(self, us, vs):
        points_on_curve = self.curve.evaluate_array(us)
        vs = vs[np.newaxis].T
        return (1.0 - vs) * points_on_curve + vs * self.point

    def get_u_min(self):
        return self.curve.get_u_bounds()[0]

    def get_u_max(self):
        return self.curve.get_u_bounds()[1]

    def get_v_min(self):
        return 0.0

    def get_v_max(self):
        return 1.0

    @property
    def u_size(self):
        m,M = self.curve.get_u_bounds()
        return M - m

    @property
    def v_size(self):
        return 1.0

Ancestors

Static methods

def build(curve, point)
Expand source code
@staticmethod
def build(curve, point):
    if hasattr(curve, 'extrude_to_point'):
        try:
            return curve.extrude_to_point(point)
        except UnsupportedCurveTypeException:
            pass
    return SvExtrudeCurvePointSurface(curve, point)

Instance variables

var u_size
Expand source code
@property
def u_size(self):
    m,M = self.curve.get_u_bounds()
    return M - m
var v_size
Expand source code
@property
def v_size(self):
    return 1.0

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    point_on_curve = self.curve.evaluate(u)
    return (1.0 - v) * point_on_curve + v * self.point
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    points_on_curve = self.curve.evaluate_array(us)
    vs = vs[np.newaxis].T
    return (1.0 - vs) * points_on_curve + vs * self.point
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.curve.get_u_bounds()[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.curve.get_u_bounds()[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return 1.0
def get_v_min(self)
Expand source code
def get_v_min(self):
    return 0.0
class SvExtrudeCurveTrackNormalSurface (profile, extrusion, resolution, origin='profile')
Expand source code
class SvExtrudeCurveTrackNormalSurface(SvSurface):
    def __init__(self, profile, extrusion, resolution, origin = PROFILE):
        self.profile = profile
        self.extrusion = extrusion
        self.origin = origin
        self.normal_delta = 0.001
        self.tracker = SvNormalTrack(extrusion, resolution)
        self.__description__ = "Extrusion of {}".format(profile)

    def get_u_min(self):
        return self.profile.get_u_bounds()[0]

    def get_u_max(self):
        return self.profile.get_u_bounds()[1]

    def get_v_min(self):
        return self.extrusion.get_u_bounds()[0]

    def get_v_max(self):
        return self.extrusion.get_u_bounds()[1]

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def evaluate_array(self, us, vs):
        profile_vectors = self.profile.evaluate_array(us)
        u_min, u_max = self.profile.get_u_bounds()
        v_min, v_max = self.extrusion.get_u_bounds()
        profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
        extrusion_start = self.extrusion.evaluate(v_min)
        extrusion_points = self.extrusion.evaluate_array(vs)
        extrusion_vectors = extrusion_points - extrusion_start

        matrices = self.tracker.evaluate_array(vs)
        profile_vectors = (matrices @ profile_vectors)[:,:,0]
        result = extrusion_vectors + profile_vectors
        if self.origin == EXTRUSION:
            result = result + extrusion_start
        return result

Ancestors

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    return self.evaluate_array(np.array([u]), np.array([v]))[0]
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    profile_vectors = self.profile.evaluate_array(us)
    u_min, u_max = self.profile.get_u_bounds()
    v_min, v_max = self.extrusion.get_u_bounds()
    profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
    extrusion_start = self.extrusion.evaluate(v_min)
    extrusion_points = self.extrusion.evaluate_array(vs)
    extrusion_vectors = extrusion_points - extrusion_start

    matrices = self.tracker.evaluate_array(vs)
    profile_vectors = (matrices @ profile_vectors)[:,:,0]
    result = extrusion_vectors + profile_vectors
    if self.origin == EXTRUSION:
        result = result + extrusion_start
    return result
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.profile.get_u_bounds()[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.profile.get_u_bounds()[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.extrusion.get_u_bounds()[1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.extrusion.get_u_bounds()[0]
class SvExtrudeCurveVectorSurface (curve, vector)
Expand source code
class SvExtrudeCurveVectorSurface(SvSurface):
    def __init__(self, curve, vector):
        self.curve = curve
        self.vector = np.array(vector)
        self.normal_delta = 0.001
        self.__description__ = "Extrusion of {}".format(curve)

    @classmethod
    def build(cls, curve, vector):
        if hasattr(curve, 'extrude_along_vector'):
            try:
                return curve.extrude_along_vector(vector)
            except UnsupportedCurveTypeException:
                pass
        return SvExtrudeCurveVectorSurface(curve, vector)

    def evaluate(self, u, v):
        point_on_curve = self.curve.evaluate(u)
        return point_on_curve + v * self.vector

    def evaluate_array(self, us, vs):
        points_on_curve = self.curve.evaluate_array(us)
        return points_on_curve + vs[np.newaxis].T * self.vector

    def get_u_min(self):
        return self.curve.get_u_bounds()[0]

    def get_u_max(self):
        return self.curve.get_u_bounds()[1]

    def get_v_min(self):
        return 0.0

    def get_v_max(self):
        return 1.0

    @property
    def u_size(self):
        m,M = self.curve.get_u_bounds()
        return M - m

    @property
    def v_size(self):
        return 1.0

Ancestors

Static methods

def build(curve, vector)
Expand source code
@classmethod
def build(cls, curve, vector):
    if hasattr(curve, 'extrude_along_vector'):
        try:
            return curve.extrude_along_vector(vector)
        except UnsupportedCurveTypeException:
            pass
    return SvExtrudeCurveVectorSurface(curve, vector)

Instance variables

var u_size
Expand source code
@property
def u_size(self):
    m,M = self.curve.get_u_bounds()
    return M - m
var v_size
Expand source code
@property
def v_size(self):
    return 1.0

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    point_on_curve = self.curve.evaluate(u)
    return point_on_curve + v * self.vector
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    points_on_curve = self.curve.evaluate_array(us)
    return points_on_curve + vs[np.newaxis].T * self.vector
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.curve.get_u_bounds()[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.curve.get_u_bounds()[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return 1.0
def get_v_min(self)
Expand source code
def get_v_min(self):
    return 0.0
class SvExtrudeCurveZeroTwistSurface (profile, extrusion, resolution, origin='profile')
Expand source code
class SvExtrudeCurveZeroTwistSurface(SvSurface):
    def __init__(self, profile, extrusion, resolution, origin = PROFILE):
        self.profile = profile
        self.extrusion = extrusion
        self.origin = origin
        self.normal_delta = 0.001
        self.extrusion.pre_calc_torsion_integral(resolution)
        self.__description__ = "Extrusion of {}".format(profile)

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]

    def evaluate_array(self, us, vs):
        profile_points = self.profile.evaluate_array(us)
        u_min, u_max = self.profile.get_u_bounds()
        v_min, v_max = self.extrusion.get_u_bounds()
        profile_vectors = profile_points
        profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
        extrusion_start = self.extrusion.evaluate(v_min)
        extrusion_points = self.extrusion.evaluate_array(vs)
        extrusion_vectors = extrusion_points - extrusion_start

        frenet, _ , _ = self.extrusion.frame_array(vs)

        angles = - self.extrusion.torsion_integral(vs)
        n = len(us)
        zeros = np.zeros((n,))
        ones = np.ones((n,))
        row1 = np.stack((np.cos(angles), np.sin(angles), zeros)).T # (n, 3)
        row2 = np.stack((-np.sin(angles), np.cos(angles), zeros)).T # (n, 3)
        row3 = np.stack((zeros, zeros, ones)).T # (n, 3)
        rotation_matrices = np.dstack((row1, row2, row3))

        profile_vectors = (frenet @ rotation_matrices @ profile_vectors)[:,:,0]
        result = extrusion_vectors + profile_vectors
        if self.origin == EXTRUSION:
            result = result + self.extrusion.evaluate(v_min)
        return result

    def get_u_min(self):
        return self.profile.get_u_bounds()[0]

    def get_u_max(self):
        return self.profile.get_u_bounds()[1]

    def get_v_min(self):
        return self.extrusion.get_u_bounds()[0]

    def get_v_max(self):
        return self.extrusion.get_u_bounds()[1]

Ancestors

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    return self.evaluate_array(np.array([u]), np.array([v]))[0]
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    profile_points = self.profile.evaluate_array(us)
    u_min, u_max = self.profile.get_u_bounds()
    v_min, v_max = self.extrusion.get_u_bounds()
    profile_vectors = profile_points
    profile_vectors = np.transpose(profile_vectors[np.newaxis], axes=(1, 2, 0))
    extrusion_start = self.extrusion.evaluate(v_min)
    extrusion_points = self.extrusion.evaluate_array(vs)
    extrusion_vectors = extrusion_points - extrusion_start

    frenet, _ , _ = self.extrusion.frame_array(vs)

    angles = - self.extrusion.torsion_integral(vs)
    n = len(us)
    zeros = np.zeros((n,))
    ones = np.ones((n,))
    row1 = np.stack((np.cos(angles), np.sin(angles), zeros)).T # (n, 3)
    row2 = np.stack((-np.sin(angles), np.cos(angles), zeros)).T # (n, 3)
    row3 = np.stack((zeros, zeros, ones)).T # (n, 3)
    rotation_matrices = np.dstack((row1, row2, row3))

    profile_vectors = (frenet @ rotation_matrices @ profile_vectors)[:,:,0]
    result = extrusion_vectors + profile_vectors
    if self.origin == EXTRUSION:
        result = result + self.extrusion.evaluate(v_min)
    return result
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.profile.get_u_bounds()[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.profile.get_u_bounds()[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.extrusion.get_u_bounds()[1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.extrusion.get_u_bounds()[0]
class SvInterpolatingSurface (u_bounds, v_bounds, u_spline_constructor, v_splines, reparametrize_v_splines=True)
Expand source code
class SvInterpolatingSurface(SvSurface):
    __description__ = "Interpolating"

    def __init__(self, u_bounds, v_bounds, u_spline_constructor, v_splines, reparametrize_v_splines=True):
        if reparametrize_v_splines:
            self.v_splines = [reparametrize_curve(spline) for spline in v_splines]
        else:
            for spline in v_splines:
                m,M = spline.get_u_bounds()
                if m != 0.0 or M != 1.0:
                    raise Exception("one of splines has to be reparametrized")
            self.v_splines = v_splines
        self.u_spline_constructor = u_spline_constructor
        self.u_bounds = u_bounds
        self.v_bounds = v_bounds

        # Caches
        # v -> Spline
        self._u_splines = {}
        # (u,v) -> vertex
        self._eval_cache = {}
        # (u,v) -> normal
        self._normal_cache = {}

    @property
    def u_size(self):
        return self.u_bounds[1] - self.u_bounds[0]
        #v = 0.0
        #verts = [spline.evaluate(v) for spline in self.v_splines]
        #return self.get_u_spline(v, verts).u_size

    @property
    def v_size(self):
        return self.v_bounds[1] - self.v_bounds[0]
        #return self.v_splines[0].v_size

    def get_u_spline(self, v, vertices):
        """Get a spline along U direction for specified value of V coordinate"""
        spline = self._u_splines.get(v, None)
        if spline is not None:
            return spline
        else:
            spline = self.u_spline_constructor(vertices)
            self._u_splines[v] = spline
            return spline

    def _evaluate(self, u, v):
        spline_vertices = []
        for spline in self.v_splines:
            point = spline.evaluate(v)
            spline_vertices.append(point)
        #spline_vertices = [spline.evaluate(v) for spline in self.v_splines]
        u_spline = self.get_u_spline(v, spline_vertices)
        result = u_spline.evaluate(u)
        return result

    def evaluate(self, u, v):
        result = self._eval_cache.get((u,v), None)
        if result is not None:
            return result
        else:
            result = self._evaluate(u, v)
            self._eval_cache[(u,v)] = result
            return result

#     def evaluate_array(self, us, vs):
#         # FIXME: To be optimized!
#         normals = [self._evaluate(u, v) for u,v in zip(us, vs)]
#         return np.array(normals)

    def evaluate_array(self, us, vs):
        result = np.empty((len(us), 3))
        v_to_u = defaultdict(list)
        v_to_i = defaultdict(list)
        for i, (u, v) in enumerate(zip(us, vs)):
            v_to_u[v].append(u)
            v_to_i[v].append(i)

        # here we rely on fact that in Python 3.7+ dicts are ordered.
        all_vs = np.array(list(v_to_u.keys()))
        v_spline_points = np.array([spline.evaluate_array(all_vs) for spline in self.v_splines])

        for v_idx, (v, us_by_v) in enumerate(v_to_u.items()):
            is_by_v = v_to_i[v]
            spline_vertices = []
            for spline_idx, spline in enumerate(self.v_splines):
                point = v_spline_points[spline_idx,v_idx]
                #point = spline.evaluate(v)
                spline_vertices.append(point)
            u_spline = self.get_u_spline(v, spline_vertices)
            points = u_spline.evaluate_array(np.array(us_by_v))
            idxs = np.array(is_by_v)[np.newaxis].T
            np.put_along_axis(result, idxs, points, axis=0)
        return result

    def _normal(self, u, v):
        h = 0.001
        point = self.evaluate(u, v)
        # we know this exists because it was filled in evaluate()
        u_spline = self._u_splines[v]
        u_tangent = u_spline.tangent(u)
        point_v = self.evaluate(u, v+h)
        dv = (point_v - point)/h
        n = np.cross(u_tangent, dv)
        norm = np.linalg.norm(n)
        if norm != 0:
            n = n / norm
        return n

    def normal(self, u, v):
        result = self._normal_cache.get((u,v), None)
        if result is not None:
            return result
        else:
            result = self._normal(u, v)
            self._normal_cache[(u,v)] = result
            return result

#     def normal_array(self, us, vs):
#         # FIXME: To be optimized!
#         normals = [self._normal(u, v) for u,v in zip(us, vs)]
#         return np.array(normals)

    def normal_array(self, us, vs):
        h = 0.001
        result = np.empty((len(us), 3))
        v_to_u = defaultdict(list)
        v_to_i = defaultdict(list)
        for i, (u, v) in enumerate(zip(us, vs)):
            v_to_u[v].append(u)
            v_to_i[v].append(i)
        for v, us_by_v in v_to_u.items():
            us_by_v = np.array(us_by_v)
            is_by_v = v_to_i[v]
            spline_vertices = []
            spline_vertices_h = []
            for v_spline in self.v_splines:
                v_min, v_max = v_spline.get_u_bounds()
                vx = (v_max - v_min) * v + v_min
                if vx +h <= v_max:
                    point = v_spline.evaluate(vx)
                    point_h = v_spline.evaluate(vx + h)
                else:
                    point = v_spline.evaluate(vx - h)
                    point_h = v_spline.evaluate(vx)
                spline_vertices.append(point)
                spline_vertices_h.append(point_h)
            if v+h <= v_max:
                u_spline = self.get_u_spline(v, spline_vertices)
                u_spline_h = self.get_u_spline(v+h, spline_vertices_h)
            else:
                u_spline = self.get_u_spline(v-h, spline_vertices)
                u_spline_h = self.get_u_spline(v, spline_vertices_h)
            u_min, u_max = 0.0, 1.0

            good_us = us_by_v + h < u_max
            bad_us = np.logical_not(good_us)

            good_points = np.broadcast_to(good_us[np.newaxis].T, (len(us_by_v), 3)).flatten()
            bad_points = np.logical_not(good_points)
            points = np.empty((len(us_by_v), 3))
            points[good_us] = u_spline.evaluate_array(us_by_v[good_us])
            points[bad_us] = u_spline.evaluate_array(us_by_v[bad_us] - h)
            points_u_h = np.empty((len(us_by_v), 3))
            points_u_h[good_us] = u_spline.evaluate_array(us_by_v[good_us] + h)
            points_u_h[bad_us] = u_spline.evaluate_array(us_by_v[bad_us])
            points_v_h = u_spline_h.evaluate_array(us_by_v)

            dvs = (points_v_h - points) / h
            dus = (points_u_h - points) / h
            normals = np.cross(dus, dvs)
            norms = np.linalg.norm(normals, axis=1, keepdims=True)
            normals = normals / norms

            idxs = np.array(is_by_v)[np.newaxis].T
            np.put_along_axis(result, idxs, normals, axis=0)
        return result

Ancestors

Instance variables

var u_size
Expand source code
@property
def u_size(self):
    return self.u_bounds[1] - self.u_bounds[0]
    #v = 0.0
    #verts = [spline.evaluate(v) for spline in self.v_splines]
    #return self.get_u_spline(v, verts).u_size
var v_size
Expand source code
@property
def v_size(self):
    return self.v_bounds[1] - self.v_bounds[0]
    #return self.v_splines[0].v_size

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    result = self._eval_cache.get((u,v), None)
    if result is not None:
        return result
    else:
        result = self._evaluate(u, v)
        self._eval_cache[(u,v)] = result
        return result
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    result = np.empty((len(us), 3))
    v_to_u = defaultdict(list)
    v_to_i = defaultdict(list)
    for i, (u, v) in enumerate(zip(us, vs)):
        v_to_u[v].append(u)
        v_to_i[v].append(i)

    # here we rely on fact that in Python 3.7+ dicts are ordered.
    all_vs = np.array(list(v_to_u.keys()))
    v_spline_points = np.array([spline.evaluate_array(all_vs) for spline in self.v_splines])

    for v_idx, (v, us_by_v) in enumerate(v_to_u.items()):
        is_by_v = v_to_i[v]
        spline_vertices = []
        for spline_idx, spline in enumerate(self.v_splines):
            point = v_spline_points[spline_idx,v_idx]
            #point = spline.evaluate(v)
            spline_vertices.append(point)
        u_spline = self.get_u_spline(v, spline_vertices)
        points = u_spline.evaluate_array(np.array(us_by_v))
        idxs = np.array(is_by_v)[np.newaxis].T
        np.put_along_axis(result, idxs, points, axis=0)
    return result
def get_u_spline(self, v, vertices)

Get a spline along U direction for specified value of V coordinate

Expand source code
def get_u_spline(self, v, vertices):
    """Get a spline along U direction for specified value of V coordinate"""
    spline = self._u_splines.get(v, None)
    if spline is not None:
        return spline
    else:
        spline = self.u_spline_constructor(vertices)
        self._u_splines[v] = spline
        return spline
def normal(self, u, v)
Expand source code
def normal(self, u, v):
    result = self._normal_cache.get((u,v), None)
    if result is not None:
        return result
    else:
        result = self._normal(u, v)
        self._normal_cache[(u,v)] = result
        return result
def normal_array(self, us, vs)
Expand source code
def normal_array(self, us, vs):
    h = 0.001
    result = np.empty((len(us), 3))
    v_to_u = defaultdict(list)
    v_to_i = defaultdict(list)
    for i, (u, v) in enumerate(zip(us, vs)):
        v_to_u[v].append(u)
        v_to_i[v].append(i)
    for v, us_by_v in v_to_u.items():
        us_by_v = np.array(us_by_v)
        is_by_v = v_to_i[v]
        spline_vertices = []
        spline_vertices_h = []
        for v_spline in self.v_splines:
            v_min, v_max = v_spline.get_u_bounds()
            vx = (v_max - v_min) * v + v_min
            if vx +h <= v_max:
                point = v_spline.evaluate(vx)
                point_h = v_spline.evaluate(vx + h)
            else:
                point = v_spline.evaluate(vx - h)
                point_h = v_spline.evaluate(vx)
            spline_vertices.append(point)
            spline_vertices_h.append(point_h)
        if v+h <= v_max:
            u_spline = self.get_u_spline(v, spline_vertices)
            u_spline_h = self.get_u_spline(v+h, spline_vertices_h)
        else:
            u_spline = self.get_u_spline(v-h, spline_vertices)
            u_spline_h = self.get_u_spline(v, spline_vertices_h)
        u_min, u_max = 0.0, 1.0

        good_us = us_by_v + h < u_max
        bad_us = np.logical_not(good_us)

        good_points = np.broadcast_to(good_us[np.newaxis].T, (len(us_by_v), 3)).flatten()
        bad_points = np.logical_not(good_points)
        points = np.empty((len(us_by_v), 3))
        points[good_us] = u_spline.evaluate_array(us_by_v[good_us])
        points[bad_us] = u_spline.evaluate_array(us_by_v[bad_us] - h)
        points_u_h = np.empty((len(us_by_v), 3))
        points_u_h[good_us] = u_spline.evaluate_array(us_by_v[good_us] + h)
        points_u_h[bad_us] = u_spline.evaluate_array(us_by_v[bad_us])
        points_v_h = u_spline_h.evaluate_array(us_by_v)

        dvs = (points_v_h - points) / h
        dus = (points_u_h - points) / h
        normals = np.cross(dus, dvs)
        norms = np.linalg.norm(normals, axis=1, keepdims=True)
        normals = normals / norms

        idxs = np.array(is_by_v)[np.newaxis].T
        np.put_along_axis(result, idxs, normals, axis=0)
    return result
class SvRevolutionSurface (curve, point, direction, global_origin=True)
Expand source code
class SvRevolutionSurface(SvSurface):
    __description__ = "Revolution"

    def __init__(self, curve, point, direction, global_origin=True):
        self.curve = curve
        self.point = point
        self.direction = direction
        self.global_origin = global_origin
        self.normal_delta = 0.001
        self.v_bounds = (0.0, 2*pi)

    @classmethod
    def build(cls, curve, point, direction, v_min=0, v_max=2*pi, global_origin=True):
        if hasattr(curve, 'make_revolution_surface'):
            try:
                return curve.make_revolution_surface(point, direction, v_min, v_max, global_origin)
            except UnsupportedCurveTypeException:
                pass
        surface = SvRevolutionSurface(curve, point, direction, global_origin)
        surface.v_bounds = (v_min, v_max)
        return surface

    def evaluate(self, u, v):
        point_on_curve = self.curve.evaluate(u)
        dv = point_on_curve - self.point
        result = np.array(rotate_vector_around_vector(dv, self.direction, v))
        if not self.global_origin:
            result = result + self.point
        return result

    def evaluate_array(self, us, vs):
        points_on_curve = self.curve.evaluate_array(us)
        dvs = points_on_curve - self.point
        result = rotate_vector_around_vector_np(dvs, self.direction, vs)
        if not self.global_origin:
            result = result + self.point
        return result

    def get_u_min(self):
        return self.curve.get_u_bounds()[0]

    def get_u_max(self):
        return self.curve.get_u_bounds()[1]

    def get_v_min(self):
        return self.v_bounds[0]

    def get_v_max(self):
        return self.v_bounds[1]

Ancestors

Static methods

def build(curve, point, direction, v_min=0, v_max=6.283185307179586, global_origin=True)
Expand source code
@classmethod
def build(cls, curve, point, direction, v_min=0, v_max=2*pi, global_origin=True):
    if hasattr(curve, 'make_revolution_surface'):
        try:
            return curve.make_revolution_surface(point, direction, v_min, v_max, global_origin)
        except UnsupportedCurveTypeException:
            pass
    surface = SvRevolutionSurface(curve, point, direction, global_origin)
    surface.v_bounds = (v_min, v_max)
    return surface

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    point_on_curve = self.curve.evaluate(u)
    dv = point_on_curve - self.point
    result = np.array(rotate_vector_around_vector(dv, self.direction, v))
    if not self.global_origin:
        result = result + self.point
    return result
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    points_on_curve = self.curve.evaluate_array(us)
    dvs = points_on_curve - self.point
    result = rotate_vector_around_vector_np(dvs, self.direction, vs)
    if not self.global_origin:
        result = result + self.point
    return result
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.curve.get_u_bounds()[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.curve.get_u_bounds()[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.v_bounds[1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.v_bounds[0]
class SvSurfaceLerpSurface (surface1, surface2, coefficient)
Expand source code
class SvSurfaceLerpSurface(SvSurface):
    __description__ = "Lerp"

    def __init__(self, surface1, surface2, coefficient):
        self.surface1 = surface1
        self.surface2 = surface2
        self.coefficient = coefficient
        self.normal_delta = 0.001
        self.v_bounds = (0.0, 1.0)
        self.u_bounds = (0.0, 1.0)
        self.s1_u_min, self.s1_u_max = surface1.get_u_min(), surface1.get_u_max()
        self.s1_v_min, self.s1_v_max = surface1.get_v_min(), surface1.get_v_max()
        self.s2_u_min, self.s2_u_max = surface2.get_u_min(), surface2.get_u_max()
        self.s2_v_min, self.s2_v_max = surface2.get_v_min(), surface2.get_v_max()

    def get_u_min(self):
        return self.u_bounds[0]

    def get_u_max(self):
        return self.u_bounds[1]

    def get_v_min(self):
        return self.v_bounds[0]

    def get_v_max(self):
        return self.v_bounds[1]

    @property
    def u_size(self):
        return self.u_bounds[1] - self.u_bounds[0]

    @property
    def v_size(self):
        return self.v_bounds[1] - self.v_bounds[0]

    def evaluate(self, u, v):
        return self.evaluate_array(np.array([u]), np.array([v]))[0]
    
    def evaluate_array(self, us, vs):
        us1 = (self.s1_u_max - self.s1_u_min) * us + self.s1_u_min
        us2 = (self.s2_u_max - self.s2_u_min) * us + self.s2_u_min
        vs1 = (self.s1_v_max - self.s1_v_min) * vs + self.s1_v_min
        vs2 = (self.s2_v_max - self.s2_v_min) * vs + self.s2_v_min
        s1_points = self.surface1.evaluate_array(us1, vs1)
        s2_points = self.surface2.evaluate_array(us2, vs2)
        k = self.coefficient
        points = (1.0 - k) * s1_points + k * s2_points
        return points

Ancestors

Instance variables

var u_size
Expand source code
@property
def u_size(self):
    return self.u_bounds[1] - self.u_bounds[0]
var v_size
Expand source code
@property
def v_size(self):
    return self.v_bounds[1] - self.v_bounds[0]

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    return self.evaluate_array(np.array([u]), np.array([v]))[0]
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    us1 = (self.s1_u_max - self.s1_u_min) * us + self.s1_u_min
    us2 = (self.s2_u_max - self.s2_u_min) * us + self.s2_u_min
    vs1 = (self.s1_v_max - self.s1_v_min) * vs + self.s1_v_min
    vs2 = (self.s2_v_max - self.s2_v_min) * vs + self.s2_v_min
    s1_points = self.surface1.evaluate_array(us1, vs1)
    s2_points = self.surface2.evaluate_array(us2, vs2)
    k = self.coefficient
    points = (1.0 - k) * s1_points + k * s2_points
    return points
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.u_bounds[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.u_bounds[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.v_bounds[1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.v_bounds[0]
class SvTaperSweepSurface (profile, taper, point, direction, scale_base='UNIT')
Expand source code
class SvTaperSweepSurface(SvSurface):
    __description__ = "Taper & Sweep"

    UNIT = 'UNIT'
    PROFILE = 'PROFILE'
    TAPER = 'TAPER'

    def __init__(self, profile, taper, point, direction, scale_base=UNIT):
        self.profile = profile
        self.taper = taper
        self.direction = direction
        self.point = point
        self.line = LineEquation.from_direction_and_point(direction, point)
        self.scale_base = scale_base
        self.normal_delta = 0.001

    def get_u_min(self):
        return self.profile.get_u_bounds()[0]

    def get_u_max(self):
        return self.profile.get_u_bounds()[1]

    def get_v_min(self):
        return self.taper.get_u_bounds()[0]

    def get_v_max(self):
        return self.taper.get_u_bounds()[1]

    def _get_profile_scale(self):
        profile_u_min = self.profile.get_u_bounds()[0]
        profile_start = self.profile.evaluate(profile_u_min)
        profile_start_projection = self.line.projection_of_point(profile_start)
        dp = np.linalg.norm(profile_start - profile_start_projection)
        return dp

    def evaluate(self, u, v):
        taper_point = self.taper.evaluate(v)
        taper_projection = np.array( self.line.projection_of_point(taper_point) )
        scale = np.linalg.norm(taper_projection - taper_point)
        if self.scale_base == SvTaperSweepSurface.TAPER:
            dp = self._get_profile_scale()
            scale /= dp
        elif self.scale_base == SvTaperSweepSurface.PROFILE:
            taper_t_min = self.taper.get_u_bounds()[0]
            taper_start = self.taper.evaluate(taper_t_min)
            taper_start_projection = np.array(self.line.projection_of_point(taper_start))
            scale0 = np.linalg.norm(taper_start - taper_start_projection)
            scale /= scale0

        profile_point = self.profile.evaluate(u)
        return profile_point * scale + taper_projection

    def evaluate_array(self, us, vs):
        taper_points = self.taper.evaluate_array(vs)
        taper_projections = self.line.projection_of_points(taper_points)
        scale = np.linalg.norm(taper_projections - taper_points, axis=1, keepdims=True)

        if self.scale_base == SvTaperSweepSurface.TAPER:
            dp = self._get_profile_scale()
            scale /= dp
        elif self.scale_base == SvTaperSweepSurface.PROFILE:
            scale0 = scale[0]
            scale /= scale0

        profile_points = self.profile.evaluate_array(us)
        return profile_points * scale + taper_projections

Ancestors

Class variables

var PROFILE
var TAPER
var UNIT

Methods

def evaluate(self, u, v)
Expand source code
def evaluate(self, u, v):
    taper_point = self.taper.evaluate(v)
    taper_projection = np.array( self.line.projection_of_point(taper_point) )
    scale = np.linalg.norm(taper_projection - taper_point)
    if self.scale_base == SvTaperSweepSurface.TAPER:
        dp = self._get_profile_scale()
        scale /= dp
    elif self.scale_base == SvTaperSweepSurface.PROFILE:
        taper_t_min = self.taper.get_u_bounds()[0]
        taper_start = self.taper.evaluate(taper_t_min)
        taper_start_projection = np.array(self.line.projection_of_point(taper_start))
        scale0 = np.linalg.norm(taper_start - taper_start_projection)
        scale /= scale0

    profile_point = self.profile.evaluate(u)
    return profile_point * scale + taper_projection
def evaluate_array(self, us, vs)
Expand source code
def evaluate_array(self, us, vs):
    taper_points = self.taper.evaluate_array(vs)
    taper_projections = self.line.projection_of_points(taper_points)
    scale = np.linalg.norm(taper_projections - taper_points, axis=1, keepdims=True)

    if self.scale_base == SvTaperSweepSurface.TAPER:
        dp = self._get_profile_scale()
        scale /= dp
    elif self.scale_base == SvTaperSweepSurface.PROFILE:
        scale0 = scale[0]
        scale /= scale0

    profile_points = self.profile.evaluate_array(us)
    return profile_points * scale + taper_projections
def get_u_max(self)
Expand source code
def get_u_max(self):
    return self.profile.get_u_bounds()[1]
def get_u_min(self)
Expand source code
def get_u_min(self):
    return self.profile.get_u_bounds()[0]
def get_v_max(self)
Expand source code
def get_v_max(self):
    return self.taper.get_u_bounds()[1]
def get_v_min(self)
Expand source code
def get_v_min(self):
    return self.taper.get_u_bounds()[0]