print_loop — SPEC
print_loop — SPEC
Section titled “print_loop — SPEC”Purpose
Section titled “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
Section titled “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
Section titled “Type signatures”type GCode = FilePathtype BagDirectory = FilePathtype SurfaceErrorMap = CSV -- columns: X, Y, Z, err_mmtype HTMLPlot = FilePath
-- Existing modules (called as subprocesses)execute_gcode :: GCode -> IO ()record_bag :: [Topic] -> IO BagDirectorymap_surface_err_to_xyz :: (BagDirectory, GCode, Params) -> (HTMLPlot, SurfaceErrorMap)proportional_correction :: (GCode_next, SurfaceErrorMap, K_p) -> GCode_corrected
-- This moduleprint_loop :: PrintJobConfig -> IO ()Config schema
Section titled “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
Section titled “Path derivation rules”Given config and layer number N:
gcode_path = slices_dir / *_layers_{N}-{N}_1layers[_corrected].gcodebag_dir = bag_output_dir / L{N}_Z{z_base + N * z_per_layer}error_html = state_dir / L{N}.htmlerror_csv = state_dir / L{N}.csvcorrected = slices_dir / *_layers_{N+1}-{N+1}_1layers_corrected.gcodeCorrected gcode is preferred over nominal when available for a given layer.
Loop algorithm
Section titled “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 stepState persistence
Section titled “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
Section titled “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
Section titled “Dry-run mode”--dry-run prints derived paths for every layer without executing anything. Useful for verifying config correctness.
Constraints
Section titled “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).