print_loop — SPEC
Purpose
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.
Domain context
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 signatures
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 ()
Config schema
A YAML file with these fields (validated by Pydantic):
| Field | Type | Description |
|---|---|---|
job_name | str | Identifier; used in state filename |
slices_dir | Path | Directory of per-layer gcode files |
bag_output_dir | Path | Where MCAP bags are written |
state_dir | Path | Where HTMLs, CSVs, and state JSON live |
start_layer | int | First layer to print (inclusive) |
end_layer | int | Last layer to print (inclusive) |
z_base | float | Z height of layer 1 (mm) |
z_per_layer | float | Layer height (mm) |
topics | [str] | ROS topics to record |
error_mapping.smooth | int | Moving-average window for error signal |
error_mapping.trim_first | int | Zero-out first N error samples |
error_mapping.trim_last | int | Zero-out last N error samples |
control.k_p | float | Proportional gain |
control.lower_bounds_corrected_feed | float | Minimum feedrate clamp |
Path derivation rules
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.
Loop algorithm
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
State persistence
JSON file at {state_dir}/{job_name}_state.json. Tracks:
current_layer: intlast_completed_step: 0 | 1 | 2 | 3layers: 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.
Subprocess strategy
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 CLIuv run python map_surface_err_to_xyz_pos.py-- run inshared/rosbag_parser/uv run python simple_proportional.py-- run inros2_ws/lib/control/simple_proportional/
Dry-run mode
--dry-run prints derived paths for every layer without executing anything. Useful for verifying config correctness.
Constraints
- Gcode files must follow
*_layers_{N}-{N}_1layers.gcodenaming convention (produced bydivide_gcode_into_n_layers.py). - ROS2 environment must be sourced for Step 1 subprocesses.
uvmust be available on PATH.- Operator must be able to view HTML files (local browser or file transfer).