问题描述
如何将 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
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
) - 计算一个将z方向旋转到法线方向的旋转矩阵R(
rotation_matrix
) - 将旋转矩阵应用于所有顶点 (
pathpatch_2d_to_3d
) - 在 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?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!