Module sverchok.utils.surface.data

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

import numpy as np

from mathutils import Matrix, Vector

class SurfaceCurvatureData(object):
    """Container class for calculated curvature values"""
    def __init__(self):
        self.principal_value_1 = self.principal_value_2 = None
        self.principal_direction_1 = self.principal_direction_2 = None
        self.principal_direction_1_uv = self.principal_direction_2_uv = None
        self.mean = self.gauss = None
        self.matrix = None

class SurfaceDerivativesData(object):
    def __init__(self, points, du, dv):
        self.points = points
        self.du = du
        self.dv = dv
        self._normals = None
        self._normals_len = None
        self._unit_normals = None
        self._unit_du = None
        self._unit_dv = None
        self._du_len = self._dv_len = None

    def normals(self):
        if self._normals is None:
            self._normals = np.cross(self.du, self.dv)
        return self._normals

    def normals_len(self):
        if self._normals_len is None:
            normals = self.normals()
            self._normals_len = np.linalg.norm(normals, axis=1)[np.newaxis].T
        return self._normals_len

    def unit_normals(self):
        if self._unit_normals is None:
            normals = self.normals()
            norm = self.normals_len()
            self._unit_normals = normals / norm
        return self._unit_normals

    def tangent_lens(self, keepdims=True):
        if self._du_len is None:
            self._du_len = np.linalg.norm(self.du, axis=1, keepdims=True)
            self._dv_len = np.linalg.norm(self.dv, axis=1, keepdims=True)
        return self._du_len, self._dv_len

    def unit_tangents(self):
        if self._unit_du is None:
            du_norm, dv_norm = self.tangent_lens()
            self._unit_du = self.du / du_norm
            self._unit_dv = self.dv / dv_norm
        return self._unit_du, self._unit_dv

    def matrices(self, as_mathutils = False):
        normals = self.unit_normals()
        du, dv = self.unit_tangents()
        matrices_np = np.dstack((du, dv, normals))
        if as_mathutils:
            matrices = [Matrix(m.tolist()).to_4x4() for m in matrices_np]
            for m, p in zip(matrices, self.points):
                m.translation = Vector(p)
            return matrices
        else:
            return matrices_np

class SurfaceCurvatureCalculator(object):
    """
    This class contains pre-calculated first and second surface derivatives,
    and calculates any curvature information from them.
    """
    def __init__(self, us, vs, order=True):
        self.us = us
        self.vs = vs
        self.order = order
        self.fu = self.fv = None
        self.duu = self.dvv = self.duv = None
        self.nuu = self.nvv = self.nuv = None
        self.points = None
        self.normals = None

    def set(self, points, normals, fu, fv, duu, dvv, duv, nuu, nvv, nuv):
        """Set derivatives information"""
        self.points = points
        self.normals = normals
        self.fu = fu   # df/du
        self.fv = fv   # df/dv
        self.duu = duu # (fu, fv), a.k.a. E
        self.dvv = dvv # (fv, fv), a.k.a. G
        self.duv = duv # (fu, fv), a.k.a F
        self.nuu = nuu # (fuu, normal), a.k.a l
        self.nvv = nvv # (fvv, normal), a.k.a n
        self.nuv = nuv # (fuv, normal), a.k.a m

    def mean(self):
        """
        Calculate mean curvature.

        Note: although mean curvature is defined as (k1 + k2)/2, where k1 and k2 are
        principal curvature values, it is possible to calculate mean curvature without
        calculating k1 and k2 first.
        """
        duu, dvv, duv, nuu, nvv, nuv = self.duu, self.dvv, self.duv, self.nuu, self.nvv, self.nuv
        A = duu*dvv - duv*duv
        B = duu*nvv - 2*duv*nuv + dvv*nuu
        return B / (2*A)

    def gauss(self):
        """
        Calculate Gaussian curvature.

        Note: although Gaussian curvature is defined as k1*k2, where k1 and k2
        are principal curvature values, it is possible to calculate Gaussian
        curvature without calculating k1 and k2 first.
        """
        duu, dvv, duv, nuu, nvv, nuv = self.duu, self.dvv, self.duv, self.nuu, self.nvv, self.nuv
        numerator = nuu * nvv - nuv*nuv
        denominator = duu * dvv - duv*duv
        return numerator / denominator

    def curvature_along_direction(self, v1, v2):
        """
        Calculate curvature value along specified direction.

        Args:
            v1, v2: coefficients in the equation v = v1*du + v2*dv, where v is
                direction in which you want to find the curvature, du is unit
                vector along df / du derivative, and dv is unit vector along df /
                dv derivative.

        Note: to calculate curvature along the direction perpendicular to (v1,v2),
        one can use formula: 2 * calc.mean() - calc.curvature_along_direction(v1, v2).
        """
        v1s, v2s = v1*v1, v2*v2
        v12 = v1*v2
        l, m, n = self.nuu, self.nuv, self.nvv
        E, F, G = self.duu, self.duv, self.dvv
        numerator = l*v1s + 2*m*v12 + n*v2s
        denominator = E*v1s + 2*F*v12 + G*v2s
        return numerator / denominator

    def values(self):
        """
        Calculate two principal curvature values.
        If "order" parameter is set to True, then it will be guaranteed,
        that k1 value is always less than k2.
        
        Note: by definition, principal curvature values are curvatures along
        principal curvature directions. But, it is possible to calculate
        principal curvature values as solutions of quadratic equation, without
        calculating corresponding principal curvature directions.
        """

        # lambda^2 (E G - F^2) - lambda (E N - 2 F M + G L) + (L N - M^2) = 0

        duu, dvv, duv, nuu, nvv, nuv = self.duu, self.dvv, self.duv, self.nuu, self.nvv, self.nuv
        A = duu*dvv - duv*duv
        B = duu*nvv - 2*duv*nuv + dvv*nuu
        C = nuu*nvv - nuv*nuv
        D = B*B - 4*A*C
        c1 = (-B - np.sqrt(D))/(2*A)
        c2 = (-B + np.sqrt(D))/(2*A)

        c1[np.isnan(c1)] = 0
        c2[np.isnan(c2)] = 0

        c1mask = (c1 < c2)
        c2mask = np.logical_not(c1mask)

        c1_r = np.where(c1mask, c1, c2)
        c2_r = np.where(c2mask, c1, c2)

        return c1_r, c2_r

    def values_and_directions(self):
        """
        Calculate principal curvature values together with principal curvature directions.
        If "order" parameter is set to True, then it will be guaranteed, that C1 value
        is always less than C2. Curvature directions are always output correspondingly,
        i.e. principal_direction_1 corresponds to principal_value_1 and principal_direction_2
        corresponds to principal_value_2.
        """
        # If we need not only curvature values, but principal curvature directions as well,
        # we have to solve an eigenvalue problem to find values and directions at once.

        # L p = lambda G p

        fu, fv = self.fu, self.fv
        duu, dvv, duv, nuu, nvv, nuv = self.duu, self.dvv, self.duv, self.nuu, self.nvv, self.nuv
        n = len(self.us)

        L = np.empty((n,2,2))
        L[:,0,0] = nuu
        L[:,0,1] = nuv
        L[:,1,0] = nuv
        L[:,1,1] = nvv

        G = np.empty((n,2,2))
        G[:,0,0] = duu
        G[:,0,1] = duv
        G[:,1,0] = duv
        G[:,1,1] = dvv

        M = np.matmul(np.linalg.inv(G), L)
        eigvals, eigvecs = np.linalg.eig(M)
        # Values of first and second principal curvatures
        c1 = eigvals[:,0]
        c2 = eigvals[:,1]

        if self.order:
            c1mask = (c1 < c2)
            c2mask = np.logical_not(c1mask)
            c1_r = np.where(c1mask, c1, c2)
            c2_r = np.where(c2mask, c1, c2)
        else:
            c1_r = c1
            c2_r = c2

        # dir_1 corresponds to c1, dir_2 corresponds to c2
        dir_1_x = eigvecs[:,0,0][np.newaxis].T
        dir_2_x = eigvecs[:,0,1][np.newaxis].T
        dir_1_y = eigvecs[:,1,0][np.newaxis].T
        dir_2_y = eigvecs[:,1,1][np.newaxis].T

        # another possible approach
#         A = duv * nvv - dvv*nuv 
#         B = duu * nvv - dvv*nuu
#         C = duu*nuv - duv*nuu
#         D = B*B - 4*A*C
#         t1 = (-B - np.sqrt(D)) / (2*A)
#         t2 = (-B + np.sqrt(D)) / (2*A)

        dir_1 = dir_1_x * fu + dir_1_y * fv
        dir_2 = dir_2_x * fu + dir_2_y * fv

        dir_1 = dir_1 / np.linalg.norm(dir_1, axis=1, keepdims=True)
        dir_2 = dir_2 / np.linalg.norm(dir_2, axis=1, keepdims=True)

        if self.order:
            c1maskT = c1mask[np.newaxis].T
            c2maskT = c2mask[np.newaxis].T
            dir_1_r = np.where(c1maskT, dir_1, -dir_2)
            dir_2_r = np.where(c2maskT, dir_1, dir_2)
        else:
            dir_1_r = dir_1
            dir_2_r = dir_2
        #r = (np.cross(dir_1_r, dir_2_r) * self.normals).sum(axis=1)
        #print(r)

        dir1_uv = eigvecs[:,:,0]
        dir2_uv = eigvecs[:,:,1]
        if self.order:
            c1maskT = c1mask[np.newaxis].T
            c2maskT = c2mask[np.newaxis].T
            dir1_uv_r = np.where(c1maskT, dir1_uv, -dir2_uv)
            dir2_uv_r = np.where(c2maskT, dir1_uv, dir2_uv)
        else:
            dir1_uv_r = dir1_uv
            dir2_uv_r = dir2_uv
            
        return c1_r, c2_r, dir1_uv_r, dir2_uv_r, dir_1_r, dir_2_r

    def calc(self, need_values=True, need_directions=True, need_uv_directions = False, need_gauss=True, need_mean=True, need_matrix = True):
        """
        Calculate curvature information.
        Return value: SurfaceCurvatureData instance.
        """
        # We try to do as less calculations as possible,
        # by not doing complex computations if not required
        # and reusing results of other computations if possible.
        data = SurfaceCurvatureData()
        if need_matrix:
            need_directions = True
        if need_uv_directions:
            need_directions = True
        if need_directions:
            # If we need principal curvature directions, then the method
            # being used will calculate us curvature values for free.
            c1, c2, dir1_uv, dir2_uv, dir1, dir2 = self.values_and_directions()
            data.principal_value_1, data.principal_value_2 = c1, c2
            data.principal_direction_1, data.principal_direction_2 = dir1, dir2
            data.principal_direction_1_uv = dir1_uv
            data.principal_direction_2_uv = dir2_uv
            if need_gauss:
                data.gauss = c1 * c2
            if need_mean:
                data.mean = (c1 + c2)/2.0
        if need_matrix:
            matrices_np = np.dstack((data.principal_direction_2, data.principal_direction_1, self.normals))
            matrices_np = np.transpose(matrices_np, axes=(0,2,1))
            matrices_np = np.linalg.inv(matrices_np)
            matrices = [Matrix(m.tolist()).to_4x4() for m in matrices_np]
            for matrix, point in zip(matrices, self.points):
                matrix.translation = Vector(point)
            data.matrix = matrices
        if need_values and not need_directions:
            c1, c2 = self.values()
            data.principal_value_1, data.principal_value_2 = c1, c2
            if need_gauss:
                data.gauss = c1 * c2
            if need_mean:
                data.mean = (c1 + c2)/2.0
        if need_gauss and not need_directions and not need_values:
            data.gauss = self.gauss()
        if need_mean and not need_directions and not need_values:
            data.mean = self.mean()
        return data

Classes

class SurfaceCurvatureCalculator (us, vs, order=True)

This class contains pre-calculated first and second surface derivatives, and calculates any curvature information from them.

Expand source code
class SurfaceCurvatureCalculator(object):
    """
    This class contains pre-calculated first and second surface derivatives,
    and calculates any curvature information from them.
    """
    def __init__(self, us, vs, order=True):
        self.us = us
        self.vs = vs
        self.order = order
        self.fu = self.fv = None
        self.duu = self.dvv = self.duv = None
        self.nuu = self.nvv = self.nuv = None
        self.points = None
        self.normals = None

    def set(self, points, normals, fu, fv, duu, dvv, duv, nuu, nvv, nuv):
        """Set derivatives information"""
        self.points = points
        self.normals = normals
        self.fu = fu   # df/du
        self.fv = fv   # df/dv
        self.duu = duu # (fu, fv), a.k.a. E
        self.dvv = dvv # (fv, fv), a.k.a. G
        self.duv = duv # (fu, fv), a.k.a F
        self.nuu = nuu # (fuu, normal), a.k.a l
        self.nvv = nvv # (fvv, normal), a.k.a n
        self.nuv = nuv # (fuv, normal), a.k.a m

    def mean(self):
        """
        Calculate mean curvature.

        Note: although mean curvature is defined as (k1 + k2)/2, where k1 and k2 are
        principal curvature values, it is possible to calculate mean curvature without
        calculating k1 and k2 first.
        """
        duu, dvv, duv, nuu, nvv, nuv = self.duu, self.dvv, self.duv, self.nuu, self.nvv, self.nuv
        A = duu*dvv - duv*duv
        B = duu*nvv - 2*duv*nuv + dvv*nuu
        return B / (2*A)

    def gauss(self):
        """
        Calculate Gaussian curvature.

        Note: although Gaussian curvature is defined as k1*k2, where k1 and k2
        are principal curvature values, it is possible to calculate Gaussian
        curvature without calculating k1 and k2 first.
        """
        duu, dvv, duv, nuu, nvv, nuv = self.duu, self.dvv, self.duv, self.nuu, self.nvv, self.nuv
        numerator = nuu * nvv - nuv*nuv
        denominator = duu * dvv - duv*duv
        return numerator / denominator

    def curvature_along_direction(self, v1, v2):
        """
        Calculate curvature value along specified direction.

        Args:
            v1, v2: coefficients in the equation v = v1*du + v2*dv, where v is
                direction in which you want to find the curvature, du is unit
                vector along df / du derivative, and dv is unit vector along df /
                dv derivative.

        Note: to calculate curvature along the direction perpendicular to (v1,v2),
        one can use formula: 2 * calc.mean() - calc.curvature_along_direction(v1, v2).
        """
        v1s, v2s = v1*v1, v2*v2
        v12 = v1*v2
        l, m, n = self.nuu, self.nuv, self.nvv
        E, F, G = self.duu, self.duv, self.dvv
        numerator = l*v1s + 2*m*v12 + n*v2s
        denominator = E*v1s + 2*F*v12 + G*v2s
        return numerator / denominator

    def values(self):
        """
        Calculate two principal curvature values.
        If "order" parameter is set to True, then it will be guaranteed,
        that k1 value is always less than k2.
        
        Note: by definition, principal curvature values are curvatures along
        principal curvature directions. But, it is possible to calculate
        principal curvature values as solutions of quadratic equation, without
        calculating corresponding principal curvature directions.
        """

        # lambda^2 (E G - F^2) - lambda (E N - 2 F M + G L) + (L N - M^2) = 0

        duu, dvv, duv, nuu, nvv, nuv = self.duu, self.dvv, self.duv, self.nuu, self.nvv, self.nuv
        A = duu*dvv - duv*duv
        B = duu*nvv - 2*duv*nuv + dvv*nuu
        C = nuu*nvv - nuv*nuv
        D = B*B - 4*A*C
        c1 = (-B - np.sqrt(D))/(2*A)
        c2 = (-B + np.sqrt(D))/(2*A)

        c1[np.isnan(c1)] = 0
        c2[np.isnan(c2)] = 0

        c1mask = (c1 < c2)
        c2mask = np.logical_not(c1mask)

        c1_r = np.where(c1mask, c1, c2)
        c2_r = np.where(c2mask, c1, c2)

        return c1_r, c2_r

    def values_and_directions(self):
        """
        Calculate principal curvature values together with principal curvature directions.
        If "order" parameter is set to True, then it will be guaranteed, that C1 value
        is always less than C2. Curvature directions are always output correspondingly,
        i.e. principal_direction_1 corresponds to principal_value_1 and principal_direction_2
        corresponds to principal_value_2.
        """
        # If we need not only curvature values, but principal curvature directions as well,
        # we have to solve an eigenvalue problem to find values and directions at once.

        # L p = lambda G p

        fu, fv = self.fu, self.fv
        duu, dvv, duv, nuu, nvv, nuv = self.duu, self.dvv, self.duv, self.nuu, self.nvv, self.nuv
        n = len(self.us)

        L = np.empty((n,2,2))
        L[:,0,0] = nuu
        L[:,0,1] = nuv
        L[:,1,0] = nuv
        L[:,1,1] = nvv

        G = np.empty((n,2,2))
        G[:,0,0] = duu
        G[:,0,1] = duv
        G[:,1,0] = duv
        G[:,1,1] = dvv

        M = np.matmul(np.linalg.inv(G), L)
        eigvals, eigvecs = np.linalg.eig(M)
        # Values of first and second principal curvatures
        c1 = eigvals[:,0]
        c2 = eigvals[:,1]

        if self.order:
            c1mask = (c1 < c2)
            c2mask = np.logical_not(c1mask)
            c1_r = np.where(c1mask, c1, c2)
            c2_r = np.where(c2mask, c1, c2)
        else:
            c1_r = c1
            c2_r = c2

        # dir_1 corresponds to c1, dir_2 corresponds to c2
        dir_1_x = eigvecs[:,0,0][np.newaxis].T
        dir_2_x = eigvecs[:,0,1][np.newaxis].T
        dir_1_y = eigvecs[:,1,0][np.newaxis].T
        dir_2_y = eigvecs[:,1,1][np.newaxis].T

        # another possible approach
#         A = duv * nvv - dvv*nuv 
#         B = duu * nvv - dvv*nuu
#         C = duu*nuv - duv*nuu
#         D = B*B - 4*A*C
#         t1 = (-B - np.sqrt(D)) / (2*A)
#         t2 = (-B + np.sqrt(D)) / (2*A)

        dir_1 = dir_1_x * fu + dir_1_y * fv
        dir_2 = dir_2_x * fu + dir_2_y * fv

        dir_1 = dir_1 / np.linalg.norm(dir_1, axis=1, keepdims=True)
        dir_2 = dir_2 / np.linalg.norm(dir_2, axis=1, keepdims=True)

        if self.order:
            c1maskT = c1mask[np.newaxis].T
            c2maskT = c2mask[np.newaxis].T
            dir_1_r = np.where(c1maskT, dir_1, -dir_2)
            dir_2_r = np.where(c2maskT, dir_1, dir_2)
        else:
            dir_1_r = dir_1
            dir_2_r = dir_2
        #r = (np.cross(dir_1_r, dir_2_r) * self.normals).sum(axis=1)
        #print(r)

        dir1_uv = eigvecs[:,:,0]
        dir2_uv = eigvecs[:,:,1]
        if self.order:
            c1maskT = c1mask[np.newaxis].T
            c2maskT = c2mask[np.newaxis].T
            dir1_uv_r = np.where(c1maskT, dir1_uv, -dir2_uv)
            dir2_uv_r = np.where(c2maskT, dir1_uv, dir2_uv)
        else:
            dir1_uv_r = dir1_uv
            dir2_uv_r = dir2_uv
            
        return c1_r, c2_r, dir1_uv_r, dir2_uv_r, dir_1_r, dir_2_r

    def calc(self, need_values=True, need_directions=True, need_uv_directions = False, need_gauss=True, need_mean=True, need_matrix = True):
        """
        Calculate curvature information.
        Return value: SurfaceCurvatureData instance.
        """
        # We try to do as less calculations as possible,
        # by not doing complex computations if not required
        # and reusing results of other computations if possible.
        data = SurfaceCurvatureData()
        if need_matrix:
            need_directions = True
        if need_uv_directions:
            need_directions = True
        if need_directions:
            # If we need principal curvature directions, then the method
            # being used will calculate us curvature values for free.
            c1, c2, dir1_uv, dir2_uv, dir1, dir2 = self.values_and_directions()
            data.principal_value_1, data.principal_value_2 = c1, c2
            data.principal_direction_1, data.principal_direction_2 = dir1, dir2
            data.principal_direction_1_uv = dir1_uv
            data.principal_direction_2_uv = dir2_uv
            if need_gauss:
                data.gauss = c1 * c2
            if need_mean:
                data.mean = (c1 + c2)/2.0
        if need_matrix:
            matrices_np = np.dstack((data.principal_direction_2, data.principal_direction_1, self.normals))
            matrices_np = np.transpose(matrices_np, axes=(0,2,1))
            matrices_np = np.linalg.inv(matrices_np)
            matrices = [Matrix(m.tolist()).to_4x4() for m in matrices_np]
            for matrix, point in zip(matrices, self.points):
                matrix.translation = Vector(point)
            data.matrix = matrices
        if need_values and not need_directions:
            c1, c2 = self.values()
            data.principal_value_1, data.principal_value_2 = c1, c2
            if need_gauss:
                data.gauss = c1 * c2
            if need_mean:
                data.mean = (c1 + c2)/2.0
        if need_gauss and not need_directions and not need_values:
            data.gauss = self.gauss()
        if need_mean and not need_directions and not need_values:
            data.mean = self.mean()
        return data

Methods

def calc(self, need_values=True, need_directions=True, need_uv_directions=False, need_gauss=True, need_mean=True, need_matrix=True)

Calculate curvature information. Return value: SurfaceCurvatureData instance.

Expand source code
def calc(self, need_values=True, need_directions=True, need_uv_directions = False, need_gauss=True, need_mean=True, need_matrix = True):
    """
    Calculate curvature information.
    Return value: SurfaceCurvatureData instance.
    """
    # We try to do as less calculations as possible,
    # by not doing complex computations if not required
    # and reusing results of other computations if possible.
    data = SurfaceCurvatureData()
    if need_matrix:
        need_directions = True
    if need_uv_directions:
        need_directions = True
    if need_directions:
        # If we need principal curvature directions, then the method
        # being used will calculate us curvature values for free.
        c1, c2, dir1_uv, dir2_uv, dir1, dir2 = self.values_and_directions()
        data.principal_value_1, data.principal_value_2 = c1, c2
        data.principal_direction_1, data.principal_direction_2 = dir1, dir2
        data.principal_direction_1_uv = dir1_uv
        data.principal_direction_2_uv = dir2_uv
        if need_gauss:
            data.gauss = c1 * c2
        if need_mean:
            data.mean = (c1 + c2)/2.0
    if need_matrix:
        matrices_np = np.dstack((data.principal_direction_2, data.principal_direction_1, self.normals))
        matrices_np = np.transpose(matrices_np, axes=(0,2,1))
        matrices_np = np.linalg.inv(matrices_np)
        matrices = [Matrix(m.tolist()).to_4x4() for m in matrices_np]
        for matrix, point in zip(matrices, self.points):
            matrix.translation = Vector(point)
        data.matrix = matrices
    if need_values and not need_directions:
        c1, c2 = self.values()
        data.principal_value_1, data.principal_value_2 = c1, c2
        if need_gauss:
            data.gauss = c1 * c2
        if need_mean:
            data.mean = (c1 + c2)/2.0
    if need_gauss and not need_directions and not need_values:
        data.gauss = self.gauss()
    if need_mean and not need_directions and not need_values:
        data.mean = self.mean()
    return data
def curvature_along_direction(self, v1, v2)

Calculate curvature value along specified direction.

Args

v1, v2: coefficients in the equation v = v1du + v2dv, where v is direction in which you want to find the curvature, du is unit vector along df / du derivative, and dv is unit vector along df / dv derivative. Note: to calculate curvature along the direction perpendicular to (v1,v2), one can use formula: 2 * calc.mean() - calc.curvature_along_direction(v1, v2).

Expand source code
def curvature_along_direction(self, v1, v2):
    """
    Calculate curvature value along specified direction.

    Args:
        v1, v2: coefficients in the equation v = v1*du + v2*dv, where v is
            direction in which you want to find the curvature, du is unit
            vector along df / du derivative, and dv is unit vector along df /
            dv derivative.

    Note: to calculate curvature along the direction perpendicular to (v1,v2),
    one can use formula: 2 * calc.mean() - calc.curvature_along_direction(v1, v2).
    """
    v1s, v2s = v1*v1, v2*v2
    v12 = v1*v2
    l, m, n = self.nuu, self.nuv, self.nvv
    E, F, G = self.duu, self.duv, self.dvv
    numerator = l*v1s + 2*m*v12 + n*v2s
    denominator = E*v1s + 2*F*v12 + G*v2s
    return numerator / denominator
def gauss(self)

Calculate Gaussian curvature.

Note: although Gaussian curvature is defined as k1*k2, where k1 and k2 are principal curvature values, it is possible to calculate Gaussian curvature without calculating k1 and k2 first.

Expand source code
def gauss(self):
    """
    Calculate Gaussian curvature.

    Note: although Gaussian curvature is defined as k1*k2, where k1 and k2
    are principal curvature values, it is possible to calculate Gaussian
    curvature without calculating k1 and k2 first.
    """
    duu, dvv, duv, nuu, nvv, nuv = self.duu, self.dvv, self.duv, self.nuu, self.nvv, self.nuv
    numerator = nuu * nvv - nuv*nuv
    denominator = duu * dvv - duv*duv
    return numerator / denominator
def mean(self)

Calculate mean curvature.

Note: although mean curvature is defined as (k1 + k2)/2, where k1 and k2 are principal curvature values, it is possible to calculate mean curvature without calculating k1 and k2 first.

Expand source code
def mean(self):
    """
    Calculate mean curvature.

    Note: although mean curvature is defined as (k1 + k2)/2, where k1 and k2 are
    principal curvature values, it is possible to calculate mean curvature without
    calculating k1 and k2 first.
    """
    duu, dvv, duv, nuu, nvv, nuv = self.duu, self.dvv, self.duv, self.nuu, self.nvv, self.nuv
    A = duu*dvv - duv*duv
    B = duu*nvv - 2*duv*nuv + dvv*nuu
    return B / (2*A)
def set(self, points, normals, fu, fv, duu, dvv, duv, nuu, nvv, nuv)

Set derivatives information

Expand source code
def set(self, points, normals, fu, fv, duu, dvv, duv, nuu, nvv, nuv):
    """Set derivatives information"""
    self.points = points
    self.normals = normals
    self.fu = fu   # df/du
    self.fv = fv   # df/dv
    self.duu = duu # (fu, fv), a.k.a. E
    self.dvv = dvv # (fv, fv), a.k.a. G
    self.duv = duv # (fu, fv), a.k.a F
    self.nuu = nuu # (fuu, normal), a.k.a l
    self.nvv = nvv # (fvv, normal), a.k.a n
    self.nuv = nuv # (fuv, normal), a.k.a m
def values(self)

Calculate two principal curvature values. If "order" parameter is set to True, then it will be guaranteed, that k1 value is always less than k2.

Note: by definition, principal curvature values are curvatures along principal curvature directions. But, it is possible to calculate principal curvature values as solutions of quadratic equation, without calculating corresponding principal curvature directions.

Expand source code
def values(self):
    """
    Calculate two principal curvature values.
    If "order" parameter is set to True, then it will be guaranteed,
    that k1 value is always less than k2.
    
    Note: by definition, principal curvature values are curvatures along
    principal curvature directions. But, it is possible to calculate
    principal curvature values as solutions of quadratic equation, without
    calculating corresponding principal curvature directions.
    """

    # lambda^2 (E G - F^2) - lambda (E N - 2 F M + G L) + (L N - M^2) = 0

    duu, dvv, duv, nuu, nvv, nuv = self.duu, self.dvv, self.duv, self.nuu, self.nvv, self.nuv
    A = duu*dvv - duv*duv
    B = duu*nvv - 2*duv*nuv + dvv*nuu
    C = nuu*nvv - nuv*nuv
    D = B*B - 4*A*C
    c1 = (-B - np.sqrt(D))/(2*A)
    c2 = (-B + np.sqrt(D))/(2*A)

    c1[np.isnan(c1)] = 0
    c2[np.isnan(c2)] = 0

    c1mask = (c1 < c2)
    c2mask = np.logical_not(c1mask)

    c1_r = np.where(c1mask, c1, c2)
    c2_r = np.where(c2mask, c1, c2)

    return c1_r, c2_r
def values_and_directions(self)

Calculate principal curvature values together with principal curvature directions. If "order" parameter is set to True, then it will be guaranteed, that C1 value is always less than C2. Curvature directions are always output correspondingly, i.e. principal_direction_1 corresponds to principal_value_1 and principal_direction_2 corresponds to principal_value_2.

Expand source code
    def values_and_directions(self):
        """
        Calculate principal curvature values together with principal curvature directions.
        If "order" parameter is set to True, then it will be guaranteed, that C1 value
        is always less than C2. Curvature directions are always output correspondingly,
        i.e. principal_direction_1 corresponds to principal_value_1 and principal_direction_2
        corresponds to principal_value_2.
        """
        # If we need not only curvature values, but principal curvature directions as well,
        # we have to solve an eigenvalue problem to find values and directions at once.

        # L p = lambda G p

        fu, fv = self.fu, self.fv
        duu, dvv, duv, nuu, nvv, nuv = self.duu, self.dvv, self.duv, self.nuu, self.nvv, self.nuv
        n = len(self.us)

        L = np.empty((n,2,2))
        L[:,0,0] = nuu
        L[:,0,1] = nuv
        L[:,1,0] = nuv
        L[:,1,1] = nvv

        G = np.empty((n,2,2))
        G[:,0,0] = duu
        G[:,0,1] = duv
        G[:,1,0] = duv
        G[:,1,1] = dvv

        M = np.matmul(np.linalg.inv(G), L)
        eigvals, eigvecs = np.linalg.eig(M)
        # Values of first and second principal curvatures
        c1 = eigvals[:,0]
        c2 = eigvals[:,1]

        if self.order:
            c1mask = (c1 < c2)
            c2mask = np.logical_not(c1mask)
            c1_r = np.where(c1mask, c1, c2)
            c2_r = np.where(c2mask, c1, c2)
        else:
            c1_r = c1
            c2_r = c2

        # dir_1 corresponds to c1, dir_2 corresponds to c2
        dir_1_x = eigvecs[:,0,0][np.newaxis].T
        dir_2_x = eigvecs[:,0,1][np.newaxis].T
        dir_1_y = eigvecs[:,1,0][np.newaxis].T
        dir_2_y = eigvecs[:,1,1][np.newaxis].T

        # another possible approach
#         A = duv * nvv - dvv*nuv 
#         B = duu * nvv - dvv*nuu
#         C = duu*nuv - duv*nuu
#         D = B*B - 4*A*C
#         t1 = (-B - np.sqrt(D)) / (2*A)
#         t2 = (-B + np.sqrt(D)) / (2*A)

        dir_1 = dir_1_x * fu + dir_1_y * fv
        dir_2 = dir_2_x * fu + dir_2_y * fv

        dir_1 = dir_1 / np.linalg.norm(dir_1, axis=1, keepdims=True)
        dir_2 = dir_2 / np.linalg.norm(dir_2, axis=1, keepdims=True)

        if self.order:
            c1maskT = c1mask[np.newaxis].T
            c2maskT = c2mask[np.newaxis].T
            dir_1_r = np.where(c1maskT, dir_1, -dir_2)
            dir_2_r = np.where(c2maskT, dir_1, dir_2)
        else:
            dir_1_r = dir_1
            dir_2_r = dir_2
        #r = (np.cross(dir_1_r, dir_2_r) * self.normals).sum(axis=1)
        #print(r)

        dir1_uv = eigvecs[:,:,0]
        dir2_uv = eigvecs[:,:,1]
        if self.order:
            c1maskT = c1mask[np.newaxis].T
            c2maskT = c2mask[np.newaxis].T
            dir1_uv_r = np.where(c1maskT, dir1_uv, -dir2_uv)
            dir2_uv_r = np.where(c2maskT, dir1_uv, dir2_uv)
        else:
            dir1_uv_r = dir1_uv
            dir2_uv_r = dir2_uv
            
        return c1_r, c2_r, dir1_uv_r, dir2_uv_r, dir_1_r, dir_2_r
class SurfaceCurvatureData

Container class for calculated curvature values

Expand source code
class SurfaceCurvatureData(object):
    """Container class for calculated curvature values"""
    def __init__(self):
        self.principal_value_1 = self.principal_value_2 = None
        self.principal_direction_1 = self.principal_direction_2 = None
        self.principal_direction_1_uv = self.principal_direction_2_uv = None
        self.mean = self.gauss = None
        self.matrix = None
class SurfaceDerivativesData (points, du, dv)
Expand source code
class SurfaceDerivativesData(object):
    def __init__(self, points, du, dv):
        self.points = points
        self.du = du
        self.dv = dv
        self._normals = None
        self._normals_len = None
        self._unit_normals = None
        self._unit_du = None
        self._unit_dv = None
        self._du_len = self._dv_len = None

    def normals(self):
        if self._normals is None:
            self._normals = np.cross(self.du, self.dv)
        return self._normals

    def normals_len(self):
        if self._normals_len is None:
            normals = self.normals()
            self._normals_len = np.linalg.norm(normals, axis=1)[np.newaxis].T
        return self._normals_len

    def unit_normals(self):
        if self._unit_normals is None:
            normals = self.normals()
            norm = self.normals_len()
            self._unit_normals = normals / norm
        return self._unit_normals

    def tangent_lens(self, keepdims=True):
        if self._du_len is None:
            self._du_len = np.linalg.norm(self.du, axis=1, keepdims=True)
            self._dv_len = np.linalg.norm(self.dv, axis=1, keepdims=True)
        return self._du_len, self._dv_len

    def unit_tangents(self):
        if self._unit_du is None:
            du_norm, dv_norm = self.tangent_lens()
            self._unit_du = self.du / du_norm
            self._unit_dv = self.dv / dv_norm
        return self._unit_du, self._unit_dv

    def matrices(self, as_mathutils = False):
        normals = self.unit_normals()
        du, dv = self.unit_tangents()
        matrices_np = np.dstack((du, dv, normals))
        if as_mathutils:
            matrices = [Matrix(m.tolist()).to_4x4() for m in matrices_np]
            for m, p in zip(matrices, self.points):
                m.translation = Vector(p)
            return matrices
        else:
            return matrices_np

Methods

def matrices(self, as_mathutils=False)
Expand source code
def matrices(self, as_mathutils = False):
    normals = self.unit_normals()
    du, dv = self.unit_tangents()
    matrices_np = np.dstack((du, dv, normals))
    if as_mathutils:
        matrices = [Matrix(m.tolist()).to_4x4() for m in matrices_np]
        for m, p in zip(matrices, self.points):
            m.translation = Vector(p)
        return matrices
    else:
        return matrices_np
def normals(self)
Expand source code
def normals(self):
    if self._normals is None:
        self._normals = np.cross(self.du, self.dv)
    return self._normals
def normals_len(self)
Expand source code
def normals_len(self):
    if self._normals_len is None:
        normals = self.normals()
        self._normals_len = np.linalg.norm(normals, axis=1)[np.newaxis].T
    return self._normals_len
def tangent_lens(self, keepdims=True)
Expand source code
def tangent_lens(self, keepdims=True):
    if self._du_len is None:
        self._du_len = np.linalg.norm(self.du, axis=1, keepdims=True)
        self._dv_len = np.linalg.norm(self.dv, axis=1, keepdims=True)
    return self._du_len, self._dv_len
def unit_normals(self)
Expand source code
def unit_normals(self):
    if self._unit_normals is None:
        normals = self.normals()
        norm = self.normals_len()
        self._unit_normals = normals / norm
    return self._unit_normals
def unit_tangents(self)
Expand source code
def unit_tangents(self):
    if self._unit_du is None:
        du_norm, dv_norm = self.tangent_lens()
        self._unit_du = self.du / du_norm
        self._unit_dv = self.dv / dv_norm
    return self._unit_du, self._unit_dv