Question: I want to draw a patch in Matplotlib constructed by hollowing it out with another patch, in a way such that the hollowed out part is completely transparent. For example, lets say I wanted to draw an ellipse hollowed out by another. I could do the following: import matplotlib.pyplot as plt from matplotlib.patches import Ellipse ellipse_1 = Ellipse((0,0), 4, 3, color='blue') ellipse_2 = Ellipse((0.5,0.25), 2, 1, angle=30, color='white') ax = plt.axes() ax.add_artist(ellipse_1) ax.add_artist(ellipse_2) plt.axis('equal') plt.axis((-3,3,-3,3)) plt.show()   However, if now I wanted to draw something behind, the part behind the hollowed out part would not be visible, for example: import matplotlib.pyplot as plt from matplotlib.patches import Ellipse, Rectangle ellipse_1 = Ellipse((0,0), 4, 3, color='blue') ellipse_2 = Ellipse((0.5,0.25), 2, 1, angle=30, color='white') rectangle = Rectangle((-2.5,-2), 5, 2, color='red') ax = plt.axes() ax.add_artist(rectangle) ax.add_artist(ellipse_1) ax.add_artist(ellipse_2) plt.axis('equal') plt.axis((-3,3,-3,3)) plt.show()   where the part of the red rectangle inside the blue shape cannot be seen. Is there an easy way to do this? Another way to solve this would be with a function to do the opposite of set_clip_path, lets say set_anticlip_path, where the line ellipse_1.set_anticlip_path(ellipse_2)  would do the trick, but I have not been able to find anything like this.

Answer: Approach for ellipses The following is a simple approach that works for the ellipse example (and, generally, for symmetric objects): import matplotlib.pyplot as plt from matplotlib.patches import Ellipse, Rectangle, PathPatch from matplotlib.path import Path from matplotlib.transforms import Affine2D ellipse_1 = Ellipse((0, 0), 4, 3, color='blue') ellipse_2 = Ellipse((0.5, 0.25), 2, 1, angle=30, color='white') rectangle = Rectangle((-2.5, -2), 5, 2, color='red') # Provide a flipping transform to reverse one of the paths flip = Affine2D().scale(-1, 1).transform transform_1 = ellipse_1.get_patch_transform().transform transform_2 = ellipse_2.get_patch_transform().transform vertices_1 = ellipse_1.get_path().vertices.copy() vertices_2 = ellipse_2.get_path().vertices.copy() # Combine the paths, create a PathPatch from the combined path combined_path = Path( transform_1(vertices_1).tolist() + transform_2(flip(vertices_2)).tolist(), ellipse_1.get_path().codes.tolist() + ellipse_2.get_path().codes.tolist(), ) combined_ellipse = PathPatch(combined_path, color='blue') ax = plt.axes() ax.add_artist(rectangle) ax.add_artist(combined_ellipse) plt.axis('equal') plt.axis((-3, 3, -3, 3)) plt.show()  Produces:  Key ideas:  We can combine their paths by getting the vertices and codes of the two ellipses and concatenating them within a new matplotlib.path.Path instance. We need to reverse one of the paths before combining, or otherwise the intersection will not be transparent. We do so with an appropriate matplotlib.transforms.Affine2D, which we use for flipping one of the given ellipses. We need to get the paths into the correct coordinate system before plotting. We do so by applying each ellipse's get_patch_transform() result before concatenating the vertices.  Generalized approach Reversing the direction of one of the paths really is important here. It is not straightforward, however. Flipping is a bit of a "cheat" above, as it only works because an ellipse has a symmetric shape. For a generic path, we need to  disassemble it according to its codes (which describe how the vertices are connected) into connected segments, reverse the resulting segments (where we need to take extra care that the codes need to be reversed a bit differently from the vertices), and then reassemble it:  import matplotlib.pyplot as plt from matplotlib.patches import Ellipse, Rectangle, PathPatch, Annulus from matplotlib.path import Path ellipse = Ellipse((0, 0), 4, 3, color='blue') annulus = Annulus((0.5, 0.25), (2/2, 1/2), 0.9/2, angle=30, color='white') rectangle = Rectangle((-2.5, -2), 5, 2, color='red') def reverse(vertices, codes): # Codes (https://matplotlib.org/stable/api/path_api.html#matplotlib.path.Path): MOVETO = 1 # "Pick up the pen and move to the given vertex." CLOSE = 79 # "Draw a line segment to the start point of the current polyline." # LINETO = 2: "Draw a line from the current position to the given vertex." # CURVE3 = 3: "Draw a quadratic Bézier curve from the current position, # with the given control point, to the given end point." # CURVE4 = 4: "Draw a cubic Bézier curve from the current position, # with the given control points, to the given end point." assert len(vertices) == len(codes), f"Length mismatch: {len(vertices)=} vs. {len(codes)=}" vertices, codes = list(vertices), list(codes) assert codes[0] == MOVETO, "Path should start with MOVETO" if CLOSE in codes: # Check if the path is closed assert codes.count(CLOSE) == 1, "CLOSEPOLY should not occur more than once" assert codes[-1] == CLOSE, "CLOSEPOLY should only appear at the last index" vertices, codes = vertices[:-1], codes[:-1] # Ignore CLOSEPOLY for now is_closed = True else: is_closed = False # Split the path into segments, where segments start at MOVETO segmented_vertices, segmented_codes = [], [] for vertex, code in zip(vertices, codes): if code == MOVETO: # Start a new segment segmented_vertices.append([vertex]) segmented_codes.append([code]) else: # Append to current segment segmented_vertices[-1].append(vertex) segmented_codes[-1].append(code) # Reverse and concatenate rev_vertices = [val for seg in segmented_vertices for val in reversed(seg)] rev_codes = [val for seg in segmented_codes for val in [seg[0]] + seg[1:][::-1]] if is_closed: # Close again if necessary, by appending CLOSEPOLY rev_codes.append(CLOSE) rev_vertices.append([0., 0.]) return rev_vertices, rev_codes transform_1 = ellipse.get_patch_transform().transform transform_2 = annulus.get_patch_transform().transform vertices_1 = ellipse.get_path().vertices.copy() vertices_2 = annulus.get_path().vertices.copy() codes_1 = ellipse.get_path().codes.tolist() codes_2 = annulus.get_path().codes.tolist() vertices_2, codes_2 = reverse(vertices_2, codes_2) # Reverse one path # Combine the paths, create a PathPatch from the combined path combined_path = Path( transform_1(vertices_1).tolist() + transform_2(vertices_2).tolist(), codes_1 + codes_2, ) combined_ellipse = PathPatch(combined_path, color='blue') ax = plt.axes() ax.add_artist(rectangle) ax.add_artist(combined_ellipse) plt.axis('equal') plt.axis((-3, 3, -3, 3)) plt.show()  Produces:  Key ideas:  The reverse() function splits the path at points where the drawing pen is moved, and only reverses the segments in-between.  Consider the pseudo-code example of a path segment with three vertices V1, V2, V3, connected by a line between V1–V2 and a curve between V2–V3 with control point C:  The (forward) path would be [move_to V1, line_to V2, curve_to C, curve_to V3]. The reversed path would be [move_to V3, curve_to C, curve_to V2, line_to V1].  We can see here, that we need to reverse the codes (move_to, curve_to, line_to) different than the points (vertices V1, V2, V3; control point C):  The points just need to be reversed completely. The first code always needs to be move_to, then the remaining codes need to be reversed.   For a closed path, the value 79 (CLOSEPOLY which I shortened to CLOSE above) always seems to be the last code. We take care of it by checking for it, removing it if present, and appending it again at the end, if necessary. The values of its associated vertex do not matter.