Source code for qrobot_simulator.grasping_robot.rendering.liveview

"""Persistent Matplotlib view for the live grasping encounter."""

from pathlib import Path

import matplotlib.pyplot as plt

from ..robots import GraspingSignals
from ..world import GraspingArena, GraspingWorld
from .config import RENDERING_CONFIG
from .utils import (
    create_robot_graphics,
    create_sensor_graphics,
    create_text_overlays,
    draw_checkerboard,
    update_robot_graphics,
    update_sensor_graphics,
    update_text_overlays,
)


[docs] class GraspingWorldLiveView: """Display a persistent view of the gripper encounter and qBrain state.""" def __init__(self, arena: GraspingArena, interactive: bool = True) -> None: """Create the figure and its static graphics. :param arena: Arena whose dimensions define the axes. :param interactive: Enable GUI updates when true. """ self._interactive = interactive if interactive: plt.ion() self.figure, self.axis = plt.subplots( num=RENDERING_CONFIG.figure_name, figsize=RENDERING_CONFIG.figure_size, ) self._setup(arena) if interactive: plt.show(block=False) # Public view lifecycle @property def is_open(self) -> bool: """Return whether the Matplotlib figure still exists. :returns: True while the view's figure remains open. """ return plt.fignum_exists(self.figure.number)
[docs] def update( self, world: GraspingWorld, signals: GraspingSignals | None = None, phase: str = "RUNNING", ) -> None: """Refresh physical geometry and observable qBrain values. :param world: Current simulation state to display. :param signals: Latest observable qBrain outputs, when available. :param phase: Short label describing the simulation phase. """ update_robot_graphics(self._robot_graphics, world) update_sensor_graphics(self._sensor_graphics, world) update_text_overlays(self._text_overlays, world, signals, phase) self._flush_canvas()
[docs] def save(self, path: Path) -> Path: """Save the current frame to an image. Parent directories are created when needed. :param path: Destination image path. :returns: Destination path after the figure is saved. """ path.parent.mkdir(parents=True, exist_ok=True) self.figure.savefig(path, dpi=RENDERING_CONFIG.save_dpi, bbox_inches="tight") return path
[docs] def close(self) -> None: """Close the Matplotlib figure owned by this view.""" plt.close(self.figure)
# Internal figure management def _setup(self, arena: GraspingArena) -> None: """Draw static content and create the dynamic graphics.""" draw_checkerboard(self.axis, arena.width, arena.height, arena.cell_size) self.axis.set( xlim=(0, arena.width), ylim=(0, arena.height), aspect="equal", title=RENDERING_CONFIG.axis_title, xlabel=RENDERING_CONFIG.axis_x_label, ylabel=RENDERING_CONFIG.axis_y_label, ) self._robot_graphics = create_robot_graphics(self.axis) self._sensor_graphics = create_sensor_graphics(self.axis) self._text_overlays = create_text_overlays(self.axis) def _flush_canvas(self) -> None: """Draw one interactive frame; do nothing for headless views.""" if self._interactive: self.figure.canvas.draw_idle() self.figure.canvas.flush_events() plt.pause(RENDERING_CONFIG.canvas_pause)