dsh-motion 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +3 -0
- package/README.md +114 -25
- package/docs/ai-native-motion.md +301 -0
- package/docs/dev-loop.md +55 -0
- package/lib/doctor.js +26 -1
- package/lib/fresh.js +21 -0
- package/lib/index.js +44 -6
- package/lib/ops.js +164 -8
- package/package.json +10 -3
- package/skills/animation-router/SKILL.md +30 -12
- package/skills/design-spec/SKILL.md +33 -1
- package/skills/qa-rubric/SKILL.md +14 -8
- package/styles/3b1b/STYLE.md +68 -0
- package/styles/3b1b/preview.jpg +0 -0
- package/styles/3b1b/style.json +50 -0
- package/styles/3b1b/theme.py +43 -0
- package/styles/3b1b/tokens.json +32 -0
- package/styles/README.md +28 -0
- package/styles/blueprint/STYLE.md +59 -0
- package/styles/blueprint/preview.jpg +0 -0
- package/styles/blueprint/style.json +47 -0
- package/styles/blueprint/theme.py +65 -0
- package/styles/blueprint/tokens.json +27 -0
- package/styles/dataviz/STYLE.md +58 -0
- package/styles/dataviz/preview.jpg +0 -0
- package/styles/dataviz/style.json +50 -0
- package/styles/dataviz/theme.py +49 -0
- package/styles/dataviz/tokens.json +31 -0
- package/styles/registry.json +212 -0
- package/styles/swiss-motion/STYLE.md +59 -0
- package/styles/swiss-motion/preview.jpg +0 -0
- package/styles/swiss-motion/style.json +47 -0
- package/styles/swiss-motion/theme.py +59 -0
- package/styles/swiss-motion/tokens.json +33 -0
- package/styles/whiteboard/STYLE.md +58 -0
- package/styles/whiteboard/preview.jpg +0 -0
- package/styles/whiteboard/style.json +47 -0
- package/styles/whiteboard/theme.py +53 -0
- package/styles/whiteboard/tokens.json +27 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"generated": true,
|
|
4
|
+
"default": "swiss-motion",
|
|
5
|
+
"styles": [
|
|
6
|
+
{
|
|
7
|
+
"slug": "swiss-motion",
|
|
8
|
+
"num": "01",
|
|
9
|
+
"name_cn": "瑞士动态排版",
|
|
10
|
+
"name_en": "Swiss Motion Graphics",
|
|
11
|
+
"category_cn": "图形与排版",
|
|
12
|
+
"uses": [
|
|
13
|
+
"Formula",
|
|
14
|
+
"Definition",
|
|
15
|
+
"Data conclusion",
|
|
16
|
+
"Typography"
|
|
17
|
+
],
|
|
18
|
+
"uses_cn": [
|
|
19
|
+
"公式",
|
|
20
|
+
"定义",
|
|
21
|
+
"数据结论",
|
|
22
|
+
"排版"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"公式",
|
|
26
|
+
"欧拉",
|
|
27
|
+
"定理",
|
|
28
|
+
"定义",
|
|
29
|
+
"恒等式",
|
|
30
|
+
"推导",
|
|
31
|
+
"排版",
|
|
32
|
+
"方程"
|
|
33
|
+
],
|
|
34
|
+
"avoid": [
|
|
35
|
+
"图表",
|
|
36
|
+
"几何证明"
|
|
37
|
+
],
|
|
38
|
+
"aspect": [
|
|
39
|
+
"16:9"
|
|
40
|
+
],
|
|
41
|
+
"fps": 30,
|
|
42
|
+
"complexity": "low",
|
|
43
|
+
"demo": "showcase/swiss-euler",
|
|
44
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/swiss-motion"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"slug": "dataviz",
|
|
48
|
+
"num": "02",
|
|
49
|
+
"name_cn": "数据叙事",
|
|
50
|
+
"name_en": "Data Storytelling",
|
|
51
|
+
"category_cn": "信息与发布",
|
|
52
|
+
"uses": [
|
|
53
|
+
"Trends",
|
|
54
|
+
"Comparison",
|
|
55
|
+
"Distribution",
|
|
56
|
+
"Scientific data"
|
|
57
|
+
],
|
|
58
|
+
"uses_cn": [
|
|
59
|
+
"趋势",
|
|
60
|
+
"比较",
|
|
61
|
+
"分布",
|
|
62
|
+
"科学数据"
|
|
63
|
+
],
|
|
64
|
+
"keywords": [
|
|
65
|
+
"数据",
|
|
66
|
+
"图表",
|
|
67
|
+
"趋势",
|
|
68
|
+
"比较",
|
|
69
|
+
"分布",
|
|
70
|
+
"斜率",
|
|
71
|
+
"切线",
|
|
72
|
+
"割线",
|
|
73
|
+
"函数",
|
|
74
|
+
"变化率",
|
|
75
|
+
"统计"
|
|
76
|
+
],
|
|
77
|
+
"avoid": [
|
|
78
|
+
"纯文字排版",
|
|
79
|
+
"角色叙事"
|
|
80
|
+
],
|
|
81
|
+
"aspect": [
|
|
82
|
+
"16:9"
|
|
83
|
+
],
|
|
84
|
+
"fps": 30,
|
|
85
|
+
"complexity": "low",
|
|
86
|
+
"demo": "showcase/dataviz-secant",
|
|
87
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/dataviz"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"slug": "blueprint",
|
|
91
|
+
"num": "03",
|
|
92
|
+
"name_cn": "蓝图工程制图",
|
|
93
|
+
"name_en": "Blueprint",
|
|
94
|
+
"category_cn": "图形与排版",
|
|
95
|
+
"uses": [
|
|
96
|
+
"Geometry proof",
|
|
97
|
+
"Structures",
|
|
98
|
+
"Dimensions"
|
|
99
|
+
],
|
|
100
|
+
"uses_cn": [
|
|
101
|
+
"几何证明",
|
|
102
|
+
"结构",
|
|
103
|
+
"尺寸关系"
|
|
104
|
+
],
|
|
105
|
+
"keywords": [
|
|
106
|
+
"几何",
|
|
107
|
+
"证明",
|
|
108
|
+
"三角形",
|
|
109
|
+
"圆",
|
|
110
|
+
"角度",
|
|
111
|
+
"尺寸",
|
|
112
|
+
"工程",
|
|
113
|
+
"结构",
|
|
114
|
+
"作图",
|
|
115
|
+
"勾股"
|
|
116
|
+
],
|
|
117
|
+
"avoid": [
|
|
118
|
+
"图表",
|
|
119
|
+
"手绘"
|
|
120
|
+
],
|
|
121
|
+
"aspect": [
|
|
122
|
+
"16:9"
|
|
123
|
+
],
|
|
124
|
+
"fps": 30,
|
|
125
|
+
"complexity": "medium",
|
|
126
|
+
"demo": "showcase/blueprint-pythagoras",
|
|
127
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/blueprint"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"slug": "whiteboard",
|
|
131
|
+
"num": "04",
|
|
132
|
+
"name_cn": "白板讲解",
|
|
133
|
+
"name_en": "Whiteboard Explainer",
|
|
134
|
+
"category_cn": "手绘与绘画",
|
|
135
|
+
"uses": [
|
|
136
|
+
"Explainers",
|
|
137
|
+
"Online courses",
|
|
138
|
+
"Science"
|
|
139
|
+
],
|
|
140
|
+
"uses_cn": [
|
|
141
|
+
"科学解释",
|
|
142
|
+
"课程",
|
|
143
|
+
"机制说明"
|
|
144
|
+
],
|
|
145
|
+
"keywords": [
|
|
146
|
+
"讲解",
|
|
147
|
+
"课程",
|
|
148
|
+
"科学",
|
|
149
|
+
"机制",
|
|
150
|
+
"流程",
|
|
151
|
+
"为什么",
|
|
152
|
+
"原理",
|
|
153
|
+
"物理",
|
|
154
|
+
"化学",
|
|
155
|
+
"生物"
|
|
156
|
+
],
|
|
157
|
+
"avoid": [
|
|
158
|
+
"高密度公式",
|
|
159
|
+
"暗场"
|
|
160
|
+
],
|
|
161
|
+
"aspect": [
|
|
162
|
+
"16:9"
|
|
163
|
+
],
|
|
164
|
+
"fps": 30,
|
|
165
|
+
"complexity": "medium",
|
|
166
|
+
"demo": "showcase/whiteboard-derivative",
|
|
167
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/whiteboard"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"slug": "3b1b",
|
|
171
|
+
"num": "05",
|
|
172
|
+
"name_cn": "3b1b 数学动画",
|
|
173
|
+
"name_en": "3Blue1Brown Style",
|
|
174
|
+
"category_cn": "数学讲解",
|
|
175
|
+
"uses": [
|
|
176
|
+
"Linear algebra",
|
|
177
|
+
"Calculus",
|
|
178
|
+
"Probability",
|
|
179
|
+
"Neural networks"
|
|
180
|
+
],
|
|
181
|
+
"uses_cn": [
|
|
182
|
+
"线代",
|
|
183
|
+
"微积分",
|
|
184
|
+
"概率",
|
|
185
|
+
"神经网络"
|
|
186
|
+
],
|
|
187
|
+
"keywords": [
|
|
188
|
+
"向量",
|
|
189
|
+
"矩阵",
|
|
190
|
+
"特征值",
|
|
191
|
+
"旋转",
|
|
192
|
+
"复数",
|
|
193
|
+
"单位圆",
|
|
194
|
+
"线代",
|
|
195
|
+
"概率",
|
|
196
|
+
"微积分",
|
|
197
|
+
"神经网络"
|
|
198
|
+
],
|
|
199
|
+
"avoid": [
|
|
200
|
+
"产品发布",
|
|
201
|
+
"纯排版"
|
|
202
|
+
],
|
|
203
|
+
"aspect": [
|
|
204
|
+
"16:9"
|
|
205
|
+
],
|
|
206
|
+
"fps": 30,
|
|
207
|
+
"complexity": "medium",
|
|
208
|
+
"demo": "showcase/3b1b-unit-circle",
|
|
209
|
+
"source": "https://github.com/subinium/3b1b-style-animation-skill"
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: swiss-motion
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
source: https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/swiss-motion
|
|
5
|
+
license: MIT(上游风格文档);demo 静帧 CC BY 4.0(LemoLab)
|
|
6
|
+
status: adapted
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Swiss Motion Graphics · 瑞士动态排版
|
|
10
|
+
|
|
11
|
+
> 上游:Lemo-Opuscar `styles/swiss-motion`(MIT)。本文件是**改编**,不是原样复制:
|
|
12
|
+
> 去掉了 WebGL/Canvas 专属实现,保留不变量与语法;数值落在 `tokens.json`。
|
|
13
|
+
|
|
14
|
+
## 1. 定位
|
|
15
|
+
|
|
16
|
+
国际主义排版风格在动:模块网格、一种字体、一个信号色,运动像印刷机一样精确。
|
|
17
|
+
|
|
18
|
+
- 适合:公式、定义、数据结论、品牌/活动信息;
|
|
19
|
+
- 不适合:角色叙事、手绘、颗粒质感、连续镜头。
|
|
20
|
+
|
|
21
|
+
## 2. 不变量
|
|
22
|
+
|
|
23
|
+
| 项 | 规则 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| 底色 | 纸灰 `#EAE9E5`,纯平;不用渐变、投影、颗粒 |
|
|
26
|
+
| 墨色 | `#111111`;辅助灰 `#6B6B6B`;规矩线 `#C9C7C1` |
|
|
27
|
+
| 强调 | 全片只有一个信号色 `#E30613`,只给“例外/结论/正在动的东西” |
|
|
28
|
+
| 字体 | 一种无衬线,粗细分层;标题最多 3 级 |
|
|
29
|
+
| 网格 | 16:9 画 12 栏;栏线只在页边距露出短刻度 |
|
|
30
|
+
| 运动 | 两个曲线:精确 ease `cubic-bezier(.7,0,.2,1)` 与 linear;无回弹、无弹簧 |
|
|
31
|
+
| 入场 | 划入 / 裁切揭示;不用淡入淡出 |
|
|
32
|
+
| 声音 | 干净的节拍与印刷 Foley;−14 LUFS;不要颗粒 |
|
|
33
|
+
|
|
34
|
+
## 3. manimgx 映射
|
|
35
|
+
|
|
36
|
+
- `theme.py`:`apply_config()`、`backdrop()`、`columns()`、`rule()`;
|
|
37
|
+
- 字体替换:上游 Archivo/Inter → `Noto Sans`;等宽 → `Menlo`;公式 → `New Computer Modern`;
|
|
38
|
+
- manimgx 没有 clip-path 遮罩,入场用 `Create` / `Write` 代替裁切揭示;
|
|
39
|
+
- 网格用上下页边距刻度,避免版式检查把“线穿字”判成问题。
|
|
40
|
+
|
|
41
|
+
## 4. Do / Don't
|
|
42
|
+
|
|
43
|
+
**Do**
|
|
44
|
+
|
|
45
|
+
- 一个画面只讲一个结论;先有线,再有字;
|
|
46
|
+
- 信号色只出现一次,结论落在它上面;
|
|
47
|
+
- 大字号与小字号并置,中间留白。
|
|
48
|
+
|
|
49
|
+
**Don't**
|
|
50
|
+
|
|
51
|
+
- 不用第二个强调色;不用 Helvetica/Akzidenz 字体文件;
|
|
52
|
+
- 不用渐变球、辉光、投影、颗粒;
|
|
53
|
+
- 不让文字压网格线;不把每个点做成卡片。
|
|
54
|
+
|
|
55
|
+
## 5. 与上游的差异
|
|
56
|
+
|
|
57
|
+
- 上游用 WebGL/Canvas 做裁切揭示与精确网格;这里用 manimgx 的矢量对象近似;
|
|
58
|
+
- 上游 24 fps;本实现默认 30 fps(可在 `tokens.motion.fps` 改回 24);
|
|
59
|
+
- 上游字体不随包分发;这里统一用系统/内置 Noto 与 Menlo,观感会有差异,验收以帧为准。
|
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"slug": "swiss-motion",
|
|
3
|
+
"en": "Swiss Motion Graphics",
|
|
4
|
+
"cn": "瑞士动态排版",
|
|
5
|
+
"category_en": "Graphic & Type",
|
|
6
|
+
"category_cn": "图形与排版",
|
|
7
|
+
"uses": [
|
|
8
|
+
"Formula",
|
|
9
|
+
"Definition",
|
|
10
|
+
"Data conclusion",
|
|
11
|
+
"Typography"
|
|
12
|
+
],
|
|
13
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/swiss-motion",
|
|
14
|
+
"license": "style doc MIT; demo stills CC BY 4.0 (LemoLab)",
|
|
15
|
+
"implementation": "manimgx (theme.py + tokens.json)",
|
|
16
|
+
"num": "01",
|
|
17
|
+
"film": "Euler Formula",
|
|
18
|
+
"line": "A formula on a Swiss grid: one red accent, five constants.",
|
|
19
|
+
"line_cn": "瑞士网格上的一个公式:一个红色信号色,五个常数。",
|
|
20
|
+
"uses_cn": [
|
|
21
|
+
"公式",
|
|
22
|
+
"定义",
|
|
23
|
+
"数据结论",
|
|
24
|
+
"排版"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"公式",
|
|
28
|
+
"欧拉",
|
|
29
|
+
"定理",
|
|
30
|
+
"定义",
|
|
31
|
+
"恒等式",
|
|
32
|
+
"推导",
|
|
33
|
+
"排版",
|
|
34
|
+
"方程"
|
|
35
|
+
],
|
|
36
|
+
"avoid": [
|
|
37
|
+
"图表",
|
|
38
|
+
"几何证明"
|
|
39
|
+
],
|
|
40
|
+
"demo": "showcase/swiss-euler",
|
|
41
|
+
"demo_duration_sec": 6.2,
|
|
42
|
+
"aspect": [
|
|
43
|
+
"16:9"
|
|
44
|
+
],
|
|
45
|
+
"fps": 30,
|
|
46
|
+
"complexity": "low"
|
|
47
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""swiss-motion 1.0.0 — Lemo-Opuscar 的 Swiss Motion Graphics,manimgx 实现层。
|
|
2
|
+
|
|
3
|
+
上游客服规范(MIT):styles/swiss-motion/STYLE.md
|
|
4
|
+
实现只依赖 tokens.json;颜色/字号/网格从这里取,不散落在场景里。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import manimgx as m
|
|
13
|
+
|
|
14
|
+
TOKENS = json.loads(Path(__file__).with_name("tokens.json").read_text(encoding="utf-8"))
|
|
15
|
+
|
|
16
|
+
GROUND = TOKENS["colors"]["ground"]
|
|
17
|
+
PAPER = TOKENS["colors"]["paper"]
|
|
18
|
+
INK = TOKENS["colors"]["ink"]
|
|
19
|
+
MUTED = TOKENS["colors"]["muted"]
|
|
20
|
+
RULE = TOKENS["colors"]["rule"]
|
|
21
|
+
SIGNAL = TOKENS["colors"]["signal"]
|
|
22
|
+
FONT = TOKENS["typography"]["display"]["family"]
|
|
23
|
+
MOTION = TOKENS["motion"]
|
|
24
|
+
LAYOUT = TOKENS["layout"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def apply_config(width: int = 1920, height: int = 1080, fps: int = 30) -> None:
|
|
28
|
+
m.config.pixel_width = width
|
|
29
|
+
m.config.pixel_height = height
|
|
30
|
+
m.config.frame_rate = fps
|
|
31
|
+
m.config.background_color = GROUND
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def backdrop() -> m.Rectangle:
|
|
35
|
+
"""Flat paper ground. Swiss 规定无渐变、无投影、无颗粒。"""
|
|
36
|
+
return (
|
|
37
|
+
m.Rectangle(width=LAYOUT["frame"]["width"], height=LAYOUT["frame"]["height"], color=GROUND)
|
|
38
|
+
.set_fill(GROUND, 1)
|
|
39
|
+
.set_stroke(width=0)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def columns(count: int = 12, margin: float = 0.5, opacity: float = 0.55) -> m.VGroup:
|
|
44
|
+
"""12 栏规矩线:只在上下页边距画短刻度,不穿过文字。"""
|
|
45
|
+
width = LAYOUT["frame"]["width"] - margin * 2
|
|
46
|
+
x0 = -LAYOUT["frame"]["width"] / 2 + margin
|
|
47
|
+
marks = m.VGroup()
|
|
48
|
+
for i in range(count + 1):
|
|
49
|
+
x = x0 + width * i / count
|
|
50
|
+
top = m.Line([x, 3.55, 0], [x, 3.78, 0], color=RULE, stroke_width=1.0)
|
|
51
|
+
bottom = m.Line([x, -3.55, 0], [x, -3.32, 0], color=RULE, stroke_width=1.0)
|
|
52
|
+
top.set_stroke(opacity=opacity)
|
|
53
|
+
bottom.set_stroke(opacity=opacity)
|
|
54
|
+
marks.add(top, bottom)
|
|
55
|
+
return marks
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def rule(y: float, width: float, x: float = 0.0, color: str = RULE, stroke_width: float = 1.2) -> m.Line:
|
|
59
|
+
return m.Line([x - width / 2, y, 0], [x + width / 2, y, 0], color=color, stroke_width=stroke_width)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "swiss-motion",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/swiss-motion",
|
|
5
|
+
"license": "style doc MIT; demo stills CC BY 4.0 (LemoLab)",
|
|
6
|
+
"colors": {
|
|
7
|
+
"ground": "#EAE9E5",
|
|
8
|
+
"paper": "#FFFFFF",
|
|
9
|
+
"ink": "#111111",
|
|
10
|
+
"muted": "#6B6B6B",
|
|
11
|
+
"rule": "#C9C7C1",
|
|
12
|
+
"signal": "#E30613"
|
|
13
|
+
},
|
|
14
|
+
"typography": {
|
|
15
|
+
"display": { "family": "Noto Sans", "weight": "BOLD", "size": 64 },
|
|
16
|
+
"body": { "family": "Noto Sans", "size": 26 },
|
|
17
|
+
"mono": { "family": "Menlo", "size": 18 },
|
|
18
|
+
"math": { "family": "New Computer Modern", "size": 96 }
|
|
19
|
+
},
|
|
20
|
+
"layout": {
|
|
21
|
+
"aspect": "16:9",
|
|
22
|
+
"frame": { "width": 14.222, "height": 8.0 },
|
|
23
|
+
"margin": 0.5,
|
|
24
|
+
"columns": 12
|
|
25
|
+
},
|
|
26
|
+
"motion": {
|
|
27
|
+
"fps": 30,
|
|
28
|
+
"easing": "precise",
|
|
29
|
+
"snap_seconds": 0.4,
|
|
30
|
+
"hold_min_seconds": 1.0
|
|
31
|
+
},
|
|
32
|
+
"audio": { "target_lufs": -14, "true_peak_db": -1 }
|
|
33
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: whiteboard
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
source: https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/whiteboard
|
|
5
|
+
license: MIT(上游风格文档);demo 静帧 CC BY 4.0(LemoLab)
|
|
6
|
+
status: adapted
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Whiteboard Explainer · 白板讲解
|
|
10
|
+
|
|
11
|
+
> 上游:Lemo-Opuscar `styles/whiteboard`(MIT)。保留“一块板 + 三支马克笔 + 书写顺序”的语法;
|
|
12
|
+
> manimgx 版省掉手/浮空笔/相机旅行,用 draw-on 与固定机位近似。
|
|
13
|
+
|
|
14
|
+
## 1. 定位
|
|
15
|
+
|
|
16
|
+
一块暖白干擦板,所有内容都是马克笔“写”出来的:
|
|
17
|
+
|
|
18
|
+
- 适合:科学解释、课程、机制/流程说明;
|
|
19
|
+
- 不适合:需要真实材质、角色表演、复杂 3D 的题材。
|
|
20
|
+
|
|
21
|
+
## 2. 不变量
|
|
22
|
+
|
|
23
|
+
| 项 | 规则 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| 板面 | 暖白 `#F7F5EF`,可有轻微不均与旧字痕 |
|
|
26
|
+
| 墨色 | 黑 `#23262C`:结构与文字 |
|
|
27
|
+
| 过程色 | 蓝 `#2A5CB3`:测量、过程、正在变化 |
|
|
28
|
+
| 强调 | 橙 `#D97757`:结论、最终答案;每区最多一次 |
|
|
29
|
+
| 字体 | 手写体(Chalkboard SE);公式 New Computer Modern |
|
|
30
|
+
| 运动 | 按书写顺序 draw-on;一笔接一笔;不弹跳 |
|
|
31
|
+
| 版面 | 一个想法一个区域;距离=论证距离;标签要么全在画面内,要么不出现 |
|
|
32
|
+
|
|
33
|
+
## 3. manimgx 映射
|
|
34
|
+
|
|
35
|
+
- `theme.py`:`apply_config()`、`backdrop()`(暖白渐变 + 细噪点);
|
|
36
|
+
- 线条与文字用 `Create` / `Write` 按顺序出现;没有笔、手、橡皮、相机;
|
|
37
|
+
- 字体替换:EMS Tech / Architects Daughter → `Chalkboard SE`;
|
|
38
|
+
- 旧字痕用低透明度 `Text`/`Line` 近似。
|
|
39
|
+
|
|
40
|
+
## 4. Do / Don't
|
|
41
|
+
|
|
42
|
+
**Do**
|
|
43
|
+
|
|
44
|
+
- 先写标题,再按“定义 → 例子 → 结论”在板面上展开;
|
|
45
|
+
- 蓝色只给“正在变化”的对象,橙色只给结论;
|
|
46
|
+
- 结论出现后停住,给读者读完。
|
|
47
|
+
|
|
48
|
+
**Don't**
|
|
49
|
+
|
|
50
|
+
- 不画手、笔、光标;
|
|
51
|
+
- 不用第四种马克笔颜色;
|
|
52
|
+
- 不做镜头急推/急拉;文字不压线。
|
|
53
|
+
|
|
54
|
+
## 5. 与上游的差异
|
|
55
|
+
|
|
56
|
+
- 上游有浮空马克笔、笔影、干擦纹理、相机旅行;这里只保留书写顺序与三色语义;
|
|
57
|
+
- 上游 24 fps、板面可远大于画幅;本实现 30 fps、固定画幅;
|
|
58
|
+
- 字体与纹理均为近似,验收以帧为准。
|
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"slug": "whiteboard",
|
|
3
|
+
"en": "Whiteboard Explainer",
|
|
4
|
+
"cn": "白板讲解",
|
|
5
|
+
"category_en": "Hand-drawn & Painting",
|
|
6
|
+
"category_cn": "手绘与绘画",
|
|
7
|
+
"uses": [
|
|
8
|
+
"Explainers",
|
|
9
|
+
"Online courses",
|
|
10
|
+
"Science"
|
|
11
|
+
],
|
|
12
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/whiteboard",
|
|
13
|
+
"license": "style doc MIT; demo stills CC BY 4.0 (LemoLab)",
|
|
14
|
+
"implementation": "manimgx (theme.py + tokens.json)",
|
|
15
|
+
"num": "04",
|
|
16
|
+
"film": "Secant on a Whiteboard",
|
|
17
|
+
"line": "A derivative explained on one warm whiteboard with three markers.",
|
|
18
|
+
"line_cn": "在一块暖白板上,用三支马克笔讲清导数。",
|
|
19
|
+
"uses_cn": [
|
|
20
|
+
"科学解释",
|
|
21
|
+
"课程",
|
|
22
|
+
"机制说明"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"讲解",
|
|
26
|
+
"课程",
|
|
27
|
+
"科学",
|
|
28
|
+
"机制",
|
|
29
|
+
"流程",
|
|
30
|
+
"为什么",
|
|
31
|
+
"原理",
|
|
32
|
+
"物理",
|
|
33
|
+
"化学",
|
|
34
|
+
"生物"
|
|
35
|
+
],
|
|
36
|
+
"avoid": [
|
|
37
|
+
"高密度公式",
|
|
38
|
+
"暗场"
|
|
39
|
+
],
|
|
40
|
+
"demo": "showcase/whiteboard-derivative",
|
|
41
|
+
"demo_duration_sec": 9.6,
|
|
42
|
+
"aspect": [
|
|
43
|
+
"16:9"
|
|
44
|
+
],
|
|
45
|
+
"fps": 30,
|
|
46
|
+
"complexity": "medium"
|
|
47
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""whiteboard 1.0.0 — Lemo-Opuscar 的 Whiteboard Explainer,manimgx 实现层。
|
|
2
|
+
|
|
3
|
+
上游规范(MIT):styles/whiteboard/STYLE.md
|
|
4
|
+
暖白板 + 三支马克笔;manimgx 版省掉笔/手/相机旅行,只保留书写与结构。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
import manimgx as m
|
|
15
|
+
|
|
16
|
+
TOKENS = json.loads(Path(__file__).with_name("tokens.json").read_text(encoding="utf-8"))
|
|
17
|
+
|
|
18
|
+
BOARD = TOKENS["colors"]["board"]
|
|
19
|
+
BOARD_EDGE = TOKENS["colors"]["board_edge"]
|
|
20
|
+
INK = TOKENS["colors"]["ink"]
|
|
21
|
+
BLUE = TOKENS["colors"]["blue"]
|
|
22
|
+
ORANGE = TOKENS["colors"]["orange"]
|
|
23
|
+
MUTED = TOKENS["colors"]["muted"]
|
|
24
|
+
GHOST = TOKENS["colors"]["ghost"]
|
|
25
|
+
LAYOUT = TOKENS["layout"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def apply_config(width: int = 1920, height: int = 1080, fps: int = 30) -> None:
|
|
29
|
+
m.config.pixel_width = width
|
|
30
|
+
m.config.pixel_height = height
|
|
31
|
+
m.config.frame_rate = fps
|
|
32
|
+
m.config.background_color = BOARD
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def backdrop(seed: int = 5) -> m.ImageMobject:
|
|
36
|
+
"""暖白板:轻微渐变 + 细噪点,避免大块纯白。"""
|
|
37
|
+
w, h = m.config.pixel_width, m.config.pixel_height
|
|
38
|
+
yy, xx = np.mgrid[0:h, 0:w]
|
|
39
|
+
u = xx / (w - 1)
|
|
40
|
+
v = yy / (h - 1)
|
|
41
|
+
glow = np.exp(-(((u - 0.45) ** 2) / 0.30 + ((v - 0.42) ** 2) / 0.30))
|
|
42
|
+
rgb = np.empty((h, w, 3), dtype=float)
|
|
43
|
+
base = np.array([247, 245, 239], dtype=float)
|
|
44
|
+
edge = np.array([233, 228, 216], dtype=float)
|
|
45
|
+
rgb[:] = edge + glow[..., None] * (base - edge)
|
|
46
|
+
rng = np.random.default_rng(seed)
|
|
47
|
+
rgb = np.clip(rgb + rng.normal(0, 1.0, rgb.shape), 0, 255).astype(np.uint8)
|
|
48
|
+
img = m.ImageMobject(rgb, scale_to_resolution=min(w, h), resampling_algorithm=3)
|
|
49
|
+
return img.move_to(m.ORIGIN)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def board_line(y: float, x0: float, x1: float, color: str = INK, width: float = 2.4) -> m.Line:
|
|
53
|
+
return m.Line([x0, y, 0], [x1, y, 0], color=color, stroke_width=width)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "whiteboard",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"source": "https://github.com/lemomo-ai/lemo-opuscar/tree/main/styles/whiteboard",
|
|
5
|
+
"license": "style doc MIT; demo stills CC BY 4.0 (LemoLab)",
|
|
6
|
+
"colors": {
|
|
7
|
+
"board": "#F7F5EF",
|
|
8
|
+
"board_edge": "#E9E4D8",
|
|
9
|
+
"ink": "#23262C",
|
|
10
|
+
"blue": "#2A5CB3",
|
|
11
|
+
"orange": "#D97757",
|
|
12
|
+
"muted": "#8A857C",
|
|
13
|
+
"ghost": "#C9C3B6"
|
|
14
|
+
},
|
|
15
|
+
"typography": {
|
|
16
|
+
"hand": { "family": "Chalkboard SE", "size": 34 },
|
|
17
|
+
"label": { "family": "Chalkboard SE", "size": 24 },
|
|
18
|
+
"math": { "family": "New Computer Modern", "size": 54 }
|
|
19
|
+
},
|
|
20
|
+
"layout": {
|
|
21
|
+
"aspect": "16:9",
|
|
22
|
+
"frame": { "width": 14.222, "height": 8.0 },
|
|
23
|
+
"margin": 0.55
|
|
24
|
+
},
|
|
25
|
+
"motion": { "fps": 30, "easing": "hand-speed", "hold_min_seconds": 1.4 },
|
|
26
|
+
"audio": { "target_lufs": -14, "true_peak_db": -1 }
|
|
27
|
+
}
|