3d matplotlib二维面片如何转换为具有任意法线的三维面片? 简短问题

3d matplotlib二维面片如何转换为具有任意法线的三维面片? 简短问题,3d,matplotlib,3d,Matplotlib,matplotlib二维面片如何转换为具有任意法线的三维面片 长问题 我想用3d投影绘制轴。但是,提供的方法仅提供沿主轴具有法线的面片的方法。如何向具有任意法线的三维轴添加面片?简短回答 将下面的代码复制到项目中并使用 def pathpatch_2d_to_3d(pathpatch, z = 0, normal = 'z'): """ Transforms a 2D Patch to a 3D patch using the given normal vector.

matplotlib二维面片如何转换为具有任意法线的三维面片

长问题 我想用3d投影绘制轴。但是,提供的方法仅提供沿主轴具有法线的面片的方法。如何向具有任意法线的三维轴添加面片?

简短回答 将下面的代码复制到项目中并使用

def pathpatch_2d_to_3d(pathpatch, z = 0, normal = 'z'):
    """
    Transforms a 2D Patch to a 3D patch using the given normal vector.

    The patch is projected into they XY plane, rotated about the origin
    and finally translated by z.
    """
将二维面片转换为具有任意法线的三维面片

from mpl_toolkits.mplot3d import art3d

def rotation_matrix(d):
    """
    Calculates a rotation matrix given a vector d. The direction of d
    corresponds to the rotation axis. The length of d corresponds to 
    the sin of the angle of rotation.

    Variant of: http://mail.scipy.org/pipermail/numpy-discussion/2009-March/040806.html
    """
    sin_angle = np.linalg.norm(d)

    if sin_angle == 0:
        return np.identity(3)

    d /= sin_angle

    eye = np.eye(3)
    ddt = np.outer(d, d)
    skew = np.array([[    0,  d[2],  -d[1]],
                  [-d[2],     0,  d[0]],
                  [d[1], -d[0],    0]], dtype=np.float64)

    M = ddt + np.sqrt(1 - sin_angle**2) * (eye - ddt) + sin_angle * skew
    return M

def pathpatch_2d_to_3d(pathpatch, z = 0, normal = 'z'):
    """
    Transforms a 2D Patch to a 3D patch using the given normal vector.

    The patch is projected into they XY plane, rotated about the origin
    and finally translated by z.
    """
    if type(normal) is str: #Translate strings to normal vectors
        index = "xyz".index(normal)
        normal = np.roll((1.0,0,0), index)

    normal /= np.linalg.norm(normal) #Make sure the vector is normalised

    path = pathpatch.get_path() #Get the path and the associated transform
    trans = pathpatch.get_patch_transform()

    path = trans.transform_path(path) #Apply the transform

    pathpatch.__class__ = art3d.PathPatch3D #Change the class
    pathpatch._code3d = path.codes #Copy the codes
    pathpatch._facecolor3d = pathpatch.get_facecolor #Get the face color    

    verts = path.vertices #Get the vertices in 2D

    d = np.cross(normal, (0, 0, 1)) #Obtain the rotation vector    
    M = rotation_matrix(d) #Get the rotation matrix

    pathpatch._segment3d = np.array([np.dot(M, (x, y, 0)) + (0, 0, z) for x, y in verts])

def pathpatch_translate(pathpatch, delta):
    """
    Translates the 3D pathpatch by the amount delta.
    """
    pathpatch._segment3d += delta
长话短说 查看art3d.pathpatch_2d_to_3d的源代码可以得到以下调用层次结构

  • art3d.pathpatch_2d_至_3d
  • art3d.PathPatch3D.set_3d_属性
  • art3d.Patch3D.set\u 3d\u属性
  • art3d.摆弄轴
  • 从2D到3D的转换发生在最后一次调用
    art3d.juggle_axes
    时。修改最后一步,我们可以获得具有任意法线的3D面片

    我们分四步进行

  • 将面片的顶点投影到XY平面(
    pathpatch_2d_到_3d
  • 计算将z方向旋转到法线方向的旋转矩阵R(
    旋转矩阵
  • 将旋转矩阵应用于所有顶点(
    pathpatch\u 2d\u到\u 3d
  • 沿z方向平移生成的对象(
    pathpatch_2d_到_3d
  • 示例源代码和结果图如下所示

    from mpl_toolkits.mplot3d import proj3d
    from matplotlib.patches import Circle
    from itertools import product
    
    ax = axes(projection = '3d') #Create axes
    
    p = Circle((0,0), .2) #Add a circle in the yz plane
    ax.add_patch(p)
    pathpatch_2d_to_3d(p, z = 0.5, normal = 'x')
    pathpatch_translate(p, (0, 0.5, 0))
    
    p = Circle((0,0), .2, facecolor = 'r') #Add a circle in the xz plane
    ax.add_patch(p)
    pathpatch_2d_to_3d(p, z = 0.5, normal = 'y')
    pathpatch_translate(p, (0.5, 1, 0))
    
    p = Circle((0,0), .2, facecolor = 'g') #Add a circle in the xy plane
    ax.add_patch(p)
    pathpatch_2d_to_3d(p, z = 0, normal = 'z')
    pathpatch_translate(p, (0.5, 0.5, 0))
    
    for normal in product((-1, 1), repeat = 3):
        p = Circle((0,0), .2, facecolor = 'y', alpha = .2)
        ax.add_patch(p)
        pathpatch_2d_to_3d(p, z = 0, normal = normal)
        pathpatch_translate(p, 0.5)
    

    这段代码非常有用,但有一点需要注意:它无法处理向下的法线,因为它只使用角度的正弦

    您还需要使用余弦:

    from mpl_toolkits.mplot3d import Axes3D
    from mpl_toolkits.mplot3d import art3d
    from mpl_toolkits.mplot3d import proj3d
    import numpy as np
    
    def rotation_matrix(v1,v2):
        """
        Calculates the rotation matrix that changes v1 into v2.
        """
        v1/=np.linalg.norm(v1)
        v2/=np.linalg.norm(v2)
    
        cos_angle=np.dot(v1,v2)
        d=np.cross(v1,v2)
        sin_angle=np.linalg.norm(d)
    
        if sin_angle == 0:
            M = np.identity(3) if cos_angle>0. else -np.identity(3)
        else:
            d/=sin_angle
    
            eye = np.eye(3)
            ddt = np.outer(d, d)
            skew = np.array([[    0,  d[2],  -d[1]],
                          [-d[2],     0,  d[0]],
                          [d[1], -d[0],    0]], dtype=np.float64)
    
            M = ddt + cos_angle * (eye - ddt) + sin_angle * skew
    
        return M
    
    def pathpatch_2d_to_3d(pathpatch, z = 0, normal = 'z'):
        """
        Transforms a 2D Patch to a 3D patch using the given normal vector.
    
        The patch is projected into they XY plane, rotated about the origin
        and finally translated by z.
        """
        if type(normal) is str: #Translate strings to normal vectors
            index = "xyz".index(normal)
            normal = np.roll((1,0,0), index)
    
        path = pathpatch.get_path() #Get the path and the associated transform
        trans = pathpatch.get_patch_transform()
    
        path = trans.transform_path(path) #Apply the transform
    
        pathpatch.__class__ = art3d.PathPatch3D #Change the class
        pathpatch._code3d = path.codes #Copy the codes
        pathpatch._facecolor3d = pathpatch.get_facecolor #Get the face color    
    
        verts = path.vertices #Get the vertices in 2D
    
        M = rotation_matrix(normal,(0, 0, 1)) #Get the rotation matrix
    
        pathpatch._segment3d = np.array([np.dot(M, (x, y, 0)) + (0, 0, z) for x, y in verts])
    
    def pathpatch_translate(pathpatch, delta):
        """
        Translates the 3D pathpatch by the amount delta.
        """
        pathpatch._segment3d += delta
    

    这里有一种更通用的方法,它允许以比普通方法更复杂的方式嵌入:

    class EmbeddedPatch2D(art3d.PathPatch3D):
        def __init__(self, patch, transform):
            assert transform.shape == (4, 3)
    
            self._patch2d = patch
            self.transform = transform
    
            self._path2d = patch.get_path()
            self._facecolor2d = patch.get_facecolor()
    
            self.set_3d_properties()
    
        def set_3d_properties(self, *args, **kwargs):
            # get the fully-transformed path
            path = self._patch2d.get_path()
            trans = self._patch2d.get_patch_transform()
            path = trans.transform_path(path)
    
            # copy across the relevant properties
            self._code3d = path.codes
            self._facecolor3d = self._patch2d.get_facecolor()
    
            # calculate the transformed vertices
            verts = np.empty(path.vertices.shape + np.array([0, 1]))
            verts[:,:-1] = path.vertices
            verts[:,-1] = 1
            self._segment3d = verts.dot(self.transform.T)[:,:-1]
    
        def __getattr__(self, key):
            return getattr(self._patch2d, key)
    
    要按照问题中的要求使用它,我们需要一个helper函数

    def matrix_from_normal(normal):
        """
        given a normal vector, builds a homogeneous rotation matrix such that M.dot([1, 0, 0]) == normal
        """ 
        normal = normal / np.linalg.norm(normal)
        res = np.eye(normal.ndim+1)
        res[:-1,0] = normal
        if normal [0] == 0:
            perp = [0, -normal[2], normal[1]]
        else:
            perp = np.cross(normal, [1, 0, 0])
            perp /= np.linalg.norm(perp)
        res[:-1,1] = perp
        res[:-1,2] = np.cross(self.dir, perp)
        return res
    
    总而言之:

    circ = Circle((0,0), .2, facecolor = 'y', alpha = .2)
    # the matrix here turns (x, y, 1) into (0, x, y, 1)
    mat = matrix_from_normal([1, 1, 0]).dot([
        [0, 0, 0],
        [1, 0, 0],
        [0, 1, 0],
        [0, 0, 1]
    ])
    circ3d = EmbeddedPatch2D(circ, mat)
    

    你怎么能这么快回答自己的问题?我只是想分享我的解决方案()。每次你问问题时,底部都有一个小按钮;如果你想马上回答你的问题,你可以勾选它。@TillHoffmann你能用这种方法把绿色的圆环变成没有填充物的圆柱体吗?我想做一些类似的事情,在曲面图上绘制3d面片形状。@代码新手:是的,这在原则上是可能的,但在实践中很棘手。你必须画两个圆圈,以圆柱体的高度分开,然后用线连接两个端盖。但是,线条的位置取决于您的视角,并且必须在交互设置中不断更新。