Source code for state_space_response_viz.renderers

"""TrajectoryRenderer: put a sampled RobotFrame on a screen.

The playback engine operates on frames and delegates to whichever
renderers are active — how a frame becomes pixels (JointState + TF for
RViz, setJointValue for the web viewer, an encoder for video export) is an
implementation detail hidden behind ``render(frame)``. Renderers hold
transport handles created in ``setup``; they are stateless between frames.
"""

from typing import Optional

from state_space_control.trajectory import RobotFrame, RobotTrajectory


[docs] class TrajectoryRenderer: """Interface: ``setup`` once before playback, ``render`` per frame."""
[docs] def setup(self, traj: RobotTrajectory) -> None: pass
[docs] def render(self, frame: RobotFrame) -> None: raise NotImplementedError
[docs] class RVizRenderer(TrajectoryRenderer): """Renders frames into RViz by publishing sensor_msgs/JointState (and a world→base TF when the trajectory carries a base pose). Those transport details stay invisible above this class. Mimic joints are intentionally absent from trajectories — robot_state_publisher derives them from the URDF. """ def __init__(self, node, *, joint_states_topic: str = 'joint_states', base_frame: str = 'base_link', world_frame: str = 'world'): self._node = node self._topic = joint_states_topic self._base_frame = base_frame self._world_frame = world_frame self._pub = None self._tf = None self._names = []
[docs] def setup(self, traj: RobotTrajectory) -> None: from sensor_msgs.msg import JointState self._names = list(traj.joint_names) self._pub = self._node.create_publisher(JointState, self._topic, 10) if traj.base_pose is not None: from tf2_ros import TransformBroadcaster self._tf = TransformBroadcaster(self._node)
[docs] def render(self, frame: RobotFrame) -> None: from sensor_msgs.msg import JointState msg = JointState() msg.header.stamp = self._node.get_clock().now().to_msg() # Every joint in the trajectory, actuated or not: omitting the # unactuated ones would leave their TF stale in RViz. msg.name = self._names msg.position = [frame.joint_positions[n] for n in self._names] msg.velocity = [frame.joint_velocities[n] for n in self._names] self._pub.publish(msg) if self._tf is not None and frame.base_pose is not None: self._tf.sendTransform(self._base_transform(frame, msg.header.stamp))
def _base_transform(self, frame: RobotFrame, stamp): from geometry_msgs.msg import TransformStamped tfm = TransformStamped() tfm.header.stamp = stamp tfm.header.frame_id = self._world_frame tfm.child_frame_id = self._base_frame x, y, z, qx, qy, qz, qw = (float(v) for v in frame.base_pose) tfm.transform.translation.x = x tfm.transform.translation.y = y tfm.transform.translation.z = z tfm.transform.rotation.x = qx tfm.transform.rotation.y = qy tfm.transform.rotation.z = qz tfm.transform.rotation.w = qw return tfm