Skip to content

print_loop — SPEC

Orchestrate the WAAM closed-loop print cycle so the operator only provides a YAML config and per-layer visual confirmation. All path derivation, subprocess management, and state tracking are automated.

Wire-Arc Additive Manufacturing (WAAM) deposits metal layer-by-layer. A camera array measures surface-height error during deposition. That error feeds back into the next layer’s toolpath via proportional feedrate correction. The loop runs once per layer.

type GCode = FilePath
type BagDirectory = FilePath
type SurfaceErrorMap = CSV -- columns: X, Y, Z, err_mm
type HTMLPlot = FilePath
-- Existing modules (called as subprocesses)
execute_gcode :: GCode -> IO ()
record_bag :: [Topic] -> IO BagDirectory
map_surface_err_to_xyz :: (BagDirectory, GCode, Params) -> (HTMLPlot, SurfaceErrorMap)
proportional_correction :: (GCode_next, SurfaceErrorMap, K_p) -> GCode_corrected
-- This module
print_loop :: PrintJobConfig -> IO ()

A YAML file with these fields (validated by Pydantic):

FieldTypeDescription
job_namestrIdentifier; used in state filename
slices_dirPathDirectory of per-layer gcode files
bag_output_dirPathWhere MCAP bags are written
state_dirPathWhere HTMLs, CSVs, and state JSON live
start_layerintFirst layer to print (inclusive)
end_layerintLast layer to print (inclusive)
z_basefloatZ height of layer 1 (mm)
z_per_layerfloatLayer height (mm)
topics[str]ROS topics to record
error_mapping.smoothintMoving-average window for error signal
error_mapping.trim_firstintZero-out first N error samples
error_mapping.trim_lastintZero-out last N error samples
control.k_pfloatProportional gain
control.lower_bounds_corrected_feedfloatMinimum feedrate clamp

Given config and layer number N:

gcode_path = slices_dir / *_layers_{N}-{N}_1layers[_corrected].gcode
bag_dir = bag_output_dir / L{N}_Z{z_base + N * z_per_layer}
error_html = state_dir / L{N}.html
error_csv = state_dir / L{N}.csv
corrected = slices_dir / *_layers_{N+1}-{N+1}_1layers_corrected.gcode

Corrected gcode is preferred over nominal when available for a given layer.

for layer N in [start_layer .. end_layer]:
1. EXECUTE + RECORD
- Popen("ros2 bag record -o {bag_dir} --topics ...")
- sleep(2) // let recorder initialise
- run("ros2 run gcode_file_parser_client ... {gcode_path}")
- SIGINT bag recorder; wait for shutdown
2. MAP SURFACE ERROR
- run("uv run python map_surface_err_to_xyz_pos.py {bag_dir} {gcode_path}
--nominal_z {z} -o {html} --smooth ... --trim_first ... --trim_last ...")
- outputs: {html}, {csv}
GATE: open {html} in browser; prompt operator [Y/n/q]
- Y: continue to step 3
- n: skip correction, advance to next layer
- q: save state, exit
3. PROPORTIONAL CORRECTION (skip if last layer)
- run("uv run python simple_proportional.py {next_gcode} {csv}
-k {k_p} --lower_bounds_corrected_feed {f_min}")
- output: {next_gcode}_corrected.gcode
GATE: prompt operator before printing next layer [Enter/q]
save state after every step

JSON file at {state_dir}/{job_name}_state.json. Tracks:

  • current_layer: int
  • last_completed_step: 0 | 1 | 2 | 3
  • layers: dict mapping layer number to per-layer state (gcode_path, bag_dir, step statuses, output paths)

On restart, the loop resumes from the first incomplete step of current_layer.

Each external tool is invoked via subprocess.run (or Popen for bag recording). The orchestrator does not import any of the tool modules directly, keeping uv environments isolated:

  • ros2 bag record / ros2 run gcode_file_parser_client — ROS2 CLI
  • uv run python map_surface_err_to_xyz_pos.py — run in shared/rosbag_parser/
  • uv run python simple_proportional.py — run in ros2_ws/lib/control/simple_proportional/

--dry-run prints derived paths for every layer without executing anything. Useful for verifying config correctness.

  • Gcode files must follow *_layers_{N}-{N}_1layers.gcode naming convention (produced by divide_gcode_into_n_layers.py).
  • ROS2 environment must be sourced for Step 1 subprocesses.
  • uv must be available on PATH.
  • Operator must be able to view HTML files (local browser or file transfer).