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

后端 未结 3 1403
天命终不由人
天命终不由人 2020-12-01 16:52

Short question

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

Long question

I would like to plot Patches in axes with

3条回答
  •  予麋鹿
    予麋鹿 (楼主)
    2020-12-01 17:27

    Very useful piece of code, but there is a small caveat: it cannot handle normals pointing downwards because it uses only the sine of the angle.

    You need to use also the cosine:

    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
    

提交回复
热议问题