|
| 1 | +=================== |
| 2 | +Choosing Type Hints |
| 3 | +=================== |
| 4 | +In order to provide the best user experience, |
| 5 | +it's important that type hints are chosen correctly. |
| 6 | +With the large variety of types provided by Manim, choosing |
| 7 | +which one to use can be difficult. This guide aims to |
| 8 | +aid you in the process of choosing the right type for the scenario. |
| 9 | + |
| 10 | + |
| 11 | +The first step is figuring out which category your type hint fits into. |
| 12 | + |
| 13 | +Coordinates |
| 14 | +----------- |
| 15 | +Coordinates encompass two main categories: points, and vectors. |
| 16 | + |
| 17 | + |
| 18 | +Points |
| 19 | +~~~~~~ |
| 20 | +The purpose of points is pretty straightforward: they represent a point |
| 21 | +in space. For example: |
| 22 | + |
| 23 | +.. code-block:: python |
| 24 | +
|
| 25 | + def status2D(coord: Point2D) -> None: |
| 26 | + x, y = coord |
| 27 | + print(f"Point at {x=},{y=}") |
| 28 | +
|
| 29 | +
|
| 30 | + def status3D(coord: Point3D) -> None: |
| 31 | + x, y, z = coord |
| 32 | + print(f"Point at {x=},{y=},{z=}") |
| 33 | +
|
| 34 | +
|
| 35 | + def get_statuses(coords: Point2D_Array | Point3D_Array) -> None: |
| 36 | + for coord in coords: |
| 37 | + if len(coord) == 2: |
| 38 | + # it's a Point2D |
| 39 | + status2D(coord) |
| 40 | + else: |
| 41 | + # it's a point3D |
| 42 | + status3D(coord) |
| 43 | +
|
| 44 | +It's important to realize that the status functions accepted both |
| 45 | +tuples/lists of the correct length, and ``NDArray``'s of the correct shape. |
| 46 | +If they only accepted ``NDArray``'s, we would use their ``Internal`` counterparts: |
| 47 | +:class:`~.typing.InternalPoint2D`, :class:`~.typing.InternalPoint3D`, :class:`~.typing.InternalPoint2D_Array` and :class:`~.typing.InternalPoint3D_Array`. |
| 48 | + |
| 49 | +In general, the type aliases prefixed with ``Internal`` should never be used on |
| 50 | +user-facing classes and functions, but should be reserved for internal behavior. |
| 51 | + |
| 52 | +Vectors |
| 53 | +~~~~~~~ |
| 54 | +Vectors share many similarities to points. However, they have a different |
| 55 | +connotation. Vectors should be used to represent direction. For example, |
| 56 | +consider this slightly contrived function: |
| 57 | + |
| 58 | +.. code-block:: python |
| 59 | +
|
| 60 | + def shift_mobject(mob: Mobject, direction: Vector3D, scale_factor: float = 1) -> mob: |
| 61 | + return mob.shift(direction * scale_factor) |
| 62 | +
|
| 63 | +Here we see an important example of the difference. ``direction`` can not, and |
| 64 | +should not, be typed as a :class:`~.typing.Point3D` because the function does not accept tuples/lists, |
| 65 | +like ``direction=(0, 1, 0)``. You could type it as :class:`~.typing.InternalPoint3D` and |
| 66 | +the type checker and linter would be happy; however, this makes the code harder |
| 67 | +to understand. |
| 68 | + |
| 69 | +As a general rule, if a parameter is called ``direction`` or ``axis``, |
| 70 | +it should be type hinted as some form of :class:`~.VectorND`. |
| 71 | + |
| 72 | +.. warning:: |
| 73 | + |
| 74 | + This is not always true. For example, as of Manim 0.18.0, the direction |
| 75 | + parameter of the :class:`.Vector` Mobject should be ``Point2D | Point3D``, |
| 76 | + as it can also accept ``tuple[float, float]`` and ``tuple[float, float, float]``. |
| 77 | + |
| 78 | +Colors |
| 79 | +------ |
| 80 | +The interface Manim provides for working with colors is :class:`.ManimColor`. |
| 81 | +The main color types Manim supports are RGB, RGBA, and HSV. You will want |
| 82 | +to add type hints to a function depending on which type it uses. If any color will work, |
| 83 | +you will need something like: |
| 84 | + |
| 85 | +.. code-block:: python |
| 86 | +
|
| 87 | + if TYPE_CHECKING: |
| 88 | + from manim.utils.color import ParsableManimColor |
| 89 | +
|
| 90 | + # type hint stuff with ParsableManimColor |
| 91 | +
|
| 92 | +
|
| 93 | +
|
| 94 | +Béziers |
| 95 | +------- |
| 96 | +Manim internally represents a :class:`.Mobject` by a collection of points. In the case of :class:`.VMobject`, |
| 97 | +the most commonly used subclass of :class:`.Mobject`, these points represent Bézier curves, |
| 98 | +which are a way of representing a curve using a sequence of points. |
| 99 | + |
| 100 | +.. note:: |
| 101 | + |
| 102 | + To learn more about Béziers, take a look at https://pomax.github.io/bezierinfo/ |
| 103 | + |
| 104 | + |
| 105 | +Manim supports two different renderers, which each have different representations of |
| 106 | +Béziers: Cairo uses cubic Bézier curves, while OpenGL uses quadratic Bézier curves. |
| 107 | + |
| 108 | +Type hints like :class:`~.typing.BezierPoints` represent a single bezier curve, and :class:`~.typing.BezierPath` |
| 109 | +represents multiple Bézier curves. A :class:`~.typing.Spline` is when the Bézier curves in a :class:`~.typing.BezierPath` |
| 110 | +forms a single connected curve. Manim also provides more specific type aliases when working with |
| 111 | +quadratic or cubic curves, and they are prefixed with their respective type (e.g. :class:`~.typing.CubicBezierPoints`, |
| 112 | +is a :class:`~.typing.BezierPoints` consisting of exactly 4 points representing a cubic Bézier curve). |
| 113 | + |
| 114 | + |
| 115 | +Functions |
| 116 | +--------- |
| 117 | +Throughout the codebase, many different types of functions are used. The most obvious example |
| 118 | +is a rate function, which takes in a float and outputs a float (``Callable[[float], float]``). |
| 119 | +Another example is for overriding animations. One will often need to map a :class:`.Mobject` |
| 120 | +to an overridden :class:`.Animation`, and for that we have the :class:`~.typing.FunctionOverride` type hint. |
| 121 | + |
| 122 | +:class:`~.typing.PathFuncType` and :class:`~.typing.MappingFunction` are more niche, but are related to moving objects |
| 123 | +along a path, or applying functions. If you need to use it, you'll know. |
| 124 | + |
| 125 | + |
| 126 | +Images |
| 127 | +------ |
| 128 | +There are several representations of images in Manim. The most common is |
| 129 | +the representation as a NumPy array of floats representing the pixels of an image. |
| 130 | +This is especially common when it comes to the OpenGL renderer. |
| 131 | + |
| 132 | +This is the use case of the :class:`~.typing.Image` type hint. Sometimes, Manim may use ``PIL.Image``, |
| 133 | +in which case one should use that type hint instead. |
| 134 | +Of course, if a more specific type of image is needed, it can be annotated as such. |
0 commit comments