While working on a project that involved spray painting using a robotic manipulator, I ran into a gap in Gazebo. There is simply no built-in way to simulate a spray painting application. So, I built one ![]()
Introducing gz_sim_spray_painting_plugin: A Gazebo Harmonic system plugin that lets you attach a spray nozzle to your robot and visualise paint coverage directly in your simulation.
Repository: GitHub - topguns837/gz_sim_spray_painting_plugin

How it works
The plugin implements two independent pipelines that run every simulation step:
-
Ray-cast paint patches: A cone of rays is fired from the nozzle every simulation step. Wherever a ray hits a surface, a coloured disc is placed flush against it. This works on any geometry: simple boxes and cylinders, complex meshes like the Prius car model in the demo, or anything in between.
-
Particle spray cloud (optional): A particle emitter at the nozzle gives you the visual mist you would expect from a spray gun. It can be turned off with a single SDF flag (
<enable_particle_emitter>false</enable_particle_emitter>) if you do not need the visual effect. That is handy for headless simulations, training ML models on spray-path data, or just keeping the simulation fast.
Triggering spray from ROS 2 the spray ON/OFF signal is a standard gz.msgs.Boolean Gazebo transport topic (default /spray_paint/trigger, configurable via SDF). It can be bridged to a ROS 2 topic with a single ros_gz_bridge node, no plugin changes needed:
Demo worlds included
-
demo_car: UR5e arm + MoveIt 2 + Prius model full autonomous spray demo -
demo_cube: Standalone nozzle, fast to load good for verifying the plugin -
demo_cylinder: Cylindrical target -
demo_ellipsoid: Ellipsoidal target
No local ROS 2 or Gazebo install needed, the whole stack runs in Docker. A single startScript.sh entrypoint handles Docker build, code build, and simulation launch via an interactive menu.
Integrating into your own robot
Drop the plugin block into any URDF or SDF link:
<plugin filename="libSprayPaintPlugin.so"
name="gz::sim::systems::SprayPaintPlugin">
<nozzle_link>my_nozzle_link</nozzle_link>
<cone_half_angle_deg>20</cone_half_angle_deg>
<cone_max_range>1.5</cone_max_range>
<spray_color>0.1 0.5 1.0 1.0</spray_color>
<spray_topic>/my_robot/spray</spray_topic>
</plugin>
All parameters are optional with sensible defaults (15° half-angle cone, 1 m range, red paint). Full parameter table in the README.
Built with: ROS 2 Humble · Gazebo Harmonic · C++17
Looking for feedback & contributors
I’d love to hear from anyone working on:
-
Industrial robotics painting, coating, or surface-coverage tasks
-
Gazebo plugin development feedback on the ECS/raycasting approach
-
Anyone who hit the same gap and worked around it differently
Bugs and feature requests are tracked via GitHub Issues. Happy to chat with anyone interested in contributing!