manim-extensions 1.0.0__py3-none-any.whl
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.
- manim_extensions/__init__.py +3 -0
- manim_extensions/animations.py +123 -0
- manim_extensions/geometry.py +342 -0
- manim_extensions/mobjects.py +193 -0
- manim_extensions-1.0.0.dist-info/METADATA +586 -0
- manim_extensions-1.0.0.dist-info/RECORD +8 -0
- manim_extensions-1.0.0.dist-info/WHEEL +5 -0
- manim_extensions-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from manim import *
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def VisDrawArc(
|
|
6
|
+
scene: Scene,
|
|
7
|
+
arc: Arc,
|
|
8
|
+
axis: np.ndarray = OUT,
|
|
9
|
+
run_time: float = 1,
|
|
10
|
+
) -> None:
|
|
11
|
+
"""可视化绘制圆弧的动画。
|
|
12
|
+
|
|
13
|
+
在场景中播放绘制圆弧的动画,同时显示一个沿圆弧移动的点及半径虚线。
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
scene: Manim 场景对象。
|
|
17
|
+
arc: 要绘制的圆弧。
|
|
18
|
+
axis: 旋转轴方向,``OUT`` 为逆时针,``IN`` 为顺时针。默认为 ``OUT``。
|
|
19
|
+
run_time: 动画持续时间(秒)。默认为 1。
|
|
20
|
+
"""
|
|
21
|
+
# 获取弧线的起点、终点和圆心
|
|
22
|
+
start_point = arc.point_from_proportion(0)
|
|
23
|
+
end_point = arc.point_from_proportion(1)
|
|
24
|
+
center = arc.get_arc_center()
|
|
25
|
+
|
|
26
|
+
# 根据轴方向确定旋转的起始点和方向
|
|
27
|
+
if np.array_equal(axis, OUT): # 逆时针
|
|
28
|
+
draw_arc = arc # 使用原始弧线
|
|
29
|
+
rotation_start = start_point
|
|
30
|
+
total_angle = arc.get_angle()
|
|
31
|
+
else: # 顺时针 (axis=IN)
|
|
32
|
+
# 创建一个与原弧线方向相反的新弧线
|
|
33
|
+
draw_arc = Arc(
|
|
34
|
+
start_angle=angle_of_vector(end_point - center),
|
|
35
|
+
angle=-arc.get_angle(), # 负角度表示相反方向
|
|
36
|
+
radius=np.linalg.norm(end_point - center),
|
|
37
|
+
arc_center=center,
|
|
38
|
+
color=arc.get_color(),
|
|
39
|
+
stroke_width=arc.get_stroke_width(),
|
|
40
|
+
)
|
|
41
|
+
rotation_start = end_point
|
|
42
|
+
total_angle = -arc.get_angle()
|
|
43
|
+
|
|
44
|
+
# 创建移动点的标记
|
|
45
|
+
moving_dot = Dot(point=rotation_start)
|
|
46
|
+
|
|
47
|
+
# 创建从圆心到移动点的虚线
|
|
48
|
+
radius_line = DashedLine(center, rotation_start)
|
|
49
|
+
|
|
50
|
+
# 计算实际弧线的半径和起始角度
|
|
51
|
+
r = np.linalg.norm(rotation_start - center)
|
|
52
|
+
start_angle = angle_of_vector(rotation_start - center)
|
|
53
|
+
|
|
54
|
+
# 创建一个跟踪旋转进度的变量
|
|
55
|
+
progress = ValueTracker(0)
|
|
56
|
+
|
|
57
|
+
# 更新移动点的位置
|
|
58
|
+
moving_dot.add_updater(
|
|
59
|
+
lambda d: d.move_to(
|
|
60
|
+
center
|
|
61
|
+
+ r
|
|
62
|
+
* np.array(
|
|
63
|
+
[
|
|
64
|
+
np.cos(start_angle + progress.get_value() * total_angle),
|
|
65
|
+
np.sin(start_angle + progress.get_value() * total_angle),
|
|
66
|
+
0,
|
|
67
|
+
]
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# 更新半径线
|
|
73
|
+
radius_line.add_updater(
|
|
74
|
+
lambda l: l.become(DashedLine(center, moving_dot.get_center()))
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# 添加所有元素到场景
|
|
78
|
+
scene.add(moving_dot, radius_line)
|
|
79
|
+
|
|
80
|
+
# 同步执行弧线绘制和点的旋转动画(1秒持续时间)
|
|
81
|
+
scene.play(
|
|
82
|
+
Create(draw_arc, rate_func=linear), # 使用调整后的弧线
|
|
83
|
+
progress.animate.set_value(1),
|
|
84
|
+
run_time=run_time,
|
|
85
|
+
rate_func=linear,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# 清除更新器
|
|
89
|
+
moving_dot.clear_updaters()
|
|
90
|
+
radius_line.clear_updaters()
|
|
91
|
+
|
|
92
|
+
# 移除临时元素
|
|
93
|
+
scene.remove(moving_dot, radius_line)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TypeWriter(Animation):
|
|
97
|
+
"""打字机效果动画。
|
|
98
|
+
|
|
99
|
+
逐个字符显示 ``Text`` 对象的内容,模拟打字机输入效果。
|
|
100
|
+
默认自动根据字符数量与间隔计算动画总时长。
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
mobject: 要动画显示的 ``Text`` 对象。
|
|
104
|
+
interval: 字符间显示间隔(秒)。默认为 2。
|
|
105
|
+
**kwargs: 传递给 ``Animation`` 的其他关键字参数。
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(self, mobject: Text, interval: float = 2, **kwargs) -> None:
|
|
109
|
+
assert isinstance(mobject, Text), "TypeWriter only supports Text mobjects."
|
|
110
|
+
self.interval = interval
|
|
111
|
+
self.char_count = len(mobject.submobjects)
|
|
112
|
+
|
|
113
|
+
# 自动计算run_time
|
|
114
|
+
if "run_time" not in kwargs:
|
|
115
|
+
kwargs["run_time"] = self.char_count * self.interval
|
|
116
|
+
|
|
117
|
+
super().__init__(mobject, **kwargs)
|
|
118
|
+
|
|
119
|
+
def interpolate_mobject(self, alpha: float) -> Text:
|
|
120
|
+
current_index = int(alpha * self.char_count)
|
|
121
|
+
for i, char in enumerate(self.mobject.submobjects):
|
|
122
|
+
char.set_opacity(1 if i < current_index else 0)
|
|
123
|
+
return self.mobject
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
from manim import *
|
|
2
|
+
import math
|
|
3
|
+
import numpy as np
|
|
4
|
+
from typing import Optional, Tuple, Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def CircleInt(
|
|
8
|
+
circle1: Circle, circle2: Circle
|
|
9
|
+
) -> Optional[Tuple[list[float], list[float]]]:
|
|
10
|
+
"""计算两个圆的交点。
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
circle1: 第一个圆。
|
|
14
|
+
circle2: 第二个圆。
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
若两圆相交,返回包含两个交点坐标的元组,每个坐标为 ``[x, y, 0]``;
|
|
18
|
+
若不相交,返回 ``None``。
|
|
19
|
+
"""
|
|
20
|
+
circle1_center = circle1.get_center()
|
|
21
|
+
circle1_radius = circle1.radius
|
|
22
|
+
circle2_center = circle2.get_center()
|
|
23
|
+
circle2_radius = circle2.radius
|
|
24
|
+
x1, y1, _ = circle1_center
|
|
25
|
+
x2, y2, _ = circle2_center
|
|
26
|
+
d = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
|
27
|
+
if d > circle1_radius + circle2_radius or d < abs(circle1_radius - circle2_radius):
|
|
28
|
+
return None
|
|
29
|
+
a = (circle1_radius**2 - circle2_radius**2 + d**2) / (2 * d)
|
|
30
|
+
h = math.sqrt(circle1_radius**2 - a**2)
|
|
31
|
+
xm = x1 + a * (x2 - x1) / d
|
|
32
|
+
ym = y1 + a * (y2 - y1) / d
|
|
33
|
+
xs1 = xm + h * (y2 - y1) / d
|
|
34
|
+
xs2 = xm - h * (y2 - y1) / d
|
|
35
|
+
ys1 = ym - h * (x2 - x1) / d
|
|
36
|
+
ys2 = ym + h * (x2 - x1) / d
|
|
37
|
+
return [xs1, ys1, 0], [xs2, ys2, 0]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def LineCircleInt(
|
|
41
|
+
line: Line, circle: Circle
|
|
42
|
+
) -> Optional[Union[Tuple[np.ndarray, np.ndarray], np.ndarray]]:
|
|
43
|
+
"""计算线段与圆的交点。
|
|
44
|
+
|
|
45
|
+
仅返回落在线段参数范围 ``[0, 1]`` 内的交点。
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
line: 线段。
|
|
49
|
+
circle: 圆。
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
若有两个交点,返回两个交点坐标的元组;
|
|
53
|
+
若有一个交点,返回该交点坐标;
|
|
54
|
+
若无交点,返回 ``None``。
|
|
55
|
+
"""
|
|
56
|
+
p1 = line.get_start()
|
|
57
|
+
p2 = line.get_end()
|
|
58
|
+
c = circle.get_center()
|
|
59
|
+
r = circle.radius
|
|
60
|
+
dx, dy, _ = p2 - p1
|
|
61
|
+
cx, cy, _ = p1 - c
|
|
62
|
+
a = dx**2 + dy**2
|
|
63
|
+
b = 2 * (dx * cx + dy * cy)
|
|
64
|
+
c = cx**2 + cy**2 - r**2
|
|
65
|
+
discriminant = b**2 - 4 * a * c
|
|
66
|
+
if discriminant < 0:
|
|
67
|
+
return None
|
|
68
|
+
t1 = (-b + math.sqrt(discriminant)) / (2 * a)
|
|
69
|
+
t2 = (-b - math.sqrt(discriminant)) / (2 * a)
|
|
70
|
+
intersections = []
|
|
71
|
+
for t in [t1, t2]:
|
|
72
|
+
if 0 <= t <= 1:
|
|
73
|
+
intersection = p1 + t * (p2 - p1)
|
|
74
|
+
intersections.append(intersection)
|
|
75
|
+
try:
|
|
76
|
+
return intersections[0], intersections[1]
|
|
77
|
+
except Exception:
|
|
78
|
+
try:
|
|
79
|
+
return intersections[0]
|
|
80
|
+
except Exception:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def LineInt(line1: Line, line2: Line) -> Optional[list[float]]:
|
|
85
|
+
"""计算两条线段的交点。
|
|
86
|
+
|
|
87
|
+
在二维平面上计算两条无限延长线段的交点
|
|
88
|
+
(返回结果不限制在线段端点范围内)。
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
line1: 第一条线段。
|
|
92
|
+
line2: 第二条线段。
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
若两直线相交,返回交点坐标 ``[x, y, 0]``;
|
|
96
|
+
若平行,返回 ``None``。
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def det(a: tuple[float, float], b: tuple[float, float]) -> float:
|
|
100
|
+
return a[0] * b[1] - a[1] * b[0]
|
|
101
|
+
|
|
102
|
+
p1 = line1.get_start()[:2]
|
|
103
|
+
p2 = line1.get_end()[:2]
|
|
104
|
+
p3 = line2.get_start()[:2]
|
|
105
|
+
p4 = line2.get_end()[:2]
|
|
106
|
+
xdiff = (p1[0] - p2[0], p3[0] - p4[0])
|
|
107
|
+
ydiff = (p1[1] - p2[1], p3[1] - p4[1])
|
|
108
|
+
div = det(xdiff, ydiff)
|
|
109
|
+
if div == 0:
|
|
110
|
+
return None
|
|
111
|
+
d = (det(p1, p2), det(p3, p4))
|
|
112
|
+
x = det(d, xdiff) / div
|
|
113
|
+
y = det(d, ydiff) / div
|
|
114
|
+
return [x, y, 0]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def LineArcInt(
|
|
118
|
+
line: Line, arc: Arc
|
|
119
|
+
) -> Optional[Union[Tuple[list[float], list[float]], list[float]]]:
|
|
120
|
+
"""计算线段与圆弧的交点。
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
line: 线段。
|
|
124
|
+
arc: 圆弧。
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
若有两个交点,返回两个交点坐标的元组;
|
|
128
|
+
若有一个交点,返回该交点坐标列表;
|
|
129
|
+
若无交点,返回 ``None``。
|
|
130
|
+
"""
|
|
131
|
+
# 获取线段起点和终点(仅x,y坐标)
|
|
132
|
+
p1 = line.start[:2]
|
|
133
|
+
p2 = line.end[:2]
|
|
134
|
+
|
|
135
|
+
# 处理线段退化为点的情况
|
|
136
|
+
direction = p2 - p1
|
|
137
|
+
length = np.linalg.norm(direction)
|
|
138
|
+
if length < 1e-8:
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
# 获取圆弧参数(关键修正:使用ManimCE的正确属性)
|
|
142
|
+
center = arc.arc_center[:2] # 圆弧中心(x,y)
|
|
143
|
+
radius = arc.radius # 半径
|
|
144
|
+
start_angle = arc.start_angle # 起始角度(弧度)
|
|
145
|
+
angle = arc.angle # 角度跨度(弧度,正=逆时针,负=顺时针)
|
|
146
|
+
|
|
147
|
+
# 线段参数方程转换(以圆弧中心为原点)
|
|
148
|
+
p1_centered = p1 - center
|
|
149
|
+
p2_centered = p2 - center
|
|
150
|
+
dx = p2_centered[0] - p1_centered[0]
|
|
151
|
+
dy = p2_centered[1] - p1_centered[1]
|
|
152
|
+
|
|
153
|
+
# 联立线段与圆的方程(二次方程)
|
|
154
|
+
a = dx**2 + dy**2
|
|
155
|
+
b = 2 * (p1_centered[0] * dx + p1_centered[1] * dy)
|
|
156
|
+
c = p1_centered[0] ** 2 + p1_centered[1] ** 2 - radius**2
|
|
157
|
+
discriminant = b**2 - 4 * a * c
|
|
158
|
+
|
|
159
|
+
# 无实根(直线与圆无交点)
|
|
160
|
+
if discriminant < 0:
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
# 计算t值(线段参数)
|
|
164
|
+
sqrt_d = np.sqrt(discriminant)
|
|
165
|
+
t1 = (-b + sqrt_d) / (2 * a)
|
|
166
|
+
t2 = (-b - sqrt_d) / (2 * a)
|
|
167
|
+
t_values = []
|
|
168
|
+
for t in [t1, t2]:
|
|
169
|
+
if 0 <= t <= 1 and (len(t_values) == 0 or abs(t - t_values[0]) > 1e-8):
|
|
170
|
+
t_values.append(t)
|
|
171
|
+
|
|
172
|
+
# 检查交点是否在圆弧范围内(关键修正:角度判断+容差)
|
|
173
|
+
intersections = []
|
|
174
|
+
TOLERANCE = 1e-6 # 角度容差(弧度)
|
|
175
|
+
for t in t_values:
|
|
176
|
+
# 计算交点相对圆弧中心的坐标
|
|
177
|
+
x = p1_centered[0] + t * dx
|
|
178
|
+
y = p1_centered[1] + t * dy
|
|
179
|
+
theta = np.arctan2(y, x) % (2 * np.pi) # 交点角度(0~2π弧度)
|
|
180
|
+
|
|
181
|
+
# 圆弧的角度范围(模2π处理)
|
|
182
|
+
start_angle_mod = start_angle % (2 * np.pi)
|
|
183
|
+
end_angle_mod = (start_angle + angle) % (2 * np.pi)
|
|
184
|
+
|
|
185
|
+
# 判断角度是否在圆弧范围内(带容差)
|
|
186
|
+
if angle > 0: # 逆时针圆弧
|
|
187
|
+
if start_angle_mod < end_angle_mod:
|
|
188
|
+
valid = (
|
|
189
|
+
start_angle_mod - TOLERANCE
|
|
190
|
+
<= theta
|
|
191
|
+
<= end_angle_mod + TOLERANCE
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
valid = (theta >= start_angle_mod - TOLERANCE) or (
|
|
195
|
+
theta <= end_angle_mod + TOLERANCE
|
|
196
|
+
)
|
|
197
|
+
else: # 顺时针圆弧
|
|
198
|
+
if end_angle_mod < start_angle_mod:
|
|
199
|
+
valid = (
|
|
200
|
+
end_angle_mod - TOLERANCE
|
|
201
|
+
<= theta
|
|
202
|
+
<= start_angle_mod + TOLERANCE
|
|
203
|
+
)
|
|
204
|
+
else:
|
|
205
|
+
valid = (theta <= start_angle_mod + TOLERANCE) or (
|
|
206
|
+
theta >= end_angle_mod - TOLERANCE
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if valid:
|
|
210
|
+
# 转换为绝对坐标(添加z=0)
|
|
211
|
+
intersection = [x + center[0], y + center[1], 0.0]
|
|
212
|
+
intersections.append(intersection)
|
|
213
|
+
try:
|
|
214
|
+
return intersections[0], intersections[1]
|
|
215
|
+
except Exception:
|
|
216
|
+
try:
|
|
217
|
+
return intersections[0]
|
|
218
|
+
except Exception:
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def TangentPoint(
|
|
223
|
+
p1: Union[np.ndarray, tuple, list],
|
|
224
|
+
p2: Union[np.ndarray, tuple, list],
|
|
225
|
+
line_start: Union[np.ndarray, tuple, list],
|
|
226
|
+
line_end: Union[np.ndarray, tuple, list],
|
|
227
|
+
) -> Optional[np.ndarray]:
|
|
228
|
+
"""计算以两点为圆上点且与线段相切的切点坐标。
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
p1: 圆上的第一个点,格式为 ``(x, y)`` 或 ``(x, y, z)``。
|
|
232
|
+
p2: 圆上的第二个点,格式为 ``(x, y)`` 或 ``(x, y, z)``。
|
|
233
|
+
line_start: 线段的起点,格式为 ``(x, y)`` 或 ``(x, y, z)``。
|
|
234
|
+
line_end: 线段的终点,格式为 ``(x, y)`` 或 ``(x, y, z)``。
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
切点坐标 ``(x, y, 0)`` 的 numpy 数组;
|
|
238
|
+
若无法计算则返回 ``None``。
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def to_3d(point: Union[np.ndarray, tuple, list]) -> np.ndarray:
|
|
242
|
+
if len(point) == 2:
|
|
243
|
+
return np.array([point[0], point[1], 0.0])
|
|
244
|
+
return np.array(point[:3])
|
|
245
|
+
|
|
246
|
+
p1 = to_3d(p1)
|
|
247
|
+
p2 = to_3d(p2)
|
|
248
|
+
line_start = to_3d(line_start)
|
|
249
|
+
line_end = to_3d(line_end)
|
|
250
|
+
|
|
251
|
+
# 计算线段方向向量
|
|
252
|
+
line_direction = line_end - line_start
|
|
253
|
+
line_length = np.linalg.norm(line_direction)
|
|
254
|
+
|
|
255
|
+
# 处理线段退化为点的情况
|
|
256
|
+
if line_length < 1e-8:
|
|
257
|
+
# 检查线段端点是否在圆上
|
|
258
|
+
dist_p1 = np.linalg.norm(line_start - p1)
|
|
259
|
+
dist_p2 = np.linalg.norm(line_start - p2)
|
|
260
|
+
if abs(dist_p1 - dist_p2) < 1e-8:
|
|
261
|
+
return line_start
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
line_direction = line_direction / line_length
|
|
265
|
+
|
|
266
|
+
# 计算线段p1-p2的中点
|
|
267
|
+
midpoint = (p1 + p2) / 2
|
|
268
|
+
|
|
269
|
+
# 计算线段p1-p2的方向向量
|
|
270
|
+
p1p2_direction = p2 - p1
|
|
271
|
+
p1p2_length = np.linalg.norm(p1p2_direction)
|
|
272
|
+
|
|
273
|
+
if p1p2_length < 1e-8:
|
|
274
|
+
# p1和p2重合,无法确定唯一的圆
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
p1p2_direction = p1p2_direction / p1p2_length
|
|
278
|
+
|
|
279
|
+
# 计算线段p1-p2的垂直向量(在二维平面上)
|
|
280
|
+
perpendicular_dir = np.array([-p1p2_direction[1], p1p2_direction[0], 0.0])
|
|
281
|
+
|
|
282
|
+
# 构建线性方程组求解圆心c = midpoint + t * perpendicular_dir
|
|
283
|
+
cross_perp_line = np.cross(perpendicular_dir, line_direction)
|
|
284
|
+
cross_mid_line = np.cross(midpoint - line_start, line_direction)
|
|
285
|
+
|
|
286
|
+
a = np.dot(perpendicular_dir, perpendicular_dir) - np.dot(
|
|
287
|
+
cross_perp_line, cross_perp_line
|
|
288
|
+
)
|
|
289
|
+
b = 2 * (
|
|
290
|
+
np.dot(midpoint - p1, perpendicular_dir)
|
|
291
|
+
- np.dot(cross_mid_line, cross_perp_line)
|
|
292
|
+
)
|
|
293
|
+
c = np.dot(midpoint - p1, midpoint - p1) - np.dot(
|
|
294
|
+
cross_mid_line, cross_mid_line
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# 处理a接近零的特殊情况(退化为一次方程)
|
|
298
|
+
if abs(a) < 1e-8:
|
|
299
|
+
if abs(b) < 1e-8:
|
|
300
|
+
return None # 无解或无穷多解
|
|
301
|
+
t = -c / b
|
|
302
|
+
centers = [midpoint + t * perpendicular_dir]
|
|
303
|
+
else:
|
|
304
|
+
# 计算判别式
|
|
305
|
+
discriminant = b**2 - 4 * a * c
|
|
306
|
+
|
|
307
|
+
if discriminant < 0:
|
|
308
|
+
# 无实数解
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
# 求解t
|
|
312
|
+
sqrt_d = np.sqrt(discriminant)
|
|
313
|
+
t1 = (-b + sqrt_d) / (2 * a)
|
|
314
|
+
t2 = (-b - sqrt_d) / (2 * a)
|
|
315
|
+
|
|
316
|
+
# 计算可能的圆心
|
|
317
|
+
centers = [midpoint + t * perpendicular_dir for t in [t1, t2]]
|
|
318
|
+
|
|
319
|
+
# 计算对应的切点(在直线上)
|
|
320
|
+
valid_tangents = []
|
|
321
|
+
for center in centers:
|
|
322
|
+
# 计算从line_start到center的向量在直线方向上的投影
|
|
323
|
+
projection = np.dot(center - line_start, line_direction)
|
|
324
|
+
|
|
325
|
+
# 检查投影是否在线段范围内 [0, line_length]
|
|
326
|
+
if 0 <= projection <= line_length:
|
|
327
|
+
# 计算切点
|
|
328
|
+
tangent_point = line_start + projection * line_direction
|
|
329
|
+
|
|
330
|
+
# 验证切点到圆心的距离是否等于圆心到p1的距离
|
|
331
|
+
radius = np.linalg.norm(center - p1)
|
|
332
|
+
dist_to_tangent = np.linalg.norm(center - tangent_point)
|
|
333
|
+
|
|
334
|
+
if abs(radius - dist_to_tangent) < 1e-6:
|
|
335
|
+
valid_tangents.append(tangent_point)
|
|
336
|
+
|
|
337
|
+
# 选择距离p1和p2较近的解
|
|
338
|
+
if not valid_tangents:
|
|
339
|
+
return None
|
|
340
|
+
|
|
341
|
+
# 如果有多个解,选择第一个
|
|
342
|
+
return valid_tangents[0]
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from manim import *
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ChineseMathTex(MathTex):
|
|
6
|
+
"""支持中文显示的 MathTex 类。
|
|
7
|
+
|
|
8
|
+
自动将输入文本中的中文字符用 ``\\text{}`` 包裹,
|
|
9
|
+
并配置 ``xelatex`` 与 ``xeCJK`` 宏包以支持中文字体渲染。
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
*texts: 要渲染的 LaTeX 文本字符串。
|
|
13
|
+
font: 中文字体名称。默认为 ``"SimSun"``。
|
|
14
|
+
tex_to_color_map: 文本到颜色的映射字典。默认为 ``{}``。
|
|
15
|
+
**kwargs: 传递给 ``MathTex`` 的其他关键字参数。
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
*texts: str,
|
|
21
|
+
font: str = "SimSun",
|
|
22
|
+
tex_to_color_map: dict = {},
|
|
23
|
+
**kwargs,
|
|
24
|
+
) -> None:
|
|
25
|
+
tex_template = TexTemplate(tex_compiler="xelatex", output_format=".xdv")
|
|
26
|
+
tex_template.add_to_preamble(r"\usepackage{amsmath}")
|
|
27
|
+
tex_template.add_to_preamble(r"\usepackage{xeCJK}")
|
|
28
|
+
tex_template.add_to_preamble(rf"\setCJKmainfont{{{font}}}")
|
|
29
|
+
|
|
30
|
+
combined_chinesetext = []
|
|
31
|
+
for text in texts:
|
|
32
|
+
chinesetext = ""
|
|
33
|
+
for i in range(len(text)):
|
|
34
|
+
if (
|
|
35
|
+
("\u4e00" <= text[i] <= "\u9fff")
|
|
36
|
+
or ("\u3000" <= text[i] <= "\u303f")
|
|
37
|
+
or ("\uff00" <= text[i] <= "\uffef")
|
|
38
|
+
):
|
|
39
|
+
chinesetext += rf"\text{{{text[i]}}}"
|
|
40
|
+
else:
|
|
41
|
+
chinesetext += text[i]
|
|
42
|
+
combined_chinesetext.append(chinesetext)
|
|
43
|
+
|
|
44
|
+
new_dict = {}
|
|
45
|
+
for key in tex_to_color_map.keys():
|
|
46
|
+
new_key = ""
|
|
47
|
+
for char in key:
|
|
48
|
+
if (
|
|
49
|
+
("\u4e00" <= char <= "\u9fff")
|
|
50
|
+
or ("\u3000" <= char <= "\u303f")
|
|
51
|
+
or ("\uff00" <= char <= "\uffef")
|
|
52
|
+
):
|
|
53
|
+
new_key += rf"\text{{{char}}}"
|
|
54
|
+
else:
|
|
55
|
+
new_key += char
|
|
56
|
+
new_dict[new_key] = tex_to_color_map[key]
|
|
57
|
+
|
|
58
|
+
super().__init__(
|
|
59
|
+
*combined_chinesetext,
|
|
60
|
+
tex_template=tex_template,
|
|
61
|
+
tex_to_color_map=new_dict,
|
|
62
|
+
**kwargs,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class LabelDot(VGroup):
|
|
67
|
+
"""带标签的圆点。
|
|
68
|
+
|
|
69
|
+
在指定位置创建一个圆点,并在其旁边添加 MathTex 标签。
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
dot_label: 标签文本内容。
|
|
73
|
+
dot_pos: 圆点的位置坐标。
|
|
74
|
+
label_pos: 标签相对圆点的方向。默认为 ``DOWN``。
|
|
75
|
+
buff: 标签与圆点之间的间距。默认为 0.1。
|
|
76
|
+
**kwargs: 传递给 ``VGroup`` 的其他关键字参数。
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
dot_label: str,
|
|
82
|
+
dot_pos: np.ndarray,
|
|
83
|
+
label_pos: np.ndarray = DOWN,
|
|
84
|
+
buff: float = 0.1,
|
|
85
|
+
**kwargs,
|
|
86
|
+
) -> None:
|
|
87
|
+
super().__init__(**kwargs)
|
|
88
|
+
dot = Dot().move_to(dot_pos)
|
|
89
|
+
label = MathTex(dot_label).next_to(dot, label_pos, buff=buff)
|
|
90
|
+
self.add(dot, label)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class MathTexLine(VGroup):
|
|
94
|
+
"""带 MathTex 公式的线段。
|
|
95
|
+
|
|
96
|
+
创建一条线段,并在其指定方向旁放置一个 MathTex 公式。
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
formula: 要放置的 MathTex 公式对象。
|
|
100
|
+
direction: 公式相对线段的方向。默认为 ``UP``。
|
|
101
|
+
buff: 公式与线段之间的间距。默认为 0.5。
|
|
102
|
+
**kwargs: 传递给 ``Line`` 的其他关键字参数。
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
formula: MathTex,
|
|
108
|
+
direction: np.ndarray = UP,
|
|
109
|
+
buff: float = 0.5,
|
|
110
|
+
**kwargs,
|
|
111
|
+
) -> None:
|
|
112
|
+
super().__init__()
|
|
113
|
+
line = Line(**kwargs)
|
|
114
|
+
tex = formula.next_to(line, direction, buff=buff)
|
|
115
|
+
self.add(line, tex)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class MathTexBrace(VGroup):
|
|
119
|
+
"""带 MathTex 公式的花括号。
|
|
120
|
+
|
|
121
|
+
为指定对象创建一个花括号,并在其旁边放置一个 MathTex 公式。
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
target: 要被花括号标注的目标对象(如线段、矩形等)。
|
|
125
|
+
formula: 要放置的 MathTex 公式对象。
|
|
126
|
+
direction: 公式与花括号相对目标的方向。默认为 ``UP``。
|
|
127
|
+
buff: 公式与花括号之间的间距。默认为 0.5。
|
|
128
|
+
**kwargs: 传递给 ``Brace`` 的其他关键字参数。
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
target,
|
|
134
|
+
formula: MathTex,
|
|
135
|
+
direction: np.ndarray = UP,
|
|
136
|
+
buff: float = 0.5,
|
|
137
|
+
**kwargs,
|
|
138
|
+
) -> None:
|
|
139
|
+
super().__init__()
|
|
140
|
+
brace = Brace(target, direction=direction, **kwargs)
|
|
141
|
+
tex = formula.next_to(brace, direction, buff=buff)
|
|
142
|
+
self.add(brace, tex)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class MathTexDoublearrow(VGroup):
|
|
146
|
+
"""带 MathTex 公式的双向箭头。
|
|
147
|
+
|
|
148
|
+
创建一个双向箭头,并在其指定方向旁放置一个 MathTex 公式。
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
formula: 要放置的 MathTex 公式对象。
|
|
152
|
+
direction: 公式相对双向箭头的方向。默认为 ``UP``。
|
|
153
|
+
buff: 公式与双向箭头之间的间距。默认为 0.5。
|
|
154
|
+
**kwargs: 传递给 ``DoubleArrow`` 的其他关键字参数。
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
def __init__(
|
|
158
|
+
self,
|
|
159
|
+
formula: MathTex,
|
|
160
|
+
direction: np.ndarray = UP,
|
|
161
|
+
buff: float = 0.5,
|
|
162
|
+
**kwargs,
|
|
163
|
+
) -> None:
|
|
164
|
+
super().__init__()
|
|
165
|
+
doublearrow = DoubleArrow(**kwargs)
|
|
166
|
+
tex = formula.next_to(doublearrow, direction, buff=buff)
|
|
167
|
+
self.add(doublearrow, tex)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class ExtendedLine(Line):
|
|
171
|
+
"""可延长的线段。
|
|
172
|
+
|
|
173
|
+
基于一条已有的 ``Line``,在其两端按原方向各延长指定距离。
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
line: 作为基准的原始线段。
|
|
177
|
+
extend_distance: 两端各延长的距离。
|
|
178
|
+
**kwargs: 传递给 ``Line`` 的其他关键字参数。
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def __init__(self, line: Line, extend_distance: float, **kwargs) -> None:
|
|
182
|
+
start_point = line.get_start()
|
|
183
|
+
end_point = line.get_end()
|
|
184
|
+
direction_vector = end_point - start_point
|
|
185
|
+
vector_length = np.linalg.norm(direction_vector)
|
|
186
|
+
if vector_length < 1e-8:
|
|
187
|
+
super().__init__(start_point, end_point, **kwargs)
|
|
188
|
+
else:
|
|
189
|
+
unit_direction_vector = direction_vector / vector_length
|
|
190
|
+
new_start_point = start_point - extend_distance * unit_direction_vector
|
|
191
|
+
new_end_point = end_point + extend_distance * unit_direction_vector
|
|
192
|
+
super().__init__(new_start_point, new_end_point, **kwargs)
|
|
193
|
+
self.match_style(line)
|
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: manim_extensions
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A package for tools and extensions in Manim
|
|
5
|
+
Author-email: Zhu Chongjing <zhuchongjing_pypi@163.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/zhuchongjing/manim_extensions
|
|
8
|
+
Project-URL: Issues, https://github.com/zhuchongjing/manim_extensions/issues
|
|
9
|
+
Keywords: manim,animation,math,latex,geometry
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Education
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Multimedia :: Video
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: manim
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
|
|
28
|
+
# manim_extensions
|
|
29
|
+
|
|
30
|
+
[English](#english) | [中文](#中文)
|
|
31
|
+
|
|
32
|
+
<a name="中文"></a>
|
|
33
|
+
## 中文
|
|
34
|
+
|
|
35
|
+
manim_extensions 是一个用于 [Manim](https://www.manim.community/) 的扩展工具包,提供了中文公式渲染、几何计算、带标注的图形以及常用动画效果,帮助你更高效地制作数学动画。
|
|
36
|
+
|
|
37
|
+
### 安装
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install manim_extensions
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
依赖:`manim`
|
|
44
|
+
|
|
45
|
+
### 模块概览
|
|
46
|
+
|
|
47
|
+
| 模块 | 说明 |
|
|
48
|
+
|------|------|
|
|
49
|
+
| `mobjects` | Mobject 类扩展,包含中文公式、带标签圆点、可延长线段等 |
|
|
50
|
+
| `geometry` | 纯几何计算函数,用于求交点、切点等 |
|
|
51
|
+
| `animations` | 动画效果,如可视化绘弧、打字机效果 |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### mobjects 模块
|
|
56
|
+
|
|
57
|
+
#### `ChineseMathTex(*texts, font="SimSun", tex_to_color_map={}, **kwargs)`
|
|
58
|
+
|
|
59
|
+
支持中文显示的 `MathTex` 子类。可直接在公式中写入中文,无需手动包裹 `\text{}`。
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from manim_extensions import ChineseMathTex
|
|
63
|
+
|
|
64
|
+
formula = ChineseMathTex(
|
|
65
|
+
"\\frac{1}{2} + 你好 = x",
|
|
66
|
+
tex_to_color_map={"你好": RED},
|
|
67
|
+
font="SimSun"
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**参数**
|
|
72
|
+
- `*texts` — LaTeX 文本字符串
|
|
73
|
+
- `font` — 中文字体名称,默认 `"SimSun"`
|
|
74
|
+
- `tex_to_color_map` — 文本到颜色的映射字典,默认 `{}`
|
|
75
|
+
- `**kwargs` — 传递给 `MathTex` 的其他参数
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
#### `LabelDot(dot_label, dot_pos, label_pos=DOWN, buff=0.1, **kwargs)`
|
|
80
|
+
|
|
81
|
+
在指定位置创建一个圆点,并在其旁边添加 `MathTex` 标签。
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from manim_extensions import LabelDot
|
|
85
|
+
|
|
86
|
+
dot = LabelDot("A", [1, 2, 0], label_pos=UP, buff=0.2)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**参数**
|
|
90
|
+
- `dot_label` — 标签文本
|
|
91
|
+
- `dot_pos` — 圆点位置
|
|
92
|
+
- `label_pos` — 标签相对圆点的方向,默认 `DOWN`
|
|
93
|
+
- `buff` — 标签与圆点的间距,默认 `0.1`
|
|
94
|
+
- `**kwargs` — 传递给 `VGroup` 的其他参数
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
#### `MathTexLine(formula, direction=UP, buff=0.5, **kwargs)`
|
|
99
|
+
|
|
100
|
+
创建一条线段,并在其指定方向旁放置 `MathTex` 公式。
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from manim_extensions import MathTexLine
|
|
104
|
+
|
|
105
|
+
line = MathTexLine(MathTex("y = x"), direction=UP, color=BLUE)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**参数**
|
|
109
|
+
- `formula` — `MathTex` 公式对象
|
|
110
|
+
- `direction` — 公式相对线段的方向,默认 `UP`
|
|
111
|
+
- `buff` — 公式与线段的间距,默认 `0.5`
|
|
112
|
+
- `**kwargs` — 传递给 `Line` 的其他参数
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
#### `MathTexBrace(target, formula, direction=UP, buff=0.5, **kwargs)`
|
|
117
|
+
|
|
118
|
+
为指定对象创建一个花括号,并在其旁边放置 `MathTex` 公式。
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from manim_extensions import MathTexBrace
|
|
122
|
+
|
|
123
|
+
line = Line(LEFT, RIGHT)
|
|
124
|
+
brace = MathTexBrace(line, MathTex("\\Delta x"), direction=UP)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**参数**
|
|
128
|
+
- `target` — 要被花括号标注的目标对象
|
|
129
|
+
- `formula` — `MathTex` 公式对象
|
|
130
|
+
- `direction` — 公式与花括号相对目标的方向,默认 `UP`
|
|
131
|
+
- `buff` — 公式与花括号的间距,默认 `0.5`
|
|
132
|
+
- `**kwargs` — 传递给 `Brace` 的其他参数
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
#### `MathTexDoublearrow(formula, direction=UP, buff=0.5, **kwargs)`
|
|
137
|
+
|
|
138
|
+
创建一个双向箭头,并在其指定方向旁放置 `MathTex` 公式。
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from manim_extensions import MathTexDoublearrow
|
|
142
|
+
|
|
143
|
+
arrow = MathTexDoublearrow(MathTex("\\Leftrightarrow"), direction=DOWN)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**参数**
|
|
147
|
+
- `formula` — `MathTex` 公式对象
|
|
148
|
+
- `direction` — 公式相对箭头的方向,默认 `UP`
|
|
149
|
+
- `buff` — 公式与箭头的间距,默认 `0.5`
|
|
150
|
+
- `**kwargs` — 传递给 `DoubleArrow` 的其他参数
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
#### `ExtendedLine(line, extend_distance, **kwargs)`
|
|
155
|
+
|
|
156
|
+
基于一条已有的 `Line`,在其两端按原方向各延长指定距离。
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from manim_extensions import ExtendedLine
|
|
160
|
+
|
|
161
|
+
original = Line(LEFT, RIGHT)
|
|
162
|
+
extended = ExtendedLine(original, extend_distance=1.0, color=RED)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**参数**
|
|
166
|
+
- `line` — 原始线段
|
|
167
|
+
- `extend_distance` — 两端各延长的距离
|
|
168
|
+
- `**kwargs` — 传递给 `Line` 的其他参数
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### geometry 模块
|
|
173
|
+
|
|
174
|
+
#### `CircleInt(circle1, circle2)`
|
|
175
|
+
|
|
176
|
+
计算两个圆的交点。
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from manim_extensions import CircleInt
|
|
180
|
+
|
|
181
|
+
c1 = Circle(radius=2).shift(LEFT)
|
|
182
|
+
c2 = Circle(radius=2).shift(RIGHT)
|
|
183
|
+
result = CircleInt(c1, c2) # ([x1, y1, 0], [x2, y2, 0]) 或 None
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**返回**
|
|
187
|
+
- 若相交,返回两个交点坐标的元组 `([x, y, 0], [x, y, 0])`
|
|
188
|
+
- 若不相交,返回 `None`
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
#### `LineCircleInt(line, circle)`
|
|
193
|
+
|
|
194
|
+
计算线段与圆的交点,仅返回落在线段参数范围 `[0, 1]` 内的交点。
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from manim_extensions import LineCircleInt
|
|
198
|
+
|
|
199
|
+
line = Line(LEFT * 3, RIGHT * 3)
|
|
200
|
+
circle = Circle(radius=1)
|
|
201
|
+
result = LineCircleInt(line, circle)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**返回**
|
|
205
|
+
- 两个交点、一个交点,或 `None`
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
#### `LineInt(line1, line2)`
|
|
210
|
+
|
|
211
|
+
在二维平面上计算两条无限延长线段的交点(不限制在线段端点范围内)。
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from manim_extensions import LineInt
|
|
215
|
+
|
|
216
|
+
l1 = Line(LEFT, RIGHT)
|
|
217
|
+
l2 = Line(DOWN, UP)
|
|
218
|
+
result = LineInt(l1, l2) # [0.0, 0.0, 0] 或 None
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**返回**
|
|
222
|
+
- 交点坐标 `[x, y, 0]`,或平行时返回 `None`
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
#### `LineArcInt(line, arc)`
|
|
227
|
+
|
|
228
|
+
计算线段与圆弧的交点,会检查交点是否真正落在圆弧的角度范围内。
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from manim_extensions import LineArcInt
|
|
232
|
+
|
|
233
|
+
line = Line(LEFT, RIGHT)
|
|
234
|
+
arc = Arc(start_angle=0, angle=PI, radius=1)
|
|
235
|
+
result = LineArcInt(line, arc)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**返回**
|
|
239
|
+
- 两个交点、一个交点,或 `None`
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
#### `TangentPoint(p1, p2, line_start, line_end)`
|
|
244
|
+
|
|
245
|
+
计算以 `p1` 和 `p2` 为圆上点、且与线段相切的切点坐标。
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from manim_extensions import TangentPoint
|
|
249
|
+
|
|
250
|
+
p = TangentPoint(
|
|
251
|
+
[1, 0, 0], [-1, 0, 0],
|
|
252
|
+
line_start=[0, -2, 0], line_end=[0, 2, 0]
|
|
253
|
+
)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**参数**
|
|
257
|
+
- `p1`, `p2` — 圆上的两个点,格式 `(x, y)` 或 `(x, y, z)`
|
|
258
|
+
- `line_start`, `line_end` — 线段的起点和终点
|
|
259
|
+
|
|
260
|
+
**返回**
|
|
261
|
+
- 切点坐标 `np.ndarray`,若无法计算则返回 `None`
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
### animations 模块
|
|
266
|
+
|
|
267
|
+
#### `VisDrawArc(scene, arc, axis=OUT, run_time=1)`
|
|
268
|
+
|
|
269
|
+
可视化绘制圆弧的动画,会同时显示一个沿圆弧移动的点及半径虚线。**直接调用即可,无需放入 `self.play()` 中。**
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from manim_extensions import VisDrawArc
|
|
273
|
+
|
|
274
|
+
class MyScene(Scene):
|
|
275
|
+
def construct(self):
|
|
276
|
+
arc = Arc(start_angle=0, angle=PI, radius=2)
|
|
277
|
+
VisDrawArc(self, arc, axis=OUT, run_time=2)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**参数**
|
|
281
|
+
- `scene` — Manim 场景对象
|
|
282
|
+
- `arc` — 要绘制的 `Arc`
|
|
283
|
+
- `axis` — 旋转方向,`OUT` 为逆时针,`IN` 为顺时针,默认 `OUT`
|
|
284
|
+
- `run_time` — 动画时长(秒),默认 `1`
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
#### `TypeWriter(mobject, interval=2, **kwargs)`
|
|
289
|
+
|
|
290
|
+
打字机效果动画,逐个字符显示 `Text` 对象的内容。自动根据字符数量与间隔计算动画总时长。
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
from manim_extensions import TypeWriter
|
|
294
|
+
|
|
295
|
+
class MyScene(Scene):
|
|
296
|
+
def construct(self):
|
|
297
|
+
text = Text("Hello World")
|
|
298
|
+
self.play(TypeWriter(text, interval=0.1))
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**参数**
|
|
302
|
+
- `mobject` — `Text` 对象
|
|
303
|
+
- `interval` — 字符间显示间隔(秒),默认 `2`
|
|
304
|
+
- `**kwargs` — 传递给 `Animation` 的其他参数
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
<a name="english"></a>
|
|
309
|
+
## English
|
|
310
|
+
|
|
311
|
+
manim_extensions is an extension toolkit for [Manim](https://www.manim.community/), providing Chinese formula rendering, geometric calculations, annotated graphics, and common animation effects to help you create mathematical animations more efficiently.
|
|
312
|
+
|
|
313
|
+
### Installation
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
pip install manim_extensions
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Dependency: `manim`
|
|
320
|
+
|
|
321
|
+
### Module Overview
|
|
322
|
+
|
|
323
|
+
| Module | Description |
|
|
324
|
+
|--------|-------------|
|
|
325
|
+
| `mobjects` | Mobject class extensions, including Chinese formulas, labeled dots, extendable lines, etc. |
|
|
326
|
+
| `geometry` | Pure geometric calculation functions for finding intersections, tangent points, etc. |
|
|
327
|
+
| `animations` | Animation effects, such as visualized arc drawing and typewriter effects |
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
### mobjects Module
|
|
332
|
+
|
|
333
|
+
#### `ChineseMathTex(*texts, font="SimSun", tex_to_color_map={}, **kwargs)`
|
|
334
|
+
|
|
335
|
+
A `MathTex` subclass that supports Chinese characters. You can write Chinese directly in the formula without manually wrapping it in `\text{}`.
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
from manim_extensions import ChineseMathTex
|
|
339
|
+
|
|
340
|
+
formula = ChineseMathTex(
|
|
341
|
+
"\\frac{1}{2} + 你好 = x",
|
|
342
|
+
tex_to_color_map={"你好": RED},
|
|
343
|
+
font="SimSun"
|
|
344
|
+
)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Parameters**
|
|
348
|
+
- `*texts` — LaTeX text strings
|
|
349
|
+
- `font` — Chinese font name, default `"SimSun"`
|
|
350
|
+
- `tex_to_color_map` — Dictionary mapping text to colors, default `{}`
|
|
351
|
+
- `**kwargs` — Other arguments passed to `MathTex`
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
#### `LabelDot(dot_label, dot_pos, label_pos=DOWN, buff=0.1, **kwargs)`
|
|
356
|
+
|
|
357
|
+
Creates a dot at the specified position and adds a `MathTex` label next to it.
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
from manim_extensions import LabelDot
|
|
361
|
+
|
|
362
|
+
dot = LabelDot("A", [1, 2, 0], label_pos=UP, buff=0.2)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Parameters**
|
|
366
|
+
- `dot_label` — Label text
|
|
367
|
+
- `dot_pos` — Dot position
|
|
368
|
+
- `label_pos` — Direction of the label relative to the dot, default `DOWN`
|
|
369
|
+
- `buff` — Spacing between label and dot, default `0.1`
|
|
370
|
+
- `**kwargs` — Other arguments passed to `VGroup`
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
#### `MathTexLine(formula, direction=UP, buff=0.5, **kwargs)`
|
|
375
|
+
|
|
376
|
+
Creates a line segment and places a `MathTex` formula next to it in the specified direction.
|
|
377
|
+
|
|
378
|
+
```python
|
|
379
|
+
from manim_extensions import MathTexLine
|
|
380
|
+
|
|
381
|
+
line = MathTexLine(MathTex("y = x"), direction=UP, color=BLUE)
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Parameters**
|
|
385
|
+
- `formula` — `MathTex` formula object
|
|
386
|
+
- `direction` — Direction of the formula relative to the line, default `UP`
|
|
387
|
+
- `buff` — Spacing between formula and line, default `0.5`
|
|
388
|
+
- `**kwargs` — Other arguments passed to `Line`
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
#### `MathTexBrace(target, formula, direction=UP, buff=0.5, **kwargs)`
|
|
393
|
+
|
|
394
|
+
Creates a brace for a target object and places a `MathTex` formula next to it.
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
from manim_extensions import MathTexBrace
|
|
398
|
+
|
|
399
|
+
line = Line(LEFT, RIGHT)
|
|
400
|
+
brace = MathTexBrace(line, MathTex("\\Delta x"), direction=UP)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Parameters**
|
|
404
|
+
- `target` — The object to be braced
|
|
405
|
+
- `formula` — `MathTex` formula object
|
|
406
|
+
- `direction` — Direction of the brace relative to the target, default `UP`
|
|
407
|
+
- `buff` — Spacing between formula and brace, default `0.5`
|
|
408
|
+
- `**kwargs` — Other arguments passed to `Brace`
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
#### `MathTexDoublearrow(formula, direction=UP, buff=0.5, **kwargs)`
|
|
413
|
+
|
|
414
|
+
Creates a double arrow and places a `MathTex` formula next to it in the specified direction.
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
from manim_extensions import MathTexDoublearrow
|
|
418
|
+
|
|
419
|
+
arrow = MathTexDoublearrow(MathTex("\\Leftrightarrow"), direction=DOWN)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Parameters**
|
|
423
|
+
- `formula` — `MathTex` formula object
|
|
424
|
+
- `direction` — Direction of the formula relative to the arrow, default `UP`
|
|
425
|
+
- `buff` — Spacing between formula and arrow, default `0.5`
|
|
426
|
+
- `**kwargs` — Other arguments passed to `DoubleArrow`
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
#### `ExtendedLine(line, extend_distance, **kwargs)`
|
|
431
|
+
|
|
432
|
+
Extends an existing `Line` by a specified distance at both ends along its original direction.
|
|
433
|
+
|
|
434
|
+
```python
|
|
435
|
+
from manim_extensions import ExtendedLine
|
|
436
|
+
|
|
437
|
+
original = Line(LEFT, RIGHT)
|
|
438
|
+
extended = ExtendedLine(original, extend_distance=1.0, color=RED)
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Parameters**
|
|
442
|
+
- `line` — Original line segment
|
|
443
|
+
- `extend_distance` — Distance to extend at each end
|
|
444
|
+
- `**kwargs` — Other arguments passed to `Line`
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
### geometry Module
|
|
449
|
+
|
|
450
|
+
#### `CircleInt(circle1, circle2)`
|
|
451
|
+
|
|
452
|
+
Calculates the intersection points of two circles.
|
|
453
|
+
|
|
454
|
+
```python
|
|
455
|
+
from manim_extensions import CircleInt
|
|
456
|
+
|
|
457
|
+
c1 = Circle(radius=2).shift(LEFT)
|
|
458
|
+
c2 = Circle(radius=2).shift(RIGHT)
|
|
459
|
+
result = CircleInt(c1, c2) # ([x1, y1, 0], [x2, y2, 0]) or None
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
**Returns**
|
|
463
|
+
- A tuple of two intersection points `([x, y, 0], [x, y, 0])` if they intersect
|
|
464
|
+
- `None` if they do not intersect
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
#### `LineCircleInt(line, circle)`
|
|
469
|
+
|
|
470
|
+
Calculates the intersection points of a line segment and a circle, returning only points within the segment parameter range `[0, 1]`.
|
|
471
|
+
|
|
472
|
+
```python
|
|
473
|
+
from manim_extensions import LineCircleInt
|
|
474
|
+
|
|
475
|
+
line = Line(LEFT * 3, RIGHT * 3)
|
|
476
|
+
circle = Circle(radius=1)
|
|
477
|
+
result = LineCircleInt(line, circle)
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Returns**
|
|
481
|
+
- Two intersection points, one intersection point, or `None`
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
#### `LineInt(line1, line2)`
|
|
486
|
+
|
|
487
|
+
Calculates the intersection point of two infinitely extended line segments in 2D (not restricted to the segment endpoints).
|
|
488
|
+
|
|
489
|
+
```python
|
|
490
|
+
from manim_extensions import LineInt
|
|
491
|
+
|
|
492
|
+
l1 = Line(LEFT, RIGHT)
|
|
493
|
+
l2 = Line(DOWN, UP)
|
|
494
|
+
result = LineInt(l1, l2) # [0.0, 0.0, 0] or None
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**Returns**
|
|
498
|
+
- Intersection point `[x, y, 0]`, or `None` if parallel
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
#### `LineArcInt(line, arc)`
|
|
503
|
+
|
|
504
|
+
Calculates the intersection points of a line segment and an arc, checking whether the points truly lie within the arc's angle range.
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
from manim_extensions import LineArcInt
|
|
508
|
+
|
|
509
|
+
line = Line(LEFT, RIGHT)
|
|
510
|
+
arc = Arc(start_angle=0, angle=PI, radius=1)
|
|
511
|
+
result = LineArcInt(line, arc)
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
**Returns**
|
|
515
|
+
- Two intersection points, one intersection point, or `None`
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
#### `TangentPoint(p1, p2, line_start, line_end)`
|
|
520
|
+
|
|
521
|
+
Calculates the tangent point of a circle (passing through `p1` and `p2`) and a line segment.
|
|
522
|
+
|
|
523
|
+
```python
|
|
524
|
+
from manim_extensions import TangentPoint
|
|
525
|
+
|
|
526
|
+
p = TangentPoint(
|
|
527
|
+
[1, 0, 0], [-1, 0, 0],
|
|
528
|
+
line_start=[0, -2, 0], line_end=[0, 2, 0]
|
|
529
|
+
)
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
**Parameters**
|
|
533
|
+
- `p1`, `p2` — Two points on the circle, in the format `(x, y)` or `(x, y, z)`
|
|
534
|
+
- `line_start`, `line_end` — Start and end points of the line segment
|
|
535
|
+
|
|
536
|
+
**Returns**
|
|
537
|
+
- Tangent point coordinates as `np.ndarray`, or `None` if calculation fails
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
### animations Module
|
|
542
|
+
|
|
543
|
+
#### `VisDrawArc(scene, arc, axis=OUT, run_time=1)`
|
|
544
|
+
|
|
545
|
+
Visualized arc drawing animation, showing a moving dot and a dashed radius line. **Call it directly; no need to wrap it in `self.play()`.**
|
|
546
|
+
|
|
547
|
+
```python
|
|
548
|
+
from manim_extensions import VisDrawArc
|
|
549
|
+
|
|
550
|
+
class MyScene(Scene):
|
|
551
|
+
def construct(self):
|
|
552
|
+
arc = Arc(start_angle=0, angle=PI, radius=2)
|
|
553
|
+
VisDrawArc(self, arc, axis=OUT, run_time=2)
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
**Parameters**
|
|
557
|
+
- `scene` — Manim scene object
|
|
558
|
+
- `arc` — The `Arc` to draw
|
|
559
|
+
- `axis` — Rotation direction, `OUT` for counterclockwise and `IN` for clockwise, default `OUT`
|
|
560
|
+
- `run_time` — Animation duration in seconds, default `1`
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
#### `TypeWriter(mobject, interval=2, **kwargs)`
|
|
565
|
+
|
|
566
|
+
Typewriter effect animation that displays `Text` content character by character. Automatically calculates the total animation duration based on character count and interval.
|
|
567
|
+
|
|
568
|
+
```python
|
|
569
|
+
from manim_extensions import TypeWriter
|
|
570
|
+
|
|
571
|
+
class MyScene(Scene):
|
|
572
|
+
def construct(self):
|
|
573
|
+
text = Text("Hello World")
|
|
574
|
+
self.play(TypeWriter(text, interval=0.1))
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
**Parameters**
|
|
578
|
+
- `mobject` — `Text` object
|
|
579
|
+
- `interval` — Display interval between characters in seconds, default `2`
|
|
580
|
+
- `**kwargs` — Other arguments passed to `Animation`
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
## License
|
|
585
|
+
|
|
586
|
+
MIT License
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
manim_extensions/__init__.py,sha256=mSVCmyhQZ_mxkTRYvesCKgedQSdsThlvZVhSaH5sHeE,74
|
|
2
|
+
manim_extensions/animations.py,sha256=4SHKkXpotYT3IPLPfWbo6GNrRHW49V8hgIxrg-N4iaY,3973
|
|
3
|
+
manim_extensions/geometry.py,sha256=ynRfxm_CqVtqOg-bslRM9lOXuZMkQF9S11jaIzGCjpU,10956
|
|
4
|
+
manim_extensions/mobjects.py,sha256=Xwytb-uxjXSi57YGmuVY6XZXmc1M8YKAvUKHnx0Cf-o,6344
|
|
5
|
+
manim_extensions-1.0.0.dist-info/METADATA,sha256=2V6uOI1r9t16Bs9KriGDNq5dk-IqTc4fPIzoY5cv47I,16143
|
|
6
|
+
manim_extensions-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
+
manim_extensions-1.0.0.dist-info/top_level.txt,sha256=tJjvk037RxPiZCC4X2Udw9at25l6iHyflk8bqqZExcU,17
|
|
8
|
+
manim_extensions-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
manim_extensions
|