Repository: ManimCommunity/manim
File: manim/mobject/polyhedra.py (Python)

Code before:
    These are the corners of the square base, given as the first four coordinates in the vertex list,
    and the apex, the last coordinate in the list.

    Next, we define the faces of the polyhedron. The triangular surfaces of the pyramid are polygons
    with two adjacent vertices in the base and the vertex at the apex as corners. We thus define these
    surfaces in the first four elements of our face list. The last element defines the base of the pyramid.

    The graph and faces of polyhedra can also be accessed and modified directly, after instantiation.
    They are stored in the `graph` and `faces` attributes respectively.

    .. manim:: PolyhedronSubMobjects
        :save_last_frame:

        class PolyhedronSubMobjects(ThreeDScene):
            def construct(self):
                self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)
                octahedron = Octahedron(edge_length = 3)
                octahedron.graph[0].set_color(RED)
                octahedron.faces[2].set_color(YELLOW)
                self.add(octahedron)
    """

    def __init__(
        self,
        vertex_coords: List[np.ndarray],
        faces_list: List[List[Hashable]],
        faces_config: dict = {},
        graph_config: dict = {},
    ):
        VGroup.__init__(self)
        self.faces_config = dict(
            {"fill_opacity": 0.5, "shade_in_3d": True}, **faces_config
        )
        self.graph_config = dict(
            {
                "vertex_type": Dot3D,
                "edge_config": {
                    "stroke_opacity": 0,  # I find that having the edges visible makes the polyhedra look weird
                },
            },
            **graph_config
        )
        self.vertex_coords = vertex_coords
        self.vertex_indices = list(range(len(self.vertex_coords)))
        self.layout = dict(enumerate(self.vertex_coords))
        self.faces_list = faces_list
        self.face_coords = [[self.layout[j] for j in i] for i in faces_list]
        self.edges = self.get_edges(self.faces_list)
        self.faces = self.create_faces(self.face_coords)
        self.graph = Graph(
            self.vertex_indices, self.edges, layout=self.layout, **self.graph_config

Review comment (suggestion):
Is `Hashable` correct? Shouldn't it only be integers and float or `np.ndarray`? Because `dict` is also a `Hashable` type.

Code after:
                self.add(pyramid)

    In defining the polyhedron above, we first defined the coordinates of the vertices.
    These are the corners of the square base, given as the first four coordinates in the vertex list,
    and the apex, the last coordinate in the list.

    Next, we define the faces of the polyhedron. The triangular surfaces of the pyramid are polygons
    with two adjacent vertices in the base and the vertex at the apex as corners. We thus define these
    surfaces in the first four elements of our face list. The last element defines the base of the pyramid.

    The graph and faces of polyhedra can also be accessed and modified directly, after instantiation.
    They are stored in the `graph` and `faces` attributes respectively.

    .. manim:: PolyhedronSubMobjects
        :save_last_frame:

        class PolyhedronSubMobjects(ThreeDScene):
            def construct(self):
                self.set_camera_orientation(phi=75 * DEGREES, theta=30 * DEGREES)
                octahedron = Octahedron(edge_length = 3)
                octahedron.graph[0].set_color(RED)
                octahedron.faces[2].set_color(YELLOW)
                self.add(octahedron)
    """

    def __init__(
        self,
        vertex_coords: List[Union[List[float], np.ndarray]],
        faces_list: List[List[int]],
        faces_config: Dict[str, Union[str, int, float, bool]] = {},
        graph_config: Dict[str, Union[str, int, float, bool]] = {},
    ):
        VGroup.__init__(self)
        self.faces_config = dict(
            {"fill_opacity": 0.5, "shade_in_3d": True}, **faces_config
        )
        self.graph_config = dict(
            {
                "vertex_type": Dot3D,
                "edge_config": {
                    "stroke_opacity": 0,  # I find that having the edges visible makes the polyhedra look weird
                },
            },
            **graph_config
        )
        self.vertex_coords = vertex_coords
        self.vertex_indices = list(range(len(self.vertex_coords)))
        self.layout = dict(enumerate(self.vertex_coords))
        self.faces_list = faces_list
        self.face_coords = [[self.layout[j] for j in i] for i in faces_list]
        self.edges = self.get_edges(self.faces_list)