You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

401 lines
13 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# difference_of_squares_final.py
from manim import *
import numpy as np
class SquareDifference(Scene):
def create_bubble(self, text, direction=UP):
"""创建自定义对话气泡(完全兼容版)"""
# 基础形状组件
bubble = VGroup()
# 文本内容(确保中文字体可用)
text_mob = Text(text, font="Microsoft YaHei", font_size=24)
text_mob.scale(0.8)
# 背景框
rect = RoundedRectangle(
width=text_mob.width + 0.5,
height=text_mob.height + 0.3,
corner_radius=0.2,
fill_color=WHITE,
fill_opacity=1,
stroke_color=BLACK,
stroke_width=2
)
# 箭头方向处理
arrow = Triangle().scale(0.3)
arrow.set_fill(WHITE, 1)
arrow.set_stroke(BLACK, 2)
# 使用向量判断方向
if np.array_equal(direction, UP):
arrow.rotate(-PI/2)
bubble.add(rect, arrow)
bubble.arrange(DOWN, buff=0.1)
elif np.array_equal(direction, DOWN):
arrow.rotate(PI/2)
bubble.add(arrow, rect)
bubble.arrange(DOWN, buff=0.1)
else: # 默认上方
arrow.rotate(-PI/2)
bubble.add(rect, arrow)
bubble.arrange(DOWN, buff=0.1)
# 定位文本
text_mob.move_to(rect)
bubble.add(text_mob)
return bubble
def construct(self):
# 高清配置
config.frame_width = 16
config.frame_height = 9
config.pixel_height = 1440
config.pixel_width = 2560
# 参数配置
a = 4
b = 1
colors = {
"a": "#1F77B4",
"b": "#FF7F0E",
"text": "#FFFFFF",
"bg": "#2E2E2E"
}
self.camera.background_color = colors["bg"]
step_delay = 2.5
# ========== 标题部分 ==========
title = Text("东师理想数学课件生成助手",
font_size=48,
color=colors["text"],
weight=BOLD,
stroke_width=8,
stroke_color=BLACK)
sub_title = Text("平方差公式可视化演示",
font_size=32,
color=colors["text"],
stroke_width=4,
stroke_color=BLACK).next_to(title, DOWN)
self.play(
Write(title, run_time=1.5),
FadeIn(sub_title, shift=UP),
run_time=2
)
self.wait(step_delay)
self.play(
FadeOut(title, shift=UP),
FadeOut(sub_title, shift=DOWN),
run_time=1.5
)
# ========== 公式推导部分 ==========
formula_steps = VGroup(
MathTex("(", "a", "+", "b", ")(", "a", "-", "b", ")", font_size=40),
MathTex("=", "a", "^2", "-", "b", "^2", font_size=40),
MathTex("=", f"{a**2}", "-", f"{b**2}", font_size=40),
MathTex("=", f"{(a+b)*(a-b)}", font_size=48, color=GREEN)
).arrange(DOWN, aligned_edge=LEFT, buff=0.8)
formula_board = SurroundingRectangle(
formula_steps,
color=WHITE,
stroke_width=2,
fill_color=BLACK,
fill_opacity=0.7,
corner_radius=0.2
)
self.play(
DrawBorderThenFill(formula_board),
FadeIn(formula_steps, shift=UP),
run_time=2
)
self.wait(step_delay)
# ========== 坐标系部分(最终修复) ==========
# 使用新版背景线直接生成网格
axes = Axes(
x_range=[0, a+b+1, 1],
y_range=[0, a+b+1, 1],
x_length=8,
y_length=6,
axis_config={
"color": colors["text"],
"stroke_width": 3,
"include_numbers": True
}
).shift(DOWN*0.5)
# 大正方形动画
big_square = Rectangle(
width=a+b,
height=a+b,
color=colors["a"],
fill_opacity=0.4,
stroke_width=4
).move_to(axes.c2p((a+b)/2, (a+b)/2))
self.play(
formula_board.animate.scale(0.8).to_edge(UP, buff=0.5),
Create(axes, run_time=2),
DrawBorderThenFill(big_square),
run_time=2
)
self.wait(step_delay)
# ========== 剩余动画部分 ==========
# 垂直分割线
v_line = Line(
start=axes.c2p(a, 0),
end=axes.c2p(a, a+b),
color=colors["text"],
stroke_width=4
)
v_label = MathTex("a",
color=colors["text"],
stroke_width=3,
stroke_color=BLACK).next_to(v_line, RIGHT, buff=0.2)
# 水平分割线
h_line = Line(
start=axes.c2p(0, a),
end=axes.c2p(a+b, a),
color=colors["text"],
stroke_width=4
)
h_label = MathTex("a",
color=colors["text"],
stroke_width=3,
stroke_color=BLACK).next_to(h_line, UP, buff=0.2)
self.play(
Create(v_line),
Write(v_label),
Create(h_line),
Write(h_label),
run_time=2
)
self.wait(step_delay)
# ========== 面积计算部分 ==========
part_a = Rectangle(
width=a,
height=a,
color=colors["a"],
fill_opacity=0.6
).move_to(axes.c2p(a/2, a/2))
part_b = Rectangle(
width=b,
height=a+b,
color=colors["b"],
fill_opacity=0.6
).move_to(axes.c2p(a + b/2, (a+b)/2))
self.play(
Transform(big_square, part_a),
FadeIn(part_b),
run_time=2
)
self.wait(step_delay)
# 移动并计算面积差
moved_part = part_b.copy().shift(DOWN*b)
area_diff = Rectangle(
width=b,
height=2*b,
color=RED,
fill_opacity=0.3
).move_to(axes.c2p(a + b/2, a - b))
self.play(
part_b.animate.shift(DOWN*b),
FadeIn(area_diff),
run_time=2
)
self.wait(step_delay)
# ========== 最终联动 ==========
final_formula = formula_steps[-1].copy()
final_formula.generate_target()
final_formula.target.scale(1.5).move_to(ORIGIN)
self.play(
MoveToTarget(final_formula),
FadeOut(axes),
FadeOut(part_a),
FadeOut(part_b),
FadeOut(area_diff),
run_time=2
)
self.wait(step_delay*2)
# 结束动画
self.play(
final_formula.animate.scale(0.5).to_edge(DOWN, buff=1),
run_time=1.5
)
self.wait(2)
# 版权信息
copyright = Text("© 东师理想 版权所有",
font_size=24,
color=colors["text"],
stroke_width=2,
stroke_color=BLACK).to_edge(DOWN, buff=0.3)
self.play(Write(copyright), run_time=1)
self.wait(3)
# 创建小熊角色(优化动画版)
bear = VGroup(
Circle(radius=0.5, color=GOLD, fill_opacity=1), # 头
Circle(radius=0.1, fill_opacity=1).shift(UR*0.3), # 右耳
Circle(radius=0.1, fill_opacity=1).shift(UL*0.3), # 左耳
Ellipse(width=0.8, height=1.2, fill_opacity=1).shift(DOWN*0.8) # 身体
).set_color([GOLD_E, LIGHT_BROWN])
bear.scale(0.8).to_corner(DL)
# 场景1小熊出场
self.play(DrawBorderThenFill(bear), run_time=1.5)
self.play(
bear.animate.shift(UP*0.5).scale(1.2),
rate_func=there_and_back_with_pause,
run_time=1.5
)
# 创建对话气泡
bubble = self.create_bubble("大家好!今天我们来认识一个神奇的三角形")
bubble.next_to(bear, UP, buff=0.3)
self.play(
LaggedStart(
DrawBorderThenFill(bubble[0]),
DrawBorderThenFill(bubble[1]),
Write(bubble[2]),
lag_ratio=0.3
),
run_time=2
)
self.wait(2)
self.play(FadeOut(bubble))
# 创建3-4-5直角三角形
triangle = Polygon(
[-2, -1, 0], [2, -1, 0], [2, 2, 0],
color=WHITE,
fill_opacity=0.5,
stroke_width=4
)
labels = VGroup(
MathTex("3").next_to(triangle, LEFT, buff=0.3),
MathTex("4").next_to(triangle, DOWN, buff=0.3),
MathTex("5", color=YELLOW).next_to(triangle.get_center(), UR, buff=0.3)
)
self.play(DrawBorderThenFill(triangle), run_time=2)
self.play(LaggedStart(*[Write(l) for l in labels], lag_ratio=0.3))
self.play(
triangle.animate.shift(LEFT*2),
bear.animate.look_at(triangle),
run_time=1.5
)
self.wait()
# 场景2构建正方形
square3 = Square(side_length=3, color=PINK, fill_opacity=0.3)
square4 = Square(side_length=4, color=BLUE, fill_opacity=0.3)
square5 = Square(side_length=5, color=GREEN, fill_opacity=0.3)
square3.next_to(triangle, LEFT, buff=0.5)
square4.next_to(triangle, DOWN, buff=0.5)
square5.rotate(np.arctan(3/4)).next_to(triangle, UR, buff=0.5)
self.play(
LaggedStart(
DrawBorderThenFill(square3),
DrawBorderThenFill(square4),
DrawBorderThenFill(square5),
lag_ratio=0.5
),
bear.animate.wave(rate_func=there_and_back),
run_time=3
)
# 添加面积标签
area_text = VGroup(
MathTex("3^2=9", color=PINK),
MathTex("4^2=16", color=BLUE),
MathTex("5^2=25", color=GREEN)
).arrange(DOWN, buff=0.5, aligned_edge=LEFT).to_edge(UR)
self.play(TransformFromCopy(square3, area_text[0]))
self.play(Indicate(square3, scale_factor=1.2))
self.play(TransformFromCopy(square4, area_text[1]))
self.play(Indicate(square4, scale_factor=1.2))
self.play(TransformFromCopy(square5, area_text[2]))
self.play(Indicate(square5, scale_factor=1.2))
self.wait()
# 场景3组合动画
self.play(
square3.animate.shift(UP*2 + LEFT*1).rotate(PI/3),
square4.animate.shift(RIGHT*3 + DOWN*1).rotate(-PI/4),
bear.animate.blink(rate_func=there_and_back),
run_time=2
)
magic_text = Text("9 + 16 = 25", font="Microsoft YaHei", color=YELLOW).scale(0.8)
self.play(Write(magic_text))
self.play(
square3.animate.move_to(ORIGIN).set_opacity(0.3),
square4.animate.move_to(ORIGIN).set_opacity(0.3),
square5.animate.move_to(ORIGIN).set_opacity(0.3),
run_time=2
)
self.wait()
# 场景4公式推导
equation = MathTex("3^2 + 4^2 = 5^2", color=YELLOW).scale(1.5)
equation_box = SurroundingRectangle(equation, color=BLUE, buff=0.5, corner_radius=0.2)
self.play(
ReplacementTransform(magic_text, equation),
Create(equation_box),
bear.animate.jump(rate_func=there_and_back),
run_time=2
)
self.wait(2)
# 场景5告别
bye_bubble = self.create_bubble("下次再见啦!")
bye_bubble.next_to(bear, UP, buff=0.3)
self.play(
FadeOut(VGroup(triangle, square3, square4, square5, equation_box, labels, area_text)),
LaggedStart(
DrawBorderThenFill(bye_bubble[0]),
DrawBorderThenFill(bye_bubble[1]),
Write(bye_bubble[2]),
lag_ratio=0.3
),
bear.animate.happy().scale(1.2),
run_time=2
)
self.play(
bear.animate.rotate(PI/2, about_point=bear.get_center()).shift(DOWN*2),
run_time=2
)
self.wait(2)
if __name__ == "__main__":
config.background_color = "#F0F8FF" # 浅蓝背景
config.frame_width = 16
config.frame_height = 9
config.pixel_width = 1280
config.pixel_height = 720
config.media_dir = "./output"
scene = SquareDifference()
scene.render()