如何使用任意法线将 matplotlib 2D 补丁转换为 3D? [英] How can matplotlib 2D patches be transformed to 3D with arbitrary normals?

查看:36
本文介绍了如何使用任意法线将 matplotlib 2D 补丁转换为 3D?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何将 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?

推荐答案

简短回答

将下面的代码复制到你的项目中并使用方法

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,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的源码给出如下调用层次

Long answer

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

  1. art3d.pathpatch_2d_to_3d
  2. art3d.PathPatch3D.set_3d_properties
  3. art3d.Patch3D.set_3d_properties
  4. 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.

我们分四步进行

  1. 将面片的顶点投影到 XY 平面 (pathpatch_2d_to_3d)
  2. 计算一个将z方向旋转到法线方向的旋转矩阵R(rotation_matrix)
  3. 将旋转矩阵应用于所有顶点 (pathpatch_2d_to_3d)
  4. 在 z 方向翻译结果对象 (pathpatch_2d_to_3d)

示例源代码和结果图如下所示.

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)

这篇关于如何使用任意法线将 matplotlib 2D 补丁转换为 3D?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆