Animora Architecture — Public API Philosophy & Conventions
1. Ergonomic Principles
Animora's public API is designed around four foundational ergonomic tenets:
- Declarative Configuration, Imperative Action: Component construction is declarative (declaring data, styles, and options), while animation sequences are expressed through intuitive, imperative semantic method calls (
graph.highlight_path(...),array.swap(...)). - Predictable Defaults, Deep Customization: Every component renders with professional typography, balanced padding, and harmonized colors out of the box without requiring manual styling parameters.
- No Black Boxes (First-Class Escape Hatch): When a user hits the boundary of Animora’s abstractions, they never need to rewrite their scene. Every component exposes its underlying Manim
VMobjectdirectly. - Seamless Manim Interoperability: Animora components can be added to any standard Manim
Sceneviaself.add()orself.play()without requiring a proprietary Animora-only runtime.
2. The Base Component Contract
Every visual element in Animora inherits from animora.core.Component. Its public interface consists of five standard facets:
class Component:
"""Base class for all Animora visual components.
(Illustrative specification — non-final implementation)
"""
# 1. Lifecycle & Construction
def __init__(self, *args, theme: Theme | None = None, **kwargs) -> None: ...
# 2. Geometric Dimensions & Bounds
@property
def width(self) -> float: ...
@property
def height(self) -> float: ...
@property
def center(self) -> np.ndarray: ...
@property
def bounding_box(self) -> BoundingBox: ...
# 3. Spatial Positioning & Alignment
def move_to(self, target: np.ndarray | Component) -> Self: ...
def next_to(
self, target: Component, direction: np.ndarray = RIGHT, buff: float = 0.5
) -> Self: ...
def align_to(self, target: Component, direction: np.ndarray = UP) -> Self: ...
def scale(self, scale_factor: float) -> Self: ...
# 4. Semantic Animation Generators (Return Manim Animation objects or AnimationGroup)
def animate_create(self, run_time: float = 1.0) -> Animation: ...
def animate_fade_out(self, run_time: float = 1.0) -> Animation: ...
def animate_transform_to(self, new_state: Any, run_time: float = 1.0) -> Animation: ...
# 5. The Manim Escape Hatch
@property
def manim_object(self) -> VMobject | Mobject:
"""Exposes the underlying Manim Mobject/VMobject/VGroup."""
...
3. The Escape Hatch: Interoperability with Raw Manim
A critical requirement of Animora is that users are never trapped inside the abstraction layer. If a user needs a specialized Manim animation (such as ApplyPointwiseFunction, Wiggle, or custom shaders), they access .manim_object.
Illustrative Example: High-Level Animora + Raw Manim Interoperability
# Illustrative snippet — non-final API demonstration
from manim import Scene, UP, DOWN, PI, Wiggle, Rotate, Circle, BLUE
from animora.components.dsa import Array, Graph
from animora.theme import ModernDarkTheme
class HybridAlgorithmScene(Scene):
def construct(self):
# 1. High-level declarative Animora component creation
arr = Array([42, 17, 89, 5, 23], theme=ModernDarkTheme())
arr.move_to(UP * 2)
# 2. Add to standard Manim scene via escape hatch
self.play(arr.animate_create(run_time=1.5))
# 3. Perform semantic Animora operations
self.play(arr.animate_swap(0, 3, run_time=1.0))
self.play(arr.animate_highlight(3, color="#10B981"))
# 4. ESCAPE HATCH: Use raw Manim objects and low-level Manim animations
raw_mobject = arr.manim_object
self.play(Wiggle(raw_mobject))
self.play(raw_mobject.animate.shift(DOWN * 1.5))
# 5. Interleave with native Manim primitives
marker = Circle(radius=0.3, color=BLUE).next_to(raw_mobject, DOWN)
self.play(Rotate(marker, angle=PI), run_time=0.8)
4. Public vs. Internal API Conventions
Animora enforces strict naming and exposure rules across the entire codebase:
1. Explicit __all__ Lists
Every public module and package __init__.py must define an explicit __all__ list. Only symbols listed in __all__ are considered part of the public, semantically versioned API.
2. Underscore Prefixing for Private Members
- All internal helper functions, private attributes, and non-public methods must be prefixed with a single leading underscore (e.g.
_compute_cell_bounds(),_cached_mobject). - Double-underscore mangling (
__name) is discouraged unless specifically required to prevent subclass namespace collisions.
3. Internal Submodules (_internal)
Implementation details shared across modules that are not part of the public API must reside in subpackages prefixed with an underscore (e.g. animora.core._internal.geometry_utils). Code outside the animora repository must not import from _internal.
4. Type Annotations
All public functions, methods, and class attributes must have 100% complete type hints (compatible with mypy --strict). Type aliases and protocols are exposed under animora.core.types.