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:
debugandinfolog lines went to stdout, soconstruct schemaemitted 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-constructmonolith service:docs/construct/headless-cli.mdin the monolith repository (this page is its construct-side counterpart). - Internal delivery spec:
content/doc/internal/specs/construct-headless-cli/.