Ir al contenido

Mobjects personalizados

Hasta ahora hemos visto como podemos usar los objetos que vienen por defecto en Manim para hacer animaciones. Pero… ¿Qué pasa si quiero hacer objetos personalizados o a mi medida?

Aquí entra en juego la herencia en python. Podemos crear objetos nuevos heredando de Mobject, de VMobject, de VGroup o de cualquier otra clase de Mobject que queramos.

Para ello, es tan sencillo como implementar una subclase y modificar lo que queramos.

Supongamos que queremos utilizar la clase Dot pero es muy chiquita para nuestro caso. Podemos implementar una versión más grande fácilmente:

custom_dot.py
from manim import *
class CustomDot(Dot):
def __init__(self, point=ORIGIN, radius=3, **kwargs):
super().__init__(point, radius, **kwargs)
class CustomDotExample(Scene):
def construct(self):
dots = VGroup(
Dot(),
CustomDot(color=RED)
).arrange()
self.add(dots)

Modificar cosas que ya existen puede estar bien, pero la gracia de saber programar es poder crear lo que nosotros queramos. Esto lo podemos hacer heredando de las clases Mobject o VMobject.

Aquí, por ejemplo, haremos una clase que dibuje una espiral. Para ello, vamos a heredar de VMobject y vamos a sobreescribir el método _generate_points.

spiral_example.py
from manim import *
import numpy as np
class Spiral(VMobject):
def __init__(
self,
start_radius: float = 0.0,
end_radius: float = 3.0,
num_turns: float = 3.0,
start_angle: float = 0.0,
direction: int = 1,
num_samples: int = 400,
**kwargs,
):
super().__init__(**kwargs)
self.start_radius = start_radius
self.end_radius = end_radius
self.num_turns = num_turns
self.start_angle = start_angle
self.direction = direction
self.num_samples = num_samples
self._generate_points()
def _radius_at(self, t: float) -> float:
"""Return the radius at normalized parameter t in [0, 1]."""
a, b = self.start_radius, self.end_radius
return a + (b - a) * t
def _generate_points(self):
total_angle = self.direction * self.num_turns * 2 * PI
ts = np.linspace(0.0, 1.0, self.num_samples)
points = []
for t in ts:
theta = self.start_angle + total_angle * t
r = self._radius_at(t)
x = r * np.cos(theta)
y = r * np.sin(theta)
points.append([x, y, 0.0])
self.set_points_as_corners(
[ORIGIN] + points if self.start_radius == 0 else points)
self.make_smooth()
def get_num_turns(self) -> float:
return self.num_turns
def get_end_point(self) -> np.ndarray:
return self.points[-1]
class SpiralExample(Scene):
def construct(self):
spiral = Spiral(
start_radius=0.2,
end_radius=2.2,
num_turns=4,
color=BLUE,
stroke_width=3,
)
self.add(spiral)

Podemos heredar de VGroup para generar objetos compuestos en lugar de ir definiendo todos sus puntos. Una gran ventaja de esto es que podemos aprovechar los VMobjects ya existentes y utilizarlos para simplificarnos el trabajo.

element_example.py
from manim import *
class Element(VGroup):
def __init__(self, symbol="H", element="Hydrogen", atomic_number=1, bg_color=BLUE_A, text_color=BLACK, height=3, widht=2, **kwargs):
self.background = Rectangle(height=height, width=widht, fill_opacity=1, color=bg_color)
self.symbol = Text(symbol, color=text_color).scale_to_fit_height(self.background.height * 0.35).move_to(self.background.get_center())
self.element = Text(element, color=text_color).scale_to_fit_width(self.background.width*0.75).next_to(self.symbol, DOWN)
self.atomic_number = Text(str(atomic_number), color=text_color).scale_to_fit_height(0.1 * self.background.height).next_to(self.symbol, UL)
self.bg_color = bg_color
self.text_color = text_color
vmobjects = [
self.background,
self.element,
self.symbol,
self.atomic_number
]
super().__init__(*vmobjects, **kwargs)
class ElementExample(Scene):
def construct(self):
hydrogen = Element()
carbon = Element("C", "Carbon", 6)
oxygen = Element("O", "Oxygen", 8)
nitrogen = Element("N", "Nitrogen", 7)
elements = VGroup(
hydrogen,
carbon,
oxygen,
nitrogen
).arrange()
self.add(elements)

Crea una figura con un punto central del cuál brotan un número indeterminado de brazos como si fuera una galaxia espiral.

Solución al ejercicio 1
custom_objects_ex_1.py
from manim import *
import numpy as np
class SpiralGalaxy(VMobject):
def __init__(
self,
arms=5,
turns=1.5,
max_radius=3.0,
curvature=0.22,
color=BLUE_B,
**kwargs,
):
self.arms = arms
self.turns = turns
self.max_radius = max_radius
self.curvature = curvature
self.arms_color = color
super().__init__(**kwargs)
def generate_points(self):
for i in range(self.arms):
offset = TAU * i / self.arms
arm = self._build_arm(offset)
self.append_points(arm.points)
def _build_arm(self, offset):
sample_points = 80
pts = []
for k in range(sample_points):
t = k / (sample_points - 1)
theta = offset + t * self.turns * TAU
r = self.max_radius * (
np.exp(self.curvature * (theta - offset)) - 1
) / (np.exp(self.curvature * self.turns * TAU) - 1)
pts.append([r * np.cos(theta), r * np.sin(theta), 0.0])
arm = VMobject()
arm.set_points_smoothly(pts)
return arm
def init_colors(self):
super().init_colors()
self.set_color(self.arms_color)
class CustomObjectsExampleOne(Scene):
def construct(self):
self.add(SpiralGalaxy())

Crea una figura que contenga un texto encuadrado en un rectángulo. Se deben poder configurar el texto, el color del texto, el color del rectángulo y el color del borde del rectángulo.

Solución al ejercicio 2
custom_objects_ex_2.py
from manim import *
import numpy as np
class BoxedText(VGroup):
def __init__(self, text="", text_color=BLACK, box_color=BLUE, box_stroke_color=BLUE_E, **kwargs):
VERTIICAL_MARGIN = 1.5
HORIZONTAL_MARGIN = 1.2
self.text = Text(text, color=text_color)
self.text_color = text_color
self.box = Rectangle(
height=VERTIICAL_MARGIN*self.text.height,
width=HORIZONTAL_MARGIN*self.text.width,
fill_opacity=1,
fill_color=box_color,
stroke_color=box_stroke_color
)
self.box_color = box_color
self.box_stroke_color = box_stroke_color
vmobjects = [self.box, self.text]
super().__init__(*vmobjects, **kwargs)
class CustomObjectsExampleTwo(Scene):
def construct(self):
self.add(BoxedText("Hello World!"))