MathLens
git clone https://github.com/shuyicc/MathLens
git clone --depth=1 https://github.com/shuyicc/MathLens ~/.claude/skills/shuyicc-mathlens-mathlens
SKILL.md数学分析
核心工作流
WORKFLOW = [ # 步骤1: 数学建模 { "step": 1, "name": "analyze_problem", "input": "题目图片/文本", "output": "math_analysis.md", "tasks": ["推导数学事实", "建立几何模型", "确定图形构建方法"] }, # 步骤2: HTML可视化 { "step": 2, "name": "html_visualization", "input": "math_analysis.md", "output": "数学_{日期}_{题目}.html", "tasks": ["SVG画图形", "展示画图过程", "标注关键要素"] }, # 步骤3: 分镜脚本 { "step": 3, "name": "storyboard", "input": "HTML内容", "output": "{日期}_{题目}_分镜.md", "tasks": ["定义幕结构(不限制幕数)", "设计画面/字幕/读白", "音频清单(时长留空)"] }, # 步骤4: TTS生成(含句级同步点) { "step": 4, "name": "generate_tts", "input": "分镜脚本", "output": "audio/audio_{三位幕号}_{幕名}.wav + audio_info.json(含sync_points)", "command": "python scripts/generate_tts.py audio_list.csv ./audio --voice xiaoxiao" }, # 步骤5: 验证更新(含同步点摘要) { "step": 5, "name": "validate_audio", "input": "分镜.md + audio/", "output": "更新后的分镜.md(填充时长) + 同步点摘要(供步骤7参考)", "command": "python scripts/validate_audio.py 分镜.md ./audio", "check": ["音频存在性", "时长>0", "数量匹配", "sync_points完整性"] }, # 步骤6: 脚手架 { "step": 6, "name": "scaffold", "input": "分镜.md + audio_info.json(含sync_points)", "output": "script.py (伪代码框架)", "template": "templates/script_scaffold.py", "must_include": [ "calculate_geometry() - 几何建模", "assert_geometry() - 几何验证(题目条件+精度) + 画布范围检查(边界+中心)", "COLORS - 颜色定义", "SCENES[] - 幕信息数组(从audio_info.json加载时长)", "start_scene_with_audio(scene_num) - 每幕起点(播放音频+记录起始时间)", "end_scene_with_audio(expected_duration, safety_margin=0.2) - 每幕收尾(自动补齐等待)", "wait_for_narration(keyword) - 等到读白说出关键词时刻再触发动画", "wait_until_scene_time(seconds) - 等到幕内指定时刻", ] }, # 步骤7: 生成代码 { "step": 7, "name": "implement", "input": "script.py脚手架 + 分镜.md + audio_info.json(含sync_points)", "output": "完整的script.py", "rules": [ "根据分镜实现每幕动画", "使用 wait_for_narration(keyword) 对齐读白和高亮", "禁止 self.wait(max(..., duration - N)) 手动兜底", "画面时长由 end_scene_with_audio() 自动补齐", "calculate_geometry()必须完整实现" ] }, # 步骤8: 检查与渲染 { "step": 8, "name": "check_and_render", "input": "script.py", "output": "视频文件 + 关键帧截图", "command": "python scripts/render.py", "check": ["代码结构检查(check.py)", "几何正确性", "高亮同步", "字幕清晰", "动画流畅"], "on_fail": "回到步骤7修改代码" } ] # 工作流状态转换 # step1 → step2 → step3 → step4 → step5 → step6 → step7 → step8 # ↑____________↓ (失败时循环)
步骤1:分析题目,推导数学事实
目标:建立正确的数学计算方法和流程,形成画出符合数学的图形的方法。
输出格式
## 数学事实分析 ### 已知条件 - 条件1:... - 条件2:... ### 推导的事实 1. **事实名称**: 描述 - 计算过程: ... - 数学表达: ... ### 图形构建方法 - 点的坐标: ... - 边的关系: ... - 圆/弧的定义: ... ### 需要证明的结论 - 结论1: ...
注意:所有证明题需要证明的结论,在分镜中都可以作为已确立的事实来使用。
步骤2:HTML + SVG 可视化
目标:用 HTML + SVG 画出图形,展示画图过程和解答流程,为分镜做规划。 补充:如果用户已经提供了准确的HTML文件,描述了画图过程,可以按文件名要求复制一份,跳过生成文件。
输出要求
- 文件命名:
数学_{日期}_{题目简述}.html - 包含:题目陈述、SVG 图形、分步解答、关键要素标注
- SVG 需要展示画图过程(如:先画三角形 → 再画圆 → 标注点)
步骤3:生成分镜脚本
目标:定义视频结构,不限制幕数,结尾预留音频文件名(时长为空)。
文件命名
{日期}_{题目}_分镜.md
分镜脚本结构
# 分镜脚本 - {题目名称} ## 分镜设计 ### 第1幕:{幕名} **画面**: ... **字幕**: ...(简洁,≤20字) **读白**: ...(详细,口语化,容易理解,适合普通学情的学生) **动画**: ... **目的**: ... --- ## 音频生成清单(步骤4填写) | 幕号 | 文件名 | 读白文本 | 时长 | 说话人 | 情感 | |------|--------|----------|------|--------|------| | 1 | audio_001_{幕名}.wav | "读白文本" | | xiaoxiao | 热情 | | 2 | audio_002_{幕名}.wav | "读白文本" | | xiaoxiao | 平和 |
关键规范
- 幕号从1开始连续编号
- 文件名格式:
audio_{三位幕号}_{幕名}.wav - 时长列为空,由步骤5填充
- 不限制幕数,根据内容需要决定
- 每幕读白建议 1
3 句,每句 820 字,避免单幕文本过长导致动画拥挤 - 动作节奏采用「句子拍点」:每句读白对应 1~2 个核心动画,不要一口气堆叠大量 Create/Transform
动画描述:使用读白关键词锚点(重要)
分镜的动画部分使用
@[关键词] 锚定读白中的句子,步骤4 TTS 生成后会自动产生每句话的精确时间戳(sync_points),步骤7中代码通过 wait_for_narration("关键词") 精确对齐。
不要写硬编码秒数(如
0.0s, 3.0s),因为 TTS 生成前无法预知准确时刻。
**动画**: - @[勾股定理]: 字幕"勾股定理"淡入 - @[正方形]: 正方形高亮 → 字幕退场 - @[所以]: 结论公式淡入,持续到幕末
规则:@[关键词]
- 关键词取自读白文本中某句话的特征词(2~6字)
- 同一幕内关键词不要重复
- 步骤5验证后,可在同步点摘要中确认每个关键词的实际时间
- 如果某动画不需要对齐读白(如幕首淡入),可以省略锚点
字幕退场约定
为了避免动画中文字忘记退场,在动画部分使用退场标记:
退场标记方式(三选一):
箭头符号表示退场(推荐,最简洁)→
或退场:
显式标记淡出:
指定显示时长持续X秒
示例:
**动画**: - @[正方形性质]: 字幕淡入,持续3秒 → 自动退场 - @[勾股定理]: 标题淡入 → @[所以] 时退场 - 幕末:所有文字退场
参考示例
见
references/storyboard_sample.md - 第九十九题:证明四点共圆
音画节奏预算(必填)
每一幕必须包含节奏预算(写在分镜的动画末尾):
**节奏预算**: - 音频时长 D: ___s(步骤5回填) - 读白句数: 3 句 - 核心动画数: 3 个(每句读白对应 1~2 个动画) - 关键同步词: "三角形ABC", "内切圆", "切点"
执行原则:
- 单幕内重动画(Create/Transform 大对象)连续不超过 3 个
- 每个关键结论后至少留
的视觉停顿0.2~0.4s - 幕末收尾由
自动补齐,不需要手动兜底end_scene_with_audio() - 禁止
模式self.wait(max(..., duration - N))
步骤4:TTS 生成语音文件(含句级同步点)
使用脚本:
scripts/generate_tts.py(Edge TTS)
脚本通过 Edge TTS 的
WordBoundary 事件自动捕获每句话的起始时间,生成 sync_points。
执行命令
python scripts/generate_tts.py audio_list.csv ./audio --voice xiaoxiao
输出文件
audio/ ├── audio_001_开场.wav ├── audio_002_展示图形.wav ├── audio_003_定理证明.wav ├── ... └── audio_info.json ← 含 sync_points
audio_info.json 格式(含 sync_points)
{ "files": [ { "scene": 1, "file": "audio_001_开场.wav", "duration": 8.5, "sync_points": [ {"idx": 0, "text": "大家好,今天我们来讲解一道经典的平面几何问题", "time": 0.0}, {"idx": 1, "text": "证明四点共圆", "time": 3.2}, {"idx": 2, "text": "这道题综合运用了切线长定理和双曲线的性质", "time": 4.8} ] }, { "scene": 2, "file": "audio_002_展示图形.wav", "duration": 15.2, "sync_points": [ {"idx": 0, "text": "首先,我们来看题目给出的图形", "time": 0.0}, {"idx": 1, "text": "三角形 ABC 的内切圆 I", "time": 2.1}, {"idx": 2, "text": "三角形内一点 K", "time": 7.4} ] } ] }
sync_points 说明:
:句子在本幕中的序号(从 0 开始)idx
:句子前 40 字(用于关键词匹配)text
:该句话在音频中的起始秒数(由 TTS WordBoundary 精确计算)time- 在步骤7中通过
查找 text 包含关键词的条目self.wait_for_narration("关键词")
步骤5:验证音频并查看同步点
使用脚本:
scripts/validate_audio.py
功能
- 读取
获取音频时长audio/audio_info.json - 验证所有音频文件存在且时长正常(>0秒)
- 更新分镜脚本的"音频生成清单"中的时长列
- 打印同步点摘要:列出每幕的句级同步点及其时间戳
- 如果缺少音频或时长异常,报错提醒
执行命令
python scripts/validate_audio.py 分镜.md ./audio
输出示例(含同步点摘要)
🎯 同步点摘要(用于 wait_for_narration) ================================================== 第1幕 (8.5s): 0.00s │ "大家好,今天我们来讲解一道经典的平面几何问题" 3.20s │ "证明四点共圆" 4.80s │ "这道题综合运用了切线长定理和双曲线的性质" 第2幕 (15.2s): 0.00s │ "首先,我们来看题目给出的图形" 2.10s │ "三角形 ABC 的内切圆 I" 7.40s │ "三角形内一点 K"
此摘要是步骤7中选择
关键词的依据。wait_for_narration()
验证失败情况
- 缺少音频文件 → 报错:"缺少第X幕音频文件"
- 时长为0或异常 → 报错:"第X幕音频时长异常,请检查格式"
- 分镜与音频数量不匹配 → 报错:"分镜X幕,音频Y个,数量不匹配"
步骤6:生成 script.py 脚手架
使用模板:
templates/script_scaffold.py
输出:
script.py(伪代码框架,由 AI 在步骤7完善)
脚手架核心方法一览
| 方法 | 作用 | 调用位置 |
|---|---|---|
| 几何建模 | construct() |
| 几何验证 + 画布范围 | construct() |
| 定义 Manim 图形对象 | construct() |
| 播放音频 + 记录起始时间 | construct() 循环 |
| 自动补齐等待 | construct() 循环 |
| 等到读白说出关键词 | play_scene_X() |
| 等到幕内指定时刻 | play_scene_X() |
| 查找关键词时间(不等待) | play_scene_X() |
脚手架必须包含的部分
from manim import * import json import os class MathScene(Scene): """ 数学教学视频场景 - 脚手架 根据分镜脚本和音频信息生成完整动画 """ # ========== 1. 颜色定义 ========== COLORS = { 'background': '#1a1a2e', # 深蓝背景 'primary': '#4ecca3', # 主色(青色) 'secondary': '#e94560', # 辅助色(红色) 'highlight': '#ffc107', # 高亮色(黄色) 'text': '#ffffff', # 文字白色 'grid': '#2a2a4e', # 网格线 } # ========== 1b. 画布分区(防遮挡核心) ========== # 默认分区,可根据题目图形大小微调坐标,但必须保证各区互不重叠 ZONES = { 'title': {'y_min': 3.0, 'y_max': 3.8, 'x_min': -6.5, 'x_max': 6.5, 'desc': '标题区(顶部)'}, 'geometry': {'y_min': -2.2, 'y_max': 2.8, 'x_min': -5.0, 'x_max': 3.0, 'desc': '几何图形主区域(中偏左)'}, 'subtitle': {'y_min': -3.8, 'y_max': -2.8, 'x_min': -6.5, 'x_max': 6.5, 'desc': '字幕区(底部)'}, 'formula': {'y_min': -2.2, 'y_max': 2.8, 'x_min': 3.5, 'x_max': 6.5, 'desc': '公式/结论区(右侧)'}, } # ========== 1c. z_index 层级定义(防遮挡核心) ========== # 数值越大越靠前(遮挡数值小的) Z_LAYERS = { 'bg_grid': 0, # 背景辅助线、网格 'fill': 1, # 填充色块(如三角形内部着色) 'geometry': 2, # 几何图形(线段、圆、多边形边框) 'label': 3, # 点标签、角标注(必须在图形之上) 'highlight': 4, # 高亮效果(闪烁、变色) 'subtitle': 5, # 字幕文字 'formula': 5, # 公式、推导步骤 'title': 6, # 标题 } # ========== 2. 幕信息数组(供AI参考) ========== # 格式: (幕号, 幕名, 音频文件名, 时长秒数) # 注意:时长从 audio_info.json 读取,确保画面等待音频原则 SCENES = [ (1, "开场", "audio_001_开场.wav", None), (2, "展示图形", "audio_002_展示图形.wav", None), # ... 根据分镜动态生成 ] def __init__(self, **kwargs): super().__init__(**kwargs) # 加载音频时长信息 self.audio_timings = self._load_audio_timings() def _load_audio_timings(self): """从 audio_info.json 加载音频时长""" # TODO: AI实现 - 读取JSON并填充 SCENES 的时长 pass # ========== 3. 几何计算函数 ========== def calculate_geometry(self): """ 计算所有几何元素的位置和属性 从步骤2的HTML画图中提取信息,建立数学模型: - 所有点的坐标 (x, y) - ⚠️ 始终使用2D,z坐标始终为0 - 边的长度和方程 - 圆/弧的圆心和半径 - 交点计算 - 切线、法线等辅助线 返回: dict 包含所有几何对象的数据 """ # TODO: AI根据题目几何关系实现 geometry = { 'points': {}, # {'A': (x, y), 'B': (x, y), ...} 'lines': {}, # {'AB': {'start': A, 'end': B, 'length': L}, ...} 'circles': {}, # {'circle1': {'center': O, 'radius': r}, ...} 'arcs': {}, # 圆弧定义 } return geometry # ========== 4. 几何验证函数 ========== def assert_geometry(self, geometry): """ 验证几何计算的正确性(最小验证原则) 验证内容: 1. 题目给定的事实(如:两条边相等,谁是谁的一半) 2. 精度问题:使用相对误差比较,而非绝对相等 3. 画布范围检查:确保图形在可视区域内 4. 错误提示:所有assert必须用中文描述问题,包含具体数值和修复建议 === 几何条件验证 === 基于题目给定的条件,编写最小但关键的验证: - 如果题目说 AB = BC,验证 |AB - BC| < epsilon - 如果题目说 E 是中点,验证 AE = EB(考虑浮点误差) - 如果题目说某角是直角,验证向量点积接近0 - 如果题目给具体数值(如边长为5),验证计算结果匹配 精度建议: - 相对误差:1e-6 或绝对误差 1e-4 - 示例:assert abs(len_AB - len_BC) < 1e-4, "中文错误提示:AB长度不等于BC长度" === 画布范围验证算法 === 目标:确保整个图形在画布内,且位于视觉中心区域 算法步骤(AI需要根据几何数据实现): 1. 计算所有几何元素的外接矩形 bounding_box - 遍历所有点、线、圆的边界点 - 取最小x, 最大x, 最小y, 最大y 2. 验证矩形在画布范围内(考虑边距) - 画布默认范围:FRAME_WIDTH=14.2, FRAME_HEIGHT=8 - 建议边距:四周各留 0.5-1.0 单位 - 验证:min_x > -7+margin 且 max_x < 7-margin min_y > -4+margin 且 max_y < 4-margin 3. 验证矩形中心在视觉中心区域 - 画布中心:(0, 0) - 视觉中心区域:中心 ±20%(即 x∈[-1.4, 1.4], y∈[-0.8, 0.8]) - 计算矩形中心:(center_x, center_y) = ((min_x+max_x)/2, (min_y+max_y)/2) - 验证:center_x ∈ [-1.4, 1.4] 且 center_y ∈ [-0.8, 0.8] 4. 如果图形超出范围,AI需要调整几何计算的缩放或平移 """ # === TODO: AI实现 - 基于题目条件的验证(失败时用中文报错)=== # 示例代码(根据实际题目修改): # epsilon = 1e-4 # len_ab = geometry['lines']['AB']['length'] # len_bc = geometry['lines']['BC']['length'] # assert abs(len_ab - len_bc) < epsilon, f"几何验证失败:AB长度({len_ab:.4f})不等于BC长度({len_bc:.4f}),题目要求两边相等" # # len_ae = geometry['lines']['AE']['length'] # len_ab = geometry['lines']['AB']['length'] # assert abs(len_ae * 2 - len_ab) < epsilon, f"几何验证失败:E不是AB中点,AE({len_ae:.4f})*2={len_ae*2:.4f} != AB({len_ab:.4f})" # === TODO: AI实现 - 画布范围检查(失败时用中文报错)=== # 1. 计算bounding_box # all_points = list(geometry['points'].values()) # min_x = min(p[0] for p in all_points) # max_x = max(p[0] for p in all_points) # min_y = min(p[1] for p in all_points) # max_y = max(p[1] for p in all_points) # # 2. 验证画布边界(失败时报具体数值) # margin = 0.5 # assert min_x > -7 + margin, f"画布验证失败:图形左边界(min_x={min_x:.2f})超出安全区域(>{-7+margin}),需要向右平移或缩小" # assert max_x < 7 - margin, f"画布验证失败:图形右边界(max_x={max_x:.2f})超出安全区域(<{7-margin}),需要向左平移或缩小" # assert min_y > -4 + margin, f"画布验证失败:图形下边界(min_y={min_y:.2f})超出安全区域(>{-4+margin}),需要向上平移或缩小" # assert max_y < 4 - margin, f"画布验证失败:图形上边界(max_y={max_y:.2f})超出安全区域(<{4-margin}),需要向下平移或缩小" # # 3. 验证视觉中心(失败时报具体偏移) # center_x = (min_x + max_x) / 2 # center_y = (min_y + max_y) / 2 # assert -1.4 <= center_x <= 1.4, f"视觉中心验证失败:图形中心X={center_x:.2f}超出视觉中心区域[-1.4, 1.4],建议调整几何计算使图形居中" # assert -0.8 <= center_y <= 0.8, f"视觉中心验证失败:图形中心Y={center_y:.2f}超出视觉中心区域[-0.8, 0.8],建议调整几何计算使图形居中" pass # ========== 5. 图形元素定义 ========== def define_elements(self, geometry): """ 定义 Manim 图形对象(但不创建动画) ⚠️ 必须遵守步骤7「防遮挡规范」:z_index层级、标签偏移、分区放置 """ # TODO: AI根据分镜需求定义 elements = { 'points': {}, # Mobject 点对象 'lines': {}, # Mobject 线对象 'circles': {}, # Mobject 圆对象 'labels': {}, # 标签文字 } return elements # ========== 6. 构造主流程 ========== def construct(self): """主构造流程""" # 计算几何 geometry = self.calculate_geometry() self.assert_geometry(geometry) elements = self.define_elements(geometry) # 设置背景 self.camera.background_color = self.COLORS['background'] # 按幕执行 for scene_num, scene_name, audio_file, duration in self.SCENES: self.play_scene(scene_num, scene_name, audio_file, duration, elements, geometry) def play_scene(self, scene_num, scene_name, audio_file, duration, elements, geometry): """ 播放单幕动画 - 必须与统一时间轴护栏配合 ========== 音频集成(强制要求) ========== 每幕必须添加对应的音频文件,否则视频将没有声音。 但推荐在 construct() 中统一调用 start_scene_with_audio(scene_num), 不要在每个 play_scene_X() 里重复散落 add_sound 逻辑。 统一入口: expected_duration = self.start_scene_with_audio(scene_num) self.play_scene_X(...) self.end_scene_with_audio(expected_duration, safety_margin=0.2) 注意: - audio_file 参数已经包含文件名(如 "audio_001_开场.wav") - 音频文件位于 audio/ 目录下 - 不要手工估算“本幕动画共几秒”,以 self.time 实际耗时为准 ========== 画面等待音频原则 ========== - 动画总时长由 end_scene_with_audio 自动兜底到 >= 音频时长 - 可以画面多等待(多出静音部分,后期可剪辑) - 不能画面比音频短(会导致音频被截断) ========== 高亮规范(必须在读白提到时同步) ========== - 提到某个边等于某个边 → 用动画高亮两条边 - 提到某个点/圆 → 闪烁或放大高亮 - 提到证明结论 → 用框或颜色强调 ========== 绘制策略 ========== - 简单图形:直接显示,然后高亮关键部分 - 复杂图形:逐步绘制,边画边讲解 """ # TODO: AI实现 - 只写本幕视觉动作,音频起止由统一护栏方法负责 pass # ========== 7. 同步对齐工具(核心新增) ========== def wait_until_scene_time(self, target_time): """等待到当前幕内指定时刻(相对于幕开始的秒数)""" # 脚手架已实现,见 templates/script_scaffold.py pass def wait_for_narration(self, keyword): """ 等到读白说出包含 keyword 的那句话时刻。 从 audio_info.json 的 sync_points 中查找匹配。 用法示例: self.wait_for_narration("内切圆") self.play(FadeIn(incircle)) """ # 脚手架已实现,见 templates/script_scaffold.py pass def get_sync_time(self, keyword): """查找关键词的时间戳(不等待),返回 float 或 None""" pass
步骤7:AI 生成最终代码
输入:
- 分镜脚本(已更新时长)
(含 sync_points)audio/audio_info.json
脚手架script.py- 步骤5输出的同步点摘要
输出:完整的
script.py
AI 生成指导:
- 根据分镜的"动画"描述实现具体动画代码
- 使用
对齐读白和高亮(参考步骤5的同步点摘要选择关键词)wait_for_narration("关键词") - 实现
的具体计算calculate_geometry() - 实现
的验证逻辑assert_geometry() - 为每幕实现
方法play_scene_X() - 音频集成(必须,防重叠):
- 在
的每幕循环中统一执行:construct()expected_duration = self.start_scene_with_audio(scene_num)play_scene_X(...)self.end_scene_with_audio(expected_duration, safety_margin=0.2)
内专注视觉动作,不手工累计总时长play_scene_X()
- 在
- 验证音频文件路径正确:
audio/audio_001_XXX.wav - 字幕显示与退场(关键):
- 使用
或show_subtitle_timed(text, duration)show_subtitle_with_audio(text, audio_duration) - 分镜中的
或→
标记表示文字退场退场: - 必须确保所有文字元素都有退场动画
- 使用
禁止事项(步骤7)
| 禁止 | 替代方案 |
|---|---|
| 删除手动兜底,由 自动补齐 |
硬编码 对齐读白 | 使用 |
在 中调用 | 由 统一管理 |
手动估算 判断切幕 | 由 自动处理 |
防遮挡规范(步骤7必须遵守)
动画元素相互遮挡是最常见的视觉问题。以下规则必须严格执行。
规则1:画布分区 — 不同类型元素各归其位
脚手架中的
ZONES 定义了默认分区(可根据题目微调坐标,但必须保证各区互不重叠):
| 区域 | 放置内容 | 核心约束 |
|---|---|---|
| 标题区(顶部) | 幕标题 | 幕切换时替换旧标题 |
| 几何区(中偏左) | 图形、点、线、圆、标签 | 主图形区,偏左留出右侧公式空间 |
| 字幕区(底部) | 字幕文字 | 字幕必须在底部(y ≈ -3.3),禁止放画布中央 |
| 公式区(右侧) | 公式、推导、结论 | 禁止与几何图形重叠;图形偏右时可将公式区换到左侧 |
规则2:z_index 层级 — 每个 Mobject 必须设置
使用脚手架
Z_LAYERS 常量,创建后立即调用 set_z_index():
line_AB = Line(A, B, color=WHITE) line_AB.set_z_index(self.Z_LAYERS['geometry']) # 层级2 label_A = Text("A", font_size=28).next_to(dot_A, UR, buff=0.15) label_A.set_z_index(self.Z_LAYERS['label']) # 层级3,在线段之上 # ❌ 忘记 set_z_index → 标签可能被后绘制的线段遮挡
层级从后到前:背景(0) → 填充(1) → 几何(2) → 标签(3) → 高亮(4) → 字幕/公式(5) → 标题(6)
规则3:点标签防碰撞
- 方向偏移:
,禁止把标签放在点的精确坐标next_to(dot, 方向, buff=0.15~0.25) - 方向选择:标签朝远离密集线段/元素的方向(如三角形内角处标签朝外)
- 背景衬底:标签可能与线段接近时,加
label.add_background_rectangle(color=bg_color, opacity=0.85, buff=0.06) - 间距检查:两标签间距 < 0.3 时,调整方向或增大 buff
规则4:元素生命周期 — 及时退场防堆叠
# 字幕:必须配对退场 self.play(FadeIn(subtitle)) self.wait_for_narration("关键词") self.play(FadeOut(subtitle)) # ❌ 漏掉这行 → 字幕堆叠 # 公式替换:先退旧再入新 self.play(FadeOut(old_formula), FadeIn(new_formula)) # 高亮:不再需要时恢复原色(如果需要持续高亮则可保留,但确保不遮挡后续元素) self.play(line_AB.animate.set_color(YELLOW)) # ...讲解完毕后... self.play(line_AB.animate.set_color(WHITE))
幕末清理:每幕结束前,将不需要带入下一幕的元素统一 FadeOut。
规则5:同区域冲突预防
- 多公式纵向排列:
VGroup(...).arrange(DOWN, buff=0.3) - 字幕排队:多条字幕依次显示(先退场再入场),禁止同时可见
- 同一位置禁止堆叠 2 个及以上可见文字元素
步骤8:代码检查与渲染
8.1 代码结构检查(必须)
使用脚本:
scripts/check.py
在渲染之前,必须先检查代码是否包含必要的函数和结构:
# 检查 script.py(默认) python scripts/check.py # 检查指定文件 python scripts/check.py my_script.py
检查内容:
- ✅
- 几何计算函数是否存在calculate_geometry() - ✅
- 几何验证函数是否存在assert_geometry() - ✅
- 图形元素定义函数是否存在define_elements() - ✅ 字幕类
/Subtitle
是否存在TitleSubtitle - ✅ 是否有
调用(音频集成)add_sound() - ✅ 是否有继承
的类Scene - ✅ 是否使用了
或wait_for_narration()
同步方法wait_until_scene_time() - ✅ 是否存在
反模式self.wait(max(..., duration - N)) - ⚠️ 防遮挡(需人工审查代码):Mobject 是否设置了
、字幕是否在底部、字幕是否配对 FadeOut、标签是否用set_z_index()
偏移next_to()
检查结果:
- ❌ 错误:必须修复,否则无法渲染
- ⚠️ 警告:建议修复,但不会阻止渲染
8.2 渲染视频
方式1:使用渲染脚本(推荐,包含检查)
# 完整流程:检查 -> 渲染 -> 拷贝到根目录 python scripts/render.py # 指定文件和场景 python scripts/render.py -f script.py -s MathScene # 指定渲染质量 python scripts/render.py -q h # 1080p60 (默认) python scripts/render.py -q k # 4K python scripts/render.py -q m # 720p30 # 跳过检查(不推荐,仅用于快速测试) python scripts/render.py --no-check
方式2:直接使用 manim(跳过检查,不推荐)
manim -pqh script.py MathScene
提取关键帧验证
python scripts/extract_frames.py media/videos/script/1080p60/MathScene.mp4 --interval 5
验证内容
- 几何图形是否正确(点、线、圆位置)
- 高亮是否与讲解同步
- 字幕是否清晰可读
- 动画是否流畅
- 防遮挡检查(关键帧逐帧确认):
- 点标签是否被线段/圆弧遮挡?
- 字幕是否与几何图形重叠?
- 多个文字元素是否堆叠在同一位置?
- 公式/结论是否侵入几何区域?
- 音频是否正确添加:
- 每幕都有声音(没有静音幕)
- 音频与分镜中的读白内容一致
- 每幕末
(没有抢跑切幕)elapsed >= audio_duration + safety_margin - 没有出现上一幕音频未结束就进入下一幕的重叠
渲染后必须执行
# 在construct()最后添加:拷贝视频到根目录 import shutil import os from pathlib import Path # 渲染完成后拷贝到根目录 video_src = Path("media/videos/script/1920p60/SquareTriangleProblem.mp4") video_dst = Path("最终视频.mp4") if video_src.exists(): shutil.copy2(video_src, video_dst) print(f"✓ 视频已拷贝到: {video_dst}")
规则:渲染完成后必须将视频拷贝到项目根目录,不要留在media/深处。
如果发现问题
- 回到步骤7修改代码
- 重新渲染验证
数学解题原则(绝对重要)
❌ 禁止使用坐标系
绝不用坐标系来求解,应该用各种定义和推理。
错误示例(坐标法):
设B=(0,0), C=(5,0), A=(1.8, 2.4)... 面积 = ½ × 底 × 高
正确示例(几何推理):
1. 由勾股定理:BC = √(AB² + AC²) = 5 2. 正方形性质:BE = BC = 5 3. 等积变换:S△ABE = S△ABC × (EB/BC) × sin(∠ABE)/sin(∠ABC) 4. 或用向量叉积、海伦公式等纯几何方法
允许的可视化辅助:
- 为了动画展示可以显示坐标,但解题过程不能用坐标计算
- 可以用几何画板的思路:旋转、平移、对称等变换
推荐的几何推理方法
- 等积变换 - 同底等高、等底同高
- 相似三角形 - 比例关系
- 勾股定理 - 边长关系
- 向量/叉积 - 纯几何意义的运算
- 旋转/对称 - 几何变换
文件结构
tutor/ ├── SKILL.md # 本文件 - 工作流程定义 ├── requirements.txt # Python 依赖 ├── references/ │ └── storyboard_sample.md # 分镜脚本示例(参考) ├── templates/ │ └── script_scaffold.py # Manim 脚手架模板(含字幕类) ├── scripts/ │ ├── generate_tts.py # TTS 生成脚本 │ ├── validate_audio.py # 音频验证脚本 │ ├── check.py # 代码结构检查脚本(渲染前必执行) │ └── render.py # 渲染流水线脚本(检查+渲染+拷贝) └── sample/ # 示例项目(保留参考) └── geometry_proof/
依赖管理
使用
uv 管理依赖:
# 创建虚拟环境 uv venv .venv # 安装依赖 uv pip install -r requirements.txt # 激活环境 source .venv/bin/activate
绕过LaTeX依赖(重要)
Manim默认需要LaTeX来渲染数学公式。如果不想安装LaTeX,全部使用Text替代MathTex:
# ❌ 需要LaTeX MathTex(r"BC^2 = AB^2 + AC^2") MathTex(r"\frac{1}{2} \times 5") MathTex("A") # 甚至连字母都需要LaTeX # ✅ 不需要LaTeX Text("BC² = AB² + AC²", font_size=36) Text("½ × 5", font_size=30) Text("A", font_size=32) # 用Text直接显示字母
常用替换对照表:
| 数学符号 | LaTeX写法 | 无LaTeX写法 |
|---|---|---|
| 上标 | | (直接输入Unicode) |
| 分数 | | 或 |
| 度数 | | |
| 平方厘米 | | |
| 根号 | | |
| 角度 | | |
脚手架默认不使用MathTex,如需数学公式请直接输入Unicode字符。
关键原则总结
| 原则 | 说明 |
|---|---|
| 数学先行 | 先建立正确的数学模型,再画图 |
| 音频必填 | 每幕必须调用 ,否则视频无声音 |
| 句级同步 | 使用 精确对齐读白和高亮(精度 ±0.3s) |
| 高亮对应 | 配音提到什么,画面高亮什么 |
| 自动补齐 | 幕末由 自动补齐,禁止 手动兜底 |
| 最小验证 | 验证题目条件(带精度) + 画布范围(边界+中心) |
| 防遮挡 | 画布分区(标题/几何/字幕/公式) + z_index层级 + 标签偏移 + 及时退场 |
| 逐步抽象 | 从 HTML 可视化 → 分镜脚本 → Manim 代码 |