当前位置: 首页 > news >正文

python中学物理实验模拟:平抛运动和抛物运动

python中学物理实验模拟:平抛运动和抛物运动

平抛运动

平抛运动的定义与特点

定义:物体以一定的初速度沿水平方向抛出,仅在重力作用下所做的运动称为平抛运动。

平抛运动是抛物运动(后面介绍)的特例。

平抛运动的分解与规律

平抛运动可分解为水平方向的匀速直线运动和竖直方向的自由落体运动,两者具有等时性。

水平方向运动

  • 性质:匀速直线运动
  • 初速度:v₀(水平方向)
  • 加速度:aₓ = 0
  • 位移公式:x = v₀t(x 为水平位移,v₀ 为初速度,t 为运动时间)
  • 速度公式:vₓ = v₀(保持不变)

竖直方向运动

  • 性质:自由落体运动
  • 初速度:v₀y = 0(竖直方向)
  • 加速度:ay = g(向下)
  • 位移公式:y = h₀ - ½gt²(y为竖直位移,g为重力加速度,通常取g≈9.8m/s²)
  • 速度公式:vy = gt (竖直速度随时间均匀增加)

平抛运动的轨迹方程

    平抛运动的轨迹是一条抛物线。可以通过消去时间 t 来得到轨迹方程:

    y = h₀ - (gx²)/(2v₀²)

运行截图:

源码如下:

import tkinter as tk
from tkinter import ttk, messagebox
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.animation as animation
import numpy as np
import mathclass ProjectileMotionApp:def __init__(self, root):self.root = rootself.root.title("平抛运动模拟器")self.root.geometry("1000x700")# 物理参数self.initial_height = tk.DoubleVar(value=80.0)  # 初始高度self.initial_velocity = tk.DoubleVar(value=20.0)  # 初始水平速度self.g = 9.8  # 重力加速度# 显示变量(格式化后)self.display_height = tk.StringVar(value="80.0")self.display_velocity = tk.StringVar(value="20.0")# 绑定变量变化事件self.initial_height.trace_add("write", self.update_height_display)self.initial_velocity.trace_add("write", self.update_velocity_display)# 模拟参数self.time_step = 0.02self.current_time = 0.0self.is_running = Falseself.animation = None# 轨迹数据self.trajectory_x = []self.trajectory_y = []# 创建界面self.create_widgets()self.setup_plot()def create_widgets(self):# 主框架main_frame = ttk.Frame(self.root)main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)# 左侧控制面板control_frame = ttk.LabelFrame(main_frame, text="控制面板", padding=10)control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))# 参数设置params_frame = ttk.LabelFrame(control_frame, text="物理参数", padding=10)params_frame.pack(fill=tk.X, pady=(0, 10))# 初始高度ttk.Label(params_frame, text="初始高度 (m):").pack(anchor=tk.W)height_frame = ttk.Frame(params_frame)height_frame.pack(fill=tk.X, pady=5)ttk.Scale(height_frame, from_=10, to=150, variable=self.initial_height, orient=tk.HORIZONTAL).pack(side=tk.LEFT, fill=tk.X, expand=True)# 修改这里:使用格式化后的显示变量ttk.Label(height_frame, textvariable=self.display_height, width=6).pack(side=tk.RIGHT)# 初始水平速度ttk.Label(params_frame, text="初始水平速度 (m/s):").pack(anchor=tk.W, pady=(10, 0))velocity_frame = ttk.Frame(params_frame)velocity_frame.pack(fill=tk.X, pady=5)ttk.Scale(velocity_frame, from_=5, to=50, variable=self.initial_velocity, orient=tk.HORIZONTAL).pack(side=tk.LEFT, fill=tk.X, expand=True)# 修改这里:使用格式化后的显示变量ttk.Label(velocity_frame, textvariable=self.display_velocity, width=6).pack(side=tk.RIGHT)# 控制按钮button_frame = ttk.LabelFrame(control_frame, text="实验控制", padding=10)button_frame.pack(fill=tk.X, pady=(0, 10))ttk.Button(button_frame, text="开始模拟", command=self.start_simulation).pack(fill=tk.X, pady=2)ttk.Button(button_frame, text="暂停/继续", command=self.pause_simulation).pack(fill=tk.X, pady=2)ttk.Button(button_frame, text="重置", command=self.reset_simulation).pack(fill=tk.X, pady=2)# 实时数据显示data_frame = ttk.LabelFrame(control_frame, text="实时数据", padding=10)data_frame.pack(fill=tk.BOTH, expand=True)self.time_label = ttk.Label(data_frame, text="时间: 0.00 s")self.time_label.pack(anchor=tk.W, pady=2)self.position_label = ttk.Label(data_frame, text="位置: (0.0, 80.0) m")self.position_label.pack(anchor=tk.W, pady=2)self.velocity_label = ttk.Label(data_frame, text="速度: 20.0 m/s")self.velocity_label.pack(anchor=tk.W, pady=2)self.height_label = ttk.Label(data_frame, text="高度: 80.0 m")self.height_label.pack(anchor=tk.W, pady=2)# 右侧绘图区域plot_frame = ttk.LabelFrame(main_frame, text="运动轨迹", padding=5)plot_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)self.fig = Figure(figsize=(8, 6), dpi=100)self.ax = self.fig.add_subplot(111)self.canvas = FigureCanvasTkAgg(self.fig, plot_frame)self.canvas.draw()self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)toolbar = NavigationToolbar2Tk(self.canvas, plot_frame)toolbar.update()# 新增格式化函数def update_height_display(self, *args):"""更新高度显示,保留1位小数"""value = self.initial_height.get()self.display_height.set(f"{value:.1f}")def update_velocity_display(self, *args):"""更新速度显示,保留1位小数"""value = self.initial_velocity.get()self.display_velocity.set(f"{value:.1f}")def setup_plot(self):"""设置绘图区域"""self.ax.clear()# 获取当前参数h0 = self.initial_height.get()v0 = self.initial_velocity.get()# 计算理论射程和飞行时间(用于设置坐标范围)flight_time = math.sqrt(2 * h0 / self.g)max_range = v0 * flight_time# 设置坐标范围(标准物理坐标系:Y轴向上)self.ax.set_xlim(0, max(100, max_range * 1.2))self.ax.set_ylim(0, max(100, h0 * 1.2))# 设置标签和标题self.ax.set_xlabel('水平距离 x (m)', fontsize=12)self.ax.set_ylabel('高度 y (m)', fontsize=12)self.ax.set_title('平抛运动轨迹模拟', fontsize=14, fontweight='bold')# 添加网格self.ax.grid(True, alpha=0.3)# 绘制地面(y=0)self.ax.axhline(y=0, color='brown', linewidth=3, label='地面')# 绘制发射点self.ax.plot(0, h0, 'go', markersize=10, label='发射点')# 初始化物体和轨迹self.ball, = self.ax.plot([], [], 'ro', markersize=8, label='物体')self.trajectory, = self.ax.plot([], [], 'b-', linewidth=2, alpha=0.7, label='轨迹')# 添加坐标轴箭头(调整位置)# X轴箭头 - 保持不变arrow_length = max_range * 0.1self.ax.annotate('', xy=(arrow_length, 5), xytext=(0, 5), arrowprops=dict(arrowstyle='->', lw=2, color='black'))self.ax.text(arrow_length/2, 8, 'x', fontsize=12, ha='center')# Y轴箭头 - 调整到更靠近Y轴的位置y_arrow_x = max_range * 0.02  # 改为射程的2%,更靠近Y轴self.ax.annotate('', xy=(y_arrow_x, h0 * 0.25), xytext=(y_arrow_x, 5), arrowprops=dict(arrowstyle='->', lw=2, color='black'))self.ax.text(y_arrow_x + max_range * 0.015, h0 * 0.15, 'y', fontsize=12, ha='center')# 添加图例self.ax.legend(loc='upper right')self.canvas.draw()def calculate_position(self, t):"""计算t时刻的位置(标准物理坐标系)使用平抛运动方程:x = v₀ * ty = h₀ - (1/2) * g * t²  # 注意:重力使高度减少,所以是减号"""v0 = self.initial_velocity.get()h0 = self.initial_height.get()x = v0 * ty = h0 - 0.5 * self.g * t * t  # 标准物理坐标系:重力项为负# 计算速度分量vx = v0  # 水平速度保持不变vy = -self.g * t  # 竖直速度(向下为负)# 总速度v_total = math.sqrt(vx*vx + vy*vy)return x, y, vx, vy, v_totaldef animate(self, frame):"""动画更新函数"""if not self.is_running:returnself.current_time += self.time_stepx, y, vx, vy, v_total = self.calculate_position(self.current_time)# 检查是否落地(y <= 0)if y <= 0:y = 0  # 确保不低于地面self.is_running = Falsemessagebox.showinfo("模拟结束", f"物体已落地!\n飞行时间: {self.current_time:.2f} s\n水平射程: {x:.1f} m")# 更新物体位置self.ball.set_data([x], [y])# 更新轨迹self.trajectory_x.append(x)self.trajectory_y.append(y)self.trajectory.set_data(self.trajectory_x, self.trajectory_y)# 更新实时数据显示self.time_label.config(text=f"时间: {self.current_time:.2f} s")self.position_label.config(text=f"位置: ({x:.1f}, {y:.1f}) m")self.velocity_label.config(text=f"速度: {v_total:.1f} m/s")self.height_label.config(text=f"高度: {y:.1f} m")self.canvas.draw()return self.ball, self.trajectorydef start_simulation(self):"""开始模拟"""# 重置状态self.reset_simulation()self.is_running = True# 重新设置绘图self.setup_plot()# 开始动画self.animation = animation.FuncAnimation(self.fig, self.animate, interval=50, repeat=False, cache_frame_data=False)self.canvas.draw()def pause_simulation(self):"""暂停/继续模拟"""if self.animation:if self.is_running:self.is_running = Falseself.animation.pause()else:self.is_running = Trueself.animation.resume()def reset_simulation(self):"""重置模拟"""self.is_running = Falseself.current_time = 0.0self.trajectory_x.clear()self.trajectory_y.clear()if self.animation:self.animation.event_source.stop()# 重置显示h0 = self.initial_height.get()v0 = self.initial_velocity.get()self.time_label.config(text="时间: 0.00 s")self.position_label.config(text=f"位置: (0.0, {h0:.1f}) m")self.velocity_label.config(text=f"速度: {v0:.1f} m/s")self.height_label.config(text=f"高度: {h0:.1f} m")self.setup_plot()def main():# 设置matplotlib中文字体plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'DejaVu Sans']plt.rcParams['axes.unicode_minus'] = Falseroot = tk.Tk()app = ProjectileMotionApp(root)root.mainloop()if __name__ == "__main__":main()

抛物运动

抛物运动(Projectile Motion)是指物体以一定的初速度被抛出,如果忽略空气阻力和其他外力的影响,只在重力作用下所做的运动。其本质是匀变速曲线运动,因为物体只受恒定的重力加速度(9.8m/s2,方向竖直向下)。

运动特征

水平方向:匀速直线运动(无外力)

  • 水平方向不受力(忽略空气阻力),物体做匀速直线运动。水平方向的速度保持不变,速度大小为vₓ = v0​cosθ,其中 v0​ 是物体的初速度,θ 是初速度与水平方向的夹角。
  • 水平方向的位移公式为:x=v0​cosθt
  • 加速度: aₓ = 0(没有水平方向的力)

竖直方向:匀变速直线运动(重力作用)

  • 竖直方向物体仅受重力作用,做匀变速直线运动。竖直方向的初速度为 vy =v0​sinθ
  • 竖直方向的速度随时间变化的公式为:vy​=v0​sinθgt
  • 竖直方向的位移公式为:y=v0​sinθt− ½gt²
  • 加速度: aᵧ = -g(重力加速度,方向竖直向下,通常取 g ≈ 9.8 m/s²,计算中有时取 10 m/s²;负号表示方向与设定的正方向 - 通常是向上 - 相反)

抛物运动轨迹方程:y = x·tan(θ) - (gx²)/(2v₀²cos²θ)

【将水平位移 x 和时间 t 的关系式代入竖直位移 y 和时间 t 的关系式中,消去时间 t,可以得到物体运动的轨迹方程:

t = x / (v₀ · cosθ)

代入 y 的表达式:
y = (v₀ · sinθ) (x / (v₀·cosθ)) - (1/2) ·g ·(x / (v₀ · cosθ))²

化简后得到y = x·tan(θ) - (gx²)/(2v₀²cos²θ)

这是一个 二次函数 方程,其图像是一条抛物线。这就是“抛物运动”名称的由来。】

理想化条件

  • 忽略空气阻力
  • 重力加速度恒定
  • 地球表面视为平面
  • 不考虑地球自转

小结

抛物运动轨迹方程:y = x·tan(θ) - (gx²)/(2v₀²cos²θ)

这确实是二次函数形式,图像是抛物线,属于二维运动。

特殊情况

  • 平抛运动(θ = 0°):y = h₀ - gx²/(2v₀²),抛物线,二维运动
  • 竖直上抛(θ = 90°):x = 0,y = v₀t - ½gt²,纯一维运动
  • 自由落体(v₀ = 0):x = 0,y = h₀ - ½gt²,纯一维运动

运行截图

源码

import tkinter as tk
from tkinter import ttk, messagebox
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.animation as animation
import numpy as np
import mathclass ProjectileMotionApp:def __init__(self, root):self.root = rootself.root.title("物体抛物运动模拟器")self.root.geometry("1200x980+100+10")self.root.configure(bg='#f0f0f0')# 物理参数self.v0 = tk.DoubleVar(value=30.0)self.height = tk.DoubleVar(value=100.0)self.angle = tk.DoubleVar(value=25.0)self.g = 9.8# 显示选项(简化后的)self.show_trajectory = tk.BooleanVar(value=True)self.show_components = tk.BooleanVar(value=True)self.show_vectors = tk.BooleanVar(value=True)# 动画控制self.is_running = Falseself.current_time = 0self.dt = 0.05self.trajectory_x = []self.trajectory_y = []self.anim = None# 设置matplotlib中文字体self.setup_matplotlib_fonts()# 创建界面self.create_widgets()self.setup_plot()# 数据记录self.experiment_data = []# 绘图元素引用self.velocity_arrow = Noneself.velocity_text = Noneself.component_lines = {'x': None, 'y': None}def setup_matplotlib_fonts(self):"""设置matplotlib中文字体"""plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'Arial Unicode MS']plt.rcParams['axes.unicode_minus'] = Falseplt.rcParams['mathtext.fontset'] = 'stix'def create_widgets(self):"""创建主界面"""main_frame = ttk.Frame(self.root)main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)self.create_control_panel(main_frame)self.create_plot_area(main_frame)self.create_status_bar(main_frame)def create_control_panel(self, parent):"""创建左侧控制面板(简化版)"""control_frame = ttk.LabelFrame(parent, text="控制面板", padding=10)control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))# 参数设置区域params_frame = ttk.LabelFrame(control_frame, text="实验参数", padding=10)params_frame.pack(fill=tk.X, pady=(0, 10))# 初速度设置ttk.Label(params_frame, text="初速度 (m/s):").grid(row=0, column=0, sticky=tk.W, pady=2)v0_frame = ttk.Frame(params_frame)v0_frame.grid(row=0, column=1, sticky=tk.EW, pady=2, padx=(10, 0))ttk.Scale(v0_frame, from_=10, to=100, variable=self.v0, orient=tk.HORIZONTAL,command=self.update_parameters).pack(side=tk.LEFT, fill=tk.X, expand=True)ttk.Label(v0_frame, textvariable=self.v0, width=6).pack(side=tk.RIGHT)# 初始高度设置ttk.Label(params_frame, text="初始高度 (m):").grid(row=1, column=0, sticky=tk.W, pady=2)height_frame = ttk.Frame(params_frame)height_frame.grid(row=1, column=1, sticky=tk.EW, pady=2, padx=(10, 0))ttk.Scale(height_frame, from_=10, to=150, variable=self.height, orient=tk.HORIZONTAL,command=self.update_parameters).pack(side=tk.LEFT, fill=tk.X, expand=True)ttk.Label(height_frame, textvariable=self.height, width=6).pack(side=tk.RIGHT)# 发射角度设置ttk.Label(params_frame, text="发射角度 (°):").grid(row=2, column=0, sticky=tk.W, pady=2)angle_frame = ttk.Frame(params_frame)angle_frame.grid(row=2, column=1, sticky=tk.EW, pady=2, padx=(10, 0))# 平抛运动:发射角度 = 0°;斜抛运动:发射角度 > 0°(最大90°);下抛运动:发射角度 < 0°(最小-30°)ttk.Scale(angle_frame, from_=-30, to=90, variable=self.angle, orient=tk.HORIZONTAL,command=self.update_parameters).pack(side=tk.LEFT, fill=tk.X, expand=True)ttk.Label(angle_frame, textvariable=self.angle, width=6).pack(side=tk.RIGHT)params_frame.columnconfigure(1, weight=1)# 显示选项(简化版)display_frame = ttk.LabelFrame(control_frame, text="显示选项", padding=10)display_frame.pack(fill=tk.X, pady=(0, 10))ttk.Checkbutton(display_frame, text="显示轨迹", variable=self.show_trajectory).pack(anchor=tk.W, pady=2)ttk.Checkbutton(display_frame, text="显示分量", variable=self.show_components).pack(anchor=tk.W, pady=2)ttk.Checkbutton(display_frame, text="显示速度矢量", variable=self.show_vectors).pack(anchor=tk.W, pady=2)# 控制按钮button_frame = ttk.LabelFrame(control_frame, text="实验控制", padding=10)button_frame.pack(fill=tk.X, pady=(0, 10))# 使用较大的按钮,便于操作ttk.Button(button_frame, text="开始实验", command=self.start_experiment,style="Start.TButton").pack(fill=tk.X, pady=3, ipady=5)ttk.Button(button_frame, text="暂停/继续", command=self.pause_experiment).pack(fill=tk.X, pady=3, ipady=5)ttk.Button(button_frame, text="重置", command=self.reset_experiment).pack(fill=tk.X, pady=3, ipady=5)ttk.Button(button_frame, text="保存数据", command=self.save_data).pack(fill=tk.X, pady=3, ipady=5)# 实时数据显示data_frame = ttk.LabelFrame(control_frame, text="实时数据", padding=10)data_frame.pack(fill=tk.X, expand=True)# 修复:移除不支持的参数,使用单独的pack配置self.time_label = ttk.Label(data_frame, text="时间: 0.00 s", font=("Microsoft YaHei", 10))self.time_label.pack(anchor=tk.W, pady=3)self.position_label = ttk.Label(data_frame, text="位置: (0, 80) m", font=("Microsoft YaHei", 10))self.position_label.pack(anchor=tk.W, pady=3)self.velocity_label = ttk.Label(data_frame, text="速度: 0.0 m/s", font=("Microsoft YaHei", 10))self.velocity_label.pack(anchor=tk.W, pady=3)self.angle_label = ttk.Label(data_frame, text="速度角度: 0.0°", font=("Microsoft YaHei", 10))self.angle_label.pack(anchor=tk.W, pady=3)# 添加简单的物理说明info_frame = ttk.LabelFrame(control_frame, text="物理说明", padding=10)info_frame.pack(fill=tk.X, pady=(10, 0))info_text = """• 90°: 竖直上抛
• 45°: 最远射程角度  
• 0°: 水平平抛
• 负角度: 向下抛射
• 重力加速度 g = 9.8 m/s²"""info_label = ttk.Label(info_frame, text=info_text, justify=tk.LEFT, font=("Microsoft YaHei", 9), foreground="blue")info_label.pack(anchor=tk.W)def create_plot_area(self, parent):"""创建右侧matplotlib绘图区域"""plot_frame = ttk.LabelFrame(parent, text="运动模拟", padding=5)plot_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)self.fig = Figure(figsize=(10, 8), dpi=100)self.ax = self.fig.add_subplot(111)self.canvas = FigureCanvasTkAgg(self.fig, plot_frame)self.canvas.draw()self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)# 简化工具栏toolbar = NavigationToolbar2Tk(self.canvas, plot_frame)toolbar.update()self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)def create_status_bar(self, parent):"""创建底部状态栏"""status_frame = ttk.Frame(parent)status_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=(10, 0))self.status_label = ttk.Label(status_frame, text="就绪 - 调整参数后点击'开始实验'", relief=tk.SUNKEN, font=("Microsoft YaHei", 10))self.status_label.pack(side=tk.LEFT, fill=tk.X, expand=True)self.progress = ttk.Progressbar(status_frame, mode='indeterminate')self.progress.pack(side=tk.RIGHT, padx=(10, 0))def setup_plot(self):"""设置matplotlib绘图"""self.ax.clear()# 设置坐标范围self.ax.set_xlim(0, 200)self.ax.set_ylim(0, 160)# 标签设置self.ax.set_xlabel('水平距离 x (m)', fontsize=12, fontweight='bold')self.ax.set_ylabel('高度 y (m)', fontsize=12, fontweight='bold')self.ax.set_title('物体的抛物运动模拟', fontsize=16, fontweight='bold', pad=20)# 网格设置self.ax.grid(True, alpha=0.3, linestyle='--')# 绘制地面self.ax.axhline(y=0, color='brown', linewidth=4, label='地面')# 绘制发射台platform_height = self.height.get()self.ax.plot([0, 12], [platform_height, platform_height], 'k-', linewidth=6, label='发射台')# 初始化绘图元素self.projectile_point, = self.ax.plot([], [], 'ro', markersize=14, zorder=5, label='物体', markeredgecolor='darkred', markeredgewidth=2)self.trajectory_line, = self.ax.plot([], [], 'b-', linewidth=3, alpha=0.8, label='运动轨迹')# 重置绘图元素引用self.velocity_arrow = Noneself.velocity_text = Noneself.component_lines = {'x': None, 'y': None}self.add_coordinate_system()# 图例设置legend = self.ax.legend(loc='upper right', fontsize=11, framealpha=0.9)legend.get_frame().set_facecolor('white')self.canvas.draw()def add_coordinate_system(self):"""添加坐标系示意"""arrow_length = 20arrow_props = dict(arrowstyle='->', color='black', lw=3)# X轴箭头self.ax.annotate('', xy=(arrow_length, 8), xytext=(8, 8), arrowprops=arrow_props)self.ax.text(arrow_length + 3, 8, 'x', fontsize=14, fontweight='bold')# Y轴箭头self.ax.annotate('', xy=(8, 8 + arrow_length), xytext=(8, 8), arrowprops=arrow_props)self.ax.text(8, 8 + arrow_length + 3, 'y', fontsize=14, fontweight='bold')# 原点标记self.ax.text(5, 5, 'O', fontsize=14, fontweight='bold')def update_parameters(self, event=None):"""更新参数时重新设置图形"""if not self.is_running:self.setup_plot()def calculate_motion(self, t):"""计算运动参数(简化版 - 不考虑空气阻力)"""v0 = self.v0.get()h0 = self.height.get()theta = math.radians(self.angle.get())v0x = v0 * math.cos(theta)v0y = v0 * math.sin(theta)# 标准抛物运动方程x = v0x * ty = h0 + v0y * t - 0.5 * self.g * t * tvx = v0xvy = v0y - self.g * tv_total = math.sqrt(vx * vx + vy * vy)angle_current = math.degrees(math.atan2(vy, vx))return x, y, vx, vy, v_total, angle_currentdef animate(self, frame):"""动画更新函数"""if not self.is_running:returnself.current_time += self.dtx, y, vx, vy, v_total, angle_current = self.calculate_motion(self.current_time)# 检查是否落地if y <= 0 or x > 190:self.is_running = Falseself.status_label.config(text="实验结束 - 物体已落地")self.progress.stop()y = max(0, y)if self.anim:self.anim.event_source.stop()# 更新物体位置self.projectile_point.set_data([x], [y])# 更新轨迹(减少密度)if self.show_trajectory.get() and frame % 3 == 0:self.trajectory_x.append(x)self.trajectory_y.append(y)self.trajectory_line.set_data(self.trajectory_x, self.trajectory_y)# 清除之前的图形元素self.clear_previous_elements()# 更新速度矢量if self.show_vectors.get():self.update_velocity_vector(x, y, vx, vy, v_total)# 更新分量显示if self.show_components.get():self.update_components(x, y)# 更新实时数据显示self.update_data_display(x, y, v_total, angle_current)# 记录数据self.experiment_data.append({'time': self.current_time,'x': x, 'y': y,'vx': vx, 'vy': vy,'v_total': v_total,'angle': angle_current})return [self.projectile_point, self.trajectory_line]def clear_previous_elements(self):"""清除之前的图形元素"""if self.velocity_arrow:self.velocity_arrow.remove()self.velocity_arrow = Noneif self.velocity_text:self.velocity_text.remove()self.velocity_text = Noneif self.component_lines['x']:self.component_lines['x'].remove()self.component_lines['x'] = Noneif self.component_lines['y']:self.component_lines['y'].remove()self.component_lines['y'] = Nonedef update_velocity_vector(self, x, y, vx, vy, v_total):"""更新速度矢量箭头"""scale = 2.0dx, dy = vx * scale, vy * scaleself.velocity_arrow = self.ax.annotate('', xy=(x + dx, y + dy), xytext=(x, y),arrowprops=dict(arrowstyle='->', color='red', lw=3),zorder=4)# 添加速度值标签label_x = x + dx/2 + 8label_y = y + dy/2 + 5self.velocity_text = self.ax.text(label_x, label_y, f'v={v_total:.1f}m/s', fontsize=10, color='red', fontweight='bold',bbox=dict(boxstyle='round,pad=0.3', facecolor='white', alpha=0.9))def update_components(self, x, y):"""更新分量显示"""h0 = self.height.get()# 水平分量(绿色虚线)self.component_lines['x'], = self.ax.plot([0, x], [h0, h0], 'g--', alpha=0.8, linewidth=2)# 竖直分量(橙色虚线)self.component_lines['y'], = self.ax.plot([x, x], [h0, y], 'orange', linestyle='--', alpha=0.8, linewidth=2)def update_data_display(self, x, y, v_total, angle_current):"""更新实时数据显示"""self.time_label.config(text=f"时间: {self.current_time:.2f} s")self.position_label.config(text=f"位置: ({x:.1f}, {y:.1f}) m")self.velocity_label.config(text=f"速度: {v_total:.1f} m/s")self.angle_label.config(text=f"速度角度: {angle_current:.1f}°")def start_experiment(self):"""开始实验"""# 停止之前的动画if self.anim:self.anim.event_source.stop()self.anim = None# 重置状态self.is_running = Trueself.current_time = 0self.trajectory_x.clear()self.trajectory_y.clear()self.experiment_data.clear()# 重新设置图形self.setup_plot()# 更新状态self.status_label.config(text="实验进行中... 观察物体的运动轨迹")self.progress.start()# 创建新的动画self.anim = animation.FuncAnimation(self.fig, self.animate, interval=50, cache_frame_data=False, repeat=False, blit=False)self.canvas.draw()def pause_experiment(self):"""暂停/继续实验"""if self.anim:if self.is_running:self.is_running = Falseself.anim.pause()self.status_label.config(text="已暂停 - 点击继续恢复实验")self.progress.stop()else:self.is_running = Trueself.anim.resume()self.status_label.config(text="实验继续进行中...")self.progress.start()def reset_experiment(self):"""重置实验"""if self.anim:self.anim.event_source.stop()self.anim = Noneself.is_running = Falseself.current_time = 0self.trajectory_x.clear()self.trajectory_y.clear()self.experiment_data.clear()self.setup_plot()# 重置显示h0 = self.height.get()self.time_label.config(text="时间: 0.00 s")self.position_label.config(text=f"位置: (0, {h0:.0f}) m")self.velocity_label.config(text="速度: 0.0 m/s")self.angle_label.config(text="速度角度: 0.0°")self.status_label.config(text="已重置 - 可以重新开始实验")self.progress.stop()def save_data(self):"""保存实验数据"""if not self.experiment_data:messagebox.showwarning("警告", "没有实验数据可保存!\n请先运行一次实验。")returnfrom tkinter import filedialogimport csvfilename = filedialog.asksaveasfilename(defaultextension=".csv",filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],title="保存实验数据")if filename:try:with open(filename, 'w', newline='', encoding='utf-8') as csvfile:fieldnames = ['time', 'x', 'y', 'vx', 'vy', 'v_total', 'angle']writer = csv.DictWriter(csvfile, fieldnames=fieldnames)writer.writeheader()for data in self.experiment_data:writer.writerow(data)messagebox.showinfo("保存成功", f"实验数据已保存到:\n{filename}")self.status_label.config(text="数据保存成功")except Exception as e:messagebox.showerror("保存失败", f"无法保存文件:\n{str(e)}")def main():root = tk.Tk()# 设置样式style = ttk.Style()style.theme_use('clam')style.configure("Start.TButton", foreground="green", font=("Microsoft YaHei", 10, "bold"))app = ProjectileMotionApp(root)root.mainloop()if __name__ == "__main__":main()

OK!

http://www.xdnf.cn/news/1066825.html

相关文章:

  • 苹果芯片macOS安装版Homebrew(亲测) ,一键安装node、python、vscode等,比绿色软件还干净、无污染
  • 触摸屏(典型 I2C + Input 子系统设备)从设备树解析到触摸事件上报
  • 深入浅出Node.js后端开发
  • Python基础之函数
  • Python基础(​​FAISS​和​​Chroma​)
  • Redis哨兵模式深度解析与实战部署
  • 如何实现财务自由
  • 操作系统 第九章 部分
  • 飞往大厂梦之算法提升-7
  • 第一节 布局与盒模型-Flex与Grid布局对比
  • Java的SpringAI+Deepseek大模型实战【二】
  • Vue实现选中多张图片一起拖拽功能
  • 华为HN8145V光猫改华为蓝色公版界面,三网通用,xgpon公版光猫
  • [NocoDB] 在局域网中调整Float类型显示精度的部署经验
  • 《哈希表》K倍区间(解题报告)
  • 数组题解——​轮转数组【LeetCode】
  • K8S下http请求在ingress和nginx间无限循环的问题
  • Docker 永久换源步骤
  • 基于ASP4644多通道降压技术在电力监测系统中集成应用与发展前景
  • Maven 之 JUnit 测试体系构建全解析
  • 基于SpringBoot + Vue 的网上拍卖系统
  • leetcode543-二叉树的直径
  • 通信网络编程3.0——JAVA
  • Spring Cloud微服务
  • Java面试题027:一文深入了解数据库Redis(3)
  • 【软考高级系统架构论文】论数据分片技术及其应用
  • Redis中的bigkey的介绍及影响
  • 安全再升级! 正也科技通过信息安全等级保护三级备案
  • 七八章习题测试
  • 高级版 Web Worker 封装(含 WorkerPool 调度池 + 超时控制)