python中学物理实验模拟:斜面受力分析
中学物理中斜面受力分析是一个非常重要的基础内容,也是牛顿运动定律应用的核心场景之一。
现在简要介绍斜面物体受力分析情况
重力分解
重力 G = mg
沿斜面平行分量:G∥ = G·sinθ = mg·sinθ
垂直斜面分量:G⊥ = G·cosθ = mg·cosθ
支持力
支持力 N = G⊥ = mg·cosθ
支持力总是垂直于接触面,大小等于重力的垂直斜面分量。
考虑静摩擦情况:
- 最大静摩擦力:fₘₐₓ = μN = μmg·cosθ
- 如果 G∥ ≤ fₘₐₓ,物体静止,实际静摩擦力 f = G∥
动摩擦情况:
- 如果 G∥ > fₘₐₓ,物体滑动,动摩擦力 f = μN = μmg·cosθ
运动状态判断
静止条件:
G∥ ≤ μN
即:mg·sinθ ≤ μmg·cosθ
简化为:tanθ ≤ μ
滑动条件:
G∥ > μN
合力 = G∥ - f = mg·sinθ - μmg·cosθ
加速度 a = (mg·sinθ - μmg·cosθ)/m = g(sinθ - μcosθ)
运行截图:
程序主要功能
1. 参数控制
- 质量调节:可设置物体质量(1-20 kg)
- 角度调节:可设置斜面角度(0-60°)
- 摩擦系数调节:可设置摩擦系数(0-1)
- 摩擦开关:可选择是否考虑摩擦力
2. 实时计算与显示
- 文字分析:详细显示各种力的计算过程和结果
- 图形显示:
- 斜面受力示意图:直观显示物体在斜面上的受力情况
- 重力分解图:展示重力如何分解为平行和垂直分量
3. 动态效果
- 物体状态显示:"静止"或"滑动"
- 滑动时文字动画效果,营造运动感
核心物理原理
源码如下:
import tkinter as tk
from tkinter import ttk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import math
class InclinedPlaneSimulator:
def __init__(self, root):
self.root = root
self.root.title("中学物理斜面受力分析模拟程序")
self.root.geometry("1000x800")
# 物理参数
self.mass = tk.DoubleVar(value=5.0) # 质量 kg
self.angle = tk.DoubleVar(value=30.0) # 角度 度
self.friction_coeff = tk.DoubleVar(value=0.2) # 摩擦系数
self.has_friction = tk.BooleanVar(value=True) # 是否有摩擦
self.g = 9.8 # 重力加速度
# 动画参数
self.animation_time = 0 # 动画时间参数
self.setup_ui()
self.update_analysis()
self.start_animation() # 启动动画
def start_animation(self):
"""启动动画"""
def update_animation():
forces = self.calculate_forces()
if forces['state'] == "滑动":
self.animation_time += 0.3 # 控制动画速度
if self.animation_time > 2 * math.pi:
self.animation_time = 0
self.update_plots(forces)
self.root.after(150, update_animation) # 每150ms更新一次
update_animation()
def setup_ui(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))
# 质量设置
ttk.Label(control_frame, text="物体质量 (kg):").pack(anchor=tk.W)
mass_scale = ttk.Scale(control_frame, from_=1, to=20, variable=self.mass,
orient=tk.HORIZONTAL, length=200, command=self.on_parameter_change)
mass_scale.pack(fill=tk.X, pady=5)
self.mass_label = ttk.Label(control_frame, text=f"{self.mass.get():.1f} kg")
self.mass_label.pack(anchor=tk.W)
# 角度设置
ttk.Label(control_frame, text="斜面角度 (°):").pack(anchor=tk.W, pady=(20, 0))
angle_scale = ttk.Scale(control_frame, from_=0, to=60, variable=self.angle,
orient=tk.HORIZONTAL, length=200, command=self.on_parameter_change)
angle_scale.pack(fill=tk.X, pady=5)
self.angle_label = ttk.Label(control_frame, text=f"{self.angle.get():.1f}°")
self.angle_label.pack(anchor=tk.W)
# 摩擦力设置
friction_frame = ttk.Frame(control_frame)
friction_frame.pack(fill=tk.X, pady=(20, 0))
friction_check = ttk.Checkbutton(friction_frame, text="考虑摩擦力",
variable=self.has_friction, command=self.on_parameter_change)
friction_check.pack(anchor=tk.W)
ttk.Label(control_frame, text="摩擦系数:").pack(anchor=tk.W, pady=(10, 0))
friction_scale = ttk.Scale(control_frame, from_=0, to=1, variable=self.friction_coeff,
orient=tk.HORIZONTAL, length=200, command=self.on_parameter_change)
friction_scale.pack(fill=tk.X, pady=5)
self.friction_label = ttk.Label(control_frame, text=f"{self.friction_coeff.get():.2f}")
self.friction_label.pack(anchor=tk.W)
# 分析结果显示
result_frame = ttk.LabelFrame(control_frame, text="受力分析结果", padding=10)
result_frame.pack(fill=tk.BOTH, expand=True, pady=(20, 0))
self.result_text = tk.Text(result_frame, height=15, width=35, font=("宋体", 10))
scrollbar = ttk.Scrollbar(result_frame, orient=tk.VERTICAL, command=self.result_text.yview)
self.result_text.configure(yscrollcommand=scrollbar.set)
self.result_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 右侧图形显示
plot_frame = ttk.Frame(main_frame)
plot_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
# 创建matplotlib图形
self.fig, (self.ax1, self.ax2) = plt.subplots(2, 1, figsize=(8, 10))
self.canvas = FigureCanvasTkAgg(self.fig, plot_frame)
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文字体
plt.rcParams['axes.unicode_minus'] = False
def on_parameter_change(self, event=None):
# 更新标签显示
self.mass_label.config(text=f"{self.mass.get():.1f} kg")
self.angle_label.config(text=f"{self.angle.get():.1f}°")
self.friction_label.config(text=f"{self.friction_coeff.get():.2f}")
# 更新分析
self.update_analysis()
def calculate_forces(self):
"""计算各种力的大小"""
m = self.mass.get()
theta = math.radians(self.angle.get())
mu = self.friction_coeff.get()
# 重力及其分量
G = m * self.g
G_parallel = G * math.sin(theta) # 沿斜面向下
G_perpendicular = G * math.cos(theta) # 垂直斜面向下
# 支持力
N = G_perpendicular
# 摩擦力
if self.has_friction.get():
f_max = mu * N # 最大静摩擦力
if G_parallel <= f_max:
# 静止状态,静摩擦力等于重力沿斜面分量
f = G_parallel
state = "静止"
a = 0
else:
# 滑动状态,动摩擦力
f = mu * N
state = "滑动"
a = (G_parallel - f) / m
else:
f = 0
state = "滑动"
a = G_parallel / m
return {
'G': G, 'G_parallel': G_parallel, 'G_perpendicular': G_perpendicular,
'N': N, 'f': f, 'state': state, 'acceleration': a
}
def update_analysis(self):
"""更新受力分析"""
forces = self.calculate_forces()
# 更新文本结果
self.update_result_text(forces)
# 更新图形
self.update_plots(forces)
def update_result_text(self, forces):
"""更新文本分析结果"""
self.result_text.delete(1.0, tk.END)
text = f"""受力分析结果:
物理量:
• 质量:{self.mass.get():.1f} kg
• 斜面角度:{self.angle.get():.1f}°
• 摩擦系数:{self.friction_coeff.get():.2f}
力的计算:
• 重力 G = mg = {forces['G']:.2f} N
• 重力沿斜面分量:
G∥ = G·sinθ = {forces['G_parallel']:.2f} N
• 重力垂直斜面分量:
G⊥ = G·cosθ = {forces['G_perpendicular']:.2f} N
• 支持力:
N = G⊥ = {forces['G_perpendicular']:.2f} N
"""
if self.has_friction.get():
text += f"""• 摩擦力:
f = {forces['f']:.2f} N
最大静摩擦力 = μN = {self.friction_coeff.get() * forces['N']:.2f} N
"""
text += f"""运动状态:
• 物体状态:{forces['state']}
• 加速度:{forces['acceleration']:.2f} m/s²
力的平衡分析:
"""
if forces['state'] == "静止":
text += "• 沿斜面方向:G∥ = f (静摩擦力)\n"
text += "• 垂直斜面方向:N = G⊥\n"
text += "• 物体处于平衡状态"
else:
if self.has_friction.get():
text += f"• 沿斜面方向:合力 = G∥ - f = {forces['G_parallel'] - forces['f']:.2f} N\n"
else:
text += f"• 沿斜面方向:合力 = G∥ = {forces['G_parallel']:.2f} N\n"
text += "• 垂直斜面方向:N = G⊥\n"
text += f"• 物体沿斜面向下加速运动"
self.result_text.insert(1.0, text)
def update_plots(self, forces):
"""更新图形显示"""
self.ax1.clear()
self.ax2.clear()
# 第一个图:斜面和力的示意图
self.draw_inclined_plane(self.ax1, forces)
# 第二个图:力的分解图
self.draw_force_decomposition(self.ax2, forces)
# 调整布局避免重叠
self.fig.tight_layout(pad=2.0)
self.canvas.draw()
def draw_inclined_plane(self, ax, forces):
"""绘制斜面受力示意图"""
theta = math.radians(self.angle.get())
theta_deg = self.angle.get()
# 斜面起点和终点
incline_start_x, incline_start_y = 1, 1
incline_length = 3
incline_end_x = incline_start_x + incline_length * math.cos(theta)
incline_end_y = incline_start_y + incline_length * math.sin(theta)
# 绘制斜面(改为细线)
ax.plot([incline_start_x, incline_end_x], [incline_start_y, incline_end_y],
'k-', linewidth=2, label='斜面') # 改为linewidth=2,比之前的4细了一半
# 绘制水平地面
ax.plot([0, 5], [1, 1], 'k-', linewidth=2, alpha=0.7)
# 物体位置(在斜面中点)
t = 0.5 # 物体在斜面中点
obj_center_x = incline_start_x + t * incline_length * math.cos(theta)
obj_center_y = incline_start_y + t * incline_length * math.sin(theta)
# 绘制物体(正方形,底边平行于斜面且紧贴斜面)
obj_size = 0.2
# 计算正方形的四个顶点,使底边平行于斜面
# 正方形底边中心点就在斜面上
bottom_center_x = obj_center_x
bottom_center_y = obj_center_y
# 沿斜面方向的单位向量
slope_unit_x = math.cos(theta)
slope_unit_y = math.sin(theta)
# 垂直斜面向上的单位向量
normal_unit_x = -math.sin(theta)
normal_unit_y = math.cos(theta)
# 正方形的四个顶点
half_size = obj_size / 2
# 底边两个顶点
bottom_left_x = bottom_center_x - half_size * slope_unit_x
bottom_left_y = bottom_center_y - half_size * slope_unit_y
bottom_right_x = bottom_center_x + half_size * slope_unit_x
bottom_right_y = bottom_center_y + half_size * slope_unit_y
# 顶边两个顶点
top_left_x = bottom_left_x + obj_size * normal_unit_x
top_left_y = bottom_left_y + obj_size * normal_unit_y
top_right_x = bottom_right_x + obj_size * normal_unit_x
top_right_y = bottom_right_y + obj_size * normal_unit_y
## # 根据运动状态选择物体颜色
## if forces['state'] == "静止":
## obj_color = 'red'
## obj_alpha = 0.7
## else:
## obj_color = 'orange' # 滑动时变成橙色
## obj_alpha = 0.9
obj_color = 'red'
obj_alpha = 0.7
# 绘制正方形
square_x = [bottom_left_x, bottom_right_x, top_right_x, top_left_x, bottom_left_x]
square_y = [bottom_left_y, bottom_right_y, top_right_y, top_left_y, bottom_left_y]
ax.plot(square_x, square_y, color=obj_color, linewidth=2)
ax.fill(square_x, square_y, color=obj_color, alpha=obj_alpha)
# 力的作用点是物体的重心(正方形中心)
force_point_x = bottom_center_x + (obj_size / 2) * normal_unit_x
force_point_y = bottom_center_y + (obj_size / 2) * normal_unit_y
# 绘制状态标识文字,角度与斜面一致
if forces['state'] == "静止":
# 静止状态:文字固定在物体上方
status_text = "静止"
status_color = 'green'
text_x = force_point_x + 0.4 * normal_unit_x
text_y = force_point_y + 0.4 * normal_unit_y
ax.text(text_x, text_y, status_text,
fontsize=12, color=status_color, fontweight='bold',
ha='center', va='center',
rotation=theta_deg, # 文字角度与斜面一致
bbox=dict(boxstyle="round,pad=0.3", facecolor='lightgreen', alpha=0.8))
else:
# 滑动状态:文字单向向后飘动
status_text = "滑动"
status_color = 'red'
# 飘动参数
drift_amplitude = 0.8 # 飘动距离
cycle_duration = 2 * math.pi # 一个完整周期
# 计算当前周期内的进度 (0 到 1)
cycle_progress = (self.animation_time % cycle_duration) / cycle_duration
# 生成多个错开的文字,营造连续效果
for i, phase_offset in enumerate([0, 0.3, 0.6]):
# 计算当前文字的进度
text_progress = (cycle_progress + phase_offset) % 1.0
# 只在进度0-0.7之间显示文字,0.7-1.0之间隐藏(用于重置)
if text_progress <= 0.7:
# 计算文字位置(线性向后移动)
drift_offset = text_progress * drift_amplitude
# 基础位置:物体上方
base_text_x = force_point_x + 0.4 * normal_unit_x
base_text_y = force_point_y + 0.4 * normal_unit_y
# 向后飘动(沿着斜面向上的方向)
text_x = base_text_x + drift_offset * slope_unit_x
text_y = base_text_y + drift_offset * slope_unit_y
# 透明度:开始清晰,向后逐渐变淡
text_alpha = max(0.2, 1.0 - text_progress * 1.2)
# 字体大小:向后逐渐变小
font_size = max(8, 12 - int(text_progress * 6))
ax.text(text_x, text_y, status_text,
fontsize=font_size, color=status_color, fontweight='bold',
ha='center', va='center',
rotation=theta_deg, # 文字角度与斜面一致
alpha=text_alpha,
bbox=dict(boxstyle="round,pad=0.2", facecolor='lightyellow',
alpha=text_alpha*0.4, edgecolor=status_color))
# 力的缩放因子
scale = 0.02
arrow_width = 0.08
arrow_length = 0.1
# 绘制重力(竖直向下)
G_length = forces['G'] * scale
ax.arrow(force_point_x, force_point_y, 0, -G_length,
head_width=arrow_width, head_length=arrow_length,
fc='blue', ec='blue', linewidth=2)
ax.text(force_point_x + 0.2, force_point_y - G_length/2, 'G',
fontsize=12, color='blue', fontweight='bold')
# 绘制支持力(垂直斜面向上)
normal_x = -math.sin(theta) * forces['N'] * scale
normal_y = math.cos(theta) * forces['N'] * scale
ax.arrow(force_point_x, force_point_y, normal_x, normal_y,
head_width=arrow_width, head_length=arrow_length,
fc='green', ec='green', linewidth=2)
ax.text(force_point_x + normal_x - 0.3, force_point_y + normal_y + 0.1, 'N',
fontsize=12, color='green', fontweight='bold')
# 绘制摩擦力(如果有)
if self.has_friction.get() and forces['f'] > 0:
friction_x = math.cos(theta) * forces['f'] * scale
friction_y = math.sin(theta) * forces['f'] * scale
ax.arrow(force_point_x, force_point_y, friction_x, friction_y,
head_width=arrow_width, head_length=arrow_length,
fc='orange', ec='orange', linewidth=2)
ax.text(force_point_x + friction_x + 0.1, force_point_y + friction_y + 0.1, 'f',
fontsize=12, color='orange', fontweight='bold')
# 绘制角度标记
arc_radius = 0.4
arc_angles = np.linspace(0, theta, 30)
arc_x = incline_start_x + arc_radius * np.cos(arc_angles)
arc_y = incline_start_y + arc_radius * np.sin(arc_angles)
ax.plot(arc_x, arc_y, 'k-', linewidth=1.5)
# 角度标注
text_x = incline_start_x + arc_radius + 0.2
text_y = incline_start_y + 0.1
ax.text(text_x, text_y, f'θ={self.angle.get():.1f}°',
fontsize=11, fontweight='bold')
# 设置图形范围,确保完整显示
ax.set_xlim(0, 6)
ax.set_ylim(0.5, 4)
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)
ax.set_title('斜面受力示意图', fontsize=14, fontweight='bold')
ax.set_xticks([]) # 隐藏x轴刻度
ax.set_yticks([]) # 隐藏y轴刻度
ax.set_xlabel('')
ax.set_ylabel('')
def draw_force_decomposition(self, ax, forces):
"""绘制重力分解图"""
G_scale = 0.08
origin_x, origin_y = 0, 0
# 获取斜面角度
theta_deg = self.angle.get()
theta_rad = math.radians(theta_deg)
# 重力(竖直向下,蓝色)
G_length = forces['G'] * G_scale
ax.arrow(origin_x, origin_y, 0, -G_length,
head_width=0.15, head_length=0.15, fc='blue', ec='blue', linewidth=3)
ax.text(-0.3, -G_length/2, f'G={forces["G"]:.1f}N',
fontsize=11, color='blue', fontweight='bold')
# 平行于斜面分量(紫色)
parallel_length = forces['G_parallel'] * G_scale
parallel_x = -parallel_length * math.cos(-theta_rad) # 翻转x分量
parallel_y = parallel_length * math.sin(-theta_rad)
ax.arrow(origin_x, origin_y, parallel_x, parallel_y,
head_width=0.12, head_length=0.12, fc='purple', ec='purple', linewidth=2)
ax.text(parallel_x - 0.2, parallel_y - 0.3, f'G∥={forces["G_parallel"]:.1f}N',
fontsize=10, color='purple', fontweight='bold')
# 垂直于斜面分量(红色)
perp_length = forces['G_perpendicular'] * G_scale
perp_x = perp_length * math.sin(theta_rad) # 翻转x分量
perp_y = -perp_length * math.cos(theta_rad)
ax.arrow(origin_x, origin_y, perp_x, perp_y,
head_width=0.12, head_length=0.12, fc='red', ec='red', linewidth=2)
ax.text(perp_x + 0.8, perp_y + 0.1, f'G⊥={forces["G_perpendicular"]:.1f}N',
fontsize=10, color='red', fontweight='bold')
# 绘制虚线构成的平行四边形
ax.plot([parallel_x, parallel_x + perp_x], [parallel_y, parallel_y + perp_y],
'k--', alpha=0.6, linewidth=1)
ax.plot([perp_x, parallel_x + perp_x], [perp_y, parallel_y + perp_y],
'k--', alpha=0.6, linewidth=1)
# 重新绘制角度弧线
arc_radius = 0.6
# 重力方向:-π/2(向下)
gravity_angle = -math.pi/2
# 垂直分量方向
perp_angle = math.atan2(perp_y, perp_x)
# 角度弧线从垂直分量到重力
start_angle = perp_angle
end_angle = gravity_angle
# 确保角度方向正确
if abs(start_angle - end_angle) > math.pi:
if start_angle > end_angle:
end_angle += 2 * math.pi
else:
start_angle += 2 * math.pi
arc_angles = np.linspace(start_angle, end_angle, 30)
arc_x = arc_radius * np.cos(arc_angles)
arc_y = arc_radius * np.sin(arc_angles)
ax.plot(arc_x, arc_y, 'k-', linewidth=2)
# 角度标注
mid_angle = (start_angle + end_angle) / 2
text_radius = arc_radius + 0.2
angle_text_x = text_radius * math.cos(mid_angle)
angle_text_y = text_radius * math.sin(mid_angle)
ax.text(angle_text_x, angle_text_y, f'θ={theta_deg:.1f}°',
fontsize=11, fontweight='bold', ha='center', va='center')
# 设置显示 - 调整x轴范围
ax.axhline(y=0, color='k', linestyle='-', alpha=0.3, linewidth=0.5)
ax.axvline(x=0, color='k', linestyle='-', alpha=0.3, linewidth=0.5)
max_val = G_length + 0.8
ax.set_xlim(-max_val*0.8, max_val*0.6) # 翻转x轴范围
ax.set_ylim(-max_val, max_val*0.3)
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)
ax.set_title('重力分解图', fontsize=14, fontweight='bold')
#ax.set_xlabel('水平方向')
#ax.set_ylabel('竖直方向')
ax.set_xticks([]) # 隐藏x轴刻度
ax.set_yticks([]) # 隐藏y轴刻度
ax.set_xlabel('')
ax.set_ylabel('')
if __name__ == "__main__":
root = tk.Tk()
app = InclinedPlaneSimulator(root)
root.mainloop()