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

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's output.template.* rows name the files, and its output.mediaType says image or video.
  • Without --size, the size is the camera's times output.multiplier. The framing view of a scene with no camera is 1920 Ɨ 1080.
  • Without --camera, the command uses the camera of the scene's output.camera, else the scene's first camera by name. A scene with no camera gets a view that frames all of it.
  • --set takes any row of the construct settings, for example output.existingFile=skip. A row that does not exist stops the command with its name.
  • A sequence path without a %f frame 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.
  • --denoise runs a denoiser once, at the end of each path-traced frame: atrous is the fast edge-aware filter, oidn the 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-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