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面片。
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
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
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)
# creation of a rotation matrix that preserves the x-axis in an xy-plane of the original coordinate system
def rotationMatrix(normal):
norm = np.linalg.norm(normal)
if norm ==0: return Rotation.identity(None)
zDir = normal/norm
if np.abs(zDir[2])==1:
yDir = np.array([0,zDir[2],0])
else:
yDir = (np.array([0,0,1]) - zDir[2]*zDir)/math.sqrt(1-zDir[2]**2)
rotMat = np.empty((3,3))
rotMat[:,0] = np.cross(zDir,yDir)
rotMat[:,1] = yDir
rotMat[:,2] = -zDir
return Rotation.from_matrix(rotMat)
def toVector(vec):
if vec is None or not isinstance(vec,np.ndarray) : vec="z"
if isinstance(vec,str):
zdir = vec[0] if len(vec)>0 else "z"
if not zdir in "xyz": zdir="z"
index = "xyz".index(vec)
return np.roll((1.0,0,0), index)
else:
return vec
# Transforms a 2D Patch to a 3D patch using a pivot point and a the given normal vector.
def pathpatch_2d_to_3d(pathpatch, pivot=np.zeros(3), zDir='z'):
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__ = mplot3d.art3d.PathPatch3D #Change the class
pathpatch._path2d = path #Copy the 2d path
pathpatch._code3d = path.codes #Copy the codes
pathpatch._facecolor3d = pathpatch.get_facecolor #Get the face color
# Get the 2D vertices and add the third dimension
verts3d = np.empty((path.vertices.shape[0],3))
verts3d[:,0:2] = path.vertices
verts3d[:,2] = pivot[2]
R = rotationMatrix(toVector(zDir))
pathpatch._segment3d = R.apply(verts3d - pivot) + pivot
return pathpatch
# places a 3D text element in axes with 3d projection.
def text3d(xyz, text, zDir="z", scalefactor=1.0, fp=FontProperties(), **kwargs):
pt = PathPatch(TextPath(xyz[0:2], text, size=scalefactor*fp.get_size(), prop=fp , usetex=False),**kwargs)
ax3D.add_patch(pathpatch_2d_to_3d(pt, xyz, zDir))
# places a 3D circle in axes with 3d projection.
def circle3d(center, radius, zDir='z', **kwargs):
pc = Circle(center[0:2], radius, **kwargs)
ax3D.add_patch(pathpatch_2d_to_3d(pc, center, zDir))
5条答案
按热度按时间sy5wg1nm1#
简答
将下面的代码复制到您的项目中并使用方法
将2D面片转换为具有任意法线的3D面片。
长回答
查看art3d.pathpatch_2d_to_3d的源代码,可以得到以下调用层次结构
art3d.pathpatch_2d_to_3d
art3d.PathPatch3D.set_3d_properties
art3d.Patch3D.set_3d_properties
art3d.juggle_axes
从2D到3D的转换发生在最后一次调用
art3d.juggle_axes
时。修改最后一步,我们可以获得具有任意法线的3D面片。我们分四步进行
1.将面片的顶点投影到XY平面(
pathpatch_2d_to_3d
)1.计算将z方向旋转到法线方向的旋转矩阵R(
rotation_matrix
)1.将旋转矩阵应用于所有顶点(
pathpatch_2d_to_3d
)1.沿z方向平移生成的对象(
pathpatch_2d_to_3d
)示例源代码和结果图如下所示。
xmd2e60i2#
非常有用的代码,但有一个小警告:它不能处理指向下的法线,因为它只使用Angular 的正弦。
你还需要使用余弦:
ix0qys7i3#
这里有一个更通用的方法,它允许以比沿着法线更复杂的方式嵌入:
要按照问题中的要求使用它,我们需要一个helper函数
一起来:
b4qexyjb4#
我想分享我的解决方案,扩展了以前的建议。它允许将3D元素和文本添加到Axes3D演示文稿中。
nlejzf6q5#
从matplotlib 3.7开始,你可以对
Patch3D.set_3d_properties
进行猴子补丁,art3d.pathpatch_2d_to_3d
就可以正常工作了。这里,
_transform_zdir
是T. Lepage's answer中rotation_matrix
的简化版本。那么,被接受的答案中的例子将简化为更方便的是,您可以使用
然后