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. construct view and construct <file> open the viewer, a window that shows a file and
edits nothing. 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 view [file]... Open each file in a viewer window: a mesh
(.stl .obj .ply .vtk) or a scene (.construct
.cpack). No file: an empty viewer window
construct <file>... The same as construct view <file>...
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
construct render <file.construct> [--out <file>] [options]
Render the scene to an image, a sequence or a
video, with its render settings and --set
construct definitions render --out <dir> [--size <px>] [--timeout <s>]
[--id <id>]...
Render one PNG of each catalog definition (or
of each --id) into <dir>, marked for the build's
edition, with definitions.json beside them.
Default size 256 px, timeout 300 s for each.
Opens no window, and needs a GPU platform: a
display (xcb, also under xvfb-run) or wayland.
Exit 3 names each definition with no image
Global options (accepted before any subcommand, and spelled identically
in every 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.
--gpu-preference <name> Which GPU a render view uses: window (the window's,
zero-copy), high-performance (the discrete GPU, a
copy per frame when the window is elsewhere) or
copy (always the copy, to measure it). Default: the
application's own choice.
--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.
--log-detail <level> Log detail for this run: normal or verbose. Verbose
logs every category, Qt's own too. The setting Log
detail chooses it for every run.
--log-color <when> Color of the log on the terminal for this run:
never, always or auto. Auto colors a terminal that
shows 24-bit color. The log file never holds color.
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
--item <name|id> Export one item: an SDF item or a mesh item by its
name, a tet volume item by its id
--kind tri|tet|tri_boundary|tet_from_mesh
What to make: triangles (default), a tet volume
(VTK), the boundary of a tet volume item, or a
tet volume (VTK) that fills a mesh item
--edge-length <float> tet_from_mesh: the target mean edge length, in the
units of the mesh file. 0 or omitted: Auto
--max-tets <n> tet_from_mesh: the most tetrahedra (default:
1000000)
--format stl|obj|ply|vtk The output format, when -o names none
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
Render options:
--out, -o <file> The image (.png, .jpg), the sequence (with a %f
frame token, or _%f before the extension) or the
video (.mp4, .mkv, .webm) to write
--size WxH The frame size in pixels (default: 1920x1080)
--engine raster|path-traced
The engine. Default: the scene's shading mode
--samples N The path tracer's most samples per frame
--noise T The noise threshold that ends a frame early
--time-limit S The most seconds per frame
--denoise off|atrous|oidn The denoiser of each frame. Default: the scene's
final denoiser
--camera <name> Render from the scene's camera item of this name.
Default: the first camera by name, else a view
that frames the scene
--frames A..B Render frames A to B, at --fps N (default 30)
Render exit codes:
0 Done
1 Bad arguments
2 The scene does not load
3 No usable GPU device
4 The render failed
Exit codes
Every headless subcommand uses the same convention, so a CI job can branch on the
process exit status alone. render is the exception: it has five codes of its
own, in its section.
| 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. For definitions render: a definition has no image. |
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.
One item, and what to make of it. --item exports one item instead of
the whole scene, as the Bake menu does: an SDF item by its name, with bounds
detected around it unless a region is named, or a tet volume item by its id.
--kind says what to make: tri (triangles, the default), tet (a tet
volume, always VTK), or tri_boundary (the boundary of a tet volume item, as
a triangle mesh). The summary line on stderr gives tetrahedra beside
triangles and vertices.
construct export part.construct --item "Bracket" --kind tet -o bracket.vtk
construct export part.construct --item 637e7756-d354-4a26-a9ed-39a3d2f7ac8e --kind tri_boundary -o skin.ply
render ā render a scene to an image, a sequence or a video
construct render renders a scene file with no window, through the same
export path that the Rendering window uses. It plans the render as
Queue render does: from the scene's render settings (the output.* rows
and the quality rows that the scene file keeps), with the options and each
--set <row>=<value> in their place for this render. It refuses what
Queue render refuses, with the same message, and exits 1.
# A still, path traced, 8 samples, from the scene's camera called "Front"
construct render tank.construct --out tank.png --size 1280x720 --engine path-traced --samples 8 --camera Front
# Frames 0 to 47 at 24 fps as tank_0000.png ⦠tank_0047.png
construct render tank.construct --out tank.png --frames 0..47 --fps 24
# The same frames as a video; the codec is the first that this computer encodes
construct render tank.construct --out tank.mp4 --frames 0..47 --fps 24
# The scene's own file names and frame range, at half its size
construct render tank.construct --set output.multiplier=0.5
- Without
--out, the scene'soutput.template.*rows name the files, and itsoutput.mediaTypesays image or video. - Without
--size, the size is the camera's timesoutput.multiplier. The framing view of a scene with no camera is 1920 Ć 1080. - Without
--camera, the command uses the camera of the scene'soutput.camera, else the scene's first camera by name. A scene with no camera gets a view that frames all of it. --settakes any row of the construct settings, for exampleoutput.existingFile=skip. A row that does not exist stops the command with its name.- A sequence path without a
%fframe token gets one before its extension. - The command prints one line per frame on standard error: the frame, its samples, its noise level and its seconds. When it is done, it prints the path it wrote on standard output.
--denoiseruns a denoiser once, at the end of each path-traced frame:atrousis the fast edge-aware filter,oidnthe Open Image Denoise network, which is cleaner and slower. Without the option, the scene's Final denoiser setting applies. A raster frame has no noise, so it skips the denoiser.- The scene's meshes and tet volumes draw from their files, with their fields, colors and deformations. A file that does not read draws nothing, and the command prints a warning that names it on standard error.
- A deform presenter's axis step selects the value of each frame, as in Queue render. One render steps one axis.
| Exit code | Meaning |
|---|---|
| 0 | Done |
| 1 | Bad arguments |
| 2 | The scene does not load |
| 3 | No usable GPU device. The command writes no file |
| 4 | The render failed |
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.
view ā the viewer
construct view part.stl # one viewer window
construct view a.stl b.construct # one window for each file
construct part.stl # the same as construct view part.stl
construct view # an empty window that asks for a file
The viewer shows a mesh (.stl, .obj, .ply, .vtk) or a scene
(.construct, .cpack) and edits nothing. It opens no dialog by itself and
shows no splash. The window shows before the file loads, and the status line
shows the load's progress. Esc cancels the load of a file whose reader
reports progress (OBJ, PLY and VTK; not STL).
A file that cannot be read shows the reason in the window, with Open in
Construct when the editor can tell more. Open in Construct starts the
editor, construct gui <file>, as a new program, and the viewer stays open.
A first word that is not a subcommand opens the viewer only when it names a
file that exists. Any other word is a usage error, exit 2, as before.
definitions render ā one image of each definition
construct definitions render --out images # every definition, 256 px
construct definitions render --out images --size 128 --id sphere --id union
xvfb-run construct definitions render --out images # a host with no display
Renders one image of each definition of the catalog, through the same thumbnail
renderer as the definition picker. Each image is <id>.png, marked for the
edition of the build: the marking adds an info bar with the definition's title
and summary under the render, and the edition's watermark over it. The published
reference shows these images, one on the page of each definition.
definitions.json in the same folder lists what the run made:
| Key | Holds |
|---|---|
schema |
1 |
edition |
The id of the edition that the build is |
size |
The side of the render, in pixels |
definitions |
One object for each image: id, title, summary, family, image |
libraries |
id and title of each library record. A library holds helper GLSL for other definitions, draws nothing, and has no image |
failed |
id and reason of each definition with no image |
The command opens no window. It needs a platform with a GPU device, because the
renderer draws with Vulkan or OpenGL. The offscreen platform has no Vulkan,
and its OpenGL needs an X display. On a host with no display, run it under
xvfb-run: Mesa's lavapipe renders there. Qt's own options, such as
-platform xcb, go after render, because the first word names the
subcommand. A render waits at
most --timeout seconds for each definition. A run starts with an empty cache
and the shipped Default material, so the same commit of the same edition makes
the same images on every host.
Exit 0 when every definition has an image. Exit 3 when one has none: stderr
then names each one with its reason, and definitions.json lists it under
failed. The other images are still written.
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/.