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

后端 未结 3 1404
天命终不由人
天命终不由人 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条回答
  •  旧时难觅i
    2020-12-01 17:24

    Here's a more generalmethod that allows embedding in more complex ways than along a normal:

    class EmbeddedPatch2D(art3d.PathPatch3D):
        def __init__(self, patch, transform):
            assert transform.shape == (4, 3)
    
            self._patch2d = patch
            self.transform = transform
    
            self._path2d = patch.get_path()
            self._facecolor2d = patch.get_facecolor()
    
            self.set_3d_properties()
    
        def set_3d_properties(self, *args, **kwargs):
            # get the fully-transformed path
            path = self._patch2d.get_path()
            trans = self._patch2d.get_patch_transform()
            path = trans.transform_path(path)
    
            # copy across the relevant properties
            self._code3d = path.codes
            self._facecolor3d = self._patch2d.get_facecolor()
    
            # calculate the transformed vertices
            verts = np.empty(path.vertices.shape + np.array([0, 1]))
            verts[:,:-1] = path.vertices
            verts[:,-1] = 1
            self._segment3d = verts.dot(self.transform.T)[:,:-1]
    
        def __getattr__(self, key):
            return getattr(self._patch2d, key)
    

    To use this as desired in the question, we need a helper function

    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
    

    All together:

    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)
    

提交回复
热议问题