A ROS2 workspace for driving a Meca500 6-DOF robot arm (5 μm resolution) as a 3D printer, through MoveIt2.
This project bridges the Meca500 proprietary API to MoveIt2 through a custom ROS2 hardware interface, enabling real trajectory planning and execution on physical hardware. A print pipeline locates the bed (from a nozzle touch probe), sweeps the robot's reachable workspace, centers and clips sliced G-code onto the densest reachable region, groups the moves into batches by type, and runs each batch as one blended MoveIt2 Pilz motion sequence (LIN for straight/extruding moves, CIRC for arcs) — after a pre-planning pass over the whole print. Everything hardware- or print-specific lives in three YAML files (see Configuration).
All first-party packages are prefixed msr_ to keep them apart from vendor packages.
- msr_meca500_hardware — ros2_control hardware interface (
Meca500System) bridging the Meca500 TCP API to MoveIt2 - msr_meca500_robot — robot description (URDF/Xacro): the arm, the mounted Ender3 extruder with its
nozzletool frame, and the Ender3 chassis/bed as environment collision geometry - msr_meca500_moveit — MoveIt2 configuration and launch files
- msr_gcode — G-code handling: a Python preprocessing tool (centers a sliced print on the densest reachable region, drops moves outside the workspace as gaps, validates and repairs arc geometry) plus a C++ parser library used at execution time. Layer count is configurable (
-l/layers) — default all layers, or a smaller count for an evenly-spaced subset - msr_meca500_print_pipeline — the print application. Nodes:
gcode_print_executor— the executor: groups G-code into batches by move type, plans and runs each batch as one blended Pilz LIN/CIRC sequence, drives the Ender3 over serial (temps, extrusion, bed re-home), and recovers from IK failures (Z-hop for travels, midpoint bisection for extruding moves) without skipping a commanded pointreachability— sweeps an N×N grid over the bed, writes the reachable points to CSVplanningscene— hosts/table_service, publishes the bed pose as/table_markerbed_from_touches— fits the bed plane from nozzle touch-probe joint poses (or applies a flat default) and pushes it to/table_service
- msr_meca500_rl — experimental: RL for adaptive tool orientation / extrusion
This project builds pilz_industrial_motion_planner from source (from moveit/moveit2) instead of using the stock apt package, with one constant changed so its CIRC arc-fitting gate matches the Meca500's 5 μm resolution instead of the stock library's much coarser industrial-scale tolerance:
MAX_COLINEAR_NORM(the near-degenerate-triangle rejection incircleFromInterim,path_circle_generator.hpp) lowered from the stock1e-5to2.5e-11(5 μm × 5 μm), so genuinely tiny print-scale arcs stop getting rejected as "no plane" errorsgcode_print_executor's own flatness check (get_arc_center/CIRC path) mirrors that same2.5e-11threshold, so an arc is only demoted to a straight line when it's below what the robot can actually resolve
The change is in patches/pilz_industrial_motion_planner.patch. To set it up:
git clone https://github.com/moveit/moveit2.git
cd moveit2
git apply /path/to/Final_Project/patches/pilz_industrial_motion_planner.patch
# then colcon build the moveit_planners/pilz_industrial_motion_planner package
# into the same workspace as this repoNeeds ROS 2 Kilted with MoveIt 2, plus the patched Pilz planner (above) built into the same workspace.
# from your workspace's src/
git clone <this repo> Final_Project
rosdep install --from-paths Final_Project --ignore-src -r -y # rclcpp, moveit, python3-serial, python3-yaml, ...
cd ..
colcon build --symlink-install
source install/setup.bashBuild order (msr_gcode + msr_meca500_robot → msr_meca500_hardware → msr_meca500_moveit → msr_meca500_print_pipeline) is resolved by colcon.
- Fill in
msr_meca500_print_pipeline/config/machine_settings.yamlfor your printer — serial port,M503E-steps,M114home position, hotend/bed temps, the nozzle tip offset. - Locate the bed (see Configuration) and write the touch poses into
bed_settings.yaml, or leavedefault_bed: truefor a flat bed at a known spot.
The pipeline reads its settings from three params files in msr_meca500_print_pipeline/config/:
machine_settings.yaml— your printer: serial port + baud,M503E-steps/mm,M114home position, hotend/bed temperatures, the extruder link names and nozzle tip offset. Set once, when you first wire up a machine.bed_settings.yaml— where the bed is:default_bed(use the flat default vs. fit from touches),default_bed_pose, and the nozzle touch poses. Regenerate whenever the bed moves (see below).print_tuning.yaml— constants already tuned by the author: reachability grid size, extrusion floor and feed-rate limits, re-home frequency. Leave it alone unless a comment in the file tells you otherwise.
The launch files load all three (<param from>); the Ender3 heat/cool scripts read machine_settings.yaml directly.
Fitting the bed — set default_bed: false, then jog the nozzle to touch the bed at three or more points plus the centre. At each, read the position list from ros2 topic echo /joint_states and paste it into bed_settings.yaml — bed_touch_poses (six values per pose), and bed_center_pose for the centre. bed_from_touches runs FK to the nozzle tip for each, fits the plane by SVD, and publishes it on /table_service.
-
3D Printing a mini cube with a hole inside at an angle
print.mp4
In the RViz views below, the green line is the ee_trace (every sampled end-effector position) and the purple line is the print_trace (only the segments where the nozzle was actually extruding).
-
With extruder, flat bed (Benchy) — full print pipeline of Benchy Boat, no. of layers printed = 21
flat.bed.print.benchy.boat.mp4
-
No extruder, random-orientation bed (cube) — table tilted to an arbitrary pose via
/table_service, no. of layers printed = 7random.orientation.print.cube.mp4
-
G1 (straight-line) moves
g1_code_robot_moving.mp4
-
msr_meca500_hardware smoke test — basic robot motion through the ros2_control hardware interface
moveit_robot_moving.mp4
System bring-up — MoveIt2 + ros2_control + RViz + planningscene + bed_from_touches + gcode_print_executor. Run once, leave up:
ros2 launch msr_meca500_print_pipeline main.launch.xml use_mock_hardware:=true # sim
ros2 launch msr_meca500_print_pipeline main.launch.xml use_mock_hardware:=false # real Meca500
# default_bed:=false to fit the bed from bed_settings.yaml instead of the flat defaultRun a print — set bed → reachability sweep → parse/center/clip → execute. Needs main.launch.xml already running (it hosts the services):
ros2 launch msr_meca500_print_pipeline print.launch.xml \
model_file:=/path/to/model.gcode.3mf \
out_file:=/path/to/out.txt \
layers:=21 # 0 = all layers
# default_bed:=false to re-fit the bed for this printSet the bed pose manually (e.g. a deliberately tilted bed for the cube demo):
ros2 service call /table_service msr_meca500_print_pipeline/srv/Table \
"{x: 0.0, y: -0.20, z: -0.15, qx: 0.0, qy: 0.0, qz: 0.0, qw: 1.0}"Send raw G-code directly (single G1/G2/G3 moves for testing):
ros2 service call /goal_service msr_meca500_print_pipeline/srv/Goal "{gcode: 'G1 X50 Y50 Z10 F3000'}"
ros2 service call /goal_service msr_meca500_print_pipeline/srv/Goal "{gcode: 'G2 X50 Y0 Z10 I25 J0 F1500'}"Run each stage individually (<config> = $(ros2 pkg prefix msr_meca500_print_pipeline)/share/msr_meca500_print_pipeline/config):
# 1. Set the bed pose
ros2 run msr_meca500_print_pipeline bed_from_touches --ros-args \
--params-file <config>/machine_settings.yaml --params-file <config>/bed_settings.yaml
# 2. Sweep the reachable workspace
ros2 run msr_meca500_print_pipeline reachability --ros-args \
--params-file <config>/machine_settings.yaml --params-file <config>/print_tuning.yaml \
-p out_file:=reachable_points.csv
# 3. Parse/center/clip the sliced model onto that workspace
python3 msr_gcode/src/gcode_parser.py model.gcode.3mf out.txt --reach-csv reachable_points.csv -l 21
# 4. Send the parsed file to the executor
ros2 service call /gcode_file_service msr_meca500_print_pipeline/srv/GcodeFile "{file_path: '/path/to/out.txt'}"ROS2 | MoveIt2 | Pilz Industrial Motion Planner | C++ | Python | Meca500 API
Rishika Bera — MS Robotics, Northwestern University