CLI: --inspect-paint — dump per-facet paint state as JSON (#14608)

* CLI: --inspect-paint — dump per-facet paint state as JSON

Reads the per-facet enforcer/blocker/extruder/fuzzy-skin state stored
on every ModelVolume (supported_facets / seam_facets /
mmu_segmentation_facets / fuzzy_skin_facets) and emits a structured
JSON summary to stdout. Machine-readable alternative to opening the
paint gizmos.

Per (object, volume, layer, state): facet count, surface area in
mm², and mesh-local bounding box. Empty layers collapse to
{"empty": true}. Summary at the top level rolls up totals.

One correctness detail worth calling out: FacetsAnnotation::
get_facets_strict returns an indexed_triangle_set whose `vertices`
array is the whole source mesh — only `indices` are filtered to the
painted triangles. A naive bounding_box(its) would report the whole
mesh's bbox even when only a few facets are painted. The helper
its_referenced_bbox() walks only the vertices actually indexed by
the painted triangles, so `bbox` correctly localizes the painted
region.

Rationale: every paint-driven workflow — GUI-painted .3mf verified
in CI, AI agents planning support enforcers, MMU color layout checks
— needs to know what's already painted on a model. Today that's a
GUI-only read. --inspect-paint closes that loop for scripted callers.

New file src/slic3r/Utils/PaintCLI.{hpp,cpp} (~215 lines). Depends
only on Model, TriangleMesh, TriangleSelector, FacetsAnnotation, and
nlohmann::json — all already in tree. No new dependencies, no
signature changes, no behavior change when the flag is absent.

Registered as an action (parallel to --info) so it satisfies the
"needs an action" check and bypasses the GUI fallback; control falls
through the normal post-action path to a clean exit 0.

Verification:
  unpainted STL:     every layer {"empty": true}, summary zero
  GUI-painted .3mf:  enforcer count / area / bbox match painter
  clean JSON:        parseable via jq

* CLI --inspect-paint: exit after printing, reject conflicting actions

- Finish like the end of CLI::run once the JSON is written, as the
  tooltip says. The callback manager is Linux-only, so its use is
  guarded.
- Reject actions that would otherwise be skipped without notice
  (--slice, --export-3mf, ...) before loading. Load-time options such as
  --uptodate are still accepted.
- Replace invalid UTF-8 in object names and paths instead of throwing.
- Report every input file as sources; inputs are merged into one model
  before actions run.

* CLI --inspect-paint: reject a run without input

Without an input file or --load-assemble-list there is nothing to
inspect, and the run printed nothing and exited 0. Reject it up front
with CLI_INVALID_PARAMS, next to the other invalid-parameter checks.
This commit is contained in:
packerlschupfer
2026-09-17 12:01:49 +08:00
committed by GitHub
parent 6b0e190e64
commit ca668a3bc9
5 changed files with 298 additions and 0 deletions
+13
View File
@@ -11991,6 +11991,19 @@ CLIActionsConfigDef::CLIActionsConfigDef()
"the --ground-* options choose from. Machine-readable alternative to --info.");
def->set_default_value(new ConfigOptionBool(false));
// --inspect-paint \u2014 dump the per-facet enforcer/blocker/extruder/fuzzy
// paint state stored on the loaded model (supports, seam, MMU color,
// fuzzy-skin) as JSON. Read-only; lets CI / scripted / AI tooling
// reason about existing paint on a .3mf without loading the GUI.
def = this->add("inspect_paint", coBool);
def->label = L("Inspect paint (JSON to stdout)");
def->tooltip = L("Print a structured JSON summary of every painted layer "
"(supports, seam, MMU color, fuzzy-skin) already stored on "
"the loaded model \u2014 per-state facet count, surface area, "
"and mesh-local bounding box \u2014 then exit. Machine-readable "
"alternative to opening the paint gizmos in the GUI.");
def->set_default_value(new ConfigOptionBool(false));
def = this->add("export_settings", coString);
def->label = L("Export Settings");
def->tooltip = L("This exports settings to a file. Use - to write them to stdout.");