Visualize a Ghost Robot
The MoveIt Pro 3D Visualizer renders moveit_msgs/DisplayRobotState messages published on the /display_robot_state topic as a translucent ghost robot beside the live one. Use it to show where the arm would go for a single suggested configuration — a candidate pick pose, a placement IK solution — without planning a trajectory for it.
This ghost is separate from the trajectory preview that the MTC debugger's Show ghost robot control toggles: that one animates a planned trajectory, while this one holds a single published configuration.
Inside a Behavior Tree, publish with the PublishGhostRobotState Behavior. Its robot_state port takes a moveit_msgs/RobotState from any Behavior that produces one — the solution output of ComputeInverseKinematics, the output of CreateRobotState, or a waypoint retrieved with RetrieveWaypoint:
<PublishGhostRobotState robot_state="{ik_solution}"/>
Set hide to clear it again:
<PublishGhostRobotState hide="true"/>
Any ROS 2 node can publish to the topic instead:
# Minimal example: show the arm at a candidate configuration
publisher = node.create_publisher(
DisplayRobotState,
"/display_robot_state",
QoSProfile(depth=1, durability=DurabilityPolicy.TRANSIENT_LOCAL),
)
message = DisplayRobotState()
message.state.joint_state.name = ["shoulder_joint", "elbow_joint"]
message.state.joint_state.position = [0.25, -1.5]
publisher.publish(message)
Publish with transient local durability, as PublishGhostRobotState does. A volatile publisher works while the Desktop App is already connected, but the ghost disappears on reload and never reaches a view opened afterwards.
A late subscriber receives a retained sample from every transient local writer on the topic, in no defined order, so keep one publisher at a time. Running PublishGhostRobotState alongside your own publisher lets a "show" and a "hide" arrive either way round.
In the 3D Visualizer's View menu, under Display, toggle Robot - Display State.
The ghost persists until a message with hide set arrives, so an Objective that shows one should clear it when the operator no longer needs it. Restarting the Runtime and reconnecting to it also clear it.
ClearSnapshot is the exception worth knowing: it drops every publisher the Runtime holds for the Desktop App, this topic included, so the retained message is gone and a Desktop App that reloads afterwards sees no ghost. It does not clear a ghost already on screen — only hide does that.
How the State Is Applied
The message is applied on top of the robot's live pose, not the URDF's zero pose. A state naming only one planning group's joints therefore leaves the rest of the robot where it actually is, instead of collapsing the base, torso, and gripper to zero. Each message is applied to the live pose afresh rather than accumulating on the previous one, so a message describes the whole ghost rather than a correction to the last.
Two cases have no live pose to overlay onto, and there the ghost does accumulate across messages: before the first /joint_states message arrives, and for any joint the message names that /joint_states never publishes.
The overlay is recomputed every time the ghost is drawn, including after a reload, so a partial state renders against wherever the robot is at that moment. If the ghost must still mean the same thing after the Objective ends, publish a complete state — the Behaviors listed above all do.
Other DisplayRobotState consumers differ here. The legacy RViz plugin poses unnamed joints from the URDF's default values rather than the live robot, so the same partial message looks different there.
A state naming no joints at all is rejected rather than drawn, since it would render a ghost exactly coincident with the live robot. Use hide to clear the ghost instead.
Joint limits are not enforced. A state outside a joint's limits renders as given, which is what makes the layer useful for inspecting a configuration a planner would reject.
Specification Coverage
| Field | Status | Notes |
|---|---|---|
state.joint_state | ✅ Implemented | Names and positions pose the ghost. Velocities and efforts are ignored. |
state.multi_dof_joint_state | ✅ Implemented | Applied to floating and planar joints, as for the live robot. |
hide | ✅ Implemented | Removes the ghost from the scene. The message needs no state when it is set. |
state.is_diff | Not implemented | The state is always applied as an overlay on the live pose, which is what MoveIt does for joint values regardless of this flag. |
state.attached_collision_objects | Not implemented | Objects attached to the ghost are not drawn. Publish them as visualization_msgs/MarkerArray instead — see Visualize 3D Markers. |
highlight_links | Not implemented | The whole ghost uses one translucent material; per-link colors are ignored. |