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

Matplotlib绘图首次显示后调用更新方法无响应的解决咨询

问题描述

我是Matplotlib新手,在开发二自由度机械臂绘图功能时遇到如下问题:首次执行代码可正常展示机械臂绘图,但调用show_plot()后,等待2秒再调用draw_mechanism_with_inverse_kinematic方法更新机械臂位置,绘图无法刷新。我已尝试不同的draw()实现方式及.ion()交互模式,但仍未定位到问题,请问如何正确实现绘图的更新?

相关代码

main.py

from two_dof_plotter import MechanismPlotter
import time

# 创建MechanismPlotter实例
two_dof_robot_plot = MechanismPlotter(0.25, 0.3, 0.1, 0, 0)
two_dof_robot_plot.start_plot()
two_dof_robot_plot.draw_required_work_envelope(0, 0)
two_dof_robot_plot.draw_mechanism_with_inverse_kinematic(0.5, -0.0375)
two_dof_robot_plot.show_plot()

time.sleep(2)

two_dof_robot_plot.draw_mechanism_with_inverse_kinematic(0.2, -0.0375)

two_dof_plotter.py

import matplotlib.pyplot as plt
import numpy as np

class MechanismPlotter:
    def __init__(self, upper_arm_length, lower_arm_length, lever_arm_length, gripper_x_offset, gripper_y_offset):
        self.lower_arm_length = lower_arm_length
        self.upper_arm_length = upper_arm_length
        self.lever_arm_length = lever_arm_length
        self.gripper_x_offset = gripper_x_offset
        self.gripper_y_offset = gripper_y_offset
        self.ploted_lines = []
        self.fig, self.ax = plt.subplots()

    def start_plot(self):
        self.ax.set_aspect('equal')
        self.ax.set_xlim(-0.6, 0.6)
        self.ax.set_ylim(-0.6,0.6)
        self.ax.set_xlabel('X')
        self.ax.set_ylabel('Y')
        plt.grid(True)

    def draw_mechanism_with_forward_kinematic(self, upper_arm_angle, lower_arm_angle):
        # 计算上下臂端点坐标
        upper_arm_x = self.upper_arm_length * np.cos(upper_arm_angle)
        upper_arm_y = self.upper_arm_length * np.sin(upper_arm_angle)
        lower_arm_x = upper_arm_x + self.lower_arm_length * np.cos(lower_arm_angle)
        lower_arm_y = upper_arm_y + self.lower_arm_length * np.sin(lower_arm_angle)
        lever_arm_x = -(self.lever_arm_length * np.cos(lower_arm_angle))
        lever_arm_y = -(self.lever_arm_length * np.sin(lower_arm_angle))

        # 绘制线条
        if len(self.ploted_lines) > 0:
            self.ploted_lines[0].set_data([0, upper_arm_x], [0, upper_arm_y])
            self.ploted_lines[1].set_data([upper_arm_x, lower_arm_x], [upper_arm_y, lower_arm_y])
            self.ploted_lines[2].set_data([0, lever_arm_x], [0, lever_arm_y])
            self.ploted_lines[3].set_data([upper_arm_x, upper_arm_x + lever_arm_x], [upper_arm_y, upper_arm_y + lever_arm_y])
            self.ploted_lines[4].set_data([lever_arm_x, upper_arm_x + lever_arm_x], [lever_arm_y, upper_arm_y + lever_arm_y])
            # 更新绘图
            self.fig.canvas.draw()
        else:
            # 首次绘制线条
            line1, = self.ax.plot([0, upper_arm_x], [0, upper_arm_y], 'b-', linewidth=2)
            line2, = self.ax.plot([upper_arm_x, lower_arm_x], [upper_arm_y, lower_arm_y], 'r-', linewidth=2)
            line3, = self.ax.plot([0, lever_arm_x], [0, lever_arm_y], 'g-', linewidth=1)
            line4, = self.ax.plot([upper_arm_x, upper_arm_x + lever_arm_x], [upper_arm_y, upper_arm_y + lever_arm_y], 'g-', linewidth=1)
            line5, = self.ax.plot([lever_arm_x, upper_arm_x + lever_arm_x], [lever_arm_y, upper_arm_y + lever_arm_y], 'g-', linewidth=1)

            # 将线条加入列表以便后续更新
            self.ploted_lines.extend([line1, line2, line3, line4, line5])

        # 计算上臂与小臂机构的距离
        distance = np.sin(np.pi-(-lower_arm_angle)-upper_arm_angle) * self.lever_arm_length        

    def draw_mechanism_with_inverse_kinematic(self, x, y):
        # 计算逆运动学角度
        x = x - self.gripper_x_offset
        y = y - self.gripper_y_offset
        c = np.sqrt(x**2 + y**2)
        upper_arm_angle = np.arccos((self.upper_arm_length**2 + c**2 - self.lower_arm_length**2) / (2 * self.upper_arm_length * c)) + np.arctan(y/x)
        lower_arm_angle = upper_arm_angle + np.arccos((self.lower_arm_length**2 + self.upper_arm_length**2 - c**2) / (2 * self.lower_arm_length * self.upper_arm_length)) - np.pi

        # 绘制机构
        ploted_lines = self.draw_mechanism_with_forward_kinematic(upper_arm_angle, lower_arm_angle)
    
    def remove_ploted_mechanism(self, ploted_lines):
        for line in ploted_lines:
            line.remove()
        self.ax.clear()
        self.fig.canvas.draw()

    def draw_required_work_envelope(self, x_por_offset, y_por_offset):
        # 定义工作包络点坐标
        x = [0.16+x_por_offset, 0.16+x_por_offset, 0.5+x_por_offset, 0.32+x_por_offset, 0.16+x_por_offset,]
        y = [0.06-y_por_offset, -0.0375-y_por_offset, -0.0375-y_por_offset, 0.06-y_por_offset, 0.06-y_por_offset,]

        # 绘制工作包络
        self.ax.fill_between(x, y, color='skyblue', alpha=0.5)

    def show_plot(self):
        plt.show()
解决方案

问题根源在于plt.show()默认会阻塞主线程,且处于非交互模式,导致后续的更新代码要么无法执行,要么执行后无法刷新界面。按以下步骤修改即可解决:

1. 开启交互模式

在MechanismPlotter的__init__方法中添加交互模式启用代码,确保绘图界面可以实时响应更新:

plt.ion()  # 启用交互模式

2. 修改show_plot()方法

将原本阻塞的plt.show()改为非阻塞的显示逻辑,用plt.pause()确保界面正常渲染:

def show_plot(self):
    plt.show(block=False)  # 非阻塞显示
    plt.pause(0.01)  # 暂停一小段时间让界面渲染完成

3. 完善绘图刷新逻辑

在draw_mechanism_with_forward_kinematic方法的更新代码中,添加flush_events()来处理界面事件,保证更新及时显示:

if len(self.ploted_lines) > 0:
    # ... 原有更新线条数据的代码 ...
    self.fig.canvas.draw()
    self.fig.canvas.flush_events()  # 新增:处理界面事件,刷新显示

4. 调整main.py的执行逻辑

因为plt.show(block=False)不再阻塞,所以需要在最后添加阻塞逻辑,防止程序直接退出:

# ... 原有代码 ...
time.sleep(2)
two_dof_robot_plot.draw_mechanism_with_inverse_kinematic(0.2, -0.0375)

# 新增:保持界面显示,直到手动关闭
plt.ioff()
plt.show()

修改后的完整代码

two_dof_plotter.py(关键修改部分)

import matplotlib.pyplot as plt
import numpy as np

class MechanismPlotter:
    def __init__(self, upper_arm_length, lower_arm_length, lever_arm_length, gripper_x_offset, gripper_y_offset):
        self.lower_arm_length = lower_arm_length
        self.upper_arm_length = upper_arm_length
        self.lever_arm_length = lever_arm_length
        self.gripper_x_offset = gripper_x_offset
        self.gripper_y_offset = gripper_y_offset
        self.ploted_lines = []
        plt.ion()  # 启用交互模式
        self.fig, self.ax = plt.subplots()

    # ... 其他原有方法 ...

    def draw_mechanism_with_forward_kinematic(self, upper_arm_angle, lower_arm_angle):
        # ... 原有坐标计算代码 ...

        # 绘制线条
        if len(self.ploted_lines) > 0:
            self.ploted_lines[0].set_data([0, upper_arm_x], [0, upper_arm_y])
            self.ploted_lines[1].set_data([upper_arm_x, lower_arm_x], [upper_arm_y, lower_arm_y])
            self.ploted_lines[2].set_data([0, lever_arm_x], [0, lever_arm_y])
            self.ploted_lines[3].set_data([upper_arm_x, upper_arm_x + lever_arm_x], [upper_arm_y, upper_arm_y + lever_arm_y])
            self.ploted_lines[4].set_data([lever_arm_x, upper_arm_x + lever_arm_x], [lever_arm_y, upper_arm_y + lever_arm_y])
            # 更新绘图
            self.fig.canvas.draw()
            self.fig.canvas.flush_events()  # 刷新界面事件
        else:
            # ... 原有首次绘图代码 ...

    # ... 其他原有方法 ...

    def show_plot(self):
        plt.show(block=False)
        plt.pause(0.01)

main.py(修改后)

from two_dof_plotter import MechanismPlotter
import time
import matplotlib.pyplot as plt

# 创建MechanismPlotter实例
two_dof_robot_plot = MechanismPlotter(0.25, 0.3, 0.1, 0, 0)
two_dof_robot_plot.start_plot()
two_dof_robot_plot.draw_required_work_envelope(0, 0)
two_dof_robot_plot.draw_mechanism_with_inverse_kinematic(0.5, -0.0375)
two_dof_robot_plot.show_plot()

time.sleep(2)

two_dof_robot_plot.draw_mechanism_with_inverse_kinematic(0.2, -0.0375)

# 保持界面显示,直到手动关闭
plt.ioff()
plt.show()

额外说明

  • plt.ion()开启交互模式后,Matplotlib不会阻塞主线程,后续代码可以正常执行
  • flush_events()确保界面事件被处理,让绘图更新及时显示
  • 最后调用plt.ioff()和plt.show()是为了在所有更新完成后,保持界面打开直到用户手动关闭,避免程序直接退出

内容的提问来源于stack exchange,提问作者Jonas ǀ HDJG

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 06:05:54