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):

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

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: 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.

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 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 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.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).