You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Matplotlib的Python外摆线动画:按尺寸适配帧范围

外摆线动画帧范围适配解决方案

问题描述

当设置ratio=2时,生成的epicycloid.gif动画无法完整显示整条外摆线,需要让动画的帧范围根据滚动小圆的半径(即ratio值)自动适配,确保完整绘制出整条曲线。

问题根源

  1. 外摆线的完整周期由大圆半径R与小圆半径r的比值ratio = R/r决定:
    • 若ratio为整数k,外摆线有k个尖点,只需小圆绕大圆转1圈(φ从0到2π)即可完成整条曲线
    • 若ratio为最简分数p/q,需小圆绕大圆转q圈(φ从0到2π*q)才能画出完整的p个尖点的外摆线
  2. 原代码使用固定的ncycles参数,无法根据ratio自动调整动画周期,导致部分场景下曲线绘制不完整
  3. 未设置合适的坐标轴范围,可能导致曲线超出显示区域

修改后的代码

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
import math


class Epicycloid:

    def __init__(self, ratio, frames_per_cycle):
        self.frames_per_cycle = frames_per_cycle
        self.fig, self.ax = plt.subplots()
        self.ax.set_aspect('equal')

        # 大圆(固定)
        theta = np.linspace(0, 2 * np.pi, 100)
        x = np.cos(theta)
        y = np.sin(theta)
        self.big_circle, = self.ax.plot(x, y, 'b-')

        # 小圆参数
        self.small_r = 1. / ratio
        r = self.small_r
        # 计算外摆线完整周期所需的转动圈数
        # 将ratio转为最简分数,分母即为需要的圈数
        numerator, denominator = self._simplify_ratio(ratio)
        self.full_cycles = denominator
        self.total_frames = self.frames_per_cycle * self.full_cycles

        # 设置坐标轴范围,确保完整显示外摆线
        max_range = 1 + 2 * r
        self.ax.set_xlim(-max_range, max_range)
        self.ax.set_ylim(-max_range, max_range)

        # 初始小圆位置
        x = r * np.cos(theta) + (1 + r)
        y = r * np.sin(theta)
        self.small_circle, = self.ax.plot(x, y, 'k-')

        # 连接线和跟踪点
        self.line, = self.ax.plot([1 + r, 1], [0, 0], 'k-')
        self.dot, = self.ax.plot([1 + r], [0], 'go', ms=5)

        # 外摆线曲线
        self.epicycloid, = self.ax.plot([], [], 'r-')

        # 创建动画
        self.animation = FuncAnimation(
            self.fig, self.animate,
            frames=self.total_frames,
            interval=50, blit=False,
            repeat_delay=2000,
        )

    def _simplify_ratio(self, ratio):
        # 将ratio转为最简分数,返回分子和分母
        if isinstance(ratio, int):
            return ratio, 1
        # 处理浮点数,转为分数
        frac = math.fraction(ratio)
        return frac.numerator, frac.denominator

    def update_small_circle(self, phi):
        # 更新滚动小圆的位置
        theta = np.linspace(0, 2 * np.pi, 100)
        x = self.small_r * np.cos(theta) + (1 + self.small_r) * np.cos(phi)
        y = self.small_r * np.sin(theta) + (1 + self.small_r) * np.sin(phi)
        self.small_circle.set_data(x, y)

    def update_epicycloid(self, phis):
        # 更新外摆线路径
        R = 1
        r = self.small_r
        # 外摆线标准参数方程(移除原代码中的+np.pi,避免相位偏移)
        x = (R + r) * np.cos(phis) - r * np.cos((R + r) / r * phis)
        y = (R + r) * np.sin(phis) - r * np.sin((R + r) / r * phis)
        self.epicycloid.set_data(x, y)

        # 更新连接线和跟踪点
        last_phi = phis[-1]
        center = [(R + r) * np.cos(last_phi), (R + r) * np.sin(last_phi)]
        self.line.set_data([center[0], x[-1]], [center[1], y[-1]])
        self.dot.set_data([x[-1]], [y[-1]])

    def animate(self, frame):
        frame = frame + 1
        # 计算当前相位,覆盖完整的周期
        phi = 2 * np.pi * frame * self.full_cycles / self.total_frames
        self.update_small_circle(phi)
        self.update_epicycloid(np.linspace(0, phi, frame))


# 创建外摆线实例:ratio=2,每个基础周期50帧
epicycloid = Epicycloid(ratio=2, frames_per_cycle=50)

# 保存为gif(需安装imagemagick)
# epicycloid.animation.save('epicycloid.gif', writer='imagemagick', fps=10, dpi=75)

plt.show()

关键修改说明

  • 自动计算完整周期:新增_simplify_ratio方法将ratio转为最简分数,根据分母确定小圆需要绕大圆转动的圈数,确保动画覆盖完整的外摆线绘制周期
  • 动态调整总帧数:总帧数由frames_per_cycle * full_cycles决定,保证每个完整曲线周期有足够的帧来平滑绘制
  • 设置坐标轴范围:根据外摆线的最大尺寸(1 + 2*small_r)设置xlim和ylim,避免曲线超出显示区域
  • 修正参数方程:移除原代码中多余的+np.pi相位偏移,确保外摆线的标准形状

内容的提问来源于stack exchange,提问作者Maroon Racoon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 20:58:17