Skip to content

Surfaces and solids

The film's code
import manimgx as m


class SurfacesHero(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=65 * m.DEGREES, theta=-50 * m.DEGREES)
        shapes = m.Group(
            m.Sphere(radius=0.9),
            m.Torus(major_radius=0.8, minor_radius=0.3),
            m.Cylinder(radius=0.7, height=1.6),
            m.Cone(base_radius=0.8, height=1.6),
            m.Cube(side_length=1.4),
            m.Icosahedron(edge_length=1.2),
        )
        shapes.arrange_in_grid(rows=2, buff=0.9)
        self.add(m.SunLight(4 * m.UP + 3 * m.OUT), m.AmbientLight(intensity=0.3))
        self.play(m.LaggedStart(*[m.FadeIn(shape) for shape in shapes], lag_ratio=0.15))
        self.wait()

A surface is the set of points a function of two numbers makes: func(u, v) for u and v over their ranges. A sphere, a torus, a cylinder and a cone are surfaces with their functions made for you. A solid with flat faces is a polyhedron, from a cube to an icosahedron. All show in a 3D scene, shaded by its lights.

Surfaces

Surface

SurfaceExample
Code
import numpy as np

import manimgx as m


class SurfaceExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=60 * m.DEGREES, theta=-60 * m.DEGREES)

        def mobius(u: float, v: float) -> np.ndarray:
            r = 2.5 + v * np.cos(u / 2)
            return np.array([r * np.cos(u), r * np.sin(u), v * np.sin(u / 2)])

        strip = m.Surface(
            mobius, u_range=(0, m.TAU), v_range=(-1, 1), resolution=(48, 6)
        )
        self.add(strip)

A parametric surface: the points func(u, v) for u and v over their ranges, drawn as the smooth surface through them; checkered in two blues, with thin light grey edges, and shaded by a three-dimensional scene's light, unless styled.

The ranges are divided into resolution steps, along u and along v, and the function is sampled at the corners of the grid's cells, the surface's faces. What is drawn is the smooth surface through those samples, not flat faces: resolution sets how closely it follows the function, and the light shades it smoothly, across the seam too where the surface closes on itself (a sphere, a torus, a cylinder). Each face keeps its own colors, so a checkerboard keeps sharp cells, and the stroke draws the faces' edges, curved with the surface. The faces are one mesh (a MeshMobject): it morphs into other meshes (drawn as flat faces while it morphs into one of another grid), and Create draws it in face by face, each face with its edges. set_fill_by_checkerboard and set_fill_by_value color it.

The checkerboard palette supplies the initial paint; use set_fill_by_checkerboard to recolor the surface later. The initial palette and ignored compatibility options are not retained as instance attributes.

m.Surface(func, u_range=(0, 1), v_range=(0, 1), resolution=32, surface_piece_config=None, checkerboard_colors=(BLUE_D, BLUE_E), should_make_jagged=False, pre_function_handle_to_anchor_scale_factor=1e-05, **kwargs)
func

The function, from (u, v) to a point in scene coordinates (on axes, their c2p of coordinates).

u_range

The range of u, (u_min, u_max).

v_range

The range of v, (v_min, v_max).

resolution

How many faces the surface has along u and along v: one positive integer for both, or a pair of positive integers (u, v).

surface_piece_config

Accepted for Manim compatibility; ignored.

checkerboard_colors

The initial checkerboard colors, in turn; False for the fill color alone.

should_make_jagged

Accepted for Manim compatibility; ignored.

pre_function_handle_to_anchor_scale_factor

Accepted for Manim compatibility; ignored.

color

The color of both fill and stroke (default white); None for the class's default.

fill_color

The fill's color; color if not given. Several colors make a gradient along sheen_direction.

fill_opacity

The fill's opacity, from 0 to 1 (default 0: no fill).

stroke_color

The stroke's color; color if not given. Several colors make a gradient.

stroke_opacity

The stroke's opacity, from 0 to 1 (default 1).

stroke_width

The stroke's width, in hundredths of a scene unit (default 4; 0: no stroke).

background_stroke_color

The color of an outline drawn behind the fill (default black).

background_stroke_opacity

The outline's opacity, from 0 to 1 (default 1).

background_stroke_width

The outline's width, in hundredths of a scene unit (default 0: none).

sheen_factor

How much the colors lighten toward sheen_direction, from -1 to 1 (default 0); a negative factor darkens.

sheen_direction

The direction the colors lighten toward (default UL).

joint_type

How the stroke is joined where its path turns: round, beveled or mitered, as a two-dimensional scene draws it (see LineJointType; default AUTO: mitered).

cap_style

How the stroke ends, at each end it shows (an open path's, a dash's): round, butt or square, as a two-dimensional scene draws it (see CapStyleType; default AUTO: butt).

shade_in_3d

Whether a three-dimensional scene's light shades the mobject.

material

How its surface reflects the scene's lights in a three-dimensional scene (see Material); None: Manim's shading (default).

name

A name for the mobject; its class's name if not given.

z_index

Its place in the drawing order: a higher index is drawn over a lower one (default 0).

target

The state MoveToTarget moves the mobject to.

It also takes the style keywords.

Source

src/manimgx/mobjects/three_d.py

def __init__(
    self,
    func: Callable[[float, float], np.ndarray],
    u_range: Sequence[float] = (0, 1),
    v_range: Sequence[float] = (0, 1),
    resolution: int | Sequence[int] = 32,
    surface_piece_config: Style | None = None,
    checkerboard_colors: Iterable[ParsableManimColor] | Literal[False] = (
        BLUE_D,
        BLUE_E,
    ),
    should_make_jagged: bool = False,
    pre_function_handle_to_anchor_scale_factor: float = 1e-05,
    **kwargs: Unpack[Style],
) -> None:
    self.u_range = list(u_range)  # values, as they are given
    self.v_range = list(v_range)
    self.resolution = resolution
    colors: list[ManimColor] | Literal[False] = (
        False
        if checkerboard_colors is False
        else [ManimColor(c) for c in checkerboard_colors]
    )
    self._func = func
    super().__init__(**kwargs)
    if colors:
        self.set_fill_by_checkerboard(*colors)

func

The surface's point at (u, v): its function's value.

surface.func(u, v)
u

The first parameter.

v

The second parameter.

Returns The point, in scene coordinates, as the surface was made (before it was moved).

Source

src/manimgx/mobjects/three_d.py

def func(self, u: float, v: float) -> np.ndarray:
    """The surface's point at (u, v): its function's value.

    Args:
        u: The first parameter.
        v: The second parameter.

    Returns:
        The point, in scene coordinates, as the surface was made (before it was
        moved).
    """
    return self._func(u, v)

set_fill_by_checkerboard

Color the faces like a checkerboard: the colors in turn, along u and along v.

The face at place (i, j) of the grid takes the color i + j places along colors, cycling: two colors make a checkerboard, more make diagonal stripes. It replaces the colors set_fill_by_value gave.

surface.set_fill_by_checkerboard(*colors, opacity=None)
*colors

The colors, in turn.

opacity

The faces' opacity, from 0 to 1; None to keep theirs.

Source

src/manimgx/mobjects/three_d.py

def set_fill_by_checkerboard(
    self, *colors: ParsableManimColor, opacity: float | None = None
) -> Self:
    """Color the faces like a checkerboard: the colors in turn, along u and along v.

    The face at place (i, j) of the grid takes the color i + j places along
    `colors`, cycling: two colors make a checkerboard, more make diagonal stripes.
    It replaces the colors [set_fill_by_value][manimgx.Surface.set_fill_by_value]
    gave.

    Args:
        *colors: The colors, in turn.
        opacity: The faces' opacity, from 0 to 1; None to keep theirs.
    """
    palette = tuple(tuple(ManimColor(c).to_rgba()) for c in colors)
    grid = self.grid
    if grid is None:
        raise ValueError("Checkerboard cells require lattice topology")
    alpha = self._opacity(opacity, grid[0] * grid[1])
    # one opacity: the checkerboard is a value, made once
    if isinstance(alpha, float):
        return self._fill_rows(_checkerboard(palette, *grid, alpha))
    i, j = self.face_indices()
    return self._paint_faces(np.array(palette)[(i + j) % len(palette)], opacity)

set_fill_by_value

SurfaceSetFillByValueExample
Code
import numpy as np

import manimgx as m


class SurfaceSetFillByValueExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(
            phi=65 * m.DEGREES, theta=-130 * m.DEGREES
        )
        axes = m.ThreeDAxes(
            x_range=(0, 5),
            y_range=(0, 5),
            z_range=(-1, 1, 0.5),
            x_length=7,
            y_length=7,
            z_length=3,
        )
        surface = m.Surface(
            lambda u, v: axes.c2p(u, v, np.sin(u) * np.cos(v)),
            u_range=(0, 5),
            v_range=(0, 5),
            resolution=8,
        )
        surface.set_fill_by_value(
            axes, colorscale=[(m.RED, -0.5), (m.YELLOW, 0), (m.GREEN, 0.5)]
        )
        self.add(axes, surface)

Color the surface point by point by a coordinate on axes: its height, z, by default, on a colorscale.

Each sample of the surface has its coordinate along axis, and the values are blended across the surface before the colorscale maps them: the colors follow the surface smoothly, and a face whose corners straddle a color of the scale shows it. The colors are the fill's, lit as a fill color is, at the fill's opacity: a fill color set later (set_fill, set_color) replaces them, as set_fill_by_checkerboard does. Without a colorscale, it warns and leaves the colors as they are.

surface.set_fill_by_value(axes, colorscale=None, axis=2, colors=None)
axes

The axes whose coordinates color the surface.

colorscale

The colors: spread evenly over the axes' range along axis, or (color, value) pairs, which span their own values; blended between, and held beyond the ends.

axis

The coordinate: 0 for x, 1 for y, 2 for z.

colors

The colorscale by its older name, accepted for Manim compatibility: used when colorscale is None.

Source

src/manimgx/mobjects/three_d.py

def set_fill_by_value(
    self,
    axes: ThreeDAxes,
    colorscale: Colorscale | None = None,
    axis: int = 2,
    colors: Colorscale | None = None,
) -> Self:
    """Color the surface point by point by a coordinate on axes: its height, z, by
    default, on a colorscale.

    Each sample of the surface has its coordinate along `axis`, and the values are
    blended across the surface before the colorscale maps them: the colors follow
    the surface smoothly, and a face whose corners straddle a color of the scale
    shows it. The colors are the fill's, lit as a fill color is, at the fill's
    opacity: a fill color set later ([set_fill][manimgx.Mobject.set_fill],
    [set_color][manimgx.Mobject.set_color]) replaces them, as
    [set_fill_by_checkerboard][manimgx.Surface.set_fill_by_checkerboard] does.
    Without a colorscale, it warns and leaves the colors as they are.

    Args:
        axes: The axes whose coordinates color the surface.
        colorscale: The colors: spread evenly over the axes' range along `axis`, or
            `(color, value)` pairs, which span their own values; blended between,
            and held beyond the ends.
        axis: The coordinate: 0 for x, 1 for y, 2 for z.
        colors: The colorscale by its older name, accepted for Manim compatibility:
            used when `colorscale` is None.

    Examples:
        ```python
        import numpy as np

        import manimgx as m


        class SurfaceSetFillByValueExample(m.ThreeDScene):
            def construct(self) -> None:
                self.set_camera_orientation(
                    phi=65 * m.DEGREES, theta=-130 * m.DEGREES
                )
                axes = m.ThreeDAxes(
                    x_range=(0, 5),
                    y_range=(0, 5),
                    z_range=(-1, 1, 0.5),
                    x_length=7,
                    y_length=7,
                    z_length=3,
                )
                surface = m.Surface(
                    lambda u, v: axes.c2p(u, v, np.sin(u) * np.cos(v)),
                    u_range=(0, 5),
                    v_range=(0, 5),
                    resolution=8,
                )
                surface.set_fill_by_value(
                    axes, colorscale=[(m.RED, -0.5), (m.YELLOW, 0), (m.GREEN, 0.5)]
                )
                self.add(axes, surface)
        ```
    """
    # the colorscale is a texture along the value (a colormap) and each sample's
    # value its coordinate: values are interpolated over the surface before they are
    # mapped
    colorscale = colorscale if colorscale is not None else colors
    if colorscale is None:
        warnings.warn(
            "The value passed to the colorscale keyword argument was None, the"
            " surface fill color has not been changed",
            stacklevel=2,
        )
        return self
    low, high = (axes.x_range, axes.y_range, axes.z_range or (0, 0))[axis][:2]
    if all(isinstance(c, (tuple, list)) and len(c) == 2 for c in colorscale):
        pivots = [
            float(cast("tuple[ParsableManimColor, float]", c)[1])
            for c in colorscale
        ]
        low, high = min(pivots), max(pivots)
    values = np.asarray(axes.point_to_coords(self.points), dtype=float)[:, axis]
    span = high - low if high > low else 1.0
    n = _COLORMAP_TEXELS
    ramp = rgbas_by_value(colorscale, np.linspace(low, high, n), low, high)
    colormap = (ramp[None] * 255).round().astype(np.uint8)
    colormap[..., 3] = 255
    colormap.flags.writeable = False
    # texel centers: the ends hold beyond them
    u = (0.5 + np.clip((values - low) / span, 0.0, 1.0) * (n - 1)) / n
    self.uvs = np.stack([u, np.full_like(u, 0.5)], axis=1)
    grid = self.grid
    count = len(self.points) if grid is None else grid[0] * grid[1]
    alpha = self._opacity(None, count)
    fill = np.ones((1 if isinstance(alpha, float) else count, 4))
    fill[:, 3] = alpha
    self.paint = self.paint.but(fill=fill, texture=colormap, colormap=True)
    return self

Sphere

SphereExample
Code
import manimgx as m


class SphereExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=70 * m.DEGREES, theta=-90 * m.DEGREES)
        self.add(
            m.Sphere(center=(-4, 0, 0), radius=1.5),
            m.Sphere(radius=1.5, resolution=(12, 6)).set_color(m.GREEN),
            m.Sphere(
                center=(4, 0, 0),
                radius=1.5,
                v_range=(0, m.PI / 2),
                checkerboard_colors=[m.RED, m.YELLOW],
            ),
        )

A sphere: checkered in two blues and shaded by a three-dimensional scene's light, unless styled.

It is a Surface of u, the longitude, and v, the angle from the south pole (the bottom, along z): their ranges may cut out a part of it.

m.Sphere(center=ORIGIN, radius=1, resolution=None, u_range=(0, TAU), v_range=(0, PI), **kwargs)
center

Where its center goes.

radius

Its radius, in scene units.

resolution

How many faces it has around and from pole to pole: one number for both, or (u, v); None for (24, 12).

u_range

The range of the longitude, in radians: (0, τ) goes all the way around.

v_range

The range of the angle from the south pole, in radians: (0, π) goes from pole to pole.

It also takes the Surface keywords.

Source

src/manimgx/mobjects/three_d.py

def __init__(
    self,
    center: Point3DLike = ORIGIN,
    radius: float = 1,
    resolution: int | Sequence[int] | None = None,
    u_range: Sequence[float] = (0, TAU),
    v_range: Sequence[float] = (0, PI),
    **kwargs: Unpack[SurfaceLook],
) -> None:
    self.radius = radius
    super().__init__(
        self.func,
        resolution=resolution if resolution is not None else (24, 12),
        u_range=u_range,
        v_range=v_range,
        **kwargs,
    )
    self.shift(center)

func

The sphere's point at a longitude and an angle from its south pole, as it is made around the origin.

sphere.func(u, v)
u

The longitude, in radians, counterclockwise from the x-axis.

v

The angle from the south pole, in radians: 0 at the bottom, π at the top.

Returns The point, relative to the sphere's center.

Source

src/manimgx/mobjects/three_d.py

def func(self, u: float, v: float) -> Point3D:
    """The sphere's point at a longitude and an angle from its south pole, as it is
    made around the origin.

    Args:
        u: The longitude, in radians, counterclockwise from the x-axis.
        v: The angle from the south pole, in radians: 0 at the bottom, π at the
            top.

    Returns:
        The point, relative to the sphere's center.
    """
    return self.radius * _revolve(np.sin(v), -np.cos(v), u)

Torus

TorusExample
Code
import manimgx as m


class TorusExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=65 * m.DEGREES, theta=-60 * m.DEGREES)
        self.add(
            m.Torus(major_radius=2, minor_radius=0.7).shift(3 * m.LEFT),
            m.Torus(
                major_radius=2,
                minor_radius=0.7,
                u_range=(0, 1.5 * m.PI),
                checkerboard_colors=[m.ORANGE, m.YELLOW],
            ).shift(3 * m.RIGHT),
        )

A torus: a tube around a circle in the xy-plane, centered at the origin; checkered in two blues and shaded by a three-dimensional scene's light, unless styled.

It is a Surface of u, the angle around its axis (z), and v, the angle around its tube: their ranges may cut out a part of it.

m.Torus(major_radius=3, minor_radius=1, u_range=(0, TAU), v_range=(0, TAU), resolution=None, **kwargs)
major_radius

The radius of the circle the tube runs around: from the torus's center to the middle of its tube, in scene units.

minor_radius

The tube's radius, in scene units.

u_range

The range of the angle around its axis, in radians: (0, τ) goes all the way around.

v_range

The range of the angle around its tube, in radians.

resolution

How many faces it has around its axis and around its tube: one number for both, or (u, v); None for (24, 24).

It also takes the Surface keywords.

Source

src/manimgx/mobjects/three_d.py

def __init__(
    self,
    major_radius: float = 3,
    minor_radius: float = 1,
    u_range: Sequence[float] = (0, TAU),
    v_range: Sequence[float] = (0, TAU),
    resolution: int | tuple[int, int] | None = None,
    **kwargs: Unpack[SurfaceLook],
) -> None:
    self.R = major_radius
    self.r = minor_radius
    super().__init__(
        self.func,
        u_range=u_range,
        v_range=v_range,
        resolution=resolution if resolution is not None else (24, 24),
        **kwargs,
    )

func

The torus's point, as it is made around the origin, at an angle around its axis and an angle around its tube.

torus.func(u, v)
u

The angle around the axis, in radians, counterclockwise from the x-axis.

v

The angle around the tube, in radians: 0 on its inner side.

Returns The point, in scene coordinates.

Source

src/manimgx/mobjects/three_d.py

def func(self, u: float, v: float) -> Point3D:
    """The torus's point, as it is made around the origin, at an angle around its
    axis and an angle around its tube.

    Args:
        u: The angle around the axis, in radians, counterclockwise from the x-axis.
        v: The angle around the tube, in radians: 0 on its inner side.

    Returns:
        The point, in scene coordinates.
    """
    return _revolve(self.R - self.r * np.cos(v), -self.r * np.sin(v), u)

Cylinder

CylinderExample
Code
import manimgx as m


class CylinderExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=70 * m.DEGREES, theta=-60 * m.DEGREES)
        self.add(
            m.Cylinder(radius=1.5, height=3).shift(4.5 * m.LEFT),
            m.Cylinder(radius=0.5, height=4, direction=m.X_AXIS + m.Z_AXIS),
            m.Cylinder(show_ends=False, v_range=(0, m.PI)).shift(4 * m.RIGHT),
        )

A cylinder, centered at the origin, its axis along direction: checkered in two blues, closed at both ends by discs, and shaded by a three-dimensional scene's light, unless styled.

It is a Surface of u, the height along its axis, and v, the angle around it: v_range may cut out a slice.

m.Cylinder(radius=1, height=2, direction=Z_AXIS, v_range=(0, TAU), show_ends=True, resolution=(24, 24), **kwargs)
radius

Its radius, in scene units.

height

Its height, along its axis, in scene units.

direction

The direction of its axis.

v_range

The range of the angle around its axis, in radians: (0, τ) goes all the way around.

show_ends

Whether discs close its ends.

resolution

How many faces it has along its axis and around it: one number for both, or (u, v).

It also takes the Surface keywords.

Source

src/manimgx/mobjects/three_d.py

def __init__(
    self,
    radius: float = 1,
    height: float = 2,
    direction: Vector3DLike = Z_AXIS,
    v_range: Sequence[float] = (0, TAU),
    show_ends: bool = True,
    resolution: int | Sequence[int] = (24, 24),
    **kwargs: Unpack[SurfaceLook],
) -> None:
    self._height = height
    self.radius = radius
    super().__init__(
        self.func,
        resolution=resolution,
        u_range=(-self._height / 2, self._height / 2),
        v_range=v_range,
        **kwargs,
    )
    if show_ends:
        self.add_bases()
    self._current_phi = 0.0
    self._current_theta = 0.0
    self.set_direction(direction)

set_direction

Turn the surface's axis to a direction, undoing its previous direction.

Both turns are about the origin, preserving other changes to the surface.

cylinder.set_direction(direction)
direction

The direction of the axis, from a cone's base to its apex.

Source

src/manimgx/mobjects/three_d.py

def set_direction(self, direction: Vector3DLike) -> Self:
    """Turn the surface's axis to a direction, undoing its previous direction.

    Both turns are about the origin, preserving other changes to the surface.

    Args:
        direction: The direction of the axis, from a cone's base to its apex.
    """
    self.direction = np.array(direction, dtype=float)
    x, y, z = self.direction
    r = np.sqrt(x**2 + y**2 + z**2)
    theta = float(np.arccos(z / r)) if r > 0 else 0.0
    if x == 0:
        phi = 0.0 if y == 0 else float(np.arctan(np.inf)) + (PI if y < 0 else 0)
    else:
        phi = float(np.arctan(y / x))
    if x < 0:
        phi += PI
    turn = (
        rotation_about_z(phi)
        @ rotation_matrix(theta - self._current_theta, Y_AXIS)
        @ rotation_about_z(-self._current_phi)
    )
    self._rotate_direction_state(turn)
    self._apply_linear(turn, ORIGIN)
    self._current_theta, self._current_phi = theta, phi
    return self

func

The cylinder's point, as it is made along z and centered on the origin, at a height along its axis and an angle around it.

cylinder.func(u, v)
u

The height along the axis, in scene units, from the middle.

v

The angle around the axis, in radians.

Returns The point, in scene coordinates.

Source

src/manimgx/mobjects/three_d.py

def func(self, u: float, v: float) -> np.ndarray:
    """The cylinder's point, as it is made along z and centered on the origin, at a
    height along its axis and an angle around it.

    Args:
        u: The height along the axis, in scene units, from the middle.
        v: The angle around the axis, in radians.

    Returns:
        The point, in scene coordinates.
    """
    return _revolve(self.radius, u, v)

Cone

ConeExample
Code
import manimgx as m


class ConeExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=70 * m.DEGREES, theta=-60 * m.DEGREES)
        frustum = m.Cone(base_radius=1.5, height=3, u_min=1.5, fill_color=m.RED)
        self.add(
            m.Cone(base_radius=1.5, height=3).shift(4 * m.LEFT + 1.5 * m.OUT),
            m.Cone(direction=m.X_AXIS + m.Z_AXIS, show_base=True),
            frustum.shift(4 * m.RIGHT + 1.5 * m.OUT),
        )

A cone, its apex at the origin, pointing along direction from its base; blue, shaded by a three-dimensional scene's light, and open at the base, unless styled.

It is a Surface of u, the distance from the apex along its side, and v, the angle around its axis: v_range may cut out a slice, and u_min its tip. It is one color, not checkered, unless given checkerboard_colors.

m.Cone(base_radius=1, height=1, direction=Z_AXIS, show_base=False, v_range=(0, TAU), u_min=0, **kwargs)
base_radius

The radius of its base, in scene units.

height

Its height, from its base to its apex, in scene units.

direction

The direction it points in, from its base to its apex.

show_base

Whether a disc closes its base.

v_range

The range of the angle around its axis, in radians: (0, τ) goes all the way around.

u_min

Where its side begins, as a distance from the apex along it, in scene units: above 0, the tip is cut off.

It also takes the Surface keywords.

Source

src/manimgx/mobjects/three_d.py

def __init__(
    self,
    base_radius: float = 1,
    height: float = 1,
    direction: Vector3DLike = Z_AXIS,
    show_base: bool = False,
    v_range: Sequence[float] = (0, TAU),
    u_min: float = 0,
    **kwargs: Unpack[SurfaceOptions],
) -> None:
    self.direction = np.array(direction, dtype=float)
    self.theta = PI - np.arctan(base_radius / height)
    kwargs.setdefault("checkerboard_colors", False)
    super().__init__(
        self.func,
        v_range=v_range,
        u_range=(u_min, np.sqrt(base_radius**2 + height**2)),
        **kwargs,
    )
    self.new_height = height
    self._current_theta = 0.0
    self._current_phi = 0.0
    self.base_circle = Circle(
        radius=base_radius,
        color=self.fill_color,
        fill_opacity=self.fill_opacity,
        stroke_width=0,
    )
    """The disc of its base, in its fill color: part of the cone with
    `show_base`."""
    self.base_circle.shift(height * IN)
    self._set_start_and_end_attributes(self.direction)
    if show_base:
        self.add(self.base_circle)
    self.set_direction(direction)

base_circle

The disc of its base, in its fill color: part of the cone with show_base.

set_direction

Turn the surface's axis to a direction, undoing its previous direction.

Both turns are about the origin, preserving other changes to the surface.

cone.set_direction(direction)
direction

The direction of the axis, from a cone's base to its apex.

Source

src/manimgx/mobjects/three_d.py

def set_direction(self, direction: Vector3DLike) -> Self:
    """Turn the surface's axis to a direction, undoing its previous direction.

    Both turns are about the origin, preserving other changes to the surface.

    Args:
        direction: The direction of the axis, from a cone's base to its apex.
    """
    self.direction = np.array(direction, dtype=float)
    x, y, z = self.direction
    r = np.sqrt(x**2 + y**2 + z**2)
    theta = float(np.arccos(z / r)) if r > 0 else 0.0
    if x == 0:
        phi = 0.0 if y == 0 else float(np.arctan(np.inf)) + (PI if y < 0 else 0)
    else:
        phi = float(np.arctan(y / x))
    if x < 0:
        phi += PI
    turn = (
        rotation_about_z(phi)
        @ rotation_matrix(theta - self._current_theta, Y_AXIS)
        @ rotation_about_z(-self._current_phi)
    )
    self._rotate_direction_state(turn)
    self._apply_linear(turn, ORIGIN)
    self._current_theta, self._current_phi = theta, phi
    return self

func

The cone's point, as it is made pointing up (along z) with its apex at the origin, at a distance from the apex along its side and an angle around its axis.

cone.func(u, v)
u

The distance from the apex, along the side, in scene units.

v

The angle around the axis, in radians.

Returns The point, in scene coordinates.

Source

src/manimgx/mobjects/three_d.py

def func(self, u: float, v: float) -> Point3D:
    """The cone's point, as it is made pointing up (along z) with its apex at the
    origin, at a distance from the apex along its side and an angle around its axis.

    Args:
        u: The distance from the apex, along the side, in scene units.
        v: The angle around the axis, in radians.

    Returns:
        The point, in scene coordinates.
    """
    return _revolve(u * np.sin(self.theta), u * np.cos(self.theta), v)

Solids

Cube

CubeExample
Code
import manimgx as m


class CubeExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=70 * m.DEGREES, theta=-45 * m.DEGREES)
        cubes = m.Group(
            m.Cube(),
            m.Cube(side_length=3, fill_color=m.TEAL, fill_opacity=1),
            m.Cube(fill_opacity=0.3, stroke_width=2, stroke_color=m.YELLOW),
        ).arrange(buff=1.5)
        self.add(cubes)

A cube: six square faces, centered at the origin with its edges along the axes; blue, three-quarters opaque, without edges and shaded by a three-dimensional scene's light, unless styled.

m.Cube(side_length=2, **kwargs)
side_length

The length of its edges, in scene units.

color

The color of both fill and stroke (default white); None for the class's default.

fill_color

The fill's color; color if not given. Several colors make a gradient along sheen_direction.

fill_opacity

The fill's opacity, from 0 to 1 (default 0: no fill).

stroke_color

The stroke's color; color if not given. Several colors make a gradient.

stroke_opacity

The stroke's opacity, from 0 to 1 (default 1).

stroke_width

The stroke's width, in hundredths of a scene unit (default 4; 0: no stroke).

background_stroke_color

The color of an outline drawn behind the fill (default black).

background_stroke_opacity

The outline's opacity, from 0 to 1 (default 1).

background_stroke_width

The outline's width, in hundredths of a scene unit (default 0: none).

sheen_factor

How much the colors lighten toward sheen_direction, from -1 to 1 (default 0); a negative factor darkens.

sheen_direction

The direction the colors lighten toward (default UL).

joint_type

How the stroke is joined where its path turns: round, beveled or mitered, as a two-dimensional scene draws it (see LineJointType; default AUTO: mitered).

cap_style

How the stroke ends, at each end it shows (an open path's, a dash's): round, butt or square, as a two-dimensional scene draws it (see CapStyleType; default AUTO: butt).

shade_in_3d

Whether a three-dimensional scene's light shades the mobject.

material

How its surface reflects the scene's lights in a three-dimensional scene (see Material); None: Manim's shading (default).

name

A name for the mobject; its class's name if not given.

z_index

Its place in the drawing order: a higher index is drawn over a lower one (default 0).

target

The state MoveToTarget moves the mobject to.

It also takes the style keywords.

Source

src/manimgx/mobjects/three_d.py

def __init__(self, side_length: float = 2, **kwargs: Unpack[Style]) -> None:
    self.side_length = side_length
    super().__init__(**kwargs)

Prism

PrismExample
Code
import manimgx as m


class PrismExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=60 * m.DEGREES, theta=-60 * m.DEGREES)
        prisms = m.Group(
            m.Prism(),
            m.Prism(dimensions=[1, 2, 3], fill_color=m.GREEN),
        ).arrange(buff=1.5)
        self.add(prisms)

A box: a Cube stretched to its width, height and depth; blue, three-quarters opaque, without edges and shaded by a three-dimensional scene's light, unless styled.

m.Prism(dimensions=None, **kwargs)
dimensions

Its size along x, y and z, in scene units; None for (3, 2, 1).

color

The color of both fill and stroke (default white); None for the class's default.

fill_color

The fill's color; color if not given. Several colors make a gradient along sheen_direction.

fill_opacity

The fill's opacity, from 0 to 1 (default 0: no fill).

stroke_color

The stroke's color; color if not given. Several colors make a gradient.

stroke_opacity

The stroke's opacity, from 0 to 1 (default 1).

stroke_width

The stroke's width, in hundredths of a scene unit (default 4; 0: no stroke).

background_stroke_color

The color of an outline drawn behind the fill (default black).

background_stroke_opacity

The outline's opacity, from 0 to 1 (default 1).

background_stroke_width

The outline's width, in hundredths of a scene unit (default 0: none).

sheen_factor

How much the colors lighten toward sheen_direction, from -1 to 1 (default 0); a negative factor darkens.

sheen_direction

The direction the colors lighten toward (default UL).

joint_type

How the stroke is joined where its path turns: round, beveled or mitered, as a two-dimensional scene draws it (see LineJointType; default AUTO: mitered).

cap_style

How the stroke ends, at each end it shows (an open path's, a dash's): round, butt or square, as a two-dimensional scene draws it (see CapStyleType; default AUTO: butt).

shade_in_3d

Whether a three-dimensional scene's light shades the mobject.

material

How its surface reflects the scene's lights in a three-dimensional scene (see Material); None: Manim's shading (default).

name

A name for the mobject; its class's name if not given.

z_index

Its place in the drawing order: a higher index is drawn over a lower one (default 0).

target

The state MoveToTarget moves the mobject to.

It also takes the style keywords.

Source

src/manimgx/mobjects/three_d.py

def __init__(
    self, dimensions: Vector3DLike | None = None, **kwargs: Unpack[Style]
) -> None:
    self.dimensions = (
        [3.0, 2.0, 1.0] if dimensions is None else [float(d) for d in dimensions]
    )
    super().__init__(**kwargs)

Polyhedron

Code
import manimgx as m


class PolyhedronExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=70 * m.DEGREES, theta=30 * m.DEGREES)
        corners = [[1, 1, 0], [1, -1, 0], [-1, -1, 0], [-1, 1, 0], [0, 0, 2]]
        faces = [[0, 1, 4], [1, 2, 4], [2, 3, 4], [3, 0, 4], [0, 1, 2, 3]]
        pyramid = m.Polyhedron(corners, faces).scale(1.5).shift(m.IN)
        self.add(pyramid)
        self.play(pyramid.graph[4].animate.move_to([1, 1, 2]), run_time=2)

A polyhedron: its faces, polygons through its vertices, and the graph of its vertices and edges; blue half-opaque faces with white dots at the vertices, shaded by a three-dimensional scene's light, unless styled.

The faces are its faces, a group of Polygons, whose outlines draw the edges; its graph is a Graph whose vertices are Dot3Ds, graph[i] the vertex of index i, and whose own edges are invisible unless configured. The faces follow the vertices: move a vertex, and an updater keeps the faces through it. Initial coordinates and graph options are construction inputs; later positions belong to the graph, and faces_list defines the faces.

m.Polyhedron(vertex_coords, faces_list, faces_config=None, graph_config=None)
vertex_coords

The vertices' points, in scene coordinates.

faces_list

The faces, each a list of the indices of its vertices, in order around it.

faces_config

Style keywords for the faces, over their defaults (half opaque, shaded in 3D); None for none.

graph_config

Graph keywords for the vertices and edges, over their defaults (Dot3D vertices, invisible edges); None for none.

Source

src/manimgx/mobjects/three_d.py

def __init__(
    self,
    vertex_coords: Point3DLike_Array,
    faces_list: list[list[int]],
    faces_config: Style | None = None,
    graph_config: GraphOptions | None = None,
):
    super().__init__()
    self.faces_config = Style(fill_opacity=0.5, shade_in_3d=True) | (
        faces_config or Style()
    )
    graph_config = GraphOptions(
        vertex_type=Dot3D, edge_config={"stroke_opacity": 0}
    ) | (graph_config or GraphOptions())
    vertex_indices = list(range(len(vertex_coords)))
    layout: dict[Hashable, Point3D] = {
        i: np.asarray(p, dtype=float) for i, p in enumerate(vertex_coords)
    }
    self.faces_list = [list(face) for face in faces_list]
    """The faces, each a list of the indices of its vertices, as they were given."""
    face_coords = [[layout[j] for j in i] for i in faces_list]
    self.edges = self.get_edges(self.faces_list)
    """The edges, each a pair of vertex indices."""
    self.faces = self.create_faces(face_coords)
    """The faces: a group of [Polygon][manimgx.Polygon]s, in the order of
    `faces_list`."""
    self.graph = Graph(
        vertex_indices,
        self.edges,
        **(graph_config | GraphOptions(layout=layout)),
    )
    """The vertices and edges: a [Graph][manimgx.Graph], `graph[i]` the vertex of
    index i."""
    self.add(self.faces, self.graph)
    self.add_updater(self.update_faces)

faces_list

The faces, each a list of the indices of its vertices, as they were given.

edges

The edges, each a pair of vertex indices.

faces

The faces: a group of Polygons, in the order of faces_list.

graph

The vertices and edges: a Graph, graph[i] the vertex of index i.

Tetrahedron

TetrahedronExample
Code
import manimgx as m


class TetrahedronExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=70 * m.DEGREES, theta=30 * m.DEGREES)
        self.add(m.Tetrahedron(edge_length=4))

A regular tetrahedron: four triangular faces, centered at the origin; a Polyhedron, blue half-opaque faces with white dots at the vertices unless styled.

m.Tetrahedron(edge_length=1, **kwargs)
edge_length

The length of its edges, in scene units.

faces_config

Style keywords for the faces, over their defaults: half opaque, shaded by a three-dimensional scene's light (default None: none).

graph_config

Graph keywords for the vertices and edges, over their defaults: Dot3D vertices, invisible edges (default None: none).

Source

src/manimgx/mobjects/three_d.py

def __init__(self, edge_length: float = 1, **kwargs: Unpack[PolyhedronOptions]):
    unit = edge_length * np.sqrt(2) / 4
    super().__init__(
        vertex_coords=[
            np.array([unit, unit, unit]),
            np.array([unit, -unit, -unit]),
            np.array([-unit, unit, -unit]),
            np.array([-unit, -unit, unit]),
        ],
        faces_list=[[0, 1, 2], [3, 0, 2], [0, 1, 3], [3, 1, 2]],
        **kwargs,
    )

Octahedron

OctahedronExample
Code
import manimgx as m


class OctahedronExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=70 * m.DEGREES, theta=30 * m.DEGREES)
        octahedron = m.Octahedron(edge_length=3)
        octahedron.faces[2].set_color(m.YELLOW)
        octahedron.graph[0].set_color(m.RED)
        self.add(octahedron)

A regular octahedron: eight triangular faces, its vertices on the axes, centered at the origin; a Polyhedron, blue half-opaque faces with white dots at the vertices unless styled.

m.Octahedron(edge_length=1, **kwargs)
edge_length

The length of its edges, in scene units.

faces_config

Style keywords for the faces, over their defaults: half opaque, shaded by a three-dimensional scene's light (default None: none).

graph_config

Graph keywords for the vertices and edges, over their defaults: Dot3D vertices, invisible edges (default None: none).

Source

src/manimgx/mobjects/three_d.py

def __init__(self, edge_length: float = 1, **kwargs: Unpack[PolyhedronOptions]):
    unit = edge_length * np.sqrt(2) / 2
    super().__init__(
        vertex_coords=[
            np.array([unit, 0, 0]),
            np.array([-unit, 0, 0]),
            np.array([0, unit, 0]),
            np.array([0, -unit, 0]),
            np.array([0, 0, unit]),
            np.array([0, 0, -unit]),
        ],
        faces_list=[
            [2, 4, 1],
            [0, 4, 2],
            [4, 3, 0],
            [1, 3, 4],
            [3, 5, 0],
            [1, 5, 3],
            [2, 5, 1],
            [0, 5, 2],
        ],
        **kwargs,
    )

Dodecahedron

DodecahedronExample
Code
import manimgx as m


class DodecahedronExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=70 * m.DEGREES, theta=30 * m.DEGREES)
        self.add(
            m.Dodecahedron(edge_length=1.5, faces_config={"color": m.PURPLE})
        )

A regular dodecahedron: twelve pentagonal faces, centered at the origin; a Polyhedron, blue half-opaque faces with white dots at the vertices unless styled.

m.Dodecahedron(edge_length=1, **kwargs)
edge_length

The length of its edges, in scene units.

faces_config

Style keywords for the faces, over their defaults: half opaque, shaded by a three-dimensional scene's light (default None: none).

graph_config

Graph keywords for the vertices and edges, over their defaults: Dot3D vertices, invisible edges (default None: none).

Source

src/manimgx/mobjects/three_d.py

def __init__(self, edge_length: float = 1, **kwargs: Unpack[PolyhedronOptions]):
    unit_a = edge_length * ((1 + np.sqrt(5)) / 4)
    unit_b = edge_length * ((3 + np.sqrt(5)) / 4)
    unit_c = edge_length * (1 / 2)
    super().__init__(
        vertex_coords=[
            np.array([unit_a, unit_a, unit_a]),
            np.array([unit_a, unit_a, -unit_a]),
            np.array([unit_a, -unit_a, unit_a]),
            np.array([unit_a, -unit_a, -unit_a]),
            np.array([-unit_a, unit_a, unit_a]),
            np.array([-unit_a, unit_a, -unit_a]),
            np.array([-unit_a, -unit_a, unit_a]),
            np.array([-unit_a, -unit_a, -unit_a]),
            np.array([0, unit_c, unit_b]),
            np.array([0, unit_c, -unit_b]),
            np.array([0, -unit_c, -unit_b]),
            np.array([0, -unit_c, unit_b]),
            np.array([unit_c, unit_b, 0]),
            np.array([-unit_c, unit_b, 0]),
            np.array([unit_c, -unit_b, 0]),
            np.array([-unit_c, -unit_b, 0]),
            np.array([unit_b, 0, unit_c]),
            np.array([-unit_b, 0, unit_c]),
            np.array([unit_b, 0, -unit_c]),
            np.array([-unit_b, 0, -unit_c]),
        ],
        faces_list=[
            [18, 16, 0, 12, 1],
            [3, 18, 16, 2, 14],
            [3, 10, 9, 1, 18],
            [1, 9, 5, 13, 12],
            [0, 8, 4, 13, 12],
            [2, 16, 0, 8, 11],
            [4, 17, 6, 11, 8],
            [17, 19, 5, 13, 4],
            [19, 7, 15, 6, 17],
            [6, 15, 14, 2, 11],
            [19, 5, 9, 10, 7],
            [7, 10, 3, 14, 15],
        ],
        **kwargs,
    )

Icosahedron

IcosahedronExample
Code
import manimgx as m


class IcosahedronExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=70 * m.DEGREES, theta=30 * m.DEGREES)
        self.add(m.Icosahedron(edge_length=2, faces_config={"color": m.TEAL}))

A regular icosahedron: twenty triangular faces, centered at the origin; a Polyhedron, blue half-opaque faces with white dots at the vertices unless styled.

m.Icosahedron(edge_length=1, **kwargs)
edge_length

The length of its edges, in scene units.

faces_config

Style keywords for the faces, over their defaults: half opaque, shaded by a three-dimensional scene's light (default None: none).

graph_config

Graph keywords for the vertices and edges, over their defaults: Dot3D vertices, invisible edges (default None: none).

Source

src/manimgx/mobjects/three_d.py

def __init__(self, edge_length: float = 1, **kwargs: Unpack[PolyhedronOptions]):
    unit_a = edge_length * ((1 + np.sqrt(5)) / 4)
    unit_b = edge_length * (1 / 2)
    super().__init__(
        vertex_coords=[
            np.array([0, unit_b, unit_a]),
            np.array([0, -unit_b, unit_a]),
            np.array([0, unit_b, -unit_a]),
            np.array([0, -unit_b, -unit_a]),
            np.array([unit_b, unit_a, 0]),
            np.array([unit_b, -unit_a, 0]),
            np.array([-unit_b, unit_a, 0]),
            np.array([-unit_b, -unit_a, 0]),
            np.array([unit_a, 0, unit_b]),
            np.array([unit_a, 0, -unit_b]),
            np.array([-unit_a, 0, unit_b]),
            np.array([-unit_a, 0, -unit_b]),
        ],
        faces_list=[
            [1, 8, 0],
            [1, 5, 7],
            [8, 5, 1],
            [7, 3, 5],
            [5, 9, 3],
            [8, 9, 5],
            [3, 2, 9],
            [9, 4, 2],
            [8, 4, 9],
            [0, 4, 8],
            [6, 4, 0],
            [6, 2, 4],
            [11, 2, 6],
            [3, 11, 2],
            [0, 6, 10],
            [10, 1, 0],
            [10, 7, 1],
            [11, 7, 3],
            [10, 11, 7],
            [10, 11, 6],
        ],
        **kwargs,
    )

ConvexHull3D

ConvexHull3DExample
Code
import numpy as np

import manimgx as m


class ConvexHull3DExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=70 * m.DEGREES, theta=30 * m.DEGREES)
        points = np.random.default_rng(1).uniform(-2, 2, size=(30, 3))
        hull = m.ConvexHull3D(*points, faces_config={"color": m.GREEN})
        self.add(hull, *(m.Dot3D(p, color=m.YELLOW) for p in points))

The convex hull of points in three dimensions: the smallest convex polyhedron around them, its faces triangles; a Polyhedron, blue half-opaque faces with white dots at the vertices unless styled.

Its vertices are the points on the hull (those inside are left out), and its faces are wound counterclockwise, seen from outside. Fewer than four points raise a ValueError.

m.ConvexHull3D(*points, tolerance=1e-05, **kwargs)
*points

The points, in scene coordinates.

tolerance

How far outside a face a point must be to count as beyond it, in scene units.

faces_config

Style keywords for the faces, over their defaults: half opaque, shaded by a three-dimensional scene's light (default None: none).

graph_config

Graph keywords for the vertices and edges, over their defaults: Dot3D vertices, invisible edges (default None: none).

Source

src/manimgx/mobjects/three_d.py

def __init__(
    self,
    *points: Point3DLike,
    tolerance: float = 1e-05,
    **kwargs: Unpack[PolyhedronOptions],
):
    array = np.array(points, dtype=float)
    hull = QuickHull(tolerance)
    hull.build(array)
    # In the hull's own order: CE iterated hash sets of point bytes, whose order changes
    # from process to process (salted hashing), and so did faces, vertex ids and draw order.
    index: dict[bytes, int] = {}
    vertices: list[Point3D] = []
    faces: list[list[int]] = []
    for facet in dict.fromkeys(hull.facets):
        if facet in hull.removed:
            continue
        a, b, c = facet.coordinates
        corners = (
            (a, b, c)
            if np.dot(np.cross(b - a, c - a), facet.normal) > 0
            else (a, c, b)
        )
        for point in corners:  # wound counterclockwise seen from outside
            if point.tobytes() not in index:
                index[point.tobytes()] = len(vertices)
                vertices.append(point)
        faces.append([index[point.tobytes()] for point in corners])
    super().__init__(vertex_coords=vertices, faces_list=faces, **kwargs)

Lines and points in space

Line3D

Line3DExample
Code
import manimgx as m


class Line3DExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=65 * m.DEGREES, theta=-45 * m.DEGREES)
        axes = m.ThreeDAxes(x_range=(-3, 3), y_range=(-3, 3), z_range=(-2, 2))
        line = m.Line3D(axes.c2p(-2, 0, 0), axes.c2p(1, 2, 1.5), thickness=0.05)
        self.add(axes, line.set_color(m.YELLOW))

A line in three dimensions: a thin cylinder from one point to another; checkered in two blues, and shaded by a three-dimensional scene's light, unless styled.

An end may be a mobject: the line then starts (or ends) at its point farthest toward the other end (see get_boundary_point).

m.Line3D(start=LEFT, end=RIGHT, thickness=0.02, resolution=24, **kwargs)
start

Where the line starts: a point, or a mobject.

end

Where it ends: a point, or a mobject.

thickness

Its radius, in scene units.

resolution

How many faces it has around it (2 along it), or (along, around).

It also takes the Surface keywords.

Source

src/manimgx/mobjects/three_d.py

def __init__(
    self,
    start: Point3DLike | Mobject = LEFT,
    end: Point3DLike | Mobject = RIGHT,
    thickness: float = 0.02,
    resolution: int | Sequence[int] = 24,
    **kwargs: Unpack[SurfaceLook],
):
    color = kwargs.pop("color", None)
    self.thickness = thickness
    self.resolution = (2, resolution) if isinstance(resolution, int) else resolution
    self.set_start_and_end_attrs(start, end, **kwargs)
    if color is not None:
        self.set_color(color)

parallel_to

Line3DParallelToExample
Code
import manimgx as m


class Line3DParallelToExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(
            phi=60 * m.DEGREES, theta=-45 * m.DEGREES
        )
        line = m.Line3D(2 * m.RIGHT, m.UP + m.OUT, color=m.RED)
        parallel = m.Line3D.parallel_to(line, color=m.YELLOW)
        self.add(m.ThreeDAxes(), line, parallel)

Make a line parallel to another, through a point: length long, centered on the point.

Line3D.parallel_to(line, point=ORIGIN, length=5, **kwargs)
line

The line to be parallel to.

point

The point the new line is centered on.

length

The new line's length, in scene units.

It also takes the Line3D keywords.

Returns A new line.

Source

src/manimgx/mobjects/three_d.py

@classmethod
def parallel_to(
    cls,
    line: Line3D,
    point: Point3DLike = ORIGIN,
    length: float = 5,
    **kwargs: Unpack[Line3DOptions],
) -> Line3D:
    """Make a line parallel to another, through a point: `length` long, centered on
    the point.

    Args:
        line: The line to be parallel to.
        point: The point the new line is centered on.
        length: The new line's length, in scene units.
        **kwargs: [Line keywords][manimgx.mobjects.three_d.Line3DOptions]:
            `thickness`, `color`, ….

    Returns:
        A new line.

    Examples:
        ```python
        import manimgx as m


        class Line3DParallelToExample(m.ThreeDScene):
            def construct(self) -> None:
                self.set_camera_orientation(
                    phi=60 * m.DEGREES, theta=-45 * m.DEGREES
                )
                line = m.Line3D(2 * m.RIGHT, m.UP + m.OUT, color=m.RED)
                parallel = m.Line3D.parallel_to(line, color=m.YELLOW)
                self.add(m.ThreeDAxes(), line, parallel)
        ```
    """
    np_point = np.asarray(point, dtype=float)
    vect = normalize(line.vect)
    return cls(np_point + vect * length / 2, np_point - vect * length / 2, **kwargs)

perpendicular_to

Line3DPerpendicularToExample
Code
import manimgx as m


class Line3DPerpendicularToExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(
            phi=60 * m.DEGREES, theta=-45 * m.DEGREES
        )
        line = m.Line3D(2 * m.RIGHT, m.UP + m.OUT, color=m.RED)
        perpendicular = m.Line3D.perpendicular_to(line, color=m.BLUE)
        self.add(m.ThreeDAxes(), line, perpendicular)

Make a line perpendicular to another, through a point: in the plane of the line and the point, length long, centered on the point.

A point on the line (which makes no plane with it) raises a ValueError.

Line3D.perpendicular_to(line, point=ORIGIN, length=5, **kwargs)
line

The line to be perpendicular to.

point

The point the new line is centered on, off the line.

length

The new line's length, in scene units.

It also takes the Line3D keywords.

Returns A new line.

Source

src/manimgx/mobjects/three_d.py

@classmethod
def perpendicular_to(
    cls,
    line: Line3D,
    point: Point3DLike = ORIGIN,
    length: float = 5,
    **kwargs: Unpack[Line3DOptions],
) -> Line3D:
    """Make a line perpendicular to another, through a point: in the plane of the
    line and the point, `length` long, centered on the point.

    A point on the line (which makes no plane with it) raises a ValueError.

    Args:
        line: The line to be perpendicular to.
        point: The point the new line is centered on, off the line.
        length: The new line's length, in scene units.
        **kwargs: [Line keywords][manimgx.mobjects.three_d.Line3DOptions]:
            `thickness`, `color`, ….

    Returns:
        A new line.

    Examples:
        ```python
        import manimgx as m


        class Line3DPerpendicularToExample(m.ThreeDScene):
            def construct(self) -> None:
                self.set_camera_orientation(
                    phi=60 * m.DEGREES, theta=-45 * m.DEGREES
                )
                line = m.Line3D(2 * m.RIGHT, m.UP + m.OUT, color=m.RED)
                perpendicular = m.Line3D.perpendicular_to(line, color=m.BLUE)
                self.add(m.ThreeDAxes(), line, perpendicular)
        ```
    """
    np_point = np.asarray(point, dtype=float)
    norm = np.cross(line.vect, np_point - line.start)
    if np.linalg.norm(norm) == 0:
        raise ValueError("Could not find the perpendicular.")
    start, end = perpendicular_bisector([line.start, line.end], norm)
    vect = normalize(end - start)
    return cls(np_point + vect * length / 2, np_point - vect * length / 2, **kwargs)

Arrow3D

Arrow3DExample
Code
import manimgx as m


class Arrow3DExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=65 * m.DEGREES, theta=-45 * m.DEGREES)
        axes = m.ThreeDAxes(x_range=(-3, 3), y_range=(-3, 3), z_range=(-2, 2))
        arrows = [
            m.Arrow3D(axes.c2p(0, 0, 0), axes.c2p(*end), color=color)
            for end, color in (
                ((2, 0, 0), m.RED),
                ((0, 2, 0), m.GREEN),
                ((0, 0, 2), m.BLUE),
                ((1.5, 1.5, 1.5), m.YELLOW),
            )
        ]
        self.add(axes, *arrows)

An arrow in three dimensions: a thin cylinder from start, ending in a cone whose apex is at end; white unless given a color, shaded by a three-dimensional scene's light.

m.Arrow3D(start=LEFT, end=RIGHT, thickness=0.02, height=0.3, base_radius=0.08, resolution=24, **kwargs)
start

Where the arrow starts.

end

Where its tip's apex is.

thickness

The radius of its shaft, in scene units.

height

The length of its tip, a Cone, in scene units.

base_radius

The radius of its tip's base, in scene units.

resolution

How many faces its shaft has around it (2 along it), or (along, around).

It also takes the Surface keywords.

Source

src/manimgx/mobjects/three_d.py

def __init__(
    self,
    start: Point3DLike = LEFT,
    end: Point3DLike = RIGHT,
    thickness: float = 0.02,
    height: float = 0.3,
    base_radius: float = 0.08,
    resolution: int | tuple[int, int] = 24,
    **kwargs: Unpack[SurfaceLook],
) -> None:
    color = kwargs.pop("color", None) or WHITE
    start, end = np.asarray(start, dtype=float), np.asarray(end, dtype=float)
    super().__init__(
        start=start,
        end=end - height * normalize(end - start),
        thickness=thickness,
        resolution=resolution,
        **kwargs,
    )
    self.cone = Cone(
        direction=self.direction, base_radius=base_radius, height=height, **kwargs
    )
    """Its tip: a [Cone][manimgx.Cone], its apex at the arrow's end."""
    self.cone.shift(end)
    self.end_point = VectorizedPoint(end)
    self.add(self.end_point, self.cone)
    self.set_color(color)

cone

Its tip: a Cone, its apex at the arrow's end.

Dot3D

Dot3DExample
Code
import manimgx as m


class Dot3DExample(m.ThreeDScene):
    def construct(self) -> None:
        self.set_camera_orientation(phi=70 * m.DEGREES, theta=-45 * m.DEGREES)
        axes = m.ThreeDAxes(x_range=(-3, 3), y_range=(-3, 3), z_range=(-2, 2))
        self.add(
            axes,
            m.Dot3D(axes.c2p(0, 0, 1.5), radius=0.2, color=m.RED),
            m.Dot3D(axes.c2p(2, 0, 0), radius=0.2, color=m.BLUE),
            m.Dot3D(axes.c2p(0, 2, 0), radius=0.2, color=m.YELLOW),
        )

A dot in three dimensions: a small sphere, white unless given a color.

m.Dot3D(point=ORIGIN, radius=DEFAULT_DOT_RADIUS, resolution=(8, 8), **kwargs)
point

Where its center goes.

radius

Its radius, in scene units.

resolution

How many faces it has around and from pole to pole: one number for both, or (u, v); None for (24, 12).

It also takes the Surface keywords.

Source

src/manimgx/mobjects/three_d.py

def __init__(
    self,
    point: Point3DLike = ORIGIN,
    radius: float = DEFAULT_DOT_RADIUS,
    resolution: int | tuple[int, int] | None = (8, 8),
    **kwargs: Unpack[SurfaceLook],
) -> None:
    color = kwargs.pop("color", None) or WHITE  # one color, over the checkerboard
    super().__init__(center=point, radius=radius, resolution=resolution, **kwargs)
    self.set_color(color)

Meshes

MeshMobject

Code
import numpy as np

import manimgx as m


class MeshMobjectExample(m.Scene):
    def construct(self) -> None:
        vertices = np.array([[-3, -2.5, 0], [3, -2.5, 0], [0, 3, 0]])
        colors = np.array([[1, 0, 0, 1], [0, 1, 0, 1], [0, 0, 1, 1]])
        triangle = m.MeshMobject(
            vertices, np.array([[0, 1, 2]]), vertex_colors=colors
        )
        self.play(m.FadeIn(triangle))

A mesh: triangles between vertices, filled with one color, a color per vertex, or a picture; white and opaque unless styled.

Any two meshes can morph into each other, whatever their triangles: the one with fewer gains some, collapsed to points. Drawn in (Create), a mesh appears triangle by triangle, in their order. A three-dimensional scene's light shades it only with shade_in_3d=True.

m.MeshMobject(vertices=None, triangles=None, *, vertex_colors=None, uvs=None, texture=None, **kwargs)
vertices

The vertices, in scene coordinates: an (n, 3) array; None for an empty mesh.

triangles

The triangles: an (m, 3) array of indices into vertices.

vertex_colors

A color for each vertex, blended across each triangle: an (n, 4) array of red, green, blue and opacity, from 0 to 1; None for the style's color.

uvs

Where each vertex is in the picture: an (n, 2) array of coordinates from 0 to 1, (0, 0) the picture's top left.

texture

The picture: an (h, w, 4) array of RGBA values from 0 to 255, or a Camera, whose view is drawn anew every frame.

color

The color of both fill and stroke (default white); None for the class's default.

fill_color

The fill's color; color if not given. Several colors make a gradient along sheen_direction.

fill_opacity

The fill's opacity, from 0 to 1 (default 0: no fill).

stroke_color

The stroke's color; color if not given. Several colors make a gradient.

stroke_opacity

The stroke's opacity, from 0 to 1 (default 1).

stroke_width

The stroke's width, in hundredths of a scene unit (default 4; 0: no stroke).

background_stroke_color

The color of an outline drawn behind the fill (default black).

background_stroke_opacity

The outline's opacity, from 0 to 1 (default 1).

background_stroke_width

The outline's width, in hundredths of a scene unit (default 0: none).

sheen_factor

How much the colors lighten toward sheen_direction, from -1 to 1 (default 0); a negative factor darkens.

sheen_direction

The direction the colors lighten toward (default UL).

joint_type

How the stroke is joined where its path turns: round, beveled or mitered, as a two-dimensional scene draws it (see LineJointType; default AUTO: mitered).

cap_style

How the stroke ends, at each end it shows (an open path's, a dash's): round, butt or square, as a two-dimensional scene draws it (see CapStyleType; default AUTO: butt).

shade_in_3d

Whether a three-dimensional scene's light shades the mobject.

material

How its surface reflects the scene's lights in a three-dimensional scene (see Material); None: Manim's shading (default).

name

A name for the mobject; its class's name if not given.

z_index

Its place in the drawing order: a higher index is drawn over a lower one (default 0).

target

The state MoveToTarget moves the mobject to.

It also takes the style keywords.

Source

src/manimgx/mobject.py

def __init__(
    self,
    vertices: Point3D_Array | None = None,
    triangles: np.ndarray | None = None,
    *,
    vertex_colors: np.ndarray | None = None,
    uvs: np.ndarray | None = None,
    texture: "np.ndarray | Camera | None" = None,
    **kwargs: Unpack[Style],
) -> None:
    # values, as they are given: regenerated, the mesh is what it was made of
    self._init_mesh = (
        None if vertices is None else np.array(vertices),
        None if triangles is None else np.array(triangles),
        None if uvs is None else np.array(uvs),
    )
    self._vertex_colors = None if vertex_colors is None else np.array(vertex_colors)
    self._texture = _value(texture) if isinstance(texture, np.ndarray) else texture
    super().__init__(**kwargs)

triangles

The mesh's readonly (m, 3) triangle indices; for a lattice these are the coarse triangles of the faces it draws, two each.

Equal assignment is a no-op. Ordinary meshes snapshot replacement indices; a sampled lattice requires constructing an explicit MeshMobject instead.

mesh_mobject.triangles
Source

src/manimgx/mobject.py

def triangles(self) -> np.ndarray:
    """The mesh's readonly (m, 3) triangle indices; for a lattice these are the
    coarse triangles of the faces it draws, two each.

    Equal assignment is a no-op. Ordinary meshes snapshot replacement indices;
    a sampled lattice requires constructing an explicit MeshMobject instead.
    """
    topology = self._topology
    return topology.triangles() if isinstance(topology, Lattice) else topology

uvs

Where each vertex is in the mesh's picture: an (n, 2) array of coordinates from 0 to 1, read-only; None for a mesh without them. Assignments snapshot the supplied coordinates; equal values keep the current array.

mesh_mobject.uvs
Source

src/manimgx/mobject.py

def uvs(self) -> np.ndarray | None:
    """Where each vertex is in the mesh's picture: an (n, 2) array of coordinates
    from 0 to 1, read-only; None for a mesh without them. Assignments snapshot
    the supplied coordinates; equal values keep the current array."""
    return self._uvs

grid

The numbers of lattice cells along u and v, or None for explicit triangles.

A (u, v) lattice has (u + 1) * (v + 1) samples. Its fill rows belong to cells and UVs to samples; ordinary mesh fill rows belong to vertices. match_points adopts this domain while retaining its recipient's style. A part of a surface has its whole lattice, and draws some of its faces.

mesh_mobject.grid
Source

src/manimgx/mobject.py

def grid(self) -> tuple[int, int] | None:
    """The numbers of lattice cells along u and v, or None for explicit triangles.

    A (u, v) lattice has (u + 1) * (v + 1) samples. Its fill rows belong to cells
    and UVs to samples; ordinary mesh fill rows belong to vertices. match_points
    adopts this domain while retaining its recipient's style. A part of a surface
    has its whole lattice, and draws some of its faces.
    """
    topology = self._topology
    return (topology.u, topology.v) if isinstance(topology, Lattice) else None

Spherical coordinates

spherical_to_cartesian

The vector of spherical coordinates (see cartesian_to_spherical).

m.spherical_to_cartesian(spherical)
spherical

The length r, the azimuth θ and the polar angle φ, in radians.

Returns The vector (r cos θ sin φ, r sin θ sin φ, r cos φ).

Source

src/manimgx/drawing/geometry.py

def spherical_to_cartesian(spherical: np.ndarray) -> Vector3D:
    """The vector of spherical coordinates (see
    [cartesian_to_spherical][manimgx.cartesian_to_spherical]).

    Args:
        spherical: The length r, the azimuth θ and the polar angle φ, in radians.

    Returns:
        The vector (r cos θ sin φ, r sin θ sin φ, r cos φ).
    """
    r, theta, phi = spherical
    return np.array(
        [
            r * np.cos(theta) * np.sin(phi),
            r * np.sin(theta) * np.sin(phi),
            r * np.cos(phi),
        ]
    )

cartesian_to_spherical

A vector's spherical coordinates.

m.cartesian_to_spherical(vec)
vec

The vector.

Returns Its length r, its azimuth θ (the angle of its xy part from the x axis, from −π to π) and its polar angle φ (from the z axis, from 0 to π); zeros for the zero vector.

Source

src/manimgx/drawing/geometry.py

def cartesian_to_spherical(vec: Vector3DLike) -> Vec:
    """A vector's spherical coordinates.

    Args:
        vec: The vector.

    Returns:
        Its length r, its azimuth θ (the angle of its xy part from the x axis, from −π
        to π) and its polar angle φ (from the z axis, from 0 to π); zeros for the zero
        vector.
    """
    v = np.asarray(vec, dtype=float)
    r = np.linalg.norm(v)
    if r == 0:
        return np.zeros(3)
    # the polar angle by its tangent: arccos(z / r) loses digits near the poles
    return np.array([r, np.arctan2(v[1], v[0]), np.arctan2(np.hypot(v[0], v[1]), v[2])])