Maiim布局管理器
摘要
在使用 Manim 制作动画时,大模型自动生成的代码常会出现元素重叠的问题。为了解决这一难题,我们设计了一个通用的 LayoutManager
,它能够:
- 为不同的 Mobject(图形、曲线等)自动分配唯一颜色,保持视觉区分度。
- 对
Text
、MathTex
、Axes
等文字或坐标系元素保留黑色,以确保可读性。 - 递归注册
VGroup
,对其子元素进行统一管理。 - 对标记为“重要”的文字自动加粗,突出重点。
- 根据元素宽高比例,沿水平或垂直方向自动排列,避免重叠。
文档首先给出 layout_manager.py
的完整实现,然后通过 01.py
示例展示如何在绘制“三角函数”动画时使用该管理器。
代码
布局管理器实现(layout_manager.py)
"""
A LayoutManager for Manim that:
1. Automatically assigns unique colors to registered objects (curves, shapes, etc.).
2. Preserves white for Text, MathTex, and Axes (including axis lines and ticks).
3. Recursively registers VGroups to color their children.
4. Sets bold weight for registered Text when marked important.
5. Auto-arranges registered VGroups along a direction.
Use:
lm = LayoutManager(scene)
lm.register(group_or_mobj, important=True)
lm.fix_overlaps(group_or_vgroup)
"""
from manim import VGroup, Text, MathTex, Axes
from manim import RED, GREEN, BLUE, YELLOW, PURPLE, ORANGE, TEAL, PINK, WHITE,BLACK
class LayoutManager:
def __init__(self, scene, direction=None, buff=0.5, palette=None):
"""
:param scene: Manim Scene (kept for extensibility)
:param direction: RIGHT or DOWN, or None to auto-pick per group
:param buff: spacing for arrange
:param palette: list of colors to cycle for unique assignments
"""
self.scene = scene
self.direction = direction
self.buff = buff
self.palette = palette or [RED, GREEN, BLUE, YELLOW, PURPLE, ORANGE, TEAL, PINK]
self._color_index = 0
self._registered = {} # mobject -> metadata
def register(self, mobject, important=False, color=None):
"""
Register a mobject or VGroup:
- VGroup: recursively register its submobjects.
- Text: if important=True, set bold weight; keep color black (unless overridden).
- MathTex/Axes: keep color black.
- Other: assign unique color from palette (or explicit color).
"""
# Recursively handle VGroup
if isinstance(mobject, VGroup):
for sub in mobject:
self.register(sub, important=important, color=color)
return
# Determine color
if color is None:
if isinstance(mobject, Text):
use_color = BLACK
elif isinstance(mobject, (MathTex, Axes)):
use_color = BLACK
else:
use_color = self.palette[self._color_index % len(self.palette)]
self._color_index += 1
else:
use_color = color
# Store metadata
self._registered[mobject] = {"important": important, "color": use_color}
# Apply color
try:
mobject.set_color(use_color)
except Exception:
pass
# Bold for important Text
if important and isinstance(mobject, Text):
try:
mobject.set_weight("bold")
except Exception:
pass
def fix_overlaps(self, group):
"""
Arrange a registered VGroup along direction or auto direction.
"""
if group not in self._registered:
return
if not isinstance(group, VGroup):
return
# Pick direction
dir_use = self.direction
if dir_use is None:
width, height = group.width, group.height
from manim import RIGHT, DOWN
dir_use = DOWN if height > width else RIGHT
group.arrange(dir_use, buff=self.buff)
示例脚本(01.py)
from layout_manager import LayoutManager
from manim import *
from manim import RED, GREEN, BLUE, YELLOW, PURPLE, ORANGE, TEAL, PINK, WHITE,BLACK
class Main(Scene):
def setup(self):
# 创建布局管理器实例
self.layout_manager = LayoutManager(self)
def construct(self):
self.camera.background_color = WHITE
# 1. 标题
title = Text("三角函数", font_size=48)
subtitle = Text("Trigonometric Functions", font_size=32)
# 将标题和副标题放置到画面上方
title.to_edge(UP)
subtitle.next_to(title, DOWN)
# 注册标题元素,标记为重要以加粗
self.layout_manager.register(title, important=True)
self.layout_manager.register(subtitle, important=True)
# 2. 引言文字
intro_text = Text(
"三角函数是描述直角三角形中\n角度与边长关系的函数",
font_size=32
)
intro_text.next_to(subtitle, DOWN, buff=1)
self.layout_manager.register(intro_text)
# 调用布局管理器,避免文字与副标题重叠
self.layout_manager.fix_overlaps(intro_text)
# 3. 绘制直角三角形
triangle = Polygon(
ORIGIN,
RIGHT * 3,
RIGHT * 3 + UP * 2,
fill_opacity=0.2,
fill_color=BLUE,
color=WHITE
)
self.layout_manager.register(triangle)
triangle.next_to(intro_text, DOWN, buff=0.8)
# 三角形各部分标签
angle_label = MathTex(r"\theta").scale(0.8)
self.layout_manager.register(angle_label)
angle_label.next_to(triangle.get_vertices()[0], UR * 0.5)
hyp_label = MathTex(r"c").scale(0.8)
self.layout_manager.register(hyp_label)
hyp_label.move_to(
(triangle.get_vertices()[0] + triangle.get_vertices()[2]) / 2 + UL * 0.3
)
adj_label = MathTex(r"a").scale(0.8)
self.layout_manager.register(adj_label)
adj_label.next_to(triangle.get_vertices()[1], DOWN * 0.5)
opp_label = MathTex(r"b").scale(0.8)
opp_label.next_to(triangle.get_vertices()[2], RIGHT * 0.5)
# 绘制直角标记
right_angle = Square(side_length=0.3)
self.layout_manager.register(right_angle)
right_angle.move_to(triangle.get_vertices()[1])
right_angle.align_to(triangle.get_vertices()[1], DL)
# 避免三角形与其他元素重叠
self.layout_manager.fix_overlaps(triangle)
# 4. 动画呈现
self.play(Write(title))
self.play(Write(subtitle))
self.wait(0.5)
self.play(Write(intro_text))
self.play(Create(triangle))
self.play(
Create(right_angle),
Write(angle_label),
Write(hyp_label),
Write(adj_label),
Write(opp_label)
)
self.wait(2)
使用说明
初始化管理器
在需要布局控制的Scene
中,先创建LayoutManager
实例,并传入当前场景对象:self.layout_manager = LayoutManager(self)
注册元素
对所有需要管理颜色或加粗的Mobject
调用register()
:self.layout_manager.register(title, important=True)
important=True
会让Text
加粗;- 未注册对象保持默认行为。
避免元素重叠
对具有多个子元素且可能重叠的VGroup
,或单个Mobject
,调用fix_overlaps()
:self.layout_manager.fix_overlaps(intro_text) self.layout_manager.fix_overlaps(triangle)
管理器会根据元素的宽高比,沿水平或竖直方向自动排列。
总结
通过引入 LayoutManager
,我们可以在 Manim 动画中:
- 自动统一管理对象的配色,提升视觉一致性;
- 保留文字和坐标系的原色,保证可读性;
- 对重要文字一键加粗,突出展示重点;
- 智能调用
arrange
,避免元素重叠。