如何matplotlib 2D补丁转化为3D任意法线?法线、转化为、补丁、matplotlib

2023-09-08 01:15:19 作者:美骚男.

如何matplotlib 2D补丁转化为3D任意法线?

How can matplotlib 2D patches be transformed to 3D with arbitrary normals?

我想绘制补丁与3D投影轴。然而,该方法所提供的mpl_toolkits.mplot3d.art3d只提供方法与沿主轴法线补丁。如何添加补丁到具有任意法线3D轴?

I would like to plot Patches in axes with 3d projection. However, the methods provided by mpl_toolkits.mplot3d.art3d only provide methods to have patches with normals along the principal axes. How can I add patches to 3d axes that have arbitrary normals?

推荐答案

下面复制code到你的项目和使用的方法

Short answer

Copy the code below into your project and use the method

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.
    """

将2D补丁变换任意法线3D补丁。

to transform your 2D patches to 3D patches with arbitrary normals.

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), index)

    normal /= 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源$ C ​​$ C给出了下面的调用层次

Long answer

Looking at the source code of art3d.pathpatch_2d_to_3d gives the following call hierarchy

art3d.pathpatch_2d_to_3d art3d.PathPatch3D.set_3d_properties art3d.Patch3D.set_3d_properties art3d.juggle_axes art3d.pathpatch_2d_to_3d art3d.PathPatch3D.set_3d_properties art3d.Patch3D.set_3d_properties art3d.juggle_axes

从2D到3D的转变发生在最后调用 art3d.juggle_axes 。修改这最后一步,我们就可以在3D获取补丁任意法线。

The transformation from 2D to 3D happens in the last call to art3d.juggle_axes. Modifying this last step, we can obtain patches in 3D with arbitrary normals.

我们进行四步

项目补丁的顶点到XY平面( pathpatch_2d_to_3d ) 计算旋转矩阵R旋转的Z方向的方向正常( rotation_matrix ) 应用旋转矩阵来所有的顶点( pathpatch_2d_to_3d ) 翻译在z方向得到的对象( pathpatch_2d_to_3d ) Project the vertices of the patch into the XY plane (pathpatch_2d_to_3d) Calculate a rotation matrix R that rotates the z direction to the direction of the normal (rotation_matrix) Apply the rotation matrix to all vertices (pathpatch_2d_to_3d) Translate the resulting object in the z-direction (pathpatch_2d_to_3d)

样本源$ C ​​$ c和所得积如下所示。

Sample source code and the resulting plot are shown below.

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)