先看一个“传统”手动布局的例子。我们想讲 x25x6(x2)(x3) 的面积模型把大矩形分成四块from manim import * class ManualX2Plus5xPlus6(Scene): def construct(self): x 2 # 让 x 取某个具体值来画图 # 手动指定四块的宽高 # 大矩形宽 (x3)高 (x2) # 四块x*x, 3*x, 2*x, 2*3 sq_x2 Rectangle(widthx, heightx, colorBLUE, fill_opacity0.4) rec_3x Rectangle(width3, heightx, colorRED, fill_opacity0.4) rec_2x Rectangle(widthx, height2, colorRED, fill_opacity0.4) rec_6 Rectangle(width3, height2, colorGREEN, fill_opacity0.4) # 手动拼左下角 sq_x2右边 rec_3x上边 rec_2x右上 rec_6 sq_x2.shift(LEFT * (3 / 2) DOWN * (2 / 2)) rec_3x.next_to(sq_x2, RIGHT, buff0) rec_2x.next_to(sq_x2, UP, buff0) rec_6.next_to(rec_3x, UP, buff0) self.play(Create(sq_x2), Create(rec_3x), Create(rec_2x), Create(rec_6)) self.wait(1)要改成 x27x12(x3)(x4)你需要改常数项3→3、4矩形rec_3x的宽度 →4矩形rec_2x的高度 →3小矩形rec_6的宽高 →4和3shift里面的偏移量也要跟着变。每一次改公式都是一次手工劳动而且容易出错。如果公式结构稍微复杂一点比如二次项系数不是 1或者有三项展开这种手动模式基本就难以维护了。2. SymPy 解决方案因式分解→几何布局SymPy的杀手锏就在这里它可以对多项式进行因式分解或展开自动抽取出每一项的系数和次数。然后我们根据这些信息自动生成对应的矩形拼图布局。整体流程是输入一个多项式字符串如x**2 5*x 6用 SymPy 对其进行因式分解得到(x 2)*(x 3)从因式结果中读出两个一次式的常数项2 和 3从展开式中读出各项系数1, 5, 6自动生成四个矩形块x2边长 $ x 、 2x 宽2高 x 、 3x 宽 x $ 高 3、6宽 2 高 3并根据这些边长自动算出左下角坐标将布局信息输出为一个统一的数据结构供Manim使用。这样无论你输入 x27x12 还是 x2−3x2甚至带参数的 x2(ab)xab布局生成逻辑都完全一样——你只需要换一个多项式字符串。下面是一段纯SymPy的布局生成器代码不依赖Manim可以直接在Jupyter里跑from sympy import symbols, factor, expand, Poly from sympy.abc import x def generate_layout(poly_expr): 输入一个关于 x 的多项式可因式分解为 (xa)(xb) 输出四个矩形的布局信息列表每个矩形包括 (标签, 宽, 高, x坐标, y坐标) # 因式分解 factored factor(poly_expr) # 例如 (x 2)*(x 3) # 确保是两个一次因式 if not factored.is_Mul: raise ValueError(多项式不能分解为两个一次因式) factors factored.args if len(factors) ! 2: raise ValueError(暂时只支持两个一次因式) # 提取常数项 a, b a None b None for fac in factors: if fac.is_Add: # 形如 x c提取常数 c coeff_dict Poly(fac, x).as_dict() c -coeff_dict.get(0, 0) if 1 in coeff_dict and coeff_dict[1]1 else None # 这里做一个简单处理假定就是 x - a 形式 # 我们用 Poly 提取根 p Poly(fac, x) if p.degree() 1: root -p.coeff_monomial(1) / p.coeff_monomial(0) # 其实标准形式是 x - r # 更稳健fac x a0 a0 const p.coeff_monomial(1) # 系数 1 const_term p.coeff_monomial(0) # a0 if const 1: val const_term else: val const_term / const if a is None: a val else: b val # 展开式获取各项 expanded expand(poly_expr) poly Poly(expanded, x) coeff_x2 poly.coeff_monomial(2) # x^2 系数 coeff_x poly.coeff_monomial(1) # x 系数 const poly.coeff_monomial(0) # 常数项 # 现在已知大矩形的宽 (x a)高 (x b)但我们是取具体 x 值画图 # 这里的布局用符号 a, b 表达注意 a,b 是常数数值不是符号 # 返回布局宽和高用数值表达式含 x留给 Manim 代入 x_val layout [ {label: x^2, w: x, h: x, x0: 0, y0: 0}, {label: f{b}x, w: b, h: x, x0: x, y0: 0}, {label: f{a}x, w: x, h: a, x0: 0, y0: x}, {label: f{a*b}, w: b, h: a, x0: x, y0: x}, ] return layout, a, b # 示例x^2 5x 6 layout, a, b generate_layout(x**2 5*x 6) print(a , a, b , b) for item in layout: print(item)输出a 2 b 3 {label: x^2, w: x, h: x, x0: 0, y0: 0} {label: 3x, w: 3, h: x, x0: x, y0: 0} {label: 2x, w: x, h: 2, x0: 0, y0: x} {label: 6, w: 3, h: 2, x0: x, y0: x}可以看到矩形的宽高和坐标全部由a和b决定而a,b是从 SymPy 因式分解中自动提取的。换一个多项式只需改一行输入布局自动生成。3. Manim 联动实战通用因式分解拼图动画现在我们把这个自动布局引擎接入Manim做一个通用场景你只需要在开头指定多项式动画就会自动展示对应的面积拼图并标注公式。代码如下from manim import * from sympy import factor, Poly from sympy.abc import x class AutoFactorPuzzle(Scene): def construct(self): # 1. 在这里输入你要讲的多项式 poly_expr x**2 5 * x 6 # 试着改成 x**2 7*x 12 看看 x_val 1.5 # 动画中 x 的具体取值可调 # 2. SymPy 自动分析多项式 factored factor(poly_expr) if not factored.is_Mul or len(factored.args) ! 2: raise ValueError(多项式需能分解为两个一次因式 (xa)(xb)) # 提取 a, b roots [] for fac in factored.args: p Poly(fac, x) if p.degree() ! 1: raise ValueError(因式必须是一次式) # 一次多项式 ax ball_coeffs() 返回 [a, b] coeffs p.all_coeffs() # 根为 -b/a root -coeffs[1] / coeffs[0] roots.append(root) a, b float(roots[0]), float(roots[1]) # 常数项 a, b # 3. 根据 a, b 生成矩形布局 # 四个矩形x^2, b*x, a*x, a*b blocks [ {label: x^2, w: x_val, h: x_val, x0: 0, y0: 0, color: BLUE}, { label: f{abs(b)} \\times x, w: abs(b), h: x_val, x0: x_val, y0: 0, color: RED, }, { label: f{abs(a)} \\times x, w: x_val, h: abs(a), x0: 0, y0: x_val, color: YELLOW, }, { label: f{abs(b)} \\times {abs(a)}, w: abs(b), h: abs(a), x0: x_val, y0: x_val, color: GREEN, }, ] # 4. Manim 绘制 # 创建四个内部矩形 for blk in blocks: rect Rectangle( widthblk[w], heightblk[h], colorblk[color], fill_opacity0.4, stroke_width1, ) # 左下角坐标转换为 Manim 中心坐标 rect.move_to([blk[x0] blk[w] / 2, blk[y0] blk[h] / 2, 0]) # 偏移一个固定位置 rect.shift(LEFT * 2 DOWN)