Construct headless CLI reference

Construct headless CLI reference

construct is both the desktop CAD editor and a headless command-line tool for continuous-integration and server use — meshing scenes, extracting their parameter schema, validating them, and authoring them from OPAL task plans, all with no display or GPU required (the CPU meshing backend is the default).

Running construct with no arguments launches the graphical editor; every other form is a headless subcommand that reads/writes files and exits.

Synopsis

The authoritative usage is what the binary itself prints — this section is construct --help verbatim, so the docs and the tool can never drift:

Construct — 3D CAD editor and headless mesh tool.

Usage:
  construct                            Launch the graphical editor
  construct gui [file.construct]       Launch the graphical editor, optionally
                                       opening a scene (.construct or .cpack)
  construct --version, -v              Print the version and exit
  construct --help, -h                 Print this help and exit
  construct schema <file.construct>    Print the scene parameter schema as JSON
  construct validate <file.construct>  Load a scene and report var/instance/root counts
  construct export <file.construct> [options]
                                       Mesh the scene and write it to disk
  construct bounds list <file.construct>
                                       List the scene's bounds volumes: name,
                                       center, size, and which one is active
  construct opal <plan.json> [<file.construct>] [-o <out>]
                                       Run an OPAL task plan against a scene (headless
                                       authoring); saves to -o, else back to the input

Global options (accepted before any subcommand, and spelled identically
in every OctoMY application — see §WFR-BC-5):
  --rhi-backend <name>      Force a render backend: vulkan, d3d12, opengl,
                            metal or null. Does not fall back: if the named
                            backend cannot be created, startup fails.
  --rhi-software            Ask the driver for a software rasterizer.
  --rhi-debug               Turn on the graphics API debug layer and Qt's RHI
                            debug output.
  --rhi-validate            Enable Vulkan validation layer
                            (VK_LAYER_KHRONOS_validation). Debug builds default
                            ON; release OFF.
  --no-rhi-validate         Disable Vulkan validation layer, overriding the
                            debug-build default.
  --tick-trace              Log the latency and depth of each worker queue item
                            and render tick.
  --gpu-markers             Name each GPU pass for PIX, Radeon GPU Profiler and
                            RenderDoc.
  --d3d-max-frame-latency <n>
                            Frames a Direct3D swap chain may queue before it
                            waits, 0 to 16. Default 1; 0 uses no waitable
                            object.

Export options:
  -o, --output <path>       Output mesh (format from extension: .stl .obj .ply .vtk)
  --params <params.json>    JSON object of varset parameter overrides
  --backend cpu|gpu|native  Meshing backend (default: native). A backend named
                            here fails loudly if unavailable; only a default
                            may fall back
  --cell-size <float>       Marching-cubes cell size
  --error-on-empty          Exit non-zero if the mesh has 0 triangles
  --postprocess <steps>     Comma-separated mesh post-process chain; steps:
                            weld sdfProject remesh coplanarMerge qemDecimate.
                            Default: weld,sdfProject(2),remesh (uniform
                            watertight finish); 'none' = raw extractor soup
  --adaptive-octree <true|false>
                            DC-octree extractor: allow adaptive (QEF-residual)
                            cell collapse (default: true)
  --residual-tolerance <float>
                            DC-octree extractor: merged-cell fit tolerance.
                            Omit for the derived default (0.01 x cell size^2)
  --bounds-center <x,y,z>   With --bounds-size: export exactly this box, in
  --bounds-size <x,y,z>     scene space. Both flags together, neither alone
  --bounds-name <name>      Export the named bounds volume. An unmatched name
                            is an error (available names on stderr); cannot
                            be combined with --bounds-center/--bounds-size.
                            Omit all three: the active volume, else auto-detect

Exit codes:
  0  Success (a valid empty mesh is success unless --error-on-empty)
  1  Input/output error (file not found, unreadable, or unwritable)
  2  Usage error (unknown argument or missing operand)
  3  Export failed

Exit codes

Every headless subcommand uses the same convention, so a CI job can branch on the process exit status alone:

Code Meaning
0 Success. An empty (zero-triangle) mesh is still success unless --error-on-empty was given.
1 Input/output error — the input file is missing, unreadable, or the output path is unwritable.
2 Usage error — an unknown subcommand or flag, or a missing operand. The GUI is not launched in this case.
3 Export (or OPAL plan) failed after the inputs were accepted.

Output streams

stdout is data; stderr is diagnostics. Every machine-readable mode writes its result to stdout and nothing else, so it can be piped into a parser directly:

Mode Writes to stdout Shape
construct schema <file> The scene's parameter schema A single JSON object — parses with any JSON reader, no filtering
construct validate <file> ok: N vars, N instances, N roots One line, and it is the first line of stdout
construct --version construct <version> One line
construct --help The usage text above Plain text

Everything else is on stderr — every log line at every severity, all progress, and every error message. Nothing a program logs ever reaches stdout.

construct schema scene.construct | jq .            # works, unfiltered
construct schema scene.construct > out.json 2> run.log   # or keep both

Exit status is independent of both streams and is the thing to branch on; see Exit codes above.

Before August 2026 this was not true: debug and info log lines went to stdout, so construct schema emitted invalid JSON. If you have a script that filters our log format out of a data stream, you can remove the filter — and should, because it was also discarding any data line that happened to begin the same way. Full detail in Output streams.

export — mesh a scene

construct export model.construct -o model.stl
construct export model.construct -o big.stl --params '@overrides.json' --cell-size 0.05

The output format is inferred from the -o extension (.stl, .obj, .ply, .vtk); with no -o the path is derived from the input filename and the scene's saved export format. Bounds, cell size, backend, and thread count come from the scene's saved export settings, overridden by the corresponding flags.

--params contract. The file is a flat JSON object mapping varset variable names to override values, e.g.:

{ "width": 40.0, "symmetric": true, "label": "PART-002" }

Each entry is applied as a scene variable before expression evaluation, so any instance parameter written as $width (or any expression referencing the varset) is recomputed against the override. This is the seam that lets one scene file drive a family of parts from CI. A value for a name the scene doesn't declare is simply set as a new variable.

Empty results. A scene whose geometry doesn't cross the export bounds meshes to a valid, empty file and exits 0 — an intentional non-error. Pass --error-on-empty when a job expects real geometry and a zero-triangle result should fail the build.

Backends. native (compiled-C++ JIT, the default) is the fastest and needs no GPU — it is the CI-safe path. cpu is the interpreter, typically 100–1000× slower; gpu needs a GPU-evaluation-capable graphics device. A backend you name explicitly fails loudly if it cannot run; only a backend chosen for you (the default, or a scene's saved setting) may fall back.

Bounds. By default the export region is the scene's active bounds volume; without one, the scene's saved bounds, else auto-detection over the whole scene. Two flag forms override all of that: --bounds-center x,y,z with --bounds-size x,y,z exports exactly that box in scene space, and --bounds-name <name> exports the named volume. The forms are mutually exclusive, and an unmatched name is a hard error that lists the scene's volume names on stderr — a batch job must never mesh the wrong region silently. Discover the names with construct bounds list.

bounds list — enumerate a scene's volumes

construct bounds list model.construct

One volume per stdout line — name, center=x,y,z, size=x,y,z, tab-separated, with a trailing active marker on the active volume — so scripts can cut or grep it directly. A scene with no volumes prints nothing to stdout and notes the fact on stderr.

schema — extract the parameter surface

construct schema model.construct

Prints the scene's varset as a JSON object (generator_type, generator_version, source_hash, and a parameters array of {name, type, default, …}) to stdout. Use it to discover which --params keys a scene accepts and their types before driving an export.

validate — quick structural check

construct validate model.construct

Loads the scene and prints ok: N vars, M instances, K roots to stdout, exit 0; a scene that fails to load exits non-zero. A cheap CI gate that a .construct file parses and has the shape you expect.

opal — headless authoring

construct opal plan.json                      # author a fresh scene, no save target
construct opal plan.json base.construct        # edit base.construct in place
construct opal plan.json -o authored.construct # author/edit, save to a new file

Runs an OPAL task plan against a scene — the same task suite the editor and the in-app assistant use, so authoring logic is never duplicated. With a scene argument the scene is loaded first; without one a fresh empty scene is created. The result is saved to -o if given, else back to the scene argument; a plan with neither target runs read-only (query plans).

The plan is a JSON object with a steps array; each step names a task and its args, and may bind an output into a ${placeholder} for a later step via produces:

{
  "steps": [
    { "task": "construct.item.add",
      "args": { "itemType": "sdf", "definitionId": "sphere", "name": "Ball" },
      "produces": { "ball": "itemId" } },
    { "task": "construct.sdf.set_parameter",
      "args": { "itemId": "${ball}", "paramName": "diameter", "value": 4.0 } },
    { "task": "construct.varset.set_variable",
      "args": { "name": "diameter", "value": 4.0, "type": "number" } }
  ]
}

Headless plans run under a file-scoped authorization tier: file-in / file-out CAD editing tasks are permitted without a logged-in identity, while sensitive higher-tier tasks remain denied. See the OPAL reference for the full task list.

Scene file shape

A .construct file is a YAML document rooted at a construct: key holding varset: (the parameter definitions), instances: (the geometry items), materials:, roots:, and optional settings: (including export.*). Both flow (roots: [a, b]) and block (roots: then dash-prefixed items at the parent column, PyYAML's default) sequence styles load. Files authored by external tools (e.g. a Python service emitting YAML) load without modification.

Cameras, meshes, bounding volumes and reference images are under items:. An optional order: list records where they sit among the instances in the scene tree: each entry has a children: list, and a parent: name unless it is the top level. A child is an instance's name, or @ and an item's id. A file without order: lists the instances first and the items after them.

See also

  • Companion consumer contract for the oc-construct monolith service: docs/construct/headless-cli.md in the monolith repository (this page is its construct-side counterpart).
  • Internal delivery spec: content/doc/internal/specs/construct-headless-cli/.
Topics
reference cli headless construct cicd
See also