python中学物理实验模拟:斜面受力分析

发布于:2025-06-27 ⋅ 阅读:(16) ⋅ 点赞:(0)

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()


网站公告

今日签到

点亮在社区的每一天
去签到