SolidCraft
SolidCraft is an open-source parametric 3D CAD application written in pure Rust. Sketch on a plane, constrain and dimension the sketch, turn profiles into solids with features on a timeline, and change a parameter to watch the whole timeline rebuild.
It runs as:
- a desktop app for Windows and Linux (egui + wgpu);
- a web app in the browser (wasm, WebGPU or WebGL2), try it here;
- a headless CLI,
solidcraft-cli, that runs command scripts, measures and exports; - an MCP server that AI agents model with, headless or driving the running app.
Everything the user can do is a command with JSON parameters. The toolbar, the dialogs, scripts, the CLI, the control channel and MCP all run the same commands, so anything you can click you can also script, test or hand to an agent.
SolidCraft is early. Sketching, solid modelling, parameters, components with joints, sheet metal and a plastic-enclosure subset work; surfaces, drawings, CAM and simulation are deferred. The roadmap has the details, and the feature list lists every command by area, and the reference tests check modelling results against analytic and FreeCAD values.
Existing .solidcraft designs and solidcraft-cli commands keep their names and behavior.
This book has two halves: the user guide for people modelling with SolidCraft, and the developer guide for people working on it. The command reference is generated from the code on every build.
SolidCraft is licensed MIT OR Apache-2.0. It is independent: behaviour is designed from public documentation, general CAD conventions and user feedback, and reference tests use analytic values and open-source tools. SolidCraft is not affiliated with Autodesk, Dassault Systèmes, PTC or any other CAD vendor; product names such as Fusion, SolidWorks and Onshape are trademarks of their respective owners, named only to describe compatibility.
Getting started
The desktop executable is solidcraft, the CLI is solidcraft-cli, and designs are saved as
.solidcraft. Designs, settings and setups from builds released under the previous product name keep
working (see the compatibility notes).
Desktop
Download the installer for your system from the Download section of the README or the releases page, or build it yourself with stable Rust (1.95 or newer):
git clone https://github.com/bherbruck/solidcraft && cd solidcraft
cargo run --release -p solidcraft -- --sample # opens with a sample part
solidcraft part.step opens a STEP, IGES, 3MF, STL or OBJ file as a new design, and
solidcraft design.solidcraft opens a saved design. Windows builds cross-compile from Linux with
cargo xwin build --release --target x86_64-pc-windows-msvc -p solidcraft.
macOS
Open the release .dmg and drag SolidCraft.app onto Applications.
The macOS build is signed ad-hoc and not notarized, so macOS blocks the first launch with “SolidCraft can’t be opened, because Apple can’t verify if the app is vulnerable”. Open System Settings › Privacy & Security, scroll down to the message about SolidCraft, click Open Anyway and confirm: the app opens normally from then on. From a terminal, remove the quarantine flag instead:
xattr -dr com.apple.quarantine /Applications/SolidCraft.app
When updating from a build released under the previous product name, follow the macOS upgrade notes so you replace it rather than install a second copy.
Linux AppImage
The first time you open the AppImage it adds a menu entry and icons under ~/.local/share
(applications/ai.storyteller.solidcraft.desktop, icons/hicolor), so the desktop shows the
right name and icon. It adds nothing if appimaged
already made an entry for the file, or if a launcher such as Gear Lever or AppImageLauncher
started it (and removes the entry SolidCraft added before). To
turn it off for good, create an empty file ~/.local/share/solidcraft/no-desktop-integration, or
start the AppImage with SOLIDCRAFT_NO_DESKTOP_INTEGRATION=1.
If the AppImage doesn’t start and prints a FUSE message followed by execv error: No such file or directory, AppImageLauncher 2.x is intercepting it: it can’t start AppImages built on the
static runtime, as SolidCraft’s is. Update AppImageLauncher to 3.0 (so far only published as
betas; 3.0.0-beta-3 or newer works), or remove it.
Linux Flatpak
Install Flatpak using your distribution’s package manager. For an installation that receives updates, add Flathub for the required Freedesktop runtime and the SolidCraft application feed to the same user installation:
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak remote-add --user --if-not-exists solidcraft https://docs.solidcraft.io/flatpak/solidcraft.flatpakrepo
flatpak install --user solidcraft ai.storyteller.solidcraft
flatpak run ai.storyteller.solidcraft
A SolidCraft remote added before the project moved to docs.solidcraft.io (from
bherbruck.github.io) no longer finds updates. Point it at the new address, then update:
flatpak remote-modify --user --url=https://docs.solidcraft.io/flatpak/repo/ solidcraft and
flatpak update --user.
Releases up to 0.7 used the application ID ai.storyteller.solvecraft. If you installed one
of those from the SolidCraft remote, flatpak update --user reports it as end-of-life and offers
to replace it with ai.storyteller.solidcraft; accept, and your settings and autosaves move with
it. If you installed a .flatpak bundle, install the new bundle, launch it once (it copies your
settings), then run flatpak uninstall --user ai.storyteller.solvecraft (see the
compatibility notes).
Alternatively, download the release’s .flatpak file for your architecture and run
flatpak install --user ./solidcraft-VERSION-linux-ARCH.flatpak. The bundle supplies the
Flathub runtime repository address; installation still needs internet access to fetch any
missing runtime. Launch with flatpak run ai.storyteller.solidcraft, rather than looking for
a host solidcraft binary. Files chosen through Open/Save use the desktop file-chooser portal;
command-line paths outside Documents may need a specific additional filesystem permission.
Linux Snap
On Ubuntu and other distributions with snapd:
sudo snap install solidcraft
solidcraft # or open SolidCraft from the app menu
solidcraft.cli --version # the CLI, for scripts and `solidcraft.cli mcp`
The snap can open designs in your home folder (not hidden folders) and, through the file
chooser, anywhere else. For USB drives and other mounted disks, run
sudo snap connect solidcraft:removable-media once. A release’s .snap file installs without
the store with sudo snap install --dangerous ./solidcraft-VERSION-linux-ARCH.snap; it then
doesn’t update automatically.
Web
The same app runs in the browser: open SolidCraft on the web (?sample opens the sample
part, ?webgl forces WebGL2 where WebGPU is missing or misbehaves; the browser remembers it until
?webgpu, and an app installed from Chrome or Edge starts with it too). Designs are saved in the
browser’s private file storage, which needs HTTPS or localhost.
Planned worker and tiered-storage integration is
described in web-runtime.md
(design; not yet wired in the shipped wasm build).
After the first visit the web app works offline: the browser keeps a copy of it. Online, each launch gets the latest published build (the browser downloads it once when it changes); offline, when the network doesn’t answer within a few seconds, or when a new build can’t be downloaded whole, the kept copy starts (and the download carries on for the next launch).
Browsers that can install web apps add Install app… to the File menu: it installs SolidCraft as an app with its own window and launcher icon, working offline like the page. In Chrome, Edge and other Chromium browsers it shows the browser’s install prompt, and once SolidCraft is installed the item is gone. Safari has no such prompt, so the item shows its steps instead: Share › Add to Home Screen on iPhone and iPad, File › Add to Dock on a Mac (Safari 17 or later).
To build it yourself:
rustup target add wasm32-unknown-unknown
cd apps/solidcraft-web && trunk serve --release --no-autoreload
Offline use needs --no-autoreload: Trunk’s live reload changes the page as it serves it, so it
no longer matches the build the browser would keep. Debug builds (trunk serve) never keep a copy.
Design links and embeds
Use ?open=<encoded HTTPS URL> to open a hosted .solidcraft design as an unsaved
copy, including its history, constraints, and joints. Encode the URL with
encodeURIComponent when it contains query parameters. The host must allow CORS
requests from the app’s origin. Downloads, including redirected URLs, must use HTTPS
or stay on the app’s own origin, and fit within the File > Open design size limit.
Failed downloads and invalid designs show an error and leave the empty design.
For self-hosting, ?open=/dutchman/jobs/abc/part.solidcraft resolves against the
page’s origin, using its scheme and port, including plain HTTP on a LAN. A full URL
on that same origin is also accepted; other absolute URLs still require HTTPS.
For example, expose a reverse-proxy service in Compose, routing / to
solidcraft:8080 and /dutchman/ to a converter service. The converter can link to
/?open=/dutchman/jobs/abc/part.solidcraft without a separate origin or CORS setup.
Paths must start with one /; protocol-relative paths (//), backslashes, control
characters, and decoded .. segments are refused. Redirects must end at HTTPS or
the page’s own origin. Embed mode and Open in SolidCraft preserve the path.
Add embed to show the viewport, timeline, browser tree, and joint controls without
the ribbon, file menus, or start page:
<iframe src="https://docs.solidcraft.io/app/?embed&open=https://example.org/arm.solidcraft"
width="960" height="540" title="Arm design"></iframe>
Embedded designs do not save designs, preferences, or recovery copies to browser
storage and do not ask before unloading. Open in SolidCraft opens the same link
in the full app in a new tab. ?embed without open keeps the normal app layout.
Self-hosting with Docker
The web app is also a container image, ghcr.io/tangency-labs/solidcraft-web, for Linux on x86_64
and ARM64: latest is the latest release, 0.14.0 or 0.14 a release, edge the newest main.
It serves the app with nginx on port 8080 and keeps nothing on the server: designs stay in each
visitor’s browser.
docker run -d --name solidcraft -p 8080:8080 --restart unless-stopped ghcr.io/tangency-labs/solidcraft-web:latest
Or with Docker Compose, using
packaging/docker/compose.yaml
(SOLIDCRAFT_PORT picks another port):
docker compose -f packaging/docker/compose.yaml up -d # start
docker compose -f packaging/docker/compose.yaml pull && docker compose -f packaging/docker/compose.yaml up -d # update
docker compose -f packaging/docker/compose.yaml up -d --build # build the image from your checkout instead
Then open http://localhost:8080/. On another machine the browser only saves designs over HTTPS,
so put a reverse proxy with a certificate in front (Caddy, Traefik, nginx); the app works under
any URL path, such as https://example.com/cad/. To build the image without Compose, from the
repository root: docker build -f packaging/docker/Dockerfile -t solidcraft-web .
3D mouse in the browser
The web app reads a 3Dconnexion SpaceMouse or SpaceNavigator through the browser’s Gamepad API, with the same switch and speed in Preferences › Navigation as the desktop app. Edge and Chrome read the device as it is. A browser shows a gamepad to a page only once it has been moved there, so if nothing happens at first, push the cap firmly once. 3DxWare also turns the cap into mouse-wheel zoom in browsers; while the cap moves, the web app ignores the wheel and reads the cap itself. Before the browser shows the cap to the page, that zoom still gets through, so the first push after opening the page may zoom a little.
Firefox doesn’t list 3D mice (Mozilla bug 1226575).
On Windows it does list 3DxWare’s virtual joystick, which can carry the cap instead. Save this
as %APPDATA%\3Dconnexion\3DxWare\Cfg\Firefox.xml, then restart Firefox (and 3DxWare, if the cap
still only zooms). It changes Firefox only: there 3DxWare sends the cap’s six axes to the
joystick instead of the mouse wheel. Delete the file to undo it.
<?xml version="1.0" encoding="UTF-8"?>
<AppCfg Default="false" xmlns="" CfgFormatVersion="1.3" ThisFileVersion="1.1">
<AppInfo>
<Signature>
<Name>STR_FIREFOX</Name>
<ExecutableName>FIREFOX.EXE</ExecutableName>
<Transport>KMJ</Transport>
</Signature>
<Options/>
</AppInfo>
<CfgProperties>
<ID>ID_STR_FIREFOX</ID>
<Name>STR_FIREFOX</Name>
<InheritsFromID>ID_STR_FIREFOX</InheritsFromID>
</CfgProperties>
<Devices>
<Device>
<ID>ID_Standard_3D_Mouse</ID>
<AxisBank>
<Name>STR_DEFAULT_BANK</Name>
<ID>Default</ID>
<Axis><Enabled>true</Enabled><Input><ActionID>HIDMultiAxis_X</ActionID><Min>-512</Min><Max>511</Max><Deadband>0</Deadband></Input><Output><ActionID>HIDJoystick_X</ActionID></Output></Axis>
<Axis><Enabled>true</Enabled><Input><ActionID>HIDMultiAxis_Y</ActionID><Min>-512</Min><Max>511</Max><Deadband>0</Deadband></Input><Output><ActionID>HIDJoystick_Y</ActionID></Output></Axis>
<Axis><Enabled>true</Enabled><Input><ActionID>HIDMultiAxis_Z</ActionID><Min>-512</Min><Max>511</Max><Deadband>0</Deadband></Input><Output><ActionID>HIDJoystick_Z</ActionID></Output></Axis>
<Axis><Enabled>true</Enabled><Input><ActionID>HIDMultiAxis_Rx</ActionID><Min>-512</Min><Max>511</Max><Deadband>0</Deadband></Input><Output><ActionID>HIDJoystick_Rx</ActionID></Output></Axis>
<Axis><Enabled>true</Enabled><Input><ActionID>HIDMultiAxis_Ry</ActionID><Min>-512</Min><Max>511</Max><Deadband>0</Deadband></Input><Output><ActionID>HIDJoystick_Ry</ActionID></Output></Axis>
<Axis><Enabled>true</Enabled><Input><ActionID>HIDMultiAxis_Rz</ActionID><Min>-512</Min><Max>511</Max><Deadband>0</Deadband></Input><Output><ActionID>HIDJoystick_Rz</ActionID></Output></Axis>
</AxisBank>
</Device>
</Devices>
</AppCfg>
CLI
solidcraft-cli runs the same commands without a window:
cargo run --release -p solidcraft-cli -- commands # every command, as JSON
cargo run --release -p solidcraft-cli -- run examples/bracket.json --out bracket.step
cargo run --release -p solidcraft-cli -- eval examples/bracket.json # volume, area, faces…
cargo run --release -p solidcraft-cli -- snapshot examples/bracket.json --out bracket.png
cargo run --release -p solidcraft-cli -- exec solid.box '{"length": 20, "width": 10, "height": 5}'
cargo run --release -p solidcraft-cli -- run mechanism.json --in arm.solidcraft # with a motion.export_animation step: a GIF
A script is a list of commands:
{"commands": [
{"command": "sketch.create", "params": {"plane": "XY"}},
{"command": "sketch.rectangle.two_point", "params": {"p0": [0, 0], "p1": [40, 30]}},
{"command": "sketch.finish", "params": {}},
{"command": "solid.extrude", "params": {"distance": 20}},
{"command": "solid.fillet", "params": {"edges": [[0, 0, 10]], "radius": 3}}
]}
snapshot looks at the model from the iso view. --view NAME takes another standard view (front, back, top, bottom, left, right) or the name of a view saved in the design with view.save. --camera JSON takes any camera, as the MCP screenshot tool does (its conventions are in the MCP page). A script that applies a view itself with view.apply sets the camera too. The ground grid lies on Z 0, so it cuts through a part that straddles it: --no-grid leaves it out of the picture, and --no-axes the origin’s axes. The picture is drawn on the GPU with the viewport’s own pipelines, so it shows what the window would (a surface analysis such as inspect.zebra included); a machine without a GPU uses a software one (Mesa’s lavapipe on Linux, WARP on Windows), and with neither (a Mac without usable Metal, a Linux box without Mesa’s Vulkan) the CPU rasterizer draws it, without surface analysis colours, and the reply’s renderer says cpu with a warning. SOLIDCRAFT_GPU=auto|hardware|software|cpu chooses (hardware and software fail rather than fall to the CPU), and solidcraft-cli gpu lists the adapters and the tier a snapshot would use.
solidcraft-cli snapshot part.solidcraft --out photo.png --view Photo
solidcraft-cli snapshot part.solidcraft --out close-up.png --no-grid --no-axes --camera '{"target": [30, -20, 0], "yaw": 0.6, "pitch": 0.25, "distance": 60}'
Seeing what a cut or a join is about to do
A picture answers in a glance what a volume cannot: does the tool body reach through the
material, and is it where you meant it to be? --body NAME:opacity=0.3 draws that body
see-through, so whatever is inside it shows; --body NAME:color=#cc3333 gives it a colour.
Either key on its own keeps what the design says about the other, and the option is a shortcut
for appearance.assign, the command the Appearance dialog runs — the picture is the one the
window would show with the same appearance assigned.
--tool NAME is the common case in one option: that body solid in the tool colour, every
other body see-through around it. --section z=27 cuts the model at a plane so the inside
shows, as inspect.section does: an axis and the world coordinate to cut at, ,flip to keep
the other side, XY, XZ or YZ for a plane through the origin, or the command’s own JSON.
# The pin the cut would remove, inside the plate it stops 6 mm short of.
solidcraft-cli snapshot plate.solidcraft --out preview.png --tool Pin
solidcraft-cli snapshot plate.solidcraft --out cut.png --tool Pin --section z=12
solidcraft-cli snapshot plate.solidcraft --out glass.png --body "Plate:opacity=0.25" --body "Pin:color=#1a7f4b"
A body drawn see-through is drawn see-through everywhere a picture is drawn, not only here:
an appearance with an opacity below 1 (Glass - Clear in the library, or any opacity you
assign) now reads as glass in snapshot, in MCP screenshots and in a design’s thumbnail, as it
always has in the window. A face painted with a see-through look of its own still draws at its
body’s opacity.
Windows PowerShell 5.1 drops the double quotes inside an argument it passes to a program: write them as \" there.
A command that fails stops the script. A feature can also fail later, when a parameter change
rebuilds it; run then exits with status 1, lists the failing features and writes none of
its --out and --save files, because the bodies lack what those features should have made.
--allow-errors writes them anyway.
run prints each command’s result with --results (otherwise the measurement), and eval
prints the measurement, as JSON on stdout. --json-out FILE writes that JSON to a file
instead. The file is plain UTF-8, which is not what a redirect gives you everywhere: Windows
PowerShell’s > re-encodes a program’s output, as UTF-16 or as UTF-8 with a byte-order mark
depending on its settings, and strict JSON readers such as Python’s json.load then refuse it.
cargo run --release -p solidcraft-cli -- run examples/bracket.json --results --json-out results.json
cargo run --release -p solidcraft-cli -- eval examples/bracket.json --json-out measures.json
To find the slow steps of a script, --timings prints how long each command took instead of
the measurement: {"timings": [{"index", "command", "ms"}], "total_ms"}, with index counted
from 0. --profile adds each command’s profile: {"boolean_ms", "tessellate_ms", "other_ms"},
its wall time in the kernel’s booleans, in tessellation and in everything else. With
--results as well, the results go in the same object as results; --results alone still
prints a plain array.
cargo run --release -p solidcraft-cli -- run examples/bracket.json --profile
A design’s history turns into a script that rebuilds it, which makes a bug that only shows on one design a one-file reproduction:
cargo run --release -p solidcraft-cli -- script part.solidcraft --out repro.json
cargo run --release -p solidcraft-cli -- eval repro.json # the same part
The script starts with the user parameters (parameters.add) and the design’s units, sheet
metal and plastic rules and materials, then adds each timeline feature with timeline.add,
exactly as the design file holds it: same ids, names and parameter names, so later steps
still find the sketches and bodies they use, and a feature that failed in the design fails
the same way. Edit a step’s values to try a change, or delete the steps after a feature to
see the design as it was there. Appearances, folders, named views and configurations are
left out (the CLI notes which), and designs with components or joints are not supported
yet. In the app and over MCP the same script comes from timeline.export_script.
Units are millimetres and degrees unless an expression says otherwise ("1 in", "30 deg"),
and Z is up.
Name and compatibility
SolidCraft was renamed from an earlier working name. Since 0.5.0 everything uses the new
name: the solidcraft and solidcraft-cli programs, .solidcraft designs, SOLIDCRAFT_*
environment variables, settings folders and the solidcraft MCP server. Upgrading keeps
working: designs, settings, recovery files, environment variables and CLI/MCP setups from
earlier builds carry over. Every compatibility shim is listed in the
compatibility notes.
Use the SolidCraft documentation and web-app URLs. GitHub redirects links to the renamed repository; old-name Pages and Flatpak URLs are not maintained as permanent aliases.
A tour of the window
-
Title bar. On Windows and Linux the application bar is the window’s title bar. On the left: the File menu (New, Open, Save, Save As, Export…), undo and redo, the Home tab (the start page) and one tab per open design (each with its own undo history, selection and camera; a dot marks unsaved changes). On the right: Report a bug, Help (?), Preferences (which hold the theme) and the window’s buttons.
-
Toolbar. The workspace switcher (DESIGN) sits on the left. The tabs (MODEL, SURFACE, ASSEMBLE, INSERT, INSPECT, MANAGE, VIEW and SHEET METAL) hold sections of commands. SKETCH appears while editing a sketch; SHEET METAL is always there and is highlighted for the active sheet body. Both select automatically when entered, and the other tabs remain available. Click a panel’s name for all its commands. Drag a command from the dropdown onto a panel’s label or empty space to append it; the insertion line shows its destination. Drop onto an existing button to insert before it. Buttons can also move between panels, and the layout is saved.
-
Browser (left). The design as a tree: document settings and units, named views, the origin, bodies, sketches, construction planes, canvases, joints and components. The eye shows or hides an item, a component’s radio button makes it the active component, and right-clicking any row gives that item’s menu. Right-click the design or a component and choose Expand All or Collapse All to open or close every folder under that row (the design row stays visible when the whole tree is collapsed). Rows drag into groups and other components.
-
Viewport. Click to select, Shift or Ctrl to add (Preferences › Selection), drag for a window or crossing box. While sketching, Ctrl/Cmd or Alt/Option suppress auto-snap by default; Shift prefers a midpoint; the pointer also snaps to where two curves cross. To reach something under something else (two lines drawn on top of each other, coincident points, an edge in front of a face, overlapping bodies), hold the left button still for half a second (long-press on a touch screen) or right-click › Select Other…: a list of everything under the cursor opens there, the sketch being edited first. Hovering a row lights it up; clicking it selects it (Shift or Ctrl adds), and in a command’s dialog it goes to the active selection box. Esc or a click elsewhere closes the list. Clicking again on the same spot, with nothing changed in between, selects the next thing in that list. The wheel zooms at the cursor, the middle button pans and right-drag orbits freely (the mouse preset in Preferences switches to SolidWorks, Inventor, Alias, Blender, Tinkercad, OpenSCAD or Touchpad style). On a trackpad, pinch zooms and two-finger scroll pans (Navigation › Two-finger scroll pans; off: scroll zooms like the wheel); mouse wheels zoom and the thumb wheel pans sideways. With the Fusion preset and two-finger pan enabled, hold Shift while scrolling with two fingers to orbit freely instead of panning. No click is needed. Releasing Shift returns to panning, even during the same gesture. Shift does not snap this gesture to standard views; orbit gestures do nothing on flat 2D pages. Other presets keep their existing scroll behaviour. Shift+middle orbits freely in the default and Inventor presets. In every preset, Ctrl+left drag (Cmd on macOS) that starts on empty space orbits; a Ctrl+click still adds to the selection. Shift+right-drag snaps to the nearest standard or diagonal view; Shift does not request snapping when the preset already requires it to orbit.
Enable Preferences → Navigation → Free orbit to rotate the mouse and 3D mouse continuously through both poles, preserving roll. During an orbit drag, Shift snaps the view direction and the picture rotation to the nearest quarter turn; releasing Shift resumes the unsnapped rotation. Free orbit defaults to off for every mouse preset. With SolidWorks navigation, right-click a planar face and choose Normal to Selection to use Look At (also Ctrl+8); repeat to flip the side, preserving scale and projection.
Mouse gestures (Preferences › Navigation, off by default) work with the SolidWorks mouse preset only (the wheel orbits, Ctrl + wheel pans) and take over the right button, which then no longer orbits: hold it and move the pointer 4 pt to open a ring of tools (280 pt inside, 350 pt outside), even while a tool is active, and touch a tool’s zone to take it. A right-click that does not move still opens the usual context menu. Each studio has its own tools, and Customize… sets how many tools a studio’s ring holds and in which place. Zones alternate between two tints, three when the number of tools is odd. The optional radial context menu is a separate setting, and gestures take the right button from it while they are on.
With Touchpad selected in Preferences › Navigation, hold Shift and move the pointer to pan, or hold Alt (Option on macOS) and move it to orbit—no mouse button is needed. Vertical two-finger scrolling zooms in this preset, regardless of the saved two-finger pan setting; switching presets restores that setting. Release the modifier to stop navigating. Shift+click and Shift+left-drag still select. Combined Shift+Alt or Ctrl/Cmd modifiers do not start pointer-only navigation. The axis view shows the world’s X/Y/Z directions in SolidCraft’s colors. Click a signed axis tip to look along that axis; click the aligned tip again to look from the opposite side. Drag the axis view to orbit around the view centre, with Shift for snapping. Top and bottom keep the nearest quarter-turn heading. The house, the navigation bar’s Home button and a middle-button double-click in the view go home, framing what is shown now (Navigation › Middle double-click can make it Fit, which keeps the orientation, or turn it off). Interface › OK and Cancel in the view’s corner puts a large check and cross at the view’s top right while a dialog or a sketch is open, and moves the axis view or cube down below them. With a dialog open they do what its OK and Cancel do (the check is grey whenever OK is); in a sketch without a dialog they are Finish Sketch and Cancel Sketch. Right-click for the marking menu: eight commands around the cursor, plus a menu for whatever is under it. Near the window’s bottom or right edge a menu opens above or to the left of the cursor, so it stays in view. Shaded views omit certified smooth joins between spline spans on extruded walls. Real creases, cap outlines and authored face splits remain visible. Wireframe retains its edge presentation, and hiding a join does not remove its CAD identity or change the exported solid. Unsupported or uncertain wall geometry keeps its existing edge presentation.
-
Dialogs dock on the right with a live preview. Most features also show drag handles and a value box in the viewport. Preferences › Interface › Dialogs › In the left column opens them in the browser’s place instead (and the History’s, when it is at the left), at least as wide as that column; the column comes back as it was when the dialog closes. Interface › Dialog buttons › Top puts OK, Cancel and Apply in the dialog’s title row, right of the title, where they stay while sections open and close; Cancel then also takes the place of the close button. Enter and Esc work as before.
-
Timeline (bottom). Every feature in order. Double-click to edit, drag the marker to roll back, and drag features to reorder them. Preferences can put the timeline vertically at the far left of the viewport, beside the browser. Its seam handle switches between icon chips and compact feature names; the browser can collapse to a rail beside either width. Previous, Play and Next sit at the top. Double-click Previous to roll to the start. Interface › Sketches › Under their feature lists a sketch in the vertical timeline one level in, under the first feature built from it, behind that feature’s fold arrow. Dragging the feature takes its sketch along. A later feature built from the same sketch gets a row that selects it, and while the marker sits between a sketch and its feature, or the two are in different folders, the sketch keeps a row of its own. The timeline along the bottom always lists features in creation order.
-
Command search (S). Click the ribbon search field or press S to open the search popup below it. Results group commands by their ribbon path and show matching features and user parameters from this design. With an empty query, recent commands appear first, followed by pinned commands in the Commands group. Up/Down choose, Enter runs, Tab fills the selected name, and Esc closes. A feature opens its existing editor; a parameter opens Change Parameters filtered to its name. To run with explicit values, enter an id and JSON object, for example
solid.extrude {"distance": 20}. The popup validates JSON as you type and shows parser errors. Shift+Enter runs it and keeps the popup open. Right-click a command for its existing pin and toolbar actions.
Preferences (File › Preferences) hold the theme, workspace layout, the units of new designs, the mouse preset, the visual style and autosave. Graphics › Colour vision gives the colours that carry a meaning (sketch states, axes, bends, analyses, icons) values for a weak red, green or blue sense: Protan, Deutan or Tritan. In a mode, the faces Draft and Accessibility Analysis mark as failing are striped too. Command messages and errors show for a few seconds in the status chip at the bottom left of the view. Click the chip, or choose Help › Messages…, for every message of the session: newest first, errors in red, each line selectable, with Copy all for a bug report. Help › Keyboard Shortcuts lists every key, and File › Keyboard Shortcuts changes them.
Workspace layout
General › Workspace layout switches between Floating cards and Compact panels. Compact joins the workspace selector and ribbon into one top bar, puts Browser flush underneath with a square join, and docks tool sheets below the compass. Tool sheets remain draggable: their four corners round away from the right edge, and their two right corners square when docked there. Browser keeps only the height its contents need. Assistant remains on the outside edge, and History keeps its chosen bottom, left or shared Browser column. The choice is saved between launches. Switching preserves the current tool, camera, selection and design; Cancel and Restore Defaults work like other preferences.
General › Show text under ribbon icons is a separate, saved option for either layout. Turn it off for shorter icon-only command buttons; tooltips, section names and command menus remain available.
Interface › Layout › Show origin and bodies at the top (off by default) puts the active component’s Bodies folder and the Origin (planes, axes and point) at the top of the History when it sits on the left or under the browser and is wide enough for names. They are the browser’s own rows: the same eyes, folds and right-click menus, so one list is enough for single-part work. The History at the bottom is unchanged.
Colour themes
Interface › Theme lists Dark, Light and System (follows the operating system), the built-in themes and any themes you have imported. A choice applies at once. Under the themes, Viewport draws the 3D view in the Light or Dark theme’s colours whatever the window uses, for example the model on a light view in a dark window (Follow theme, the default, keeps the theme’s own). The built-in themes are Gruvbox (dark and light, each in hard, normal and soft contrast), SolidCraft 95, Platinum 9, Cupertino, Green Phosphor, Amber Phosphor, Blueprint, One Dark, Tokyo Night, Solarized Light, Nord, High Contrast, Graphite, Nosferatu, Catppuccin Mocha, Okibi and Akari (dark and light, from the Okibi design system), Clay and Clay Dark (warm ivory paper and warm charcoal, with a terracotta accent and rounded corners), and 1-bit and 1-bit Dark (pure black and white with square corners: black on white, or white on black, with selections drawn inverted).
A theme is a small JSON file that starts from the Dark or Light palette and changes any of its colours and corner radii by name:
{
"format": "solidcraft-theme/1",
"name": "My Theme",
"base": "dark",
"colors": {
"panel": "#282828",
"text": "#ebdbb2",
"accent": "#83a598",
"viewport_top": "#32302f",
"viewport_bottom": "#32302f",
"axis_x": "#fb4934",
"axis_y": "#b8bb26",
"axis_z": "#83a598"
},
"radii": {
"radius_sm": 2,
"radius_md": 4,
"radius_lg": 6,
"radius_xl": 8,
"control_radius": 4,
"sketch_mode_radius": 4
}
}
Colours are #rrggbb, or #rrggbbaa for translucent ones. A few colours, such as the four
Continuity Analysis seams (continuity), take a list. Any colour the file leaves out keeps the
base palette’s value. axis_x, axis_y and axis_z colour every X, Y and Z axis: the origin,
the view triad, the navigation cube and the Move gizmo. A file from an earlier version may set
triad or gizmo instead; it still reads, and those colours now go to every axis.
radii is optional. Its six keys above accept integers from 0 to 32 in logical UI points;
zero makes square corners. Omitted keys keep today’s base values: 4, 6, 8, 10, 6 and 7,
respectively. The first four style small accents through large surfaces; control_radius
styles form controls, and sketch_mode_radius styles sketch creation buttons. Themes own
these settings, with matching shapes across light and dark variants. Dock joins remain
square, circular controls remain circular, and control sizes and spacing stay the same.
Platinum 9, 1-bit, 1-bit Dark, and the phosphor themes use square corners, SolidCraft 95 uses subtle 3-point
corners, Blueprint uses the standard radii, Cupertino uses softer corners, and Clay and Clay Dark use rounded corners. Other built-in
themes retain the defaults.
Tab accents also have separate fill, small-text ink and tint keys. Sketch uses
sketch_accent, sketch_ink, sketch_soft; Surface, Mesh, Sheet Metal and Render
use <tab>_fill, <tab>_ink, <tab>_soft (sheet for Sheet Metal). Form uses
form_fill, form_tab_ink, form_soft because form_ink names the general form
text. Model keeps accent, selected_ink, accent_soft. The older
surface_accent, mesh_accent, form_accent and render_accent keys retain their
existing roles and values. tab_on_fill_dark is the dark text fallback for tab
pills when on_accent has less than 4.5:1 contrast with the fill. See the
per-tab token reference for all pairs.
- Import… adds a theme file and turns it on. Importing a file with the same name again updates that theme. A file with an unknown colour name or a malformed colour is refused, and the message names every problem.
- Export… saves the theme on now as a complete file listing every colour and radius. It is a good starting point for your own theme.
- Remove deletes the selected imported theme.
Retired window-band and procedural icon colours in older exports are discarded on import. Command icons use the shared SVG palette.
Imported themes are kept with your preferences, so they work in the browser app too. Colour vision modes still apply on top of any theme.
Graphics › Frame rate shows frames per second in the view’s bottom-right corner. Click it, or turn on Frame rate details, for the performance overlay:
- a graph of the last few seconds of frames, one bar per frame, with lines at 60 and 30 frames per second. The triangles drawn are a line over it, and a tick at the top marks each frame that rebuilt the scene, so you can see how geometry changes the frame time;
- a histogram of frame times, the 1% low (the mean of the slowest 1% of frames) and the worst frame, which show stutters that an average hides;
- what the GPU drew last frame (triangles, line segments, draw calls), the viewport’s size in pixels with its MSAA and supersampling, the time the interface took, the last scene build and the graphics backend and adapter.
While the readout is on, the view redraws every frame. With vertical sync the frame rate tops out at the display’s refresh rate; the interface time and the GPU counts still show the work.
Navigation › Smooth wheel zoom is on by default. Turn it off to apply each wheel or trackpad event immediately, with no smoothing tail. It is independent of Animate view changes, which controls axis views, Look At, Home and Fit. Re-enabling smoothing during a wheel gesture takes effect after that gesture ends, avoiding leftover zoom being replayed.
Navigation › Alias uses Alias’s camera keys: Shift+Alt+left drag orbits, Shift+Alt+middle drag pans and Shift+Alt+right drag zooms (drag down to move away, up to move closer). Without Shift+Alt, a left drag still selects with a box and drags sketch geometry.
Some Linux desktops take Alt+drag for themselves (moving or resizing windows), so the viewport never sees it. To free it:
- KDE Plasma: System Settings › Window Management › Window Behavior › Window Actions, set Modifier key to Meta (or None).
- GNOME: run
gsettings set org.gnome.desktop.wm.preferences mouse-button-modifier '<Super>'(or set Tweaks › Windows › Window Action Key to Super).
Shift-orbit snapping includes 45-degree edge views and isometric corner views, with a lightly damped spring transition and a small boundary margin to prevent flicker.
On desktop, Ctrl/Cmd+Plus (or Equals) enlarges the interface, Ctrl/Cmd+Minus reduces it, and Ctrl/Cmd+0 restores 100%. These shortcuts update the UI scale preference. The UI scale slider in Preferences applies the new layout when released, so it stays still while dragging.
Theme files can also define borders: primary outer outlines, secondary major section
rules, tertiary small dividers, container solid preview/card/keycap outlines, and independent
control outlines. Each accepts a width
from 0 to 4 UI points and a color in hex. Optional primary_inset adds a companion outline
inside panel chrome, with a gap from 0 to 12 points. Missing settings keep existing borders.
Blueprint uses a single white outer outline, dotted section lines and dark minor dividers. Secondary rules optionally accept pattern: "dotted"; the default is solid. Focus, selection and
error outlines retain their semantic colours. See the UI-kit border hierarchy for the schema.
SolidCraft 95 also defines optional borders.bevel: a per-band width and the
highlight, light, shadow and dark_shadow colours. The shared UI uses these
for raised panels/buttons, recessed fields and reversed pressed-button lighting.
Themes that omit this setting keep their existing flat borders.
Theme files may also set icon_palette: opaque colours for SVG display roles,
and three-colour pale/medium/strong ramps for Solid, Surface, Mesh, Form, Sketch,
constraints and construction. These settings colour the existing command artwork
without changing its shapes. Omitted roles keep the default light/dark palette.
Sketching and constraints
A sketch lies on a plane: an origin plane, a construction plane or a planar face. Start one with
Create Sketch (sketch.create) and click a plane, or pick a face first. Right-clicking a
plane, in the view or on its browser row, offers Create Sketch on that plane too.
Finish Sketch returns to the model. Cancel Sketch (sketch.cancel) returns without
keeping what changed since the sketch was created or opened for editing: a new sketch is removed,
an edited one goes back to how it was, as one step that Undo brings back.
Opening a sketch turns the view to face it. When the sketch ends, the view stays where it is by default. Preferences › Sketch › When a sketch ends changes that: Return to the previous view goes back to the view from before the sketch, and Return unless rotated goes back only when you panned or zoomed the sketch view but didn’t turn it.
In scripts, XY, XZ, YZ and their Top, Front, Right aliases name origin planes.
These names are reserved: a construction plane cannot be created with or renamed to one
(case-insensitive). If an older design already has a construction plane with such a name, a
string argument naming it is ambiguous and is rejected; use the other alias (XY for Top)
for the origin plane, the construction feature’s numeric ID to select it, or an explicit
{origin: [x,y,z], normal: [x,y,z]} plane. Existing saved references retain their meaning;
opening a design does not reinterpret old sketches.
Curves adapt to the current zoom and display scale. Picking and highlighting use the same visible boundary, including small circles at Fit and sketches in rotated components. Profile shading follows those curves while preserving holes. Zooming changes the display without changing sketch geometry, dimensions, or undo history. On short windows or enlarged displays, the Sketch Palette’s options scroll while its constraint status and Finish Sketch button remain visible.
Geometry
Point is available directly on the default Sketch CREATE toolbar. Click it, then click in the sketch to place a point. Existing toolbar customizations are kept; use Reset Toolbar from the toolbar’s context menu to restore the default buttons.
Lines and polylines (L), rectangles (two-point R, three-point, centre), circles (centre C,
two-point, three-point, tangent), arcs (three-point, centre, tangent), polygons, slots, ellipses,
fit-point and control-point splines, conics, points and text. Construction (X) turns curves
into reference geometry that doesn’t form profiles. Modify tools include trim (T), extend, break,
fillet, chamfer, offset (O), mirror, patterns, move/copy and scale. With trim on, click a
piece to remove it, or press and drag a path across several pieces: the ones it crosses light up
and all go when you let go, in one undo step (sketch.trim.path).
Tools with several variants (line and midpoint line; two-point, three-point, centre and rounded rectangle; the circle, arc, slot, polygon and spline variants) share one toolbar button with a drop-down. The button and the family’s shortcut start the variant you picked last, so a shortcut such as L can start a Midpoint Line; pick the variant you want from the drop-down to change it (Keyboard shortcuts).
Once a shape’s first point is down, value boxes (a line’s length and angle, a rectangle’s width
and height, a circle’s diameter…) follow the cursor. Typing goes into the first box, Tab moves to
the next and Enter places the shape; each typed value becomes a dimension. A value is an
expression (25, 1 in, d1 * 2), or width = 20 (also #width = 20), which also adds the
user parameter width and dimensions with it, as when placing a dimension. A
box that does not become a dimension (a polygon’s sides, a rounded rectangle’s corner radius)
takes a value only.
The Sketch Palette’s Construction Geometries option hides construction curves while you work. A hidden construction curve cannot be picked by a click or by a box. Nothing invisible is selectable. The option belongs to one sketch and is saved with the design. Centerlines are a line type of their own and keep showing. Projected geometry that carries the construction line type, such as the trace of a plane, answers to Projected Geometries instead. The option changes display only. Construction curves were already outside every profile.
Polygon tools show their edge outline while positioning the second point, so the orientation can be checked before placement. Inscribed and circumscribed polygons retain their support circle and update the outline when the inline Sides value changes.
Text is set in the built-in font (Liberation Sans) unless it is given another: sketch.text and
sketch.edit_text take a font, the family or full name of an installed font (“DejaVu Sans”) or the path
of a .ttf / .otf file, and the Edit Text dialog has a Font field for it. An unknown font is an
error, and the font is kept with the text in the design, so the text stays editable in it. SOLIDCRAFT_FONT_DIRS
adds folders to the ones searched for installed fonts. A text’s height, its angle and each coordinate of
its at can be expressions of the design’s parameters, given as strings ("height": "plate_height / 10",
"at": ["plate_width / 2", 5]): the text keeps the expression and follows the parameters when they change, and
saves it with the design. A number takes the place of an expression, and so does moving the text or dragging its
handles (what moves becomes a number; turning a text leaves its height as it was). A string that names no
parameter is the number it comes to. The same two commands refuse a text that is not a string, an at that is
not two numbers or expressions, a height or angle that is neither (or a height that is not positive) and an
expression that cannot be worked out with a message, instead of drawing the text with a default; left out, they
keep their defaults.
Right-click a letter’s filled interior or outline and choose Edit Text. You can also
start Edit Text first and click the letter. All outlines belonging to that text are
highlighted while the dialog is open. A profile bounded by multiple text sources or other
geometry cannot identify one text: use Select Other to choose its outline instead.
Change the text, font, baseline X/Y, height or angle, then confirm with OK; Cancel
keeps the original text. Position and height use the document’s units. The four fields take
expressions: one that names a parameter (plate_width / 2) stays an expression, is shown as typed
when the dialog is opened again, and keeps the text following the parameter.
To work on the letters’ shapes themselves, right-click the text and choose Explode Text
(sketch.break_link with the text’s link). Its outlines stay where they are as ordinary
lines, arcs and curves that can be picked, trimmed, dimensioned and extruded one by one; the
text itself is gone, so it can no longer be edited as text or follow parameters. Undo brings it back.
A text can follow a curve of its sketch (its Path). That curve becomes construction: the letters stand on it and their descenders cross it, so as a normal curve it would cut them into pieces, and picking the text would pick those pieces. Taking the text off the curve leaves it construction; Normal/Construction turns it back.
Drag a text outline to move all its letters together. Let go to finish in one undo step,
or press Esc during the drag to restore the original text. The text stays editable.
For an exact displacement, select an outline and choose Move/Copy from its context menu.
Enter Distance X, Distance Y and an optional Angle about the text’s baseline;
lengths accept document units or explicit units such as 1 in. Enable Create Copy to
leave the original in place and create a separately editable text. Cancel leaves the
design unchanged. Projected geometry remains protected from ordinary dragging.
Project (P) and Intersect bring model edges into the sketch as linked geometry that follows later changes. Sketching on a face projects its edges automatically. A projected edge stays the edge that was picked, wherever a later change moves it; when that edge no longer exists, the projection keeps its last shape and the sketch says it was lost. For Project, select visible source curves or points from another sketch, model edges, faces, vertices or bodies, or axes and planes, then press P. The entire supported selection is projected in one undo step. A rejected projection keeps the selection for correction; an empty or mixed selection starts the ordinary picking tool. External sketch selections retain their source sketch, even when the active sketch uses the same point or curve names.
A sketch on a construction plane built from the model (through a sketch line, through three points, midway between two faces…) projects, intersects, includes and holds text like a sketch on any other plane, and its links follow when a parameter moves that plane.
Run from a script or the control channel, every command that adds sketch geometry returns the
ids of what it added (created: {points, curves, constraints, dimensions}). Ids are stable and
never reused within a sketch, even after a delete, so a script can dimension or constrain the new
geometry right away.
Extend finds intersections with exact ellipses, rational conics and spline spans. It keeps the original line or circular support and extends only beyond the selected endpoint; conflicting constraints or an ambiguous overlap leave the sketch unchanged.
Closed convex chains of tangent lines and circular arcs support inward offsets that consume a rounded corner. When the offset passes the corner radius, the neighbouring straight supports meet at a sharp corner. The surviving lines and endpoint IDs stay stable; the retired arcs return under their original IDs if a later edit restores their radius. Whole-boundary profile selections follow this transition through save and reopen, so an extruded lip can remain associative. Existing eligible closed analytic offsets upgrade without changing their saved geometry or IDs.
This transition requires a closed convex boundary with tangent junctions and a certified nonempty region. An extinguished arc referenced individually by a constraint or another offset cannot retire. Creating another offset from an already sharp retired boundary also requires breaking its association first. Open, nonconvex and mixed free-form chains keep their existing behavior. Break Offset Association freezes the current boundary, including private arc centres and construction flags, for independent editing.
Offsets of smooth ellipses, conics and splines follow their source after dimension changes,
dragging and reopening a design. Their stored cubic spline has a whole-span normal-distance
error bound of 0.002 mm. Singular tangents, curvature cusps, possible self-intersections and
excessive subdivisions are refused. Offset these curves individually: mixed chain junctions
currently require separate geometry. sketch.offset.break releases an offset for independent
editing, and undo restores its association. Older independently sampled offsets remain independent.
The fit points of such an offset stay hidden, except the two ends of an open one: lines and
arcs snap to them and stay coincident with them as the offset follows its source, so the gap
between a spline and its offset can be closed into a profile.
Smooth open and newly created smooth closed offsets can be offset again, including bounded
refinement after a source edit. New closed outputs use a periodic cubic fit with continuous
tangent and curvature at the seam. Break Offset Association preserves that interpolation,
and its closing point remains joined when dragged. Deleting an output also removes its
unused private fit points; deleting a source with a retained dependent is refused.
For explicit closed fit splines, sketch.spline.fit_point accepts periodic: true and at
least three distinct cyclic samples. It closes the loop automatically. Existing calls and
older saved closed splines retain natural interpolation; older associative closed offsets
also retain their saved natural fit. A legacy seam without a unique normal still refuses
another offset. Periodic intent is stored on each curve and survives save, reopen and Break.
While drawing a fit spline, click its starting point after three distinct samples to close
it smoothly. Right-click finishes an ordinary natural fit. Offset accepts both ellipse
handles and dense fitted curves; the second click sets the side and distance from the curve.
While drawing in a planar sketch, Construction (X) changes the line type without ending the tool or losing picked points and inline values. The preview and hover feedback keep construction dashes and centerline dash-dot patterns visible. With curves selected, Construction changes those curves as before.
The pointer snaps to existing sketch points, to a line’s midpoint, and to where two
curves cross (two lines, a line and a circle or arc, or two circles or arcs). The snap
hint shows X. The new point stays on both curves when either one moves. Hold Shift
to prefer a line midpoint instead; hold Ctrl/Cmd or Alt/Option (the default suppress chord)
to stop snapping for that gesture. Scripts pass "x:<curve>+<curve>:x,y" as a point
argument, the same way "mid:<line>" and "on:<curve>:x,y" work.
Enable 3D Sketch in the Sketch Palette to draw lines, fit splines and points off the plane.
While placing these points, you can snap to points on other visible sketches. For a bridge
line, start Line and click an endpoint on each source sketch; each endpoint uses its source
plane, even when that plane differs from the active sketch. These snaps capture positions;
they do not create associative links to the source points.
New 3D geometry follows its sketch’s component placement, including the selected occurrence
and pending position changes. Click a drawn fit point to show its move triad; its arrows move
along world X, Y and Z. Scripts also supply world coordinates to sketch.line3d,
sketch.spline3d, sketch.point3d and sketch.move3d. Activate the desired occurrence before
editing an explicit sketch belonging to a component with multiple placements.
Older drawn or detached 3D wires keep their saved world positions. Included and surface-linked wires with an explicit component marker keep that meaning after the link is broken. Old files whose links became lost after an incorrectly scoped cross-component refresh may require a fresh link once the source is available; their stored point values are preserved on opening.
Constraints and dimensions
Horizontal/Vertical accepts a click on a line or clicks on two distinct sketch points, including the origin, endpoints and circle centres. For two points, it chooses horizontal or vertical alignment from their current positions. Hold Shift near a line’s midpoint to highlight and select it as either point. The midpoint follows both endpoints as the line moves, resizes or rotates. Two line midpoints can also be aligned. Hovering and the first pick are only previews; Esc cancels them without changing the sketch. Accepting the second pick reuses or creates a sketch point with an ordinary Midpoint constraint, then adds the H/V constraint in one undo step. Both constraints can be selected and deleted independently; deleting either keeps the point. After a fillet or chamfer, its Midpoint constraint refers to the original full side, retained as construction geometry, just like a manually constrained midpoint.
Scripts use the same command, with a midpoint reference in the points array:
{"command":"sketch.constraint.horizontal_vertical","params":{"points":[{"midpoint":"l1"},"c1.center"],"mode":"vertical"}}
mode can be horizontal or vertical; omitting it chooses from the current positions.
The solver is SolidCraft’s own (damped Gauss–Newton with degree-of-freedom analysis). Constraints:
coincident, horizontal/vertical, parallel, perpendicular, tangent, equal, concentric, collinear,
midpoint, symmetry, fix, curvature (G2) and polygon. Sketch Dimension (D) adds linear,
aligned, horizontal, vertical, point-to-line, line-to-circle, point-to-circle, circle-to-circle,
radius, diameter, arc length and angle dimensions. For an arc’s length, click the arc and then one of its end points. Each is a parameter
(d1, d2, …), so it can hold an expression.
Press D, click what you want to measure and type the value: as soon as the click settles the
dimension (one line, circle or arc, or the second point or line of a distance or angle), its
value box takes the keyboard with the measured value selected, so typing replaces it. Numbers,
units (1 in), expressions (2*width) and parameter names all work; letters go into the box,
not to tool shortcuts. width = 40 (or #width = 40) defines the user parameter width and dimensions with it,
in one step. While you type, the dimension text follows the cursor, and Enter places
it there (or in its default place if you haven’t moved the cursor) as one undo step. Esc
cancels the dimension.
You can still place the text first. Before you type, a click on empty sketch space puts the text there and keeps the value box focused; a click on another point or line makes the dimension a distance or angle instead; Tab leaves the picks waiting for a click to place the text.
To change a dimension you already have, double-click it, or, with Sketch Dimension on and nothing picked yet, click it once: its value box opens with the value selected, Enter applies, and the tool stays on for the next dimension. The dimension’s text always takes the click; its lines take it only away from sketch points and curves, which still start a new dimension.
Between two points, or on a line, that don’t line up with an axis, where you place the dimension picks what it measures: across from them, the distance between them; above or below them, the horizontal distance; beside them, the vertical distance.
A line and a circle or arc (click the line, then the circle’s edge, or the other way round)
measure from the line to the circle’s edge, square to the line: the gap between them, 0 when
the circle touches the line. When the line cuts through the circle, the dimension measures how
far the edge reaches past the line instead. An arc is measured to the circle it lies on. For
the distance to the centre, click the centre point instead of the edge. In scripts:
{"command": "sketch.dimension", "params": {"entities": ["l1", "c1"], "value": 5}}.
A point and a circle or arc measure from the point to the circle’s edge, along the radius through the point: 0 puts the point on the circle, and a point inside the circle measures how far within the edge it lies. Two circles or arcs measure between their edges, along the line through their centres: the gap between them (0 when they touch), how far they overlap when they cross, or, when one lies inside the other, the narrowest gap between them (the thinnest part of the wall). For the distance between the centres, click the two centre points instead.
A dimension the sketch can’t take, because the geometry is already fixed by other constraints and dimensions, is added as a driven (reference) dimension instead: it is shown in parentheses, measures the sketch, and has no value to edit.
For Coincident, pick two points, a point and a curve (in either order), or two distinct straight lines. Two lines join their closest pair of endpoints; the first picked line stays in place when the existing constraints allow it. Pick endpoints explicitly when you want to connect a different pair.
Horizontal/Vertical also applies immediately to one selected line or two distinct selected points. Shift-click to add the second point before choosing the constraint.
For Parallel, Perpendicular, Equal, Tangent, Concentric and Collinear, select exactly two sketch curves (Shift-click to add the second) and choose the constraint to apply it immediately. You can also start the constraint first and then pick its curves. An incomplete or mixed selection leaves the tool ready for ordinary picking.
Coincident accepts an exact selection of two points or a point and a curve. Midpoint accepts one point and one line, in either selection order, or two lines: the second line moves (keeping its length) until its midpoint sits on the first line’s midpoint, and stays centred there as either line changes. Symmetry accepts two distinct points and one symmetry line, regardless of where the line occurs in the selection. Successful constraints clear the selection; rejected constraints preserve it.
Under-constrained geometry is blue and fully constrained geometry turns dark, and the status shows the remaining degrees of freedom. A constraint or dimension that would over-constrain the sketch is refused rather than applied. Drag a point to move it; the solver keeps every constraint while you drag.
Profiles
Closed loops become profiles automatically, including regions split by crossing lines and islands inside outlines. Extrude, revolve, sweep and loft pick profiles.
A feature remembers a picked profile by the curves around it, not by where it was clicked:
"profiles": [{"curves": ["l1", "l2", "a1", "l3"]}] (or [{"curves": ["c1"]}] for a circle)
picks the region whose outer loop is made of exactly those curves, or the pieces of them that
other curves cut off. It is found again on every recompute, so the feature keeps the same region
when a parameter moves the geometry. Islands inside the region are its holes. When no region, or
more than one (both halves of a circle cut by a line), is bounded by exactly those curves, the
feature reports an error rather than taking another region. Profiles picked by index or by point
([{"point": [x, y]}]) are stored the same way when the feature is made.
Measurement units
Change the current design through Document Settings > Units in the browser.
The units preference applies to new designs. Existing sketch annotations, live drawing
lengths and Measure follow the current design units without resizing geometry.
Inches display at least three decimal places and retain a meaningful fourth digit;
a higher display precision can be selected in Preferences. Bare length values typed
in the dimension tool use the current design units. Explicit units such as 25.4 mm
remain valid in an inch design. Angular dimensions keep their angular units. Deliberately typed four-place inch literals
retain trailing zeros. Component/canvas distance controls and inspection, parts-library
and flat-pattern measurements follow design units too; parameter expressions and metric
standard names retain their explicit authored units.
Measure shows the currently selected distance (centre, minimum or maximum) and separate X, Y and Z distances on the canvas. The component dimensions follow the world axes and remain visible until the Measure dialog closes. To hide these extra lines and labels, turn off Preferences > Graphics > Show X/Y/Z distances. The choice is saved between runs.
Reviewing mirror, pattern, and offset results
Mirror, rectangular/circular Pattern, and Offset show the active selection step in their shared tool window. Click an input to change it; click a selected object again or Ctrl/Cmd-click it to remove it. Each input has its own Clear button. Source inputs show a count, and single references identify the chosen mirror line, direction, or centre. Box selection also works while the window is open. Sketch modify tools take geometry from the sketch being edited.
Proposed sketch geometry appears green before confirmation. Change the inputs or values to update the preview, then press OK. Cancel discards the preview. A circular pattern centre can be an existing point or a position clicked in empty sketch space.
Sketch patterns keep each instance related to the seed geometry: moving the seed updates the copies, and a copy cannot deform independently. Circle and ellipse radii follow the seed too. Choose a circle’s centre point to keep a circular pattern centred on that circle. A centre placed in empty space becomes a sketch point that can be dimensioned or made coincident. Double-click the pattern badge (at the circular centre or beside the rectangular seed), or right-click a seed or copy and choose Edit Sketch Pattern, to change quantity, spacing, direction or total angle. Confirmed edits preserve surviving instance ids; Cancel discards the preview. Pattern settings survive save/reopen and undo/redo. Deleting a seed, copy or pattern relation breaks that association and leaves the remaining geometry independent. Patterns saved by older versions remain independent copies; recreate them to add associations.
Offset distances are editable driving dimensions: double-click a dimension or change its model parameter to resize the offset without moving its source curves. The selected side, distance, and association survive save/reopen and undo/redo. Symmetric offsets share one driving distance; Two Sides has an independent distance for each side. Disconnected selected curves or chains are all offset. Breaking an association retains its current geometry and removes that association’s driver. Older offsets retain their saved fixed distances.
Solid features
For prerequisites, a readable options table and a first example, open the practical Extrude guide.
Features live on the timeline and rebuild incrementally from the first one that changed. Each one can be edited (double-click), suppressed, renamed, reordered (if nothing it depends on moves after it), rolled back to, and deleted. Deleting asks first when other features depend on it.
| Panel | Features |
|---|---|
| Create | extrude (one side, two sides, symmetric, taper, start offset, to a distance or through all; new body, join, cut, intersect; thin walls along curves), revolve, sweep, loft, box, cylinder, sphere, torus, coil, pipe, rib, web, emboss, hole (simple, counterbore, countersink, tapped; on faces, at distances from edges, or at sketch points), thread, rectangular/circular/path patterns, mirror |
| Modify | press pull, fillet, chamfer, shell, draft, scale, combine, split body, offset face, replace face, move/copy, align, remove |
| Construct | offset plane (at a distance or through a point), plane at angle, plane along path |
| Inspect | measure, section analysis, interference, compare bodies, curvature comb and map, zebra, draft, minimum radius, isocurves, centre of mass |
Extrude automatically selects Join for additions when the active component already has a
solid, including sideways elbow connections and screw bosses that meet only part of a face.
Extruding into existing material selects Cut. In an empty component it selects New Body;
choose New Body explicitly to keep an addition separate. Changing the default does not alter
operations already saved in a design. A Join (of an extrude, revolve, sweep, loft, primitive…)
whose material touches none of its target bodies still joins it, to the first one as a separate
piece, and the feature’s warning says so: “Body1 is now in 2 separate pieces”. Combine joins
the bodies it is given without that warning.
Extrude to a picked target (Preferences › Design, off by default) lets a pick choose the extent. With it on and a profile selected in the solid Extrude dialog, picking a face away from its plane, a curved face, an off-plane vertex or a parallel plane automatically switches Distance to To Object. Coplanar faces still join the profile selection; non-parallel planes are not targets. The extent can still be chosen manually. For a new extrusion without start or end offsets or a From Object input, the target also sets the stored distance and one-sided direction, so returning to Distance keeps that reach. Thin Extrude keeps its existing picks. With the setting off, picks only add profiles, as before.
A positive extrude taper slopes the side walls outward, so the profile grows along the
extrusion direction and the far end is larger; a negative taper slopes them inward, making
the far end smaller. For example, a 10 × 10 mm square extruded 10 mm with taper: "5 deg"
has an approximately 11.75 × 11.75 mm far face; with taper: "-5 deg", it is approximately
8.25 × 8.25 mm.
A taper slopes the sides of any profile, sketch text included: lines, arcs and spline or conic curves move in or out together, and a flat tip shorter than the taper closes to a point. On spline and conic sides the slope is approximate: the moved outline is a curve of the same kind within 2 % of the taper’s offset (or 10 µm) of the true one, a curve that turns too much for that being cut into pieces first; where a curve bends tighter than the offset, the offset folds and is smoothed over. A taper steeper than the profile’s details (a stroke, a curve or a gap narrower than it) is refused with that reason; a smaller taper or distance builds. A taper composes with a start offset (a drafted pocket that starts part way up a part): the offset moves the profile along the normal and the sides are drafted from there, so the profile is the section at the start whatever the offset. A taper with a two-sided or symmetric extent is not built yet and says so.
Thin Extrude (thin: true on solid.extrude, the Thin Extrude box in its dialog) builds a
wall of constant thickness along sketch curves instead of filling profiles: open chains (a single
wall) and closed loops (a ring), lines, arcs, circles, ellipses and splines. In the dialog, a
picked profile stands for the curves around it (its outline and its holes). The wall lies on the curves’ centre, inside,
outside, or on both sides with two thicknesses (side: center, inside, outside,
two_sides with thickness2). A closed loop’s inside is the area it encloses; an open chain’s
is the side it bends toward (the inside of a U), and a straight line’s is the left of its first
curve as drawn. Curves that meet end to end are one chain whose corners are mitred; walls that
cross or meet (a ring and a cross, a honeycomb) become one piece. Every extent, operation and target of an extrude applies, and a taper
for walls of straight lines or of whole circles. A wall thicker than the
radius of a curve it follows on the inside is refused. Spline walls follow the exact offset
within 0.0001 mm.
Editing an extrusion retains its signed distances, From/To Object inputs, taper expressions and explicit body targets. Revolve edits retain world, sketch, explicit-line and model-edge or face axes, both angle expressions and To Object targets. An unchanged All profiles selection continues to include profiles added later. Cancel restores the complete timeline; a successful edit is one undo step, including downstream parameter changes. Editing a placed component previews the edited feature before its downstream history. Cylindrical Emboss profile guides follow the sketch’s chosen occurrence frame.
Newly selected adaptive profiles store complete outer and hole source identities when the entire boundary can be qualified. Changing an ellipse or periodic offset after extrusion can then change its spline span count while keeping the same selected material region. A partial, intersected or ambiguous boundary cannot satisfy that whole-source reference; changing the hole identities also makes it lost. Older curve-span selections retain their exact stored multisets. If an older feature has already lost its profile after subdivision changed, edit the feature and reselect the intended current region to capture the new identity.
New symmetric solid extrusions treat the entered distance as the total thickness and split it evenly around the start plane: 10 mm spans -5 to +5 mm on an XY sketch. Switching from One Side to Symmetric keeps the entered total thickness. Through All still reaches targets in both directions, and Two Sides retains its independent distances. Symmetric extrusions saved before this change keep their original per-side distances when reopened or edited. Selecting another direction in the feature editor uses the current total-distance convention.
New symmetric revolves split the entered angle evenly around the sketch profile, regardless of which way the selected axis points. Two Sides measures the second angle backward and the first angle forward along the sketch-normal sweep direction. Profiles on opposite sides of an axis each use their own sweep direction. Designs saved before format 11 keep their original angular placement when opened, edited or copied; creating another revolve uses the new behavior. To Object follows that same direction for each selected profile, including curved target faces. Pick a target away from the axis so its angular position is defined.
For cylindrical Emboss, select a supported exterior cylinder face and a tangent sketch profile. Deboss cuts radially into the wall. Orthogonal line profiles, including holes and concave outlines, wrap with exact radial walls; the profile must stay clear of seams and ends. Complete cylinders and coaxial sleeves are supported. Capture pending component positions before applying. The feature stores the selected target and author occurrence paths, so edits and reopening retain the chosen instances. Other curved surfaces and free-form profiles remain unsupported. A new Emboss without an explicit face infers a unique eligible supported cylinder; select the face when the target is ambiguous. Older saved features without a face retain their planar placement.
A Hole with Single placement can also sit at distances from up to two straight edges of its face. After picking the face, click Reference 1 and pick an edge, then pick a second one into Reference 2. Each edge gets a Distance row, measured from where you picked; the canvas shows each one as a dimension from the edge to the hole’s centre, with a box beside it. Type a distance in either place and the hole moves so its centre is that far from the edge, measured in the face’s plane. Moving the hole measures the distances again. The hole keeps its distances when the part changes: it follows its edges, and the distances are parameters like its diameter.
A Hole with Single placement drills where you pick a planar face. A pick on or near a point of a shown sketch that lies on that face, such as a circle’s centre, places the hole exactly at that point, as Multiple placement picks it; Select Other (press and hold) also offers the face where you clicked. The corners of text outlines don’t count, nor do projected points while the sketch hides projected geometry. The hole keeps that place on the face; to have holes follow later edits of the sketch, place them with Multiple.
Two settings under Create Hole in Preferences › Design, both off by default, change how a Single hole is placed:
- Snap to face and edge centres: the face itself offers snap points too: its middle, and the centre of each round edge on it (a boss standing on it, a rounded corner, a hole already in it). While the cursor is over the face they show as small marks; a pick near one, or on or beside one of those round edges, places the hole at that centre, and the mark it takes is named. Hold Alt or Ctrl to place the hole exactly where you click.
- Move holes by clicking or dragging: each placed hole has a handle at its centre. Drag it to move the hole (it snaps when snapping is on), or click it to take the hole out. A pick elsewhere on a face that already has a hole moves the hole there instead of taking it out. Edit Feature shows the handle as well, so a hole can be moved later.
The Hole dialog draws a section through the hole as its options make it (type, extents, drill point, tapped) and puts each size in a field beside the dimension it sets; hovering a field, or typing in it, lights its dimension.
Tapped holes and Thread features have a Thread direction choice: Right-hand
(the default) or Left-hand. Editing and reopening keep the chosen direction. Tapped
holes carry a cosmetic thread; the separate Thread feature can also cut a Modeled
left-hand helix. Cosmetic direction is recorded with the thread annotation and does not
change the body. Scripts use handedness: "left" or "right" on solid.hole (with thread)
and solid.thread. Existing designs and omitted values remain right-handed. Changing the
direction keeps the pitch, thread length and axis; it does not change a hole’s drill size.
Fillets and chamfers handle straight edges between planar faces, whole smooth loops, runs of neighbouring edges with mitred or spherical corners, and closed chains of curved edges (a rolling ball). See the roadmap’s known limitations for the rest.
Curvature (G2) in the Fillet dialog’s Radius Type blends straight edges between two planar faces with an exact quintic section. The tangent plane and curvature match each side face: curvature goes to zero at both contact rails, unlike a circular G1 fillet. Each end must meet one planar face; perpendicular and sloped planar ends are supported. Curved edges/faces, multi-face ends, and intersecting corner blends are not supported; they return an error, not a G1 fallback. Equivalent Radius sets the same contact setback as a circular fillet (for a right angle, setback equals the entered value); it is not a constant curvature radius. Reduce/Taper to Fit are unavailable for this type.
Scripts and MCP use the same command, for example on a 20 mm box:
{"command": "solid.fillet", "params": {"edges": [[20, 0, 10]], "radius": 2, "type": "curvature"}}
type: "g2" is an alias. Existing fillets still default to circular, constant-radius G1 blends.
Feature references to faces and edges are stored by name, so an upstream edit that moves geometry keeps them on the right face (see Persistent naming). When a reference can’t be found, the feature warns instead of silently picking another face.
Plane Along Path accepts one sketch curve (including a drawn 3D wire) or model edge. Pick the path in the viewport and enter Position (0 to 1): 0 starts the path, 1 ends it, and 0.5 places the plane halfway along its length. The plane is perpendicular to the path’s local segment. Preview does not change the design; OK creates one editable timeline feature. The plane follows later path edits. Curved paths use their sampled polyline; absolute distance, reference-object placement and multi-curve chains are not yet available in this dialog.
Comparing two bodies
Compare Bodies (inspect.compare) lays two versions of one part over each other, such as a
part and the same part exported by another program, and finds the places where only one of them
has material. It works from the two surfaces, not from a boolean of the bodies, so it also
compares bodies that share almost every face, the case a boolean (Combine, Interference) handles
worst.
{"command": "inspect.compare", "params": {"a": "Bracket", "b": "Bracket (2)", "tolerance": 0.01, "color": true}}
Without a and b it compares the two selected bodies, or the design’s only two. Both bodies
are measured (volume, area, centre of mass and box, and difference: b − a of each). Each
surface is sampled at the corners of its triangles and at points at most spacing (0.5 mm)
apart on larger faces, and every sample measures its distance to the other surface: deviation
gives the largest and the mean distance each way. Samples further than tolerance (0.01 mm) form
the regions: only_in_a where a has material and b has none, only_in_b the other way
round. Each region gives its volume, the deviating surface area, the largest deviation and where
it is, its centre and box, and the faces of each body it lies on (faces_a, faces_b, numbered
as model.faces lists them), largest volume first. The volume is integrated along a grid of
parallel lines through the region (resolution gives their spacing), so a thin sliver is
measured as well as a block. identical is true when no region is found, and verdict says it in
one line. warnings lists what makes the result less certain: a body whose surface does not
enclose the volume it measures (open, or with faces that failed to import), and lines left out of
a region’s volume because they crossed a gap. With color, the faces that hold a region turn
red where only a has material and green where only b has, on both bodies: the faces mostly
beyond the tolerance (a small change on a large face leaves the face alone), or, for a region
with none, the face with the largest share.
That is one undo step; without color the command changes nothing.
The bodies are meshed within a quarter of the tolerance. The kernel meshes no finer than a
millionth of a body’s size, so keep the tolerance at least twice that (0.02 mm for a part 10 m
across). Large bodies are sampled more coarsely, at about max_points (eight million) points
each; sampling.spacing_mm says what was used, and a larger max_points (up to 50 million)
samples them finer, at the cost of time.
Editing a feature from a script
For solid.pattern.circular, axis accepts X, Y, Z, {origin, dir}, or an edge or
round face of the model, {edge: [x, y, z]} or {face: [x, y, z]} (a point on it).
An explicit origin must contain three finite numeric coordinates in millimetres
([x, y, z] or {x, y, z}); omitting it uses the world origin. Expressions and
construction-axis references are not supported. Invalid origins are rejected rather than
silently replaced with zero.
Axes and points on round edges and faces
Wherever a 3D command takes an axis (Revolve, Surface Revolve, Circular Pattern, Plane at Angle, and Move’s Rotate) you can pick a circular edge, which gives the axis through its centre along its normal, or a cylinder, cone or torus face, which gives that surface’s axis. Hovering one in an axis input draws the axis as a dashed construction line, and the line stays drawn once the axis is picked. Flat faces and edges that are neither straight nor circular can’t be picked there.
Revolve, Surface Revolve, Circular Pattern and Plane at Angle keep the picked edge or face as a
reference ({edge: [x, y, z]} or {face: [x, y, z]} from a script), found again by its
persistent name every time the design is evaluated. If a parameter moves the cylinder, the
feature turns about it where it is now. The axis always points the same way, with its largest
component positive, so a rebuild that reverses the edge doesn’t flip the turn. Straight edges
picked as axes keep working as before, as a fixed line.
Where a 3D command takes a point (the construction points, axes and planes built from points, Revolve and Extrude’s To Object, a surface patch’s pass-through points, and Move’s pivot and points), a circular edge stands for its centre. A hovered circle shows a small star at its centre. A construction point keeps the edge, so it follows the circle’s centre as the model changes.
timeline.inspect {feature} returns a feature’s definition: the fields timeline.edit changes,
with their current values. timeline.edit {feature, set} merges set into that definition
(objects are merged field by field, anything else replaces the value), rebuilds the feature and
reports recomputed and restored. The feature’s type cannot change, and sketch geometry is
edited with the sketch commands. Expressions are text ("30", "30 mm", "width / 2",
"45 deg"); a bare number is a length in the design’s units, or degrees for an angle.
{"command": "timeline.inspect", "params": {"feature": "Slab"}}
→ {"type": "ExtrudeFeature",
"definition": {"sketch": 1, "profiles": {"type": "all"}, "operation": "new_body",
"extent": {"distance": "10", "direction": "positive"}},
"inputs": [{"label": "Distance", "param": "d1", "expression": "10", "unit": "mm"}], …}
{"command": "timeline.edit", "params": {"feature": "Slab", "set": {"extent": {"distance": "12"}}}}
inputs lists the feature’s named inputs. parameters.change {name: "d1", expression} sets one
of those directly. Optional fields that are not set do not appear in the definition; they can
still be added through set. The fields of the common features:
| Feature | Fields |
|---|---|
| Extrude | extent: {distance, direction: positive|negative|symmetric, distance2, start_offset, through_all, taper, to, to_offset, from}; operation: new_body|join|cut|intersect; targets; profiles; sketch; thin: {curves, thickness, side: center|inside|outside|two_sides, thickness2} |
| Revolve | angle, angle2, symmetric, axis, to, operation, targets, profiles, sketch |
| Fillet | radius, edges (a point on each edge), body, style |
| Chamfer | distance, distance2 or angle, flip, edges, body |
| Box | length, width, height, corner, operation |
| Cylinder | radius, height, base, axis, operation |
| Hole | diameter, depth, position, direction, hole (simple, drilled, counterbore, countersink), thread, to, references (an edge point and a distance each) |
| Shell | thickness, faces, direction, body, tangent_chain |
| Draft | angle, faces, neutral, pull, body |
| Offset face | distance, faces, body |
| Pattern | pattern (its kind and counts/spacing), features, bodies |
| Mirror | plane, features, bodies, combine |
For any other feature, timeline.inspect shows the fields.
Where a command takes a face as a point (face.delete, solid.shell, solid.draft,
solid.offset_face, solid.split_face, the faces of solid.replace_face, and the face of
sheet.convert), a script can give the face’s name instead, as model.faces lists it:
{"command": "face.delete", "params": {"faces": ["F2:side#1", "F2:side#2"], "body": "Plate"}}.
model.faces also lists a point on each face that picks it (the centroid of a face with a hole
in it is in the hole, that of a half cylinder inside the cylinder) and, for a cylindrical face,
its cylinder: origin and axis of its axis, and radius. A name that is more than one face
(the pieces of a split face, or one face of several bodies) is refused; body says which body to
look on.
Press Delete on anything selected (bodies, faces, sketches, features, planes, components, canvases). It runs as one undoable step and lists the features that would go with it or fail.
Face Extrude, face Revolve, and Press Pull retain linked generated profiles. When a modified planar face has no construction certificate, these internal profiles use the legacy named-face attachment and projection behavior so upstream edits still update the plane and outline. This compatibility path is private to those modeling tools: newly attached user sketches require a certified face or an explicitly fixed plane. Legacy internal attachments can follow the nearest matching face after topology changes; inspect their result after major upstream edits. Activate the intended occurrence before generating a face profile from a repeated component; capture a separately moved source position before creating a legacy profile across components.
Negative Press Pull across supported aligned prismatic faces retains round through-holes, including after a joined extrusion and in a rotated component occurrence. The cut partitions the current material into axial sections, preserving the hole through undo, redo, and reopen. Walls that cannot be qualified as a parallel extrusion still use the general Boolean path and may report an unsupported coincident-surface operation atomically.
Parameters and expressions
Every dimension and feature input is a model parameter (d1, d2, …). User parameters
are ones you add yourself, with a unit, an expression and an optional comment. Change them in
Change Parameters (parameters.change). The timeline rebuilds from the first feature that
uses them.
When creating or editing a sketch dimension, its value editor shows its model parameter
name (d1, d2, …). Click another driving dimension to use its name: when the value is
just a number (such as 10 or 10 mm) the name replaces it; otherwise it goes in at the
cursor, replacing selected text and keeping what you typed, so typing 10 + and clicking
d1 gives 10 + d1. Add arithmetic such as / 2 to build a dependent dimension. Press Enter to apply
the expression or Escape to cancel the edit. When placing a new dimension, width = 40 (or #width = 40) adds
the user parameter width with that value (a bare number is in the design’s units) and
dimensions with it; a name that is already taken is refused. The value boxes shown while drawing
a shape (a line’s length, a rectangle’s width and height, a circle’s diameter) take the same
form: the shape is drawn at that size, and its dimension uses the new parameter.
Add and edit user parameters through the existing Change Parameters command.
In the MANAGE tab’s PARAMETERS section, click Parameters to open the user and model parameter editor. The Configurations button in CONFIGURATIONS opens the configuration table; Configure remains in the dropdown for configuring columns.
Press Enter in the new parameter’s name, expression or comment field to add it.
Valid entries return focus to the name field for the next parameter; invalid entries remain
editable. In expression fields, type a parameter or dimension name for autocomplete,
or press Ctrl+Space to open suggestions, including in an empty field.
Up/Down selects an autocomplete suggestion,
Enter/Tab accepts it, and Escape dismisses the suggestions. Accepting a suggestion
does not also submit the parameter. When what you typed is already a complete name
(width), longer names such as width_extra stay listed without a selection: Enter
applies what you typed, and Up/Down picks one of them first. A unit right after a number
(10 mm) is left alone; anywhere else a unit is a prefix like any other, so rad followed by
Enter gives radius.
With Preferences › Design › Step values with the arrow keys and the wheel on (it is off
by default) and no suggestions open, Up/Down step a plain number in any value field (a dialog’s rows,
the value box beside the geometry, a sketch dimension, a parameter’s expression) by 1 in the
unit it is written in: 20 mm becomes 21 mm, 2,5 mm becomes 3,5 mm. Hold Shift for 10
or Alt/Option for 0.1. The mouse wheel over the focused field does the same, a step a notch,
and the preview follows each step. An expression such as width / 2 is left as typed.
Expressions are unit-aware: 2 * width + 5 mm, angle / 2, sqrt(area), 1 in + 3 mm, using
+ - * / ^, parentheses, sin cos tan asin acos atan atan2 sqrt abs min max floor ceil round mod trunc and unit conversion. A number and the unit after it are one value: 10 mm / 2 mm is 5
and 90 deg / 1 rad is π/2, and a fraction of numbers before a unit is one value too (1/2 in
is half an inch; after a / only written without spaces: 1 in / 1/2 in is 2, while
width / 10 / 2 mm divides by 10 and then by 2 mm). A decimal comma works too, outside parentheses: 2,5 mm is
2.5 mm (inside a function’s parentheses the comma separates its arguments, so write
max(2.5, 3) there). A mixed number is one value, as imperial sizes are written: 2 1/8" and
3 7/16 in are 2.125 in and 3.4375 in (a whole number, a space, then a proper fraction of whole
numbers). A length where an angle is expected (or the other way round) is an
error, not a silent conversion. Cycles are reported with their path (a → b → a), renaming a
parameter updates every expression, dimension and feature input that uses it, and a parameter
in use can’t be deleted (the error lists its users).
The design’s units (Document Settings in the browser: mm, cm, m, in or ft) set how a bare number
or unitless arithmetic expression typed into a length field is read. In an inch design 0.25
and 1 / 4 mean 0.25 in. Operation dialogs start with lengths in the design’s units, including
secondary distances, wall thicknesses, gaps, offsets and radii. Canvas handles and live sketch
values use the same units as the operation. Angles, counts and scale factors keep their own
units. Preferences selects the default units for new designs; existing designs use their own
Document Settings.
Explicit units override that choice: 2 mm is still two millimetres in an inch design.
Parameter expressions such as width / 2 remain driving expressions. Editing a saved feature
preserves its physical size; an existing millimetre expression can still show mm so accepting
an unchanged field cannot reinterpret it. Computed sizes and tolerances keep their full
precision when written back into editable fields, independently of display precision.
Parameters export to and import from CSV or JSON (parameters.export, parameters.import).
Configurations (config.*) keep a table of rows, each with its own parameter values and
suppressed features, physical materials and body appearances, switched with config.activate.
The table’s Library picker selects material or appearance values. Choose Follow material
to clear an appearance override. Custom looks retain their colour and opacity across
activation, undo and save/reopen. See
configuration commands and examples.
Checking a design across a parameter’s range
Parameter Sweep (inspect.parameter_sweep) rebuilds a copy of the design at each of a
parameter’s values and reports what no longer holds, so a design meant to be driven by its
parameters (one template, many sizes) can be checked before it is trusted. The design itself is
not changed.
{"command": "inspect.parameter_sweep", "params": {"parameter": "Width", "from": 300, "to": 510, "steps": 21}}
values lists the values instead (numbers or expressions). For each value it gives the rebuild’s
time and what changed from the design as it is now: features that fail (failed) or warn
(warned), joints whose origin is no longer found (joints_lost) or turned over on its own part
(joints_flipped, more than 90°), sketch planes that turned over (sketches_flipped) and sketch
solves that fail or only hold by flipping a triangle’s turn or an offset’s side (sketches_failed).
summary counts the values that show each.
Components and joints
A design is a tree of components. New Component (component.create) adds one, and
Create Components from Bodies (component.from_bodies) turns bodies into components. Each
component has its own origin, bodies, sketches and timeline entries. The active component
(the radio button in the browser) receives new sketches and features; the others are drawn
faded.
A component is placed by its occurrences. Paste creates another instance of the same component, and Paste New creates an independent copy. Move an occurrence with the move triad, then Capture Position to keep it or Revert to put it back. SolidCraft asks before the next command if a move wasn’t captured. Ground fixes an occurrence in place, and Unground (the same row of the browser’s context menu) frees it again.
The design’s first part is grounded when it is made: the first occurrence placed directly in the
design by New Component, Create Components from Bodies or an import. Parts made after it,
standard parts and copies made with Paste or Paste New start free, so do parts made inside other
components. Any number of parts may be grounded, or none; opening a design never grounds
anything. A grounded part refuses Move until it is ungrounded, and component.create takes
grounded: true or false to choose for itself.
Drag a part to put it somewhere else. With the design active, press the left button on a part
and drag: the part follows the pointer, the point you grabbed moving in the plane through it that
faces the view, and stays where you let go, with its position captured. The whole drag is one
undo step, and Escape during the drag puts the part back. The drag moves the placement of the
active component that holds the part, so with the design active a part inside a subassembly
moves the whole subassembly; activate the subassembly to move the part inside it. Moving a
definition shared by several placements moves it in all of them, as Move does. A grounded part
does not move, and a part that joints hold to something grounded moves only as far as its joints
allow (below). Dragging any member of a chain joined to nothing grounded moves the whole chain,
preserving its joint values. Shared nested definitions move in every instance; the selected
instance follows the pointer even when its parent is rotated differently from the first one. A drag that starts on empty space, on a part that can’t move, or
with Shift, Ctrl (Cmd) or Alt held draws a selection box instead. Other uncaptured moves stay
pending. Scripts and agents do the same with occurrence.drag {occurrence or path, at, point},
which moves the placement by point - at in world coordinates and answers the placements it
moved.
Paste New copies nested component definitions, sketches, feature histories and generated dimension parameters. Repeated child instances remain shared within the new copy. Materials, appearances and wholly internal joints/motion records receive copied references. Design-wide user parameters remain shared; retained external links and omitted cross-boundary motion records are reported. Unsupported destructive links refuse the entire copy. See individual-instance and copy details.
Extract to File (component.extract, in a component’s right-click menu) moves a component
into a design of its own: its sketches, features, bodies, subcomponents and the joints among them
go to the new .solidcraft file at its origin, with the parameters they use, and the component
becomes linked to that file, as Insert Design makes it. Its placements and the joints to the
rest of the assembly stay. Components whose own history builds the same bodies are offered to
become placements of the new link too (component.copies, component.replace_with_link).
The menu offers Extract only for a component that can leave: nothing outside uses its sketches
or changes its bodies, no bodies were moved into or out of it, and none of its subcomponents is
joined to the rest of the design.
Joints (joint.create, J) connect two components by joint origins that snap to faces, edges,
circles and vertices: rigid, revolute, slider, cylindrical, pin-slot, planar and ball, with
offsets, flip and limits. As-built Joint keeps the current position, Rigid Group locks
components together, Drive Joints moves a joint through its values, and Motion Link
couples two joints. A rigid joint holds its parts still, so Drive Joints has nothing to drive on
it: it says so and offers Edit Joint, where the joint’s type changes to Revolute (rotate),
Slider (slide) or Cylindrical (both). Links can be chained (one joint drives a second, which drives a
third), and a value is carried through a chain of up to 64 links in whatever order they were
made. Contact sets stop motion where bodies would pass through each other,
Motion Study keys joint values along a timeline, and Interference shows overlapping
volumes in red, lists the pairs closer than its Clearance and each body’s nearest neighbour.
In the Joint and Joint Origin dialogs, picking a straight edge puts the origin at its midpoint with the motion axis along that edge. A revolute joint turns about the picked edge; a slider moves along it. Round edges use their circle’s centre and normal. Existing saved joint origins keep their axes.
A joint origin on a face or a hole keeps the turn it had when it was picked: when the face tilts (a bend angle or a rotation changing with a parameter) the part on it tilts with the face but doesn’t spin about it.
A joint origin can also sit on an edge of a flat face ("edge": [x, y, z]): at the edge’s middle,
pointing out of the face with its y into it, or with "corner": [x, y, z] at the corner near that
point, its x and y running along the two edges into the face. The edge, the face and the corner
are found again by name, so a bracket set 40 mm in from a panel’s corner (a planar joint with
values [40, 40, 0]) stays 40 mm in from that corner when the panel changes size.
A joint’s offset, angle and values can be typed as expressions over the design’s parameters
("offset": "gap + 2", "values": ["travel"]). The joint keeps them and works them out again
whenever the design changes, so a part seated gap above a panel moves with gap. Renaming a
parameter carries into the joints that use it, and deleting one they use is refused. If an
expression stops working out (say a parameter becomes an angle), the joint is left out of the
solve and joint.solve gives the reason, instead of placing the part from old numbers. Typing a
plain number, or driving the value, drops that expression.
Relations are joints that tie only part of a placement, so a part is placed the way a
designer describes it, one relation at a time: parallel (z along z, free to move and turn
about it), perpendicular and angle (z square to z, or at the joint’s angle from mated),
point_on_plane (a point on a face’s plane, offset off it), point_on_line (a point on an
axis) and line_on_plane (an axis on a face’s plane: a cylinder’s radius as offset lays it
tangent). A relation is made where the parts are: B moves no further than the relation needs.
Several relations between the same two parts add up; the solver meets them together, each one’s
degrees of freedom come off what is left, and one that can’t be met is reported, not forced:
{"command": "joint.create", "params": {"type": "parallel", "a": "Panel top", "b": "Bracket bottom"}}
{"command": "joint.create", "params": {"type": "point_on_plane", "a": "Panel top", "b": "Bracket corner"}}
{"command": "joint.create", "params": {"type": "parallel", "a": "Panel front", "b": "Bracket face", "flip": true}}
{"command": "joint.create", "params": {"type": "point_on_plane", "a": "Panel front", "b": "Bracket corner", "offset": -40}}
{"command": "joint.create", "params": {"type": "point_on_plane", "a": "Panel end", "b": "Bracket corner", "offset": -40}}
leaves the bracket flat on the panel, square to its front, its corner 40 mm in from the front and
the end, wherever the panel’s size takes them. Coincident faces are a planar joint, a distance
between faces is planar with an offset, concentric is cylindrical and two points that meet
are ball.
The check behind it, inspect.clearance, does all of that in one call for any number of bodies,
which suits scripts and agents checking a whole assembly:
{"command": "inspect.clearance", "params": {"clearance": 1, "nearest": 2, "skip_within": "component"}}
It returns overlaps (pairs sharing at least min_volume, 0.01 mm³ by default, with the shared
volume, area, box and centre), near (pairs closer than clearance, with the distance and the
two nearest points; a contact sharing less than min_volume is listed here at 0 with its
overlap_mm3), nearest (each body’s nearest closest bodies) and unchecked (pairs whose
overlap could not be worked out, with the reason). against limits the check to pairs with one
body in bodies and the other in against, and skip_within leaves out pairs inside one
occurrence ("occurrence") or one placed component, its subcomponents included
("component"): the parts of a bought component that touch by design. Distances are measured on
the exact faces and edges; the meshes only rule out pairs that are too far apart, and the pairs
left are checked in parallel. inspect.interference remains for the overlaps alone: it clears
pairs whose meshes stay apart without intersecting them, intersects the rest in parallel, and
returns after budget_s seconds (default 240) at most, listing the pairs it had no time for as
not checked.
Mesh bodies (STL, 3MF and OBJ imports) are not checked by either command: each is listed in
unchecked as {a: name, b: null, reason: "mesh body: not checked"}, so count: 0 or an empty
overlaps is not mistaken for “no overlap” when a mesh body was in the design.
Gear, Rack and Pinion and Screw (on the ASSEMBLE panel) are motion links whose ratio comes from the parts rather than a typed number:
- Gear (
joint.gear {a, b, ra, rb}) couples two joints that turn. Joint 2 turns ra/rb as far as joint 1, the other way round; Internal gear (one of them is a ring gear) turns both the same way. Pick the round face of each gear, such as its pitch cylinder, or type the radii. Tooth counts work as well as radii, since only their proportion counts. Picking a gear’s face also chooses the joint that gear turns on. - Rack and Pinion (
joint.rack_pinion {a, b, radius}) couples a joint that turns with one that slides: the rack travels the pinion’s pitch radius times its turn in radians (r·θ). Either joint may drive the other. Injoint.listthe ratio is given per degree, as for any link between an angle and a distance. - Screw (
joint.screw {joint, pitch}, or{a, b, pitch}for a revolute joint and a slider) advancespitchper turn. A cylindrical joint is a screw on its own: its turn drives its travel. The thread is right-handed; Reverse makes it left-handed.
A radius or pitch may be an expression of the design’s parameters, and a picked face is found
again whenever the design changes. Change the parameter or the gear’s diameter, and the ratio
follows. The new link leaves the driven joint where it is, so teeth modelled in mesh stay in mesh.
Reverse turns the driven joint the other way, for joints whose axes point opposite ways.
Redefining a link (link: i, such as turning it the other way with Reverse) also leaves the
driven joint where it is, so the gears stay in mesh at the pose they are in. These
couplings are motion links, so they work in closed loops, with contact sets and in motion studies:
a rack can push the piston of a slider-crank, or a pinion can steer through a rack and its tie
rods. Drive the joint that drives the coupling (joint 1, the pinion, or the screw’s turn), not the
driven one. A joint follows one link at most, and a coupling that would drive its own driver is
refused. joint.list lists each coupling’s definition under links with an error when it can’t
be worked out (the picked face is gone); the link then keeps its last ratio.
Cam (joint.cam {a, b, profile}, on the ASSEMBLE panel) makes a follower ride a cam. The
cam turns on a revolute joint (a); the follower is a slider (b, a translating follower such as a
valve tappet) or a revolute joint (b, an oscillating follower such as a rocker arm). The profile
is the cam’s outline in a plane square to its axis: a closed profile of a sketch in the cam’s
component, or the cam’s edges picked on its body (one click on an edge takes the closed loop it is
on in that plane). The follower is:
- a Knife edge: a point on the cam;
- a Roller of a radius, typed or taken from the roller’s round face (picking that face also says where the roller is);
- a Flat face square to a slider’s line, such as a bucket tappet.
A translating follower’s line runs along its slider’s axis, through its Tip when one is picked
(its knife’s point or its roller’s centre), so a follower offset from the cam’s axis rides the
right flank. An oscillating follower needs its tip, or its roller’s face, to know its arm. The
follower rides the cam from the side it is on (Other side takes the other one). The follower’s
travel over a turn of the cam is worked out from the profile and kept with the link, one value a
degree with a smooth curve between them that never overshoots its samples. It is worked out again
whenever the design changes: change the cam’s sketch, a parameter or the roller, and the follower
follows. Like the gears, the new link leaves the follower where it is (model it touching the cam),
and the cam is a motion link: it drives in Drive Joints, plays in motion studies, works in
closed loops (a tappet can be the piston of a slider-crank) and stops at contact sets. The profile
may also be on the grounded part, with the follower’s guide on a turret that the cam joint turns.
joint.list gives a cam’s link lift (the follower’s lowest and highest values and its rise)
instead of a ratio. A profile that is not closed, a line or an arm that misses the cam somewhere,
a follower that would jump, and axes that are not square or parallel are refused.
A joint moves one component against another, so its two picks must be on different components.
A STEP file with several parts often comes in as bodies of one component, and picks on two of
those bodies make no joint. The Joint and As-built Joint dialogs say so under the picks and offer
Create Components from Bodies: each picked body gets a component of its own and stays where it
is, the picks are kept, and the joint previews. joint.create refuses such origins with the same
advice.
Joints may close a loop, as in a four-bar linkage or a slider-crank. When one joint of the loop
is driven, the other joints in the loop follow so that every joint still meets, and the
mechanism stays in the assembly mode it started in (an open four-bar does not flip to the
crossed one). If the motion would take a loop past the point where it can still close, for
example driving a rocker beyond its swing, the motion stops at the last position where the loop
closes, and the drive reports stopped_by_loop. A loop that cannot close at all is listed
under conflicts and nothing in it moves. joint.list reports how many degrees of freedom each
part still has with its loops closed: the moving parts of a four-bar have one between them, and a
loop locked by a rigid joint has none. A joint a motion link drives adds no freedom of its own:
two meshed gears on a fixed housing have one between them, each turning with the driving gear. The Drive Joints dialog says when a drive stops short and
why. While it previews, a row under the values gives the value the joint reached and the reason
(the loop that can’t close further, or the two parts a contact set keeps apart), and after OK the
status line repeats it. The value box keeps what you typed. The dialog opens on the joint
picked in the browser, or on the first joint that moves when none is picked.
Drag a part that joints place and it moves as far as its joints allow: the point you
grabbed follows the pointer in the plane through it that faces the view, the joints on the
part’s chain take the values that bring it nearest, within their limits, through motion links,
and with closed loops kept closed, and a four-bar’s rocker stops at the end of its swing. The
whole drag is one undo step. A grounded part, or one its joints hold fixed, is not moved; a drag
on it draws a selection box. Each frame runs joint.reach, which scripts and agents use
directly to put a part’s point somewhere: joint.reach {occurrence, point} moves the joints on
its chain from where they are to the nearest pose (the arm keeps its elbow up or down, and a
linkage stays on its branch). at picks the part’s point (the centre of its bodies by
default), z with axis also turns one of its axes, and joints names the only joints to
move (the other joints of a closed loop they are in follow). The answer says whether it
reached the point and how close it got (error_mm, error_deg), and when it falls short,
the limits it rests on (stopped_by_limit) or the loop that stopped it (stopped_by_loop).
Contact sets are not checked by a reach; drive the joints for that.
The solver places what it reaches from the root and from grounded components. A chain of joints
that is joined to nothing grounded is never placed, so driving one of its joints moves nothing.
The drive says so: joint.drive answers moved: false with a reason and the unconnected
part (“Pedestal:1 is not connected to anything grounded”), the Drive Joints dialog shows the same
row, motion.play lists such joints under unmoved, and joint.list names every such chain under
floating. Ground a part of the chain, or join it to a grounded part (a rigid as-built joint keeps
it where it is), and it moves.
joint.drive answers the joint’s values as joint.list gives them, in degrees and millimetres
with their units and limits, and moved says whether any part moved. A joint’s origins are mated
by default: B’s z axis is turned against A’s, as two faces meet. flip: true lines them up
instead, so two origins given at one point with the same z stay as they are. Without an
angle, a new joint (other than a relation, which starts where its parts are) takes no turn or a half turn about its axis, whichever turns B less: two
faces that already meet, a plate under a panel, stay as they are instead of B spinning about
their shared normal. For scripts that drive many steps, quiet: true leaves the per-part
degrees of freedom out of joint answers, and
motion.play with quiet: true gives the poses of only the parts that moved.
Motion Trace (motion.trace, on the ASSEMBLE panel) draws the path a point of a part
takes while a joint is driven or a motion study plays, such as the coupler curve of a four-bar.
Pick the point on the part (a face centre, a circle centre, an edge midpoint or a vertex), then
either a joint with the values to drive it from and to (a turn of the joint from where it is, at
first) or a motion study. The path previews in the viewport as the mechanism would move: closed
loops follow, and the path ends where a loop can no longer close or a contact set stops the
motion, which the dialog says under the values. OK keeps the path as a 3D sketch spline in a new
sketch of the root component (Motion Trace1, …), where no joint moves it, and it can serve
as a sweep or pipe path. New traces retain their source and update when the mechanism’s
joint origins, limits, motion links or driving conditions change. Playing or dragging the
mechanism does not redefine the trace: it keeps the assembly branch from when it was made.
Unrelated parameter and display changes do not resample it. A path that comes back to where
it started is kept as a closed, smooth spline.
motion.trace {occurrence, point, joint, from, to, index?, samples?} (or {occurrence, point, study, samples?}) answers the world points without changing the design, and
motion.trace.keep with the same parameters (and name?) keeps them. point_ref can retain a
joint-origin snap (such as {occurrence, face: [x,y,z]}), so the point follows its resized
face; without it, the point is fixed in the part’s coordinates. The dialog retains its face
or circle snap. motion.trace.edit {sketch, from?, to?, samples?} changes a joint trace’s
driving conditions; joint with from and to, or study, replaces its motion source.
Expressions in from and to follow parameter edits. Study traces follow edits to the
referenced study, including its step count when Samples was left at the default.
auto_update: false in motion.trace.keep makes a fixed curve; in motion.trace.edit it
detaches an existing association, preserving the curve. Curves saved by earlier versions
remain fixed. motion.list includes the associations and their errors. If a source is lost,
the sketch warns and keeps its last successful curve until the source is restored or the
association is detached. Changes and their generated curves undo and redo together.
A trace takes 2 to 500
samples: 100 by default for a joint, one per step for a study.
The Chebyshev linkage demo builds a complete four-joint mechanism, plays a saved study and demonstrates the same retained trajectory updating after a link-length or driving-range edit.
Export Animation… in the Motion Study dialog saves the study as an animated GIF, drawn
from the viewport’s camera as the study plays (the keys in the dialog count, even before OK).
motion.export_animation does the same for scripts, the CLI and agents, without a window: give a
study, or a joint with from and to (and index?) to animate a drive, and a path. A path
ending in .gif gets an animated GIF that loops; any other path is a folder that gets the frames
as frame-0001.png, frame-0002.png, and so on (made if missing; frames of a longer earlier
export there are removed), for a video tool. The web app writes GIFs only. samples is
the number of frames (one per step for a study by default, 100 for a joint), width and height
their size in pixels (800 × 500 by default, at most 4096 a side), and fps the GIF’s speed (20
by default); grid: false and axes: false leave out the ground grid and the origin’s axes.
camera takes the reply of the control channel’s ui.camera (or any of its fields;
the rest come from the view view.apply chose, or the home view framing the model). The frames
are drawn by the same renderer as solidcraft-cli snapshot, on a copy of the design, which is not
changed. A GIF keeps one palette for all its frames, so colours don’t flicker, and stores only
what changed from one frame to the next. Frames are capped: at most 1000, and a GIF holds at most
2^26 pixels of frames (about 160 frames of 800 × 500).
An exploded view animates the same way: give its name or id as view (instead of a study or a
joint) and the parts come apart at a steady pace, from the assembled placement to where the view
shows them; back: true brings them back together, exploded at the middle frame. That takes 41
frames by default, or 81 with back, and the home view frames the parts both assembled and
exploded. As with explode.show, the design keeps its placements.
Motion analysis. The Motion Study dialog’s Study box opens a saved study to edit, and by its timeline the dialog charts what the study does. Plot picks a moving joint value (a joint picked in the Browser is picked here too), or Picked point once a point is picked in the dialog’s Point box, and Quantity whether to show its value, velocity or acceleration; the chart runs over the study’s steps with a cursor at the step shown, and a click or a drag on it moves to that step. Values are in degrees and millimetres; rates are per step, or per second once Duration says how many seconds the whole study takes. A picked point charts its distance from where it starts, its speed and the size of its acceleration. Check collisions plays the study and lists the pairs of parts that pass through each other, with the steps where they do (a click on one shows the first of them); those steps are shaded on the chart. It checks the parts picked in Components (against the ones picked in Against, when there are any), else every component, or the pairs of the design’s contact sets (a choice beside the button once it has any).
motion.plot {study, joints?, occurrence?, point?, samples?, duration?} answers the same
numbers: for each joint value its value, velocity and acceleration per sample (with their
units), and for a point its position, velocity and acceleration vectors, speed and
acceleration_norm. The study plays as motion.play places it (closed loops follow, contact sets
stop it), each sample from the one before, on a copy: the design is not changed. Velocities and
accelerations are finite differences between samples, central inside and one-sided at the ends,
so they are as smooth as the samples are dense (up to 1000, one per step by default). Angles are
unwrapped, so a joint that turns on past half a turn keeps counting.
motion.interference {study, occurrences?, against?, contact_sets?, samples?} answers each
colliding pair with its first_step, last_step, the steps ranges where they overlap, how
far into each other (depth_mm, the most at deepest_step) and their shared volume_mm3
there (null when it is not cheap to work out). It plays the study as motion.play does, but
contact sets don’t stop it, and parts are tested at the samples only: a part that passes right
through another between two samples is missed. Parts are told apart by occurrence, as contact
sets tell them apart: an occurrence inside a sub-assembly placed twice is one part, so its two
copies are not checked against each other. A check takes at most 256 bodies; on a large design,
list the occurrences to check (an arm, with against the parts it must miss). When the work
runs out it says truncated and the step it checked_to.
joint.sweep {joint, from, to, index?, step?, refine?, occurrences?, against?, contact_sets?, report_gaps?, apply?} drives one joint value from from toward to and answers how far it gets
before parts touch: stopped_at (the last value clear of contact), stopped_by (the two parts
and the point at which they touch, or "loop" where a closed loop can no longer close, or
"limit"), and with report_gaps the nearest gap at every sample and the nearest each part came
to another (min_gap). The samples are fine enough that no part passes through another between
two (or step apart), and the first contact is bisected until no part moves more than refine
(0.01 mm by default) across it. Gaps are distances between the parts’ tessellations
(gap_method: "mesh"): exact for flat faces, within gap_tolerance_mm for curved ones. Pairs
already touching at from, such as parts joined face to face or a door resting on its frame, are
listed in touching_at_start and stop the sweep only once they pass into each other, deeper than
they start. The sweep runs on a copy (closed loops follow, contact sets don’t stop it) and
leaves the design as it was unless apply is true.
Insert Design (component.insert_design {path, translate?}, on the ASSEMBLE panel and
in the File menu) adds a component linked to another saved design, so each part can live in
its own document. A STEP or mesh file picked there is inserted as Insert STEP or Insert
Mesh would. The part’s solid bodies come
in as they sit in that design, and they are kept in the assembly, so it opens even without the
part file. The link is stored relative to the assembly’s folder once the assembly is saved.
After the part file changes, Update Links (component.update_links) reads it again.
To edit the source, right-click a linked component or occurrence in the Browser and choose
Open Linked Design. It opens the part in another tab and reports if its file is missing.
Break Link (component.break_link) keeps the bodies and forgets the file, and
component.links lists the links and whether their files are found. A linked component holds
the part’s bodies only: its timeline, parameters and sheet-metal data (flat pattern, bend
table) stay in the part’s own design.
If a source file moves or is renamed, right-click its linked component or occurrence in the
Browser and choose Relink Source (All Instances)…. Choose the saved .solidcraft design
to use. Relinking updates that component’s stored path and solid bodies for all its
instances, keeping their identities and placements. Cancelling or choosing a missing,
invalid, empty or self-referencing design leaves the assembly unchanged. The operation is
one undo step and the new path is saved relative to the assembly when possible.
Scripts use component.relink {component, path}. The reply includes previous_path, path,
warnings, joint_errors and joint_conflicts. Changed geometry can invalidate a joint’s
face reference: relinking reports those joints in the reply and UI status/message history;
repair the joint or undo the relink. Relink does not repair or verify the intended meaning of
existing joints.
When linked geometry changes and the component has joints, a reminder to check those joints
is also shown even if the solver reports no error: a successful solve does not prove that a
joint still describes the intended connection.
STEP export writes the component tree as an assembly.
Right-click a component in the Browser and choose Properties to inspect
material-aware mass, volume, surface area, centre of mass and world bounds. Descendants and
repeated instances contribute to assembly totals; pending moves are included. A component
uses its first placement; occurrence.properties selects a specific instance. Root Properties
measures the complete assembly. Volumes are summed, including overlapping bodies, rather than
measuring their union. The same workflow is available through component.properties and
occurrence.properties; see
placement and measurement details.
Part properties and the bill of materials
A component definition carries its own part properties: a part number, a description and a
revision, other tools’ data under a reverse-DNS key (the same rules as the design’s own
properties), and free text properties such as a material grade or a finish. Every occurrence of
the component shares them, Paste New copies them, and they are saved with the component.
component.metadata {component} reads them and
component.metadata.set {component, part_number?, description?, revision?, values?, properties?}
changes them in one undo step (an empty value removes a field). The root component is the
design itself: its properties are document.metadata’s. Standard parts (parts.insert) get a
part number from their standard and size (ISO 4762 M6x20, or a bearing’s designation) and a
description; Change Part Size renumbers them, keeping a field you changed yourself.
Bill of Materials (assembly.bom, on the INSPECT panel) counts the placed components:
levels: "flat"(the default) lists the parts at any depth, each counted through its subassemblies: a part placed once in a subassembly that is placed twice counts 2. A subassembly is listed through its parts, unless it has bodies of its own.levels: "top"lists the direct children, a subassembly as one row.group_by: ["part_number"](the default) puts definitions with the same part number in one row; a definition without a part number gets a row of its own.["definition"]gives every definition its own row.group_children: truekeeps what is placed inside a part with a part number in that part’s row: a bolt with a washer and a nut placed inside it is one row, the washer and the nut listed under it aschildren(items 1.1, 1.2…, witheachper bolt andcountin all) instead of rows of their own. Bolts with different contents (one with a nut, one without) are different rows.total_occurrencescounts the children too, so it is the same either way.componentlists one subassembly’s parts instead of the design’s.
Rows are sorted by part number (rows without one come last) and hold item, part_number,
description, revision, count, definition (the component names), components (their ids)
and properties, with total_occurrences beside them. path writes them to a .csv file
(the columns item, part_number, description, revision, count and definition, every field
quoted; children follow their row) or a .json file. The window lists the same rows, switches
between the levels and groupings and shows children under their part, edits a picked row’s part
number, description and revision, and exports them. Changing part properties changes no
geometry, so it doesn’t evaluate the model or re-seat joints again.
Sheet metal
MODEL → MODIFY offers Convert to Sheet Metal. The SHEET METAL tab is always available (it is highlighted
and selected when the active body is a sheet), and builds parts from a sheet of constant thickness under a sheet metal rule
(thickness, bend radius, K-factor), set in Sheet Metal Rules (sheet.manage_rules).
- Flange: a base flange from a sketch profile, a contour flange from an open sketch, or an edge flange from a part edge.
- Hem returns a sheet edge back toward the sheet; no new sketch is required.
- Fold (
sheet.fold) bends the sheet along a sketch line. - Unfold / Refold (
sheet.unfold,sheet.refold) for working on the flat part. - Convert (
sheet.convert) turns a plate of constant thickness into sheet metal, or, with bend edges, a solid or a shelled body. - Create Flat Pattern (
sheet.flat_pattern) shows the flat part with its bend lines and a bend table, and exports it as DXF or a bend-table CSV through the save picker.
Bends follow the rule’s K-factor, so flat lengths match what a press brake will make. Tests check flat lengths against hand-computed bend allowances.
A copy of a sheet metal body is sheet metal too. Mirror (solid.mirror with bodies)
makes the opposite hand: its own flat pattern, DXF and bend table, the mirror image of the
original’s flat, with the same thickness side, bends and flat lengths, and holes where the
mirror puts them. A copy from Move (copy: true) or a body pattern keeps the original’s
flat pattern. Every copy takes further sheet metal features of its own. Mirroring with
combine joins the two halves into one solid, which has no single sheet model yet; mirroring
or patterning flange features themselves is not supported yet.
An edge flange’s height runs along the flange to its far edge. Where it is measured from is
the feature’s height_from (Height From in the Flange dialog): outer (the default, Outer
Sharp) starts at the outer virtual sharp, where the lines of the base’s and the flange’s outer
faces meet, so (R + T)·tan(θ/2) of it is bend before the flat leg starts (θ the bend angle, R
the inner radius, T the thickness); inner (Inner Sharp) starts at the inner virtual sharp,
R·tan(θ/2) before the leg; tangent is for bends past 90°, where the outer sharp runs far
out: it starts at the line square to the flange that touches the outside of the bend, R + T
before the leg. Up to 90° tangent is the outer sharp (at 90° the two are the same line, the
base’s outer face, so the leg does not jump as the angle passes 90°). A height shorter than
what lies before the leg is refused with the smallest one that fits. With a 2 mm sheet and a
2 mm radius, a 90° flange 20 high has a leg of 16 from the outer sharp or the tangent and 18
from the inner sharp; at 120° it has a leg of 13.07 from the outer sharp, 16.54 from the inner
sharp and 16 from the tangent.
{"command":"sheet.flange","params":{"edges":[[50,0,2]],"height":20,"height_from":"inner"}}
Where an edge flange’s bend starts is its position (Bend Position in the Flange dialog): how
far inside the picked edge the base is cut back. inside (the default) puts the outer virtual
sharp on the edge, (R + T)·tan(θ/2) inside it, so at 90° the flange’s outer face is flush with
the edge’s face; outside (sheet.flange also takes bend_outside and saves it as outside) starts the bend at the edge, which
leaves the base whole and the bend wholly outside; middle cuts back half of inside;
material_outside puts the inner virtual sharp on the edge, R·tan(θ/2) inside it, so at 90°
the flange’s inner face is flush with the edge’s face and its material is outside; tangent
is for bends past 90°: it cuts back R + T, so the outside of the bend is tangent to the edge’s
face, touching it a quarter turn into the bend with nothing of the bend past it. At 90° that
is inside. Below 90° the outside of the bend never turns parallel to the edge’s face, so
tangent cuts back (R + T)·sin θ instead: the bend’s end is on the edge’s face and the leg
leans on out past it.
{"command":"sheet.flange","params":{"edges":[[50,0,2]],"height":20,"angle":"120 deg","position":"tangent"}}
An edge flange takes its setback off its own edge only. Where that edge ends at an inside
corner of the base (the base runs on past it, like the leg of an L), the rest of the base
stays, and the rule’s bend relief (relief_width and relief_depth; Relief Width and
Relief Depth in Sheet Metal Rules) is cut into it beside the bend’s end: a slot the relief
width wide, from the relief depth before the bend line to the relief width past the flange’s
far edge in the flat pattern. The bend and the flange are then free of the base, folded and
flat. A relief that can’t be cut (a width under 0.01 mm, one as wide as the base beside the
bend, or a slot that would cut the base apart) is left out, and the flange feature reports a
warning saying where.
Where a cut-out has taken part of an edge away (a corner cut off the plate, a notch in the middle of an edge), an edge flange picked on that edge runs along the stretch the cut-outs leave nearest the pick, and takes its setback off that stretch only. The base on the other side of a notch keeps its edge, and a pick there, in the same feature or a later one, makes a flange of its own. Where the base is still left beside the bend’s end at such a cut-out, it gets the bend relief as above. An edge that cut-outs have taken whole is refused.
The setback is cut out of the base’s outline as it was drawn: where the bend line crosses an arc of the outline (a plate with rounded corners, bent inside or in the middle), the arc stays an arc and ends there, in the flat pattern and the DXF. A free-form curve (an ellipse, a spline) in an outline that a setback cuts is followed by tangent arcs within 0.001 mm.
Where two bends of one edge flange feature meet at a corner of the base, the rule’s corner
relief shape (corner_relief_shape; Corner Relief Shape in Sheet Metal Rules) says what is
cut there. notch, the default, cuts nothing more: the bends end square at the corner, which
leaves a step in the flat pattern between each bend’s end and its flange’s closed end. tear
cuts no relief either, and no step: both bends run to the corner where the two bend lines
meet, and each bend’s end is cut straight, in the flat pattern, from that corner to where its
flange’s closed end starts. Folded, the two bends touch at the corner and open to the rule’s
gap where the flanges start; the bend table lists a torn bend between the middles of its two
ends. A tear whose cuts would cross in the flat pattern, or that would run one bend into the
other folded (flanges at very different angles), is left out with a warning and the corner
keeps its notch. round
and rectangular cut a relief the size of the rule’s corner relief across, centred on the corner where
the two bend lines meet: out of the base a round with half that size as its radius, or a square
with half that size as its side, and both bends stop where it begins, cut straight across. The
flange panels keep their ends. The relief is in the folded part, the flat pattern, the bend
table (the bends are shorter) and the DXF (a round relief is an arc). At a corner that isn’t
square the round follows the angle of the base and the square becomes a kite, its two cuts
square to the edges. Changing the shape in a rule changes the parts made with that rule. A
relief that doesn’t fit (under 0.01 mm across, leaving nothing of a bend, or reaching as far
from its corner as another edge of the base is) is left out, and the flange feature reports a
warning saying where. The reliefs at all the corners are cut together, so a corner’s relief
does not depend on the order the corners are cut in; two of them that would overlap are both
left out, each with a warning saying which the other is.
{"command":"sheet.manage_rules","params":{"name":"Sheet 2 mm","thickness":"2 mm","k_factor":0.5,
"bend_radius":"1 mm","corner_relief_shape":"round","corner_relief":"8 mm","active":true}}
The rule’s shape and size are the default. An edge flange feature can choose its own for the
corners its flanges make: corner_relief_shape (notch, round, rectangular or tear) and
corner_relief (the size across, a length or an expression) in sheet.flange, or Corner Relief
Shape and Corner Relief in the Flange dialog. In the dialog, Rule as the shape and an empty size
take the rule’s; either one left out follows the rule while the other is the feature’s. Corners are
relieved only between the flanges of one feature, so a feature’s choice never meets another’s at a
corner, and two edge flange features of one part can have different reliefs under one rule. A
base or contour flange takes its corners from the rule only and refuses both parameters. The
flange gap and the rule’s other values stay with the rule for every flange. The feature’s choice is
saved with the design (file format 23).
{"command":"sheet.flange","params":{"edges":[[60,0,2],[120,45,2],[60,90,2],[0,45,2]],
"height":20,"corner_relief_shape":"round","corner_relief":"6 mm"}}
Where the edge flanges of one feature meet at a corner of the base, their panels are closed toward each other at any bend angle: each end is cut square to the sheet, on a line in the flat chosen so that the edges nearest each other (on the inner faces) run parallel, the rule’s gap apart, either side of the plane halfway between the flanges. Flanges at 72° get slanted ends in the flat; corners that aren’t square close the same way, and at 90° it is the corner the rule always gave. A corner that can’t be closed (a gap under 0.01 mm, flanges bending opposite ways, ends that would overlap in the flat pattern) stays open, and the flange feature reports a warning saying where. A flange added by a later feature leaves the corner as it was: the earlier flange keeps its length and the new one ends at its bend.
The flanges of one edge flange feature can each have an angle and a height of their own. An
entry of edges is a point [x, y, z], which takes the feature’s height and angle, or an
object {"point": [x, y, z], "angle": "105 deg", "height": "30 mm"} whose angle and height
(either one, or both) replace them for that edge. They are expressions like the feature’s own,
so parameters work, and each is a feature input of its own (Edge angle, Edge height) after
Height and Angle. The corners between such flanges close like those between equal ones:
the ends run toward each other the rule’s gap apart, either side of the plane halfway between
the two flanges, and a taller flange’s end keeps that line up to its own tip. This is how to
close a corner between flanges at different angles, because flanges of separate features
don’t close toward each other. In the Flange dialog, picking more than one edge shows a row
per edge with an angle and a height box; an empty box takes the value above.
{"command":"sheet.flange","params":{"edges":[[60,0,2],{"point":[120,45,2],"angle":"90 deg"},
{"point":[60,90,2],"angle":"105 deg","height":"30 mm"},{"point":[0,45,2],"angle":"120 deg","height":"25 mm"}],
"height":38.6,"angle":"75 deg"}}
An edge flange can bend more than once: then lists further bends, each an angle and a
height, and each is a flange on the far edge of the one before it, made in the same command
and undone with one step. It is the way to make a flap with two or more bends that is part of
the same sheet (a Contour Flange is always a sheet of its own). The example bends the end edge of
a wall 45° outward, then 45° again, the second leg 80 mm long. Each bend is a flange feature of
its own in the timeline, so each can be edited there. In the Flange dialog, an edge flange has
Further Bends under Bend Position: Add bend adds a row with an angle and a height, and the
bin beside a row takes it out.
{"command":"sheet.flange","params":{"edges":[[1,0,20]],"height":40,"angle":"45 deg",
"then":[{"angle":"45 deg","height":80}]}}
A further bend turns the way the one before it did. "flip": true on a bend turns it the other
way, which makes an offset (a jog): bend 45° out, then 45° back, and the last leg runs parallel to
the first. In the dialog each further bend has a turns choice, Same way or Other way.
When the shape is easier to draw than to count out, Path Flange (sheet.path_flange) makes
the same chain from a sketch. Sketch an open chain of lines on a plane across the edge (square to
it), starting at the edge, then pick the edge and the lines. Each line becomes a flange as long
as the line (measured like an edge flange’s height: to the outer face from the virtual sharp),
and each corner a bend. The first line’s angle to the sheet sets the first bend, and the path’s
turns set which way each later bend goes, so a jog is just a path that turns back. Changing the
sketch changes the flap. It is one feature in the timeline and part of the same sheet. Lines
that run straight on from each other count as one; a line that does not turn from the sheet is
refused, and so is a line too short for its bend. Arcs are not supported yet.
{"command":"sheet.path_flange","params":{"edge":[1,0,20],"sketch":"Path","curves":["p1","p2"]}}
A flange can also stand on the far edge of another flange: a return, such as a lip turned in along the top of a tray’s walls. It runs along that edge from end to end, so it follows the closed end of the flange it stands on, slanted or not. Two returns of one feature whose flanges meet at a corner of the base are closed toward each other as well, with the rule’s gap. They are mitred either side of a plane that runs along the mean of the two panels’ normals, bisects their bend lines and passes where the lines their panels start on cross. Returns that lie in one plane (level returns on a tray’s walls, whatever the walls’ angle) then meet on the corner’s bisector, like the sides of a picture frame. Each panel ends on a slanted line in the flat. Each bend stops square where all of it keeps half the gap from that plane, at the latest at the end of the flange it stands on, which can leave a short stretch of that flange’s edge bare. Returns turned opposite ways pass each other. A return that meets no other at a corner ends with the flange it stands on; where the flange beside that one leans in over it, the return is cut back until it keeps as clear of that flange as the end of the flange it stands on does. Returns that can’t be closed stay open with a warning, as on the base. Returns of separate features don’t close toward each other either: make the returns round a corner in one feature. A corner relief or a tear changes none of this: it leaves the flanges’ closed ends as they are, so the returns on them close over a relieved or torn corner as over a whole one.
{"command":"sheet.flange","params":{"edges":[[60,2,20],[118,45,20],[60,88,20],[2,45,20]],
"height":12,"angle":"90 deg"}}
(The four returns of a 120 x 90 tray in 2 mm sheet with 90° walls 20 high: each point is the middle of a wall’s far edge on its inner face, so the returns turn in over the base.)
A flange can stand on a straight side edge of an existing flange as well. Select that edge
and the adjoining edge of its supporting panel in one feature to close the new flanges across
the existing bend. The panels are mitred with the rule’s gap; round and rectangular corner
reliefs cut a rounded or rectangular slot across the existing bend zone in the flat material.
The slot’s width is corner_relief, and it survives unfolding, refolding and DXF export.
notch keeps the gap without another cut. A tear across an existing bend is not supported
yet and leaves the notch with a warning. A mitre that would overlap the flat pattern stays
open with a warning. Side-edge setbacks on curved panels or on only part of an edge left by
a cut-out are not supported yet. The edge connecting a panel to its existing bend is not a
free edge. See examples/regressions/sheet-side-flange-corner.json for a small reproduction.
Convert also takes a solid or a shelled body: pick the face that stays fixed and the sharp
edges that become bends (bends; Bend Edges in the dialog). The flat faces reached from the
fixed face across those edges become the sheet. A shelled body keeps its walls and their
thickness; any other solid’s faces become the outside of the sheet, with the rule’s thickness
taken into the body. Each sharp edge stays where the faces of its bend would meet, so the part
keeps its envelope and the flat pattern is the outside lengths less a bend deduction per bend,
2·(R + T)·tan(θ/2) − θ·(R + K·T). A bend can be picked on either skin of a shell; bends turn
either way, follow one another and take any angle (the walls of a tapered tray at 72°). An
edge between two faces of the sheet that is no bend is cut open, each side half the rule’s
gap from the plane halfway between the two, like the corners of a tray. A bend drawn as a
chamfer (a flat strip between two faces, the chord of the bend’s arc, as models without bend
radii draw one) is picked in the middle of the strip: the two faces then meet at their virtual
sharp in one bend of the rule’s radius, and the strip is no face of the sheet. A bend modelled
with its radius (a cylinder between two flat faces tangent to it, as sheet metal parts from
other systems come, or a filleted edge) is picked anywhere on its cylinder, on either skin, and
converts the same way: the sheet is bent there with the rule’s radius and K-factor, not the
modelled radius, so the flat pattern is the one the rule makes. The result is an
ordinary sheet body: edge flanges, cuts, Unfold, the flat pattern and its exports work on it,
and it follows the solid when a parameter changes (the edges and the fixed face are found by
their names). Without bend edges Convert expects a plate, as before. Either way round walls
(holes, slot ends, rounded corners, notches) stay circles and arcs in the flat pattern and the
DXF, also where a STEP file writes one in pieces, and the holes and slots of every face of the
sheet are its cut-outs, where the bends put them. An engraving in a face of the sheet (a recess
with a flat floor, at most half the thickness deep) becomes a pocket of it, as a plate’s does
(below), on either skin of a shell. Not converted yet: curved faces other than bends (rolled
sheet), with bend edges a hole or an engraving that reaches into a bend or a pocket deeper than
half the thickness (cut them after converting), and bends that close a ring.
A plate may carry recesses in one of its faces, such as an engraved label or a counterbore: a
recess with a flat floor and walls square to the plate, at most half the plate’s thickness
deep. Each becomes a pocket of the sheet (see the pockets below), so the converted part keeps
it: the sheet is as thick as the plate, the flat pattern is the whole plate with the recess
listed under pockets (its outline, side and depth), and the DXF draws its outline on the
pocket layer of its depth, apart from the cut contours, for a laser to mark rather than cut.
The face the recesses are in can be the one picked or the other. A recess deeper than half the
thickness, recesses in both faces, and a plate whose recesses are larger than the face around
them (a thinner plate with raised parts) are refused with the reason. A recess lies in an
opening of the face it is cut into, so the faces of a bent part’s legs are none: a bent part is
refused as no plate, and converts with its bend edges. A bend across a pocket is refused as for
any pocket, so fold or flange clear of an engraving.
{"command":"solid.box","params":{"length":100,"width":60,"height":30,"body_name":"Tray"}}
{"command":"solid.shell","params":{"faces":[[50,30,30]],"thickness":2}}
{"command":"sheet.convert","params":{"body":"Tray","face":[50,30,0],
"bends":[[50,0,0],[100,30,0],[50,60,0],[0,30,0]]}}
The face can also be the name model.faces lists for it ("face":"F1:+z"), which saves
computing a point on a face with holes or a curved outline: model.faces gives every face a
point that lies on it.
For a shop drawing or spreadsheet, sheet.export_bend_table returns CSV by default; choose
format: "json" for structured data. It includes the sheet rule, thickness, bend angles,
radii, allowances and flat centre-line coordinates. Each bend has its bend angle (angle_deg)
and its included angle (included_angle_deg, below; the CSV’s last column, so the columns before
it keep their places). Lengths use millimetres and
angles use degrees. A bend is listed where its centre line has material: one that a cut-out has shortened
gives the length and ends it has left, and one that cut-outs split gives a row for every piece,
all with the bend’s number (the flat pattern and the DXF draw the same pieces as bend lines).
Supply path to save it, or omit path to receive the content through the command
response. body defaults to the last sheet body. Exporting does not add a timeline entry or
consume an undo step.
{"command":"sheet.export_bend_table","params":{"format":"csv","path":"bracket-bends.csv"}}
{"command":"sheet.export_dxf","params":{"path":"bracket-flat.dxf"}}
The DXF is an AutoCAD 2000 (AC1015) drawing in millimetres, made for a laser-cutting shop’s
nesting or CAM software: each kind of line is on a layer of its own, with a colour (AutoCAD’s
index) and, for the bends, a linetype that the file defines.
| Layer | Colour | Line | Holds |
|---|---|---|---|
OUTLINE | 7 white or black | continuous | the outer contour of each piece of the sheet |
CUT | 4 cyan | continuous | what is cut out inside it: holes, slots, and the loose pieces a cut leaves (the middle of a ring) |
TEXT | 2 yellow | continuous | the letters that text cut through the sheet, with their counters; only with "text": "cut", the default |
ETCH | 6 magenta | continuous | letters as single contours, to be marked and not cut: "text": "etch", and always text machined to a depth |
BEND_UP, BEND_DOWN | 1 red, 5 blue | DASHED | the bend centre lines, by direction |
BEND_NOTES | 8 grey | continuous | the note beside each bend line, only with "bend_notes": true |
POCKET_… | 3 green | continuous | pockets, a layer for each depth (see below) |
PERFORATION | 3 green | continuous | the holes of a perforation field |
OUTLINE, CUT, BEND_UP and BEND_DOWN are always in the layer table, the others when the
sheet has something for them. The inner contours (CUT, TEXT) are written before the outline,
so a program that follows the file’s order cuts inside-out.
A round hole is a CIRCLE, a circular arc an ARC, a whole ellipse one ELLIPSE and an arc of
an ellipse an elliptical ELLIPSE; the pieces of a spline, the outlines of text and the other
conics are SPLINEs of their own degree, so no curve is written as chords. For a machine that
reads only lines and arcs, pass "curves": "arcs" to sheet.export_dxf: the free-form curves
are then tangent arcs and lines within 0.001 mm.
Letters are told from other cut-outs by where they came from: a cut whose sketch holds text
(Sketch Text) marks the cut-outs it makes as letters, counters included, while the design is
built; nothing more is saved in the design file. A letter that another cut-out overlaps, or
that the sheet’s edge clips, has become one shape with it and stays on CUT.
{"command":"sheet.export_dxf","params":{"path":"tray-flat.dxf","text":"etch","bend_notes":true}}
With "bend_notes": true each bend line gets a note on BEND_NOTES, B1 UP 90° R1 (the bend’s
number, its direction, its angle and its inner radius; %%d is AutoCAD’s degree sign). It is 3 mm
high ("note_height" changes that), centred half a height beside the middle of the line,
parallel to it and readable from below or from the right. A bend that cut-outs split has a note
for each piece. Notes are off by default, because a program that cuts or marks every layer would
mark them.
For a program that cuts every line it is given, "bend_lines": false leaves the bend lines out
(BEND_UP and BEND_DOWN are then empty), and "layers": "single" writes every entity on layer
0 instead of a layer for each role.
A bend’s angle is how far the sheet turns from flat. Many press brakes are set by the
included angle instead: the angle left between the bend’s two sides, 180° less the bend
angle. A 90° bend is 90° either way, but a 58° bend is set as 122°. The rule’s angle_display
(Angle Display in Sheet Metal Rules) says which one the DXF’s bend notes give: bend (Bend,
the default) or included (Included). Under an included rule the note of a 58°
bend reads B1 UP 122° R1. The bend table gives both angles whatever the rule says, in columns
of their own (angle_deg and included_angle_deg), so neither is taken for the other. The
setting does not change the flange angle yet: angle in sheet.flange and in the Flange dialog
is always the bend angle, so a 122° included angle is a flange at 180 deg - 122 deg. A design
keeps the setting in its rule, so templates made from it read their bends the same way.
{"command":"sheet.manage_rules","params":{"name":"Sheet 2 mm","angle_display":"included"}}
{"command":"sheet.flange","params":{"edges":[[50,0,2]],"height":20,"angle":"180 deg - 122 deg"}}
{"command":"sheet.export_dxf","params":{"path":"bracket-flat.dxf","bend_notes":true}}
A design with several sheet bodies exports one file for each with "per_body": true: the files
are <path>-<body>.dxf (characters a file name cannot hold become _; two bodies that would
share a name get _2, _3…), and the reply lists them in files. Without a path it lists
their DXF texts instead.
Base-flange profiles keep their interior holes. Through cuts that cross a sheet edge become
notches in the flat outline; overlapping and repeated cuts remove their combined area once.
The flat preview, DXF and unfolded solid use the same material boundaries. Lines and circular
arcs remain exact. An ellipse, a spline or text stays the curve it was drawn as in the flat
pattern and the unfolded solid; the folded solid follows it with tangent arcs within 0.001 mm,
and the point lists write it as chords within 0.001 mm (the DXF keeps the curve, see above).
Flat-pattern JSON lists each cut-out twice, in the same order: as points (cutouts), and
exactly (cutout_shapes): a round hole as {"circle": {"center", "radius"}}, any other as its
line, arc (centre, radius, start and sweep in degrees) and polyline segments (one
polyline for each ellipse quarter, spline or glyph curve).
Cuts can leave separate material pieces, including the centre of a ring cut. These pieces remain one logical sheet group through Unfold / Refold and export as separate flat outlines. A cut that parts a piece from the base (a flange cut off along its bend, the part of a ring’s inside on a flange’s edge) keeps it too, folded or flat, and its warning says how many pieces are no longer joined to the base, their area and where they lie in the flat pattern. Islands inside other material (a ring’s centre, a letter’s counter) are not counted. Large profiles that exceed the geometry processing budget report an error instead of exporting a partial outline.
A Box cut, or a Cylinder cut whose axis is the sheet’s normal, machines the sheet like the extrude of its footprint: through the sheet it is a cut-out (a cylinder a round one), and part way it is a pocket. A primitive cut at any other angle, or across a folded bend, is refused.
Hole machines the sheet the same way from a flat face of any panel, the base or a flange, on
either side, folded or unfolded: square to that panel, a through hole (plain or tapped) is a
round cut-out of the flat pattern and a CIRCLE in the DXF, and a counterbore or a blind
flat-bottomed hole is a round pocket. A hole slanted to its panel or drilled into the edge of
the sheet, a countersink and a drill point are refused.
Parallel, untapered extrude cuts also preserve shallow pockets through Unfold / Refold.
The pocket floor and side walls remain solid geometry; overlapping cuts from opposite sides
become a through hole wherever they remove the whole thickness. Flat-pattern JSON lists
pockets with their side, depth and bottom/top thickness coordinates in millimetres.
DXF pocket boundaries use POCKET_BOTTOM_Z…_TO_…_MM, POCKET_TOP_Z…_TO_…_MM, or
POCKET_INTERNAL_Z…_TO_…_MM layers. These are machining operations, so do not treat every
pocket layer as a through-cut contour when importing the drawing into a CAM tool. (Text
machined to a depth is a marking, on ETCH.)
Extrude Intersect on a straight base or an unfolded sheet retains the material inside both the current tool footprint and its depth interval, including interior holes, detached islands and earlier pockets. A shallow intersection records the removed layers above and below the retained material as top and bottom pockets. The sheet rule keeps the original stock gauge; the remaining solid can be thinner. A completely empty intersection removes the sheet and its metadata together. DXF records pocket depth in its layer names; importing that DXF as ordinary sketch geometry does not reconstruct the machining depths automatically. Each positive retained or removed depth layer must meet the kernel’s 0.000001 mm extrusion resolution; smaller layers report an error rather than being rounded away. To Object can retain a tool that trims to a straight prism, using its actual trimmed footprint. A curved tool with varying depth is refused before changing sheet geometry. Pocket profiles can move with planar panels when forming, but a pocket crossing a bend zone cannot yet be refolded. Machining folded bend material, tapered sheet cuts and other unsupported 3D sheet Cut/Intersect operations report an error before changing the design; they cannot silently restore removed material during a later sheet rebuild.
Other features that change a sheet body’s material without the sheet knowing it (a fillet or chamfer on a corner, an extrude joined onto it, a Mirror or Combine joined into it) are not in the flat pattern yet. Such a feature warns, and while the body carries its change the flat pattern, the DXF and the bend table are refused with an error that names it, rather than writing a part that does not match the body. A later sheet metal feature rebuilds the body from its sheet features and says that the change is not kept. Moving the body, splitting its faces and cosmetic threads leave the flat pattern as it is.
A sketch can be drawn on a flat face of the sheet: the base or a flange, on either side. Pick
the face (or give sketch.create the point plane: {face: [x, y, z]}) and the sketch attaches
to that panel instead of to a certified source face. It keeps the face’s name,
sheet:<body>:<panel>:<top|bottom> (the panel is base or the flange’s feature tag), and finds
the face again whenever the sheet is rebuilt: after a parameter change such as the thickness or
a flange angle, through Unfold / Refold, after the file is reopened and when the body is
renamed. Sketch coordinates are the flat pattern’s, so a point drawn on a flange lies where the
flat pattern and its DXF have it, and on the bottom face y runs the other way, as seen from
below. When the face is gone (its flange is suppressed or deleted) the sketch reports a lost
attachment instead of moving to another face; Redefine Sketch Plane attaches it to another
face. fixed: true still freezes the picked plane in space.
Every face of a folded sheet has a name of that kind, which model.faces lists as sheet_face:
<panel>:top and <panel>:bottom, <panel>:rim.<edge> for a rim (a base sketch line’s id, or a
flange’s far, side0, side1; rim.bend0/rim.bend1 for a bend’s flat ends and
rim.ext0/rim.ext1 where a flange runs on past its bend toward a neighbour), and
<flange>:bend.inner / <flange>:bend.outer for a bend’s curved faces. A command that takes a
face name takes it too (sheet:Body1:F3.1:top), and finds the face from the sheet itself, so the
name stays with that face while the sheet’s sizes change and other flanges come and go. Faces the
sheet does not describe (a hole’s wall) have none.
Plastic parts
MODEL → CREATE lists the plastic enclosure features:
- Boss (
plastic.boss): screw bosses with a hole, fillets and optional ribs. - Lip / Groove (
plastic.lip): the mating lip and groove along a shell’s opening. - Snap Fit (
plastic.snap_fit): a cantilever snap hook and its catch. - Rest (
plastic.rest): a pad for a part to rest on. - Plastic Rules (
plastic.manage_rules,plastic.assign_rule): wall thickness, draft and clearance per material, used as the features’ defaults.
Plastic Rules are in MANAGE → RULES. Rib and web in MODEL → CREATE complete the set. These features are checked against hand-computed geometry.
Import and export
| Format | Read | Write |
|---|---|---|
.solidcraft (versioned container or JSON design) | yes | yes |
.json (self-contained plain JSON design) | yes | yes |
| STEP (AP203, AP214, AP242) | solids, assemblies, units, names, colours | AP242 with names, colours and the component tree |
| IGES 5.3 | manifold solids, or trimmed surfaces sewn into solids | manifold solids with names and colours |
FreeCAD .FCStd, Rhino .3dm | solids, through cadmpeg | |
| 3MF, STL, OBJ | mesh bodies | yes |
glTF 2.0 (.glb, .gltf with its .bin files) | mesh bodies in the node tree’s components, materials as appearances | |
| 3MF with face tags | as 3MF | each triangle tagged with its B-rep face, exact coordinates |
| URDF | the assembly as a robot: links with meshes, mass and inertia, joints from the assembly’s joints | |
| DXF, SVG | into a sketch | sketches and flat patterns as DXF |
| PNG, JPEG | canvases and decals |
Opening a STEP, IGES, FreeCAD or Rhino file creates a design with the file’s bodies as a base feature, and inserting one adds them to the current design. Later features build on them. Mesh bodies render, measure, move and export, but solid features need B-rep bodies.
Every imported file (STEP, IGES, 3MF, STL, OBJ or glTF) comes in as a new component named after the file, so its bodies never mix with yours. A STEP assembly keeps its structure: its parts and sub-assemblies become nested components, and a part used several times is one component with an occurrence for each place. The imported assembly is rigid: moving its component moves everything, and its parts don’t move on their own. Right-click the component and choose Unlock Contents to joint the parts one by one. Undo removes a whole import in one step.
A glTF file (.glb, or .gltf with the .bin files it names in the same folder) comes in the
same way: its nodes become nested components, a mesh placed several times is one component with
an occurrence for each place, and the whole import is rigid until you unlock it. glTF is in metres
with Y up, so the import is scaled to millimetres and turned to stand Z up (up: "z" keeps the
file’s axes). Each material’s base colour, metal and roughness become the body’s appearance, so
the model looks right in Render; textures, skins and animations are not imported. Scaled or
mirrored nodes are baked into their meshes. Compressed meshes (Draco, meshopt) are refused with a
message: save the file uncompressed first.
A 3MF project saved by a slicer comes in too, also when it keeps its objects in model parts of
their own inside the file (the 3MF Production extension): each object on the build plates becomes
one mesh body, with all its volumes merged where the slicer placed them. Each body is named after
its object: the name in the model, else the one the slicer keeps in its project settings (Bambu
Studio’s Metadata/model_settings.config), else Mesh1, Mesh2, … Other slicer-only data
(modifier, negative and support volumes, painting, per-object settings) is not read: modifier and
negative volumes come in as ordinary solid geometry.
File → Export STL… and File → Export 3MF… (also Save As Mesh on the Insert tab’s Files panel, and in the right-click menu of a body) open the same dialog with that format chosen; it saves the selected bodies as STL, 3MF or 3MF with face tags (below) to a file you name, and you can change the format there.
File → Send to Slicer (also on the Insert tab’s Files panel, and in the right-click menu of a
body) is the short way: it saves the selected bodies as a 3MF in millimetres, without face tags,
and sends it to a slicer. It always asks which one and pre-selects the slicer you used last time,
so the usual case is one click or Enter. The list shows the slicers found on the computer with their
icons; Custom… adds any other program to the top of the list (kept between runs; hover an entry
you added and press Delete to remove it). On Linux only, a program whose desktop entry says it opens
3MF or STL files is listed even if SolidCraft doesn’t know it by name. Windows and macOS list the
known slicers only, found in the usual program folders and on PATH. It falls back to an STL if the
3MF can’t be written. Scripts and the control channel use file.send_to_slicer
with a slicer, which sends without a dialog. They can start only the slicers in the
list and programs you added under Custom…; any other program path is refused, and an AI agent’s
request to save a mesh or send it to a slicer asks first and names the slicer.
The list also has a plain EasyPrint (Printables, in the browser) row, and it is the one entry
that works in the browser build too. EasyPrint is Printables’ slicer, so choosing it uploads the
selected bodies (a 3MF, 100 MB at most) to Printables’ servers and opens the model there in your
browser. The link expires after 30 minutes, and you need to be signed in to Printables. SolidCraft
asks once before the first upload and remembers that you agreed. Nothing is uploaded unless you pick
this row. SolidCraft isn’t affiliated with Printables. Scripts use file.open_in_easyprint, which refuses unless it gets consent: true,
and an AI agent’s request to use it asks first.
Files sent to a slicer go to a folder only you can
read: ~/.cache/solidcraft/slicer on Linux, ~/Library/Caches/SolidCraft/slicer on macOS and
%LOCALAPPDATA%\SolidCraft\slicer on Windows. The name stays the same for the same design and
the same bodies, so sending again replaces the file the slicer has open: use the slicer’s
Reload from disk to pick it up. If Windows won’t let the file be replaced because the slicer holds
it, the new copy gets a number (name-2.3mf) and the status line says so. Files older than seven days are
deleted the next time SolidCraft starts.
If the slicer is already running, PrusaSlicer, SuperSlicer and Cura load the file in
the open window instead of starting another one (they are started with --single-instance).
Other slicers, Bambu Studio and OrcaSlicer among them, are started as they are, so they may open a
second window. You can turn this off in
Preferences → General, except for PrusaSlicer 3.0, whose launcher can’t hand a file to a running
window without it. Scripts pass single_instance: false to file.send_to_slicer or file.save_mesh.
PrusaSlicer 2 and 3 behave differently, so SolidCraft reads which one an install is from the
version in its installed metadata (a Flatpak’s own files, or the metainfo file of a package); it
never runs the program to ask. PrusaSlicer 2 is sent the file with --single-instance. PrusaSlicer 3
is started on its own first, SolidCraft waits up to ten seconds for it to be ready, and then sends the
file. If the version can’t be read, the command decides: the slic3r-app-launcher launcher is
treated as PrusaSlicer 3, and any other entry is left as it is.
Closed mechanical meshes with a constant cross-section can be rebuilt as solids with the
mesh.convert command (command palette, scripts and the control channel).
File → Export 3MF… with the format changed to 3MF with face tags (file.export or file.save_mesh with format: "3mf-tagged") writes an
ordinary 3MF in which every triangle also names the B-rep face it lies on and every coordinate
is the tessellation’s own double-precision value. Meshing and analysis tools can use the tags to
keep a model’s faces (for loads, boundary conditions or feature edges); other 3MF readers ignore
them. The format is specified in docs/formats/3mf-face-tags.md.
File → Export URDF… (file.export with a .urdf path) writes the assembly for robot
simulators and ROS: a link per component placement with its mesh, mass and inertia, and the
assembly’s joints, so the parts move in the simulator as they do in SolidCraft. The meshes are
written as STL files next to the .urdf. See docs/urdf.md.
Saves are atomic: a crash at any moment leaves either the old file or the new one, and the
replaced version is kept as <file>.bak. A save keeps the file’s permissions, and a design
opened through a symbolic link (a synced folder linked into a project) is saved into the file the
link points to, so the link stays. Unsaved changes in every open tab are autosaved to separate
entries in a recovery folder, and the next launch offers to recover each design. Saving or
closing one tab removes only its recovery entry. Older design files are upgraded on open, and files
from a newer version are refused with a message.
Save writes a container by default; files named .json keep JSON on subsequent saves.
Save As offers a container or plain JSON on
desktop; Save As JSON is also available in the File menu on desktop and web. Choose
the .json extension for plain JSON. Export Design as JSON writes an independent,
self-contained copy without changing the current file. Scripts can explicitly pass
"format": "json" to file.save or file.save_as even with a .solidcraft name.
Opening detects the format from the contents, so existing JSON designs keep working.
Containers favor fast reading and saving by default. Scripts can request
"compression": "compact" when file size matters more than CPU time.
Containers deduplicate identical STEP, mesh and image payloads. Mesh coordinates retain
their original double precision; this is not a conversion to STL. Geometry still rebuilds
from the timeline when opening. When a saved display preview is available, desktop Open
shows it while checking geometry; editing starts after the complete rebuild. Cancelling
keeps the current design. Previews cannot be edited or measured. Scripts can opt in to a disposable geometry
cache with cache: true on warm saves; saving never rebuilds cold geometry just to
create one. Ordinary saves and browser downloads default to cache: false. An additional display-mesh cache remains opt-in
with preview: true. Unsupported or mismatched caches are ignored. Browser opening
still rebuilds synchronously.
Warm saves with explicit cache: true can also include a small PNG thumbnail. It travels with the file, so
Home can show the recent design without reconstructing it when no local picture
is available. Missing or incompatible pictures use the usual thumbnail generation.
The default cache: false omits the portable thumbnail too.
The repository’s docs/design-container.md describes limits and encoding details.
Designs are saved in the newest format this version of SolidCraft knows ("format": "solidcraft/N"
in the file). It opens designs of every older format and upgrades them as they open; a design
from a newer version asks you to update instead of being guessed at. Development (nightly) builds
save a format number no release reads yet.
Sketch patterns retain their live direction-line references and placement settings when saved. Older patterns keep their fixed vector directions and original placement defaults.
Upgrading keeps what a design meant. Designs keep recorded body transfers, explicit 3D curve coordinate spaces and certified face bindings, alongside semantic configuration styles, associative free-form offsets and cylindrical emboss selections. Older ownership maps, geometry and expressions retain their saved meaning. Legacy 3D curves with retained associations receive their producer’s coordinate-space marker without changing any saved points; detached curves retain world coordinates. A lost cross-component association refreshed by an older build may need an explicit relink or refresh when its source returns. Loading never guesses a numerical correction. Association records, coordinate arrays and placements are bounded and checked.
Design properties
File → Design Properties… (also under Document Settings in the Browser, and on the
top component’s right-click menu) records who made a design and what it is: project, part
number, revision, author, organization, description and licence. OK applies the changes as
one undo step. Scripts and agents use the same commands: document.metadata reads them,
document.metadata.set (key and value, or values: {key: value, …}) and
document.metadata.remove (key or keys) change them. document.inspect and the MCP
inspect_design tool include them.
Exports carry them where the format has a place for them:
| Format | Written |
|---|---|
| STEP | author and organization in FILE_NAME, description in FILE_DESCRIPTION (on one line) |
| IGES | author and organization in the global section |
| 3MF | project as Title, author as Designer, description as Description, license as LicenseTerms |
The part number and revision have no standard place in these formats and stay in the design.
In the file they are one optional top-level metadata object; a design without properties has
none, and is saved exactly as before:
"metadata": {
"project": "ACME enclosures",
"part_number": "ACME-1042",
"revision": "B",
"author": "Jane Doe",
"organization": "ACME",
"description": "Vented case for Raspberry Pi 4",
"license": "CC-BY-4.0",
"com.acme.description": { "shop_id": 88123 },
"org.printfarm": { "slicer_profile": "PETG-0.2", "internal_id": 4 }
}
- SolidCraft’s keys are unprefixed and hold text. Every other unprefixed key is reserved for later SolidCraft versions.
- Other tools keep their data under one reverse-DNS key of their own (
com.example.tool: two or more dot-separated labels of letters, digits,-and_), holding an object. A tool writes only under its own key and leaves the others alone. - SolidCraft keeps every key as it is through open, edit and save, in both the container
and plain JSON, and never checks what another tool wrote: a well-known key holding something
other than text (
"author": 42) is kept, reads as absent, and never stops the design from opening. Typing over it in Design Properties replaces it. - Other tools’ keys are never exported; Design Properties lists them read-only.
- Keep values small:
document.metadata.setrefuses text over 4 KiB, an object over 64 KiB or nested more than 32 levels deep, and more than 256 keys or 256 KiB in all. Binary data belongs in a file next to the design.
Design properties are first saved in format 21. Inserting or linking another design doesn’t
copy its properties; Save As keeps them. A command script of the design
(timeline.export_script) sets them in one step.
Designs in shared or synced folders
A design kept on a network share or in a synced folder (Dropbox, OneDrive, Syncthing, Google Drive) can be open on two computers at once. The desktop app watches for two writers:
- Open elsewhere. While a design is open, SolidCraft keeps a small lock file next to it,
.~<file>.lock(computer, user, SolidCraft version, when it was opened), refreshes it every minute and removes it when the design closes. Opening a design that another SolidCraft has open says so: Open Read-Only (look at it; Save asks for a new file), Open Anyway or Cancel. A lock not refreshed for three minutes (left by a crash or a lost connection) is ignored. Lock files never show in Open or the recent lists, and a design in a folder you can’t write to still opens. - Changed on disk. SolidCraft remembers the file’s size and modification time when it opens or saves a design. If another program or computer wrote the file since, Save asks first: Save a Copy (the default), Overwrite or Cancel.
Scripts, the CLI and the control channel never get these questions. doc.open opens the design
and reports open_elsewhere in its result (read_only: true opens it read-only), and file.save
over a file that changed on disk fails with an error until it is given overwrite: true.
Saving in the web app
In Chrome, Edge and Opera, Save As opens your browser’s save dialog and writes the
.solidcraft file to the folder you choose, and Open opens the browser’s open dialog.
Save then writes back to that same file. The browser remembers the file, so after you
reload the page the first Save asks once for permission to write to it, and carries on.
A design you open from your computer saves back to the file you opened.
In Firefox and Safari, which can’t write to a folder, Save and Save As keep designs in this browser’s private storage instead, and the app says so. Clearing the site’s data can remove those designs. Download a copy to keep it outside the browser or move it to another device.
To get a copy as a download in any browser, choose File → Download design. It holds the current design, including its unsaved changes. In File → Files in this browser… (and in Firefox and Safari, File → Open), Download open design does the same, and Download beside a stored file gives its last saved contents. Whether a folder chooser appears depends on your browser’s download settings.
Use Open from computer… in the same window to open a design from your computer; where the browser can write to a folder it saves back to that file, otherwise it works on a copy kept in the browser. Use Rename beside a stored file to change its name. Autosave and recovery stay in the browser’s private storage in every browser. Exports such as STEP and STL are downloaded.
Keyboard shortcuts
Command shortcuts can be changed in File › Keyboard Shortcuts; these are the defaults.
Click a binding to record a new shortcut. A shortcut can contain Shift, Ctrl/Cmd and Alt/Option modifiers plus exactly one other key. Hold the modifiers, then press the final key; you can take your time assembling the modifiers before that key. The recorder also allows modifiers to arrive after the ordinary key: it previews the chord and waits one second after the last added modifier, and until all keys are released, before confirming. For example, record Shift+Cmd+M for Sketch Mirror on macOS. Escape cancels recording without changing the old binding. Pressing two different ordinary keys cancels the attempt.
The binding button shows modifiers immediately as you press them, even before adding the ordinary key. Releasing a modifier before that key removes it from the preview. A status line tells you to add a key, release all keys, or wait for confirmation; the proposed chord stays visible during confirmation. Nothing is saved while the preview is still pending.
On macOS, the editor, Help list, toolbar hints, context menus and S search display Cmd and Option. Existing settings retain SolidCraft’s Ctrl/Cmd alias for the primary modifier; Control and Command pressed together are recorded explicitly as Ctrl+Cmd. Conflicting bindings still require confirmation, and built-in reserved shortcuts remain reserved. Recording changes app settings, not the design file.
This follows the usual modifier-before-key convention documented by Apple and the key-event and modifier-state model documented by W3C. The one-second grace period is SolidCraft’s recording aid, not a standard-mandated delay; using an already assigned shortcut has no added delay. Operating systems and browsers can intercept reserved combinations before SolidCraft receives them. Choose a different chord if that happens; recording is cancelled if the app loses focus.
| Key | Command | Key | Command |
|---|---|---|---|
| L | Line | E | Extrude |
| R | Two-Point Rectangle | Q | Press Pull |
| C | Center Diameter Circle | F | Fillet |
| D | Sketch Dimension | H | Hole |
| X | Construction | M | Move/Copy |
| O | Offset | J | Joint |
| T | Trim | I | Measure |
| P | Project | A | Appearance |
| Ctrl+Z | Undo | Ctrl+Y | Redo |
| Ctrl+N | New Design | Ctrl+O | Open |
| Ctrl+S | Save | Delete | Delete the selection |
| Ctrl+Shift+S | Save As | ||
| Ctrl+1 | Front View | Ctrl+2 | Back View |
| Ctrl+3 | Left View | Ctrl+4 | Right View |
| Ctrl+5 | Top View | Ctrl+6 | Bottom View |
| Ctrl+7 | Isometric View |
L, R and C start their tool family (lines, rectangles, circles) in the variant you used last, as the family’s toolbar button does: after a Midpoint Line, L starts a Midpoint Line again. Pick another variant from the button’s drop-down (or the Sketch Palette) and the key starts that one from then on. The choice is kept across designs and restarts. With Pressing a tool’s shortcut again switches its mode on (Preferences › Sketch), pressing the key again before the first click steps to the family’s next variant.
Keys handled by the window itself:
| Key | Action |
|---|---|
| S | command search under the ribbon search field |
| V | show or hide the selected bodies, sketches and planes |
| F2 | rename the selected browser or timeline item |
| F6 | fit the view to the model |
| ← → ↑ ↓ | turn the view 15° (with Shift, a quarter turn) |
| Ctrl+← → ↑ ↓ | pan the view |
| Ctrl+8 | look straight at the selected face, plane or sketch |
| Esc | cancel the running command, or clear the selection |
While a value box has the keyboard (a dimension being added, a dialog field), keys type into it and single-letter shortcuts don’t fire. With Sketch Dimension (D), the box opens as soon as you click a line, circle or arc (or the second point or line), so D, click, type the value, Enter. With Preferences › Design › Step values with the arrow keys and the wheel on, ↑ ↓ in a value box, or the wheel over it, step a plain number by 1 (with Shift by 10, with Alt by 0.1); see Parameters and expressions.
Mouse and touch:
| Action | Result |
|---|---|
| Click | select (Shift or Ctrl adds) |
| Click again on the same spot | select the next thing under the cursor (click cycling) |
| Hold the left button still (long-press on touch), or right-click › Select Other… | list everything under the cursor to pick from |
| Double-click a sketch curve or edge | select its chain or tangent loop |
| Double-click the middle button | go Home: the home view, framing everything shown (Preferences › Navigation › Middle double-click: Home, Fit or Off) |
Help › Keyboard Shortcuts in the app lists the current bindings, including any you changed.
With the Touchpad navigation preset, hold Shift while moving the pointer to pan, or Alt/Option to orbit, without pressing a mouse button. Vertical scrolling zooms. These viewport gestures are separate from rebindable command shortcuts; mouse-button selection and editing keep their existing modifiers.
Sketch modifiers
While drawing or dragging a sketch point:
| Keys | Action |
|---|---|
| Ctrl/Cmd or Alt/Option (default) | suppress auto-snap for that gesture |
| Shift | prefer a line midpoint when targeting (skips a curve-curve crossing) |
Change the snap-suppress chord in Preferences › Sketch. Selection still uses Shift and Ctrl/Cmd to add or toggle (Preferences › Selection can make Shift add-only).
Updates and privacy
SolidCraft tells you when a newer release is out. It never updates itself without asking, and the check sends nothing about you or your designs.
What you see
When a newer stable release exists, a small accent-coloured pill reading Update to 0.14.0 (the new version) appears in the title bar next to Report a bug. Nothing pops up by itself: clicking the pill opens a small window with the new version, its release notes and three choices. In a narrower window the pill shortens to Update so the document tabs keep their room, then to its icon alone.
- Download opens the file that fits your installation (see the table below) in your browser, or the release page when there isn’t one.
- Skip this version hides the pill until an even newer release comes out.
- Remind me later hides it for three days.
SolidCraft checks every time it starts, so a release that came out since you last opened it shows up straight away, and again once a day while it stays open.
Preferences ▸ General ▸ Updates has Check for updates automatically (on by default) and Check now, which checks right away and shows the window even for a version you skipped.
Updating in one click
Where SolidCraft can replace itself safely, the window also has Update now:
| Installed as | Update now does |
|---|---|
| AppImage | Downloads the new AppImage next to the old one, verifies it, renames it over the old file and offers Restart now; a file named like a release (solidcraft-<version>-linux-<arch>.AppImage) takes the new release’s name, so tools such as Gearlever keep finding its update data, and any other name is kept |
Windows installer (.msi) | Install (closes SolidCraft): downloads and verifies the .msi, closes SolidCraft (asking about unsaved designs first) and starts the installer |
Windows portable .zip or .exe | Downloads and verifies the package, swaps in the new solidcraft.exe (and solidcraft-cli.exe) and offers Restart now; the old file is removed at the next start |
| macOS | Download and open: downloads and verifies the .dmg and opens it; drag SolidCraft to Applications to replace the old copy |
Flatpak, Snap, .deb, .rpm, .tar.gz | Nothing: your package manager updates these (the window shows the command), or you unpack the new archive |
Every download is verified before anything changes: its SHA-256 must match the release’s
SHA256SUMS.txt, fetched over HTTPS from the same release, and, once the project signs its
releases, that list must carry a valid signature by SolidCraft’s release key that names the
release (see releasing).
A download that doesn’t verify is deleted and the installed copy is left as it was; Download
still gets the package for a manual install. On macOS SolidCraft opens the disk image rather than
replacing the app itself, because the app is ad-hoc signed and not notarized: replacing it in the
background would install code macOS can’t vouch for without you seeing it.
Update now needs write access to the installed file: an AppImage in a folder you can’t write to fails with a message, and Download is the way to update it.
Updating from 0.11 to 0.14
SolidCraft 0.11.0 to 0.14.0 only accept packages from the project’s old GitHub address. Since
the repository moved to github.com/tangency-labs/solidcraft, every release names the new address,
so these versions see that a newer release is out but find no package in it: Update now can’t
install it, and Download opens the release page.
Download 0.15.0 or later once yourself, from the release page or the
download links, and install it over the old
version as you first installed it. From 0.15.0 on, SolidCraft accepts either address, and
Update now works again. Package managers (Flatpak, Snap, WinGet, .deb, .rpm) aren’t
affected.
What is sent
One HTTPS request each time SolidCraft starts, and once a day while it stays open:
GET https://api.github.com/repos/bherbruck/solidcraft/releases/latest
User-Agent: SolidCraft-update-check
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28
Update now then downloads SHA256SUMS.txt (and its signature) and the one package from the
release’s own github.com/bherbruck/solidcraft/releases/download/… links, with the same
User-Agent. That’s all: no version number, no machine or user identifier, no usage data, no cookies. GitHub
sees the request like any other visit to the API (your IP address, the time); see
GitHub’s privacy statement.
SolidCraft compares the answer with its own version on your computer.
- Offline, or GitHub doesn’t answer within 15 seconds: nothing is shown, and the check is tried again an hour later (or at the next start, whichever comes first).
- GitHub’s rate limit for anonymous requests (60 an hour per address) is respected: when GitHub says to wait, SolidCraft waits that long, Check now and the check at start included.
- Turning off Check for updates automatically stops the checks; only Check now asks.
- Setting the environment variable
SOLIDCRAFT_UPDATE_CHECK=0turns all checks off, for example on managed machines.
Builds you compile yourself, the Flatpak and the browser app never check, and Preferences shows no check controls for them. The browser app updates itself: reload the page to get the latest version. When the network is unavailable, or the update can’t finish downloading in time, the saved version opens instead, and the download continues for the next reload.
Which file Download gets
Releases attach the main packages under fixed names (see releasing), and SolidCraft picks the one matching how it was installed:
| Installed as | Detected by | Download | How to update |
|---|---|---|---|
| AppImage | APPIMAGE is set | solidcraft-linux-<arch>.AppImage | Replace the old AppImage with the new one |
| Flatpak | FLATPAK_ID is set | doesn’t check (see below) | flatpak update ai.storyteller.solidcraft |
| Snap from the Snap Store | SNAP is set | release page | sudo snap refresh solidcraft (snapd also refreshes by itself) |
| Snap from a downloaded file | SNAP_REVISION starts with x | solidcraft-linux-<arch>.snap | sudo snap install --dangerous ./solidcraft-linux-<arch>.snap |
.deb | under /usr, dpkg knows solidcraft | solidcraft-linux-<arch>.deb | sudo apt install ./solidcraft-linux-<arch>.deb |
.rpm | under /usr, an rpm database exists | solidcraft-linux-<arch>.rpm | sudo dnf install ./solidcraft-linux-<arch>.rpm |
.tar.gz | anywhere else on Linux | solidcraft-<version>-linux-<arch>.tar.gz | Unpack over the old copy |
| Windows installer | under Program Files, or in the folder the installer recorded | solidcraft-windows-<arch>.msi | Run the installer |
| Windows portable | solidcraft-cli.exe beside it | solidcraft-windows-<arch>-portable.zip | Unzip over the old copy |
Windows .exe | anywhere else on Windows | SolidCraft-<version>-windows-x64.exe | Replace the old .exe |
| macOS | inside SolidCraft.app | solidcraft-macos-universal.dmg | Drag the new app to Applications |
The window shows the command for Snap, .deb and .rpm installations, with a Copy button. The
Flatpak has no network permission and Flatpak itself (or your software center) offers its
updates, so it never checks. Release builds compare versions as semver: a nightly build counts
as the release it was built after, so it’s offered only a newer stable release, and release
candidates are offered their final release.
Troubleshooting: SolidCraft won’t start
0.10.0 closes a few seconds after it starts (Windows)
SolidCraft 0.10.0 crashes at startup on Windows PCs that draw with Direct3D 12 and the system shader compiler. That covers older Intel graphics, PCs without a graphics driver (the “Microsoft Basic Display Adapter”), virtual machines and Remote Desktop sessions. Install 0.10.1 or later, which fixes it. Versions before 0.10.0 don’t have this problem.
Start in safe mode
Safe mode starts with default settings (your own settings stay as they are, for the next normal start), skips the “Recover unsaved design?” offer and draws with the software renderer.
- Windows: run
solidcraft.exe --safe-modefrom a terminal in the install folder, or add--safe-modeto the end of a shortcut’s Target. - macOS:
open -a SolidCraft --args --safe-mode - Linux:
solidcraft --safe-mode(Flatpak:flatpak run ai.storyteller.solidcraft --safe-mode)
Setting SOLIDCRAFT_SAFE_MODE=1 does the same. From 0.10.2, if two launches in a row stop
before the window opens, the next one starts in safe mode by itself. SOLIDCRAFT_RENDERER=cpu
uses only the software renderer and keeps your settings.
A design won’t open or hangs while opening
- Open the previous save. Every save keeps the version it replaced next to it as
<file>.bak(see Files). Copy it to a new name ending in.solidcraft, for examplebracket-previous.solidcraft, and open that. Work saved after the previous save is not in it. - Check the crash reports. If SolidCraft crashed while the design was open, the newest report
in the
crashesfolder (see the table below) usually holds adesign.solidcraftwith the design as it was.solidcraft-cli crasheslists the reports. - Unsaved designs are in the
recoveryfolder, not next to the file. - Report it. GitHub doesn’t accept
.solidcraftattachments, so zip the file (design.solidcraft.zip) and attach it to an issue, with the crash report if there is one.
Reset the settings
SolidCraft keeps its settings and its data in the folders below. All installed versions share them. No version changes anything outside them: no system settings, drivers or registry entries. Quit SolidCraft, then rename (don’t delete) the folders. SolidCraft starts with defaults and makes new ones. Rename them back to restore your settings, or send them to us with a bug report.
| System | Settings (app.ron, graphics.json) | Data (recovery, crashes, control, thumbnails) |
|---|---|---|
| Windows | %APPDATA%\SolidCraft | %LOCALAPPDATA%\SolidCraft |
| macOS | ~/Library/Application Support/SolidCraft | (the same folder) |
| Linux | ~/.local/share/solidcraft | (the same folder); thumbnails in $XDG_CACHE_HOME/SolidCraft or /tmp/SolidCraft |
| Linux (Flatpak) | ~/.var/app/ai.storyteller.solidcraft/data/solidcraft | (the same folder) |
On Windows, paste %APPDATA% or %LOCALAPPDATA% into the Explorer address bar to open the
folder. Releases before the rename used SolveCraft folders next to these; SolidCraft only
reads them, so you can leave them alone.
- The
recoveryfolder holds autosaves of designs that were never saved. Rename the data folder rather than deleting it, so you can still open those autosaves (.solidcraftfiles) with File > Open. - The
crashesfolder holds crash reports. Please attach the newest one to a bug report.
From 0.10.2, SolidCraft also checks the settings files at start. A file it can’t read is renamed
to app.ron.bad-<time> or graphics.json.bad-<time>, and SolidCraft starts with defaults
instead.
“No graphics adapter” or nothing happens at all
SolidCraft needs Direct3D 12 or Vulkan on Windows, Metal on macOS, and Vulkan or OpenGL on Linux. Without a graphics driver it falls back to the system’s software renderer (WARP on Windows, llvmpipe or lavapipe on Linux), which is slow but works. From 0.10.2, if no window can be opened at all, SolidCraft tries the software renderer once and otherwise shows a message saying what failed (earlier versions closed without a word).
On Windows, if the graphics API cannot create the window’s renderer, SolidCraft tries the
remaining APIs in order: Direct3D 12 → Vulkan → OpenGL, then software rendering. The status
message and Preferences › Graphics say which API failed and why. The saved graphics
preference stays unchanged. With an explicit WGPU_BACKEND, a failed start shows the error
and exits without switching; change or remove that variable to try another API.
Things to try:
- Install the graphics driver from the GPU maker (Intel, AMD, NVIDIA) rather than Windows Update.
- In a virtual machine, turn on 3D acceleration. Over Remote Desktop, try it on the machine itself.
- Run
solidcraft --crash-logto list recent crash reports, and attach the newest one to an issue. - On an Intel Mac (Intel Iris graphics, macOS 12), “Parent device is lost” at start comes from
the Metal compiler refusing wgpu’s indirect-call validation shader. SolidCraft leaves that
validation off on Intel Macs. To change it, set
WGPU_VALIDATION_INDIRECT_CALL=1(on) or0(off) before starting; a value you set is always kept.
The desktop stutters or freezes while SolidCraft builds bodies
Some graphics drivers hang on the shader SolidCraft uses to build bodies on the GPU, and the
system then resets the GPU. SolidCraft already builds bodies on the CPU on the AMD FirePro
D300/D500/D700 (Mac Pro 2013). On another GPU that does this, start SolidCraft with
SOLIDCRAFT_CPU_TESS=1 set to build bodies on the CPU, and open an issue with the GPU’s name.
The interface looks blurry on Linux with display scaling
On Linux, SolidCraft runs through XWayland whenever an X display is available, because file drag and drop from a file manager only works there (niri is the exception and stays on native Wayland). A compositor that doesn’t let X11 apps scale themselves draws an XWayland window at 100 % and stretches it to the monitor’s scale, so text and icons look soft. That happens with fractional scaling (125 %, 140 %), and on GNOME 47 (RHEL and Rocky Linux 10) at any scale, 200 % included. The diagnostics in a bug report show it as about 1 physical pixel per point at a scale of 200 %.
Run SolidCraft on native Wayland instead: Preferences › Graphics › Window system ›
Wayland, which applies at the next start. Versions without that setting (0.21.0 and earlier)
take SOLIDCRAFT_WAYLAND=1 in the environment:
SOLIDCRAFT_WAYLAND=1 solidcraft
On native Wayland, dropping files onto the window doesn’t work; use File > Open or pass the file on the command line. Wayland, XWayland and niri has the details.
SolidCraft closes right away on Windows
A graphics driver can crash the app while it starts, before SolidCraft can show anything (an
old Intel Vulkan driver, igvk64.dll, did). Windows’ Event Viewer names the driver under
Windows Logs › Application (“Faulting module name”).
From 0.11.1, SolidCraft on Windows starts Direct3D 12 first and loads no other graphics API when Direct3D 12 has a graphics card; Vulkan and then OpenGL are tried only on a computer without one. Earlier versions started Vulkan, Direct3D 12 and OpenGL together and preferred Vulkan. To choose the graphics API yourself, in any version, open a Command Prompt and start SolidCraft from it:
set WGPU_BACKEND=dx12
"C:\Program Files\SolidCraft\solidcraft.exe"
vulkan and gl are the other choices. setx WGPU_BACKEND dx12 keeps it for apps started
from the Start menu after you sign in again. From 0.11.1 the setting can also live in
graphics.json in the settings folder, as {"msaa": 4, "backend": "dx12"} (auto is the
order above). Safe mode ignores it and always uses the software renderer.
With OpenGL on Windows, the window has the Windows title bar above SolidCraft’s application bar instead of using the application bar as its title bar. Some OpenGL drivers (older Intel HD and UHD graphics) otherwise show the picture a title bar’s height lower than the window, so clicks land above what they point at.
SolidCraft restarted, saying the graphics device was reset
When the graphics driver stops responding, the system resets it (on Windows, “Display driver stopped responding and has recovered”), and the graphics device SolidCraft draws with is gone for good: the window can’t draw anything more. SolidCraft then closes, keeps your unsaved changes for recovery as it does after a crash, and starts again; the new window says what happened and offers the changes back. If the device is reset again within ten minutes of that restart, the next start uses software rendering, which doesn’t depend on the graphics driver. Updating the graphics driver from the GPU maker usually stops the resets.
A design saved by 0.10 won’t open in an older version
Designs saved by a newer SolidCraft use a newer file format. Older versions refuse them with “this design was saved by a newer SolidCraft” and don’t change the file. Open them with the current version.
Save As says the file can’t be found (Windows)
If saving into Documents (or Pictures, Desktop and other protected folders) fails with a
“File not found” message from the Windows save dialog and nothing is saved, Windows Security’s
Controlled folder access is probably blocking SolidCraft. Windows usually also shows an
“Unauthorized changes blocked” notification. Either save to a folder that isn’t protected, or
allow SolidCraft: Windows Security › Virus & threat protection › Ransomware protection ›
Controlled folder access › Allow an app through Controlled folder access, then add
solidcraft.exe.
SolidCraft and AI agents (MCP)
SolidCraft speaks the Model Context Protocol over stdio (JSON-RPC 2.0, one message per line; protocol revisions 2025-06-18, 2025-03-26 and 2024-11-05). An agent models exactly like a person does: every tool runs engine commands, the same ones the toolbar, palette, scripts and control channel use, and never opens a dialog.
Persistent headless and bridged sessions expose asynchronous product images through the
ordinary execute tool: render.preflight, render.start, render.status,
render.cancel and render.export. Keep the complete returned job ID; image bytes remain
in the session until explicit PNG export. See render jobs.
For how the same command surface should work in the browser (workers, tiered storage, in-tab
agents), see web-runtime.md. For sandboxed plugins and parametric timeline
features that also use run(command, args), see plugins.md. Desktop MCP in this
document remains the reference implementation today.
Start it
# Headless: an in-process design session, no window (screenshots are drawn on the GPU).
solidcraft-cli mcp
# Headless, starting from a design file or a command script.
solidcraft-cli mcp --in part.solidcraft
# Bridged to the running app, so you can watch the agent model. The app's control channel is
# on by default (see below), so no flag is needed on either side:
solidcraft &
solidcraft-cli mcp # finds the running app; headless when none runs (--headless forces that)
# A fixed port, or a second app: name it.
solidcraft --control 7878 &
solidcraft-cli mcp --connect 127.0.0.1:7878
The app listens by default
The desktop app opens its control channel at start-up on 127.0.0.1:27878 (or the next free port
when that one is taken), unless Preferences > Assistant > “Let AI agents connect (control
channel / MCP)” is off; the setting takes effect at the next start. It is reachable from this
computer only, and every connection must show the token made fresh for that launch, readable only
by your user account (control-protocol.md). The app writes the port to
control/current.port in the same folder as its token file; solidcraft-cli mcp with no
arguments reads it. --control PORT and SOLIDCRAFT_CONTROL_PORT still choose the port and apply
even when the setting is off. With the setting off and no flag, the app listens on nothing.
Bridged, the server finds the app’s control token in the token file the app wrote for that port
(control-protocol.md); --token T or SOLIDCRAFT_CONTROL_TOKEN give
it explicitly when the app runs in a sandbox whose data folder the server can’t see (prefer the
variable: command-line arguments are visible to other users in the process list).
A bridged server starts even when no app listens on that port yet, so an MCP client that
launches it before the app (or after the app was closed) keeps its tools. The server’s
instructions then say the app was not running, and every tool call answers with an error that
starts no SolidCraft app is listening at HOST:PORT and says how to start it
(solidcraft --control PORT). The first call after the app starts connects to it, reading that
launch’s token. When the app quits or crashes mid-session, the next call says the same; a call
that was running when it went away says the app did not answer and may have quit or crashed,
after running the call or not, so inspect the design before sending a change again.
A batch that could not be sent says the app is not running, so it is safe to send again.
stdout carries only protocol messages; errors go to stderr. Bridged screenshots are written to a temporary file by the app and read back by the server, so both must run on the same machine.
Parameter checking
Commands with a typed parameter contract (every new command, and more of the older ones as they
are converted; describe_command shows a command’s schemas) can be checked as they are called.
--param-check off|warn|strict sets the level for a headless session, including a script or
recipe given with --in; the default is off, which changes nothing. warn runs the call and
adds param_warnings to its result, such as unknown key `operaton`; did you mean `operation`? (a starting script’s warnings go to stderr). strict refuses the call, and the
design is left as it was. Calls bridged to the app are not checked yet.
Which files the agent can reach
Tool calls read, write and delete files only inside the server’s folders:
- the folder of the open design (the
--indesign, or whatever the agent last opened or saved), but only when it lies strictly inside your home folder (or the temporary folder) and not in a hidden folder. Home itself, a folder above it (/,/home,/Users, a drive’s root), hidden folders such as~/.configand folders elsewhere don’t count. Hidden files and folders inside the design’s folder (.env,.git) are out of reach too; - SolidCraft’s own recovery folder, so
doc.recovery_list,doc.recoveranddoc.recovery_discardwork out of the box (discarding deletes only real recovery entries); - the server’s own scratch folder (a new private
solidcraft-mcp-*folder in the temporary folder), wherescreenshotwithout apathgoes; - every
--allow-path DIR(repeat it for more). A folder named here is reached in full, hidden files included.
The working folder the server was started in does not count by itself, so ~/.ssh, ~/.aws,
~/.bashrc and other dotfiles are never in reach by default. stderr lists the folders at
start-up and says when the --in design’s folder was left out; --allow-path adds it back.
Paths are resolved before they are compared, so .. and symlinks that lead out are refused
(saving through a link writes the file it points to, so the link’s target is what counts), and
a new file must go into a folder that exists (a missing folder inside an allowed one is reported as missing; it is not created and does not call for a wider --allow-path). On Windows a path must be on a local drive
(C:\… or \\?\C:\…): UNC, device and NT paths in any spelling (\\server\share,
//server/share, \\?\UNC\…, \??\…, \\.\…), drive-relative paths (C:file), a : in a
file name (alternate data streams), reserved device names with any extension (NUL, CON,
COM1.txt, …) and short 8.3 names (PROGRA~1) are refused before any file system call, and the
resolved path is checked again, so a link or junction that leads to a share is refused too. This
includes a sketch.text font given as a file path and the font file a design’s text names when
the design is opened; installed fonts named by family still work.
The server spells its own folders (scratch, recovery) with long names, so they work when the
temporary folder’s path has a short name (…\NAME~1.XYZ\AppData\Local\Temp, as Windows often
gives when the user folder’s name is long).
A refused path is a tool error saying why.
A hard link inside an allowed folder can’t be told apart from an ordinary file, so a hard link to a file elsewhere is reachable through it; don’t keep such links in a folder you allow.
solidcraft-cli mcp --allow-path ~/designs --allow-path ~/exports
solidcraft-cli mcp --allow-any-path # no limit (the behaviour before this option existed)
Bridged, the server sends its folders with every request (allow_paths, see
control-protocol.md) and the app adds its own recovery folder and the
folder of the design open in it, under the same rules, so give absolute paths: a relative one
resolves against the app’s working folder, not the server’s. The app’s own
file dialogs are never limited.
Tool annotations
Each tool says what it may do, for clients that ask before running tools (or auto-approve on
these hints). list_commands, describe_command, inspect_design, measure, body_topology,
skill_reference, crash_reports, pending and running are read-only. set_parameter, undo and redo
change the design undoably. new_design replaces it (destructive). execute, batch, open, save, export and
screenshot (whose path writes a file) can read, write, overwrite or delete files: they are
not read-only, destructiveHint and openWorldHint are true.
Client configuration
solidcraft-cli is on PATH after every package install: the Windows installer adds its folder, the deb, rpm and FreeBSD packages install it in /usr/bin, the Homebrew cask links it, the AppImage writes a small solidcraft-cli launcher into ~/.local/bin on its first run, and the macOS app links it into /opt/homebrew/bin or /usr/local/bin when it starts (Help > Install solidcraft-cli command does it with an administrator prompt when those are not writable). Two packages keep it out of PATH by design: Snap exposes it as solidcraft.cli, and Flatpak runs it as flatpak run --command=solidcraft-cli ai.storyteller.solidcraft; use those as the client’s command (with mcp as the arguments).
The quickest way: Help > Connect AI assistants… in the app, or
solidcraft-cli mcp setup # every client found on this computer
solidcraft-cli mcp setup --dry-run # show what it would write, change nothing
solidcraft-cli mcp setup --client opencode --live
(mcp install is the same.) It adds a solidcraft server, using the full path of the
solidcraft-cli that runs it, so nothing has to be on PATH. A client counts as installed when its
configuration folder exists, and no client program is started. Supported:
--client | Configuration file |
|---|---|
opencode | ~/.config/opencode/opencode.json (or .jsonc) |
claude-desktop | %APPDATA%\Claude\claude_desktop_config.json, ~/Library/Application Support/Claude/, ~/.config/Claude/, and the Microsoft Store package’s folder |
claude-code | ~/.claude.json (user scope mcpServers) |
cursor | ~/.cursor/mcp.json |
codex | $CODEX_HOME or ~/.codex, config.toml (CLI, IDE extension and desktop app) |
gemini | ~/.gemini/settings.json |
copilot | ~/.copilot/mcp-config.json (GitHub Copilot CLI) |
windsurf | ~/.codeium/windsurf/mcp_config.json |
zed | Zed’s settings.json (context_servers) |
vscode | VS Code’s user mcp.json |
The files are edited in place: only the solidcraft entries change, everything else in the file
(other servers, comments, order) stays as it was, and the file is copied to <name>.bak first. A
file that does not parse is left alone. Running it again after an update moves the program only
refreshes the path of an entry it wrote; an entry you changed (extra arguments, another program)
is left as you have it. --live also adds solidcraft-live, which bridges to the open app (port
7878, see below). Restart the client afterwards.
SolidCraft also does this by itself: when the app starts it adds the solidcraft entry to each
installed client (and refreshes its path after an update), and says once which clients it
connected. It never touches an entry you changed. Settings > Assistant > Make SolidCraft
available to AI apps on this computer turns it off, which removes the entries it added. Runs
with --control or SOLIDCRAFT_CONTROL_PORT (automation) leave your clients alone.
To configure a client by hand instead, these are the entries. OpenCode (opencode.json):
{
"mcp": {
"solidcraft": { "type": "local", "command": ["/path/to/solidcraft-cli", "mcp"], "enabled": true }
}
}
Claude Code (project .mcp.json, or claude mcp add solidcraft -- solidcraft-cli mcp; a setup
added under the previous name keeps working, see the compatibility notes):
{
"mcpServers": {
"solidcraft": { "command": "solidcraft-cli", "args": ["mcp"] }
}
}
If a terminal doesn’t find solidcraft-cli (older Windows installers don’t put its folder on
PATH; see below for where it is), give its full path (by default
C:\Program Files\SolidCraft\solidcraft-cli.exe):
claude mcp add solidcraft -- "C:\Program Files\SolidCraft\solidcraft-cli.exe" mcp.
Claude Desktop (claude_desktop_config.json), bridged to a running app:
{
"mcpServers": {
"solidcraft": { "command": "/path/to/solidcraft-cli", "args": ["mcp", "--connect", "127.0.0.1:7878"] }
}
}
On Windows the installer puts solidcraft-cli.exe next to solidcraft.exe in
C:\Program Files\SolidCraft (the portable zip has both too). Give the full path, with each
backslash doubled in JSON. This configuration declares two servers: solidcraft models
headless (no window; steadier for timings), solidcraft-live drives the app you have open, so
you can watch the agent model. --allow-path names a folder where the agent may save and
export; it must already exist.
{
"mcpServers": {
"solidcraft": {
"command": "C:\\Program Files\\SolidCraft\\solidcraft-cli.exe",
"args": ["mcp", "--allow-path", "C:\\Users\\NAME\\SolidCraft-Agent"]
},
"solidcraft-live": {
"command": "C:\\Program Files\\SolidCraft\\solidcraft-cli.exe",
"args": ["mcp", "--connect", "127.0.0.1:7878", "--allow-path", "C:\\Users\\NAME\\SolidCraft-Agent"]
}
}
}
Start the app with its control channel for solidcraft-live, from PowerShell:
& "C:\Program Files\SolidCraft\solidcraft.exe" --control 7878, or once for every launch:
setx SOLIDCRAFT_CONTROL_PORT 7878.
Open claude_desktop_config.json with Claude Desktop’s Settings > Developer > Edit Config
rather than by its path: the Microsoft Store (MSIX) build of Claude Desktop can keep its data in
its own package folder (%LOCALAPPDATA%\Packages\…\LocalCache\Roaming\Claude) instead of
%APPDATA%\Claude, and Edit Config opens the file the app actually reads. Quit Claude Desktop
completely (from the notification area) and start it again to load the servers.
Agent skills
skills/ holds five Agent Skills for agents that model through this server:
solidcraft-modelingcovers building a new part.solidcraft-editingcovers changing an existing design.solidcraft-checkingadds checks for errors SolidCraft does not report, regression tests and porting, next to the other two.solidcraft-mechanismscovers assemblies: joints placed by world coordinates, motion links, closed loops such as actuators, contact sets and motion studies, checked against analytic values.solidcraft-reportingturns a problem the user hit into a bug report: it reproduces it in a fresh design, shrinks it to a minimal command script, checks the numbers withmeasureand a screenshot, and searches existing issues first.
They cover the workflow, the command shapes, how to verify with measure, and the known traps. With them an agent builds a part in a few batch calls and reads far less output. To install them for Claude Code, copy the folders into ~/.claude/skills/; skills/README.md has the details.
The skill_reference tool returns these documents from the copies built into SolidCraft, so an agent without file access can follow them: {"skill": "solidcraft-modeling"} returns its reference.md, the default file. Mechanisms also has reference.md, checking has reference/porting.md, and every skill has SKILL.md (editing uses modeling’s reference). It never reads a path from disk, and an unknown skill or file is refused with the available choices. SolidCraft’s own chat turns the agent’s file tools off, so this is how its agent reads the skills there.
Tools
| Tool | Arguments | What it does |
|---|---|---|
list_commands | filter? | The command catalog: id, label, tab/panel, shortcut, parameter docs, enabled now. filter matches id, label, tab and panel. |
describe_command | id | One command’s parameter docs (params) and, for commands with a typed contract (every new command, and more of the older ones as they are converted), the JSON Schemas (2020-12) of its parameters (input) and of a successful result (output); null for the others. The same as execute engine.describe {command}. |
execute | command, params?, verbose? (default true) | Run any command with JSON parameters (list_commands shows them). Failures leave the design unchanged. A feature command replies with the feature (feature, name), every body (bodies) and the bodies it created, changed and removed (created_bodies, changed_bodies, removed_bodies). verbose: false uses the same compact reply as batch. |
batch | commands: [{command, params}], rollback? (default true), verbose? (default true), timings?, profile?, coalesce_parameters? (default false) | Run steps in order, stop at the first error and undo the completed steps. The reply names the failing step (failed.index, failed.command, failed.error), every result before it, rolled_back (true only when the design really went back to where the batch started), undone, applied (the indices of the steps whose changes are in the design) and not_run (the steps never sent). A step may name a tool instead of a command, with that tool’s arguments as params: set_parameter (runs parameters.change; value or expression), list_commands (engine.commands, filtered by filter as the tool filters it), describe_command (engine.describe), execute ({command, params}), measure, inspect_design (document.inspect: each sketch as its counts, not its full contents, and no joints), undo, redo, export, save, open, new_design and crash_reports. screenshot, body_topology (use model.edges and model.faces), batch, pending, running and skill_reference can’t be steps: such a batch fails before anything is sent. So does a step the batch would certainly refuse at its turn (#5853): a step without a command, an unknown command id (the error names the closest ids) or, with --param-check strict, parameters that break a command’s contract. A headless session checks this itself; an app checks it against its own commands when it gets the batch. verbose: false keeps the reply small in large designs: feature steps leave out the full body list (created_bodies, changed_bodies, removed_bodies stay) and sketch steps the ids they repeat outside created. Bridged to an app that has engine.batch, the whole batch is one control request (one frame of the app). Older apps, and apps showing the agent cursor, get the steps one by one. timings: true adds timings: [{index, command, ms}] for every step run, the failing one included, and total_ms. profile: true also adds each step’s profile: {boolean_ms, tessellate_ms, other_ms}, its time in kernel booleans, in tessellation and the rest. Steps are timed where they run: in the app when bridged. An app without engine.batch is timed step by step, without profile. coalesce_parameters: true sets each run of consecutive plain parameter edits (parameters.change or set_parameter with a name and a value, or changes) together, so the design rebuilds once per run instead of once per edit. Steps between runs see the design up to date. Each step keeps its own result (coalesced: the run’s length), and the run is one undo step. The run skips the states between its edits, so an edit refused on its own can go through with the run’s later ones. A refused run goes step by step, naming the refused step. |
inspect_design | measure? (default true), sketches? (default true), joints? (default true) | Design properties (metadata: project, author… and other tools’ keys; set them with execute document.metadata.set), parameters, timeline (type, suppressed, rolled back, error, warning, timing), bodies (volume, area, centre of mass, bbox, face/edge/vertex counts, visible; removed_later: true while a sketch is being edited on a rolled-back timeline and a later feature deletes the body, which is then not shown), sketches (curves, points, constraints, profiles, DOF, solve status, visible), joints when the design has any (as joint.inspect gives them: each origin’s occurrence, component and path, the snap as saved, the feature, face, edge or sketch curve (sketch: {id, name, curve}) its persistent names refer to, resolves and the error when it is lost; kind, offset, angle, flip, values, limits, expressions, motion links; execute joint.inspect {joint} gives one joint), selection, undo depth. Hide helper sketches, bodies or construction planes with execute browser.visibility ({items, visible} or {folder, visible}), and joint glyphs with {joints: [id or name], visible} or {folder: "joints", visible} (a hidden joint still constrains and drives; joint.list reports each joint’s visible). |
measure | body? or items? | Area (mm²), bbox, topology counts and closed per body. Volume (mm³), volume centre of mass, density and mass are reported only for closed, consistently oriented bodies (null or omitted for open surfaces). Volume/mass totals skip open bodies; total.open_bodies_skipped gives their count. Area and topology totals include all bodies. With items (one or two selections as execute takes them: face, edge, vertex, sketch_curve, profile), the picked geometry instead: faces and edges need only type, body and point; index is optional. See below. |
body_topology | body | Edges (index, midpoint, length, ends) and faces (index, name, area, centroid, normal, point, and for a cylindrical face its cylinder: origin, axis, radius). An edge midpoint is the [x,y,z] that picks that edge for fillet and chamfer. A face’s point lies on it and picks it (its centroid may not: that of a face with a hole is in the hole); a face’s name picks it for the commands that take faces (face.delete, solid.shell, solid.draft, solid.offset_face, solid.split_face, solid.replace_face, and face of sheet.convert). |
set_parameter | name, value or expression (number or expression), unit?, comment? | Create or change a user parameter and recompute. expression is another name for value (the one parameters.change uses): give one of them, or both only when they agree; neither, or two that differ, is an error. Reports recomputed (features rebuilt), restored (features whose earlier result for the same inputs was reused, e.g. a value set back) and features that now fail. |
screenshot | view? (iso, top, front, back, bottom, left, right, home, fit, or a named view saved with view.save), camera? ({target, yaw, pitch, distance, fov?}, as view.apply), path?, width?, height?, source? (window, model), grid?, axes? | A PNG image content block. Headless: a render with the viewport’s pipelines on a headless GPU device (see solidcraft-cli gpu; with no adapter, the CPU rasterizer, without analysis colours), fitted to the model, or from the named view or camera; the reply’s renderer says which (gpu, gpu-software, cpu, with a warning for cpu). Bridged: the app window (default) or a model render with the app’s camera; a named view or camera moves the app’s viewport there first (execute view.apply does the same without a screenshot). grid: false and axes: false leave the ground grid (it lies on Z 0 and cuts through a part that straddles it) and the origin’s axes out of a model render; axes hidden with browser.visibility stay out whatever axes says. Bridged, a screenshot that gives grid or axes is a model render, which follows the app’s Grid and Origin switches for what it does not give; source: "window" with either is an error. A window that is minimised or on another macOS Space presents no frame: with no source the reply is a model render and says so (fallback); source: "window" is an error saying why, at once. Agents that don’t need the app’s chrome can ask for source: "model". |
export | path, format? (step, 3mf, 3mf-tagged, stl, stla, obj, urdf), bodies?, mesh_prefix?, effort?, velocity? | STEP, 3MF or mesh export; URDF writes the whole assembly with its meshes next to it (docs/urdf.md). |
undo, redo | — | Undo / redo one change. |
pending | — | Bridged: the result of a request that was still running when its call returned (an error starting still running:), waiting up to 60 s for it; done: false while it runs (call again), pending: null when nothing runs. See Long-running requests. |
crash_reports | id?, limit? | The crash reports on this computer, newest first; with id, the whole report (backtrace, command, recent commands, design_path/replay_path). See crash-reports.md. |
skill_reference | skill, file? (default reference.md) | Read a bundled skill document. Skills: solidcraft-modeling, solidcraft-mechanisms, solidcraft-editing, solidcraft-checking. |
running | - | What the app is running or has queued, for every client (control.running, see control-protocol.md): each request’s connection and id, method, command id or a batch’s step count, running_s and queued, never its parameters. Answers at once while the app is busy: ask it when a call timed out before sending a change again. Headless: always {"running": []}. |
new_design | name? | Start an empty design. |
open | path | Open a .solidcraft design, a STEP file (.step/.stp) as a new design holding its bodies (an Import base feature), or a 3MF/STL file as mesh bodies. |
save | path? | Save the design (to its current file without path). |
measure with items reports each item under items (length_mm, area_mm2 or position).
A circle, arc, hole or other cylindrical face also gives diameter, radius, center and
axis, with circumference when it goes all the way round, or arc_length and angle_deg
for an arc; round says which (circle, arc, cylinder). A slot (a sketch profile of two
half circles and two lines, or one half-cylinder end of a slot in a solid) gives slot:
width, center_distance and overall_length. With one item these fields are repeated at
the top level. With two items:
min(alsodistance_mm, withfrom,toanddelta): the closest distance;max(withmax_from,max_to): the farthest distance;center_distance(withcenter_from,center_to): when an item is round, from its centre or axis: axis to axis for parallel axes, the closest distance between the axes otherwise, and axis to plane, line or point;measure: the distance the Measure panel shows first (centerwhere there is one, elsemin);angle_degbetween two planar faces or straight edges.
Earlier fields are unchanged, so older scripts keep working.
To change a feature after it is made, read its definition with execute timeline.inspect
({feature}: id or name) and change fields with timeline.edit ({feature, set}; e.g.
{"extent": {"distance": "30"}}). The fields per feature type are listed in the book’s
Solid features chapter.
To tell whether the design changed since a call, without reading all of it again, execute
document.revision answers {document_id, revision}: revision moves only when the design
changes, and document_id names the open design (another one after new_design or open).
See control-protocol.md.
Resources: solidcraft://design (inspect JSON with measurements) and solidcraft://commands
(the catalog).
Errors: an unknown tool or arguments that don’t match the tool’s schema are JSON-RPC
-32602 errors (an unknown argument’s error lists the tool’s arguments); an engine failure
(bad command parameters, a feature that fails) is a tool result with isError: true and the
engine’s message, which the model can read and correct.
Long-running requests
The app answers a control request that runs longer than 60 seconds (a big fillet, say) with
“still running” (control-protocol.md); it has not failed, and was
not cancelled. The bridged server then keeps the connection, where the reply will come, and
asks the app (control.pending) every half second for up to 240 seconds more, saying on stderr
how long it waits; when the request finishes, the tool call returns its real result. If it still
runs after that, the tool call fails with an error starting still running: that names the
request and says what it still does: it will still apply when it finishes, or will still write
the file (an export or a save: the design does not change), or only reads (nothing changes). Don’t
send it again: call the pending tool, which waits up to 60 seconds for its result each time.
Any other tool call first waits up to 60 seconds for it too, and is not sent while it still runs
(so nothing is queued to apply unseen behind it); a request that finishes in that wait keeps its
result for pending. If the connection to the app is lost meanwhile, the result is lost with it,
and pending says so: inspect the design before sending a change again.
So a single tool call may block for up to 300 seconds (the app’s 60 s wait plus 240 s of polling)
before it returns still running:, and a call made while an earlier request still runs for up to
60 seconds more. An MCP client needs a per-call timeout above that (say 330 s) to see that error;
a client that gives up sooner only sees its own timeout, though the request goes on running in the
app: then call pending (or inspect_design) before sending anything again.
A batch step that is still running stops the batch like a failure. Its rollback first waits for
the step (as above), so a restored checkpoint takes the step back too; if it still runs, the
reply says rolled_back: false, marks the step still_running and lists it in applied, as it
will apply when it finishes.
batch rollback goes back to an engine checkpoint (edit.checkpoint) taken before the first
step: the design, the active sketch and the undo history are restored exactly as they were,
however long the history is. Batches that call file.new or doc.open get the design back,
not its file path. (An older app without checkpoints falls back to undoing as many steps as
the batch added to the undo history.)
Camera and close-up recipe
The camera of screenshot (the object view.apply, view.save and solidcraft-cli snapshot --camera take too) is an orbit camera, {target, yaw, pitch, distance, fov?}, in millimetres and radians. Unlike the angle of solid.move, a bare number is not degrees here. The eye is at target + distance * (-sin(yaw) * cos(pitch), -cos(yaw) * cos(pitch), sin(pitch)), looking at target with Z up on the screen. So yaw: 0, pitch: 0 is the front view: the eye at -Y, looking along +Y, with +X to the right. A yaw of π/2 looks from -X, π from +Y and -π/2 from +X. The pitch is the elevation angle up from the XY plane, and π/2 looks straight down with +X to the right and +Y up. fov is the vertical field of view in radians. 0, or no fov, is an orthographic view whose visible half-height is 0.4 × distance; with a perspective fov the half-height at the target is distance * tan(fov / 2). inspect.section removes what lies on the side its normal points to: it keeps the points p with n · p < d, where d = n · origin (flip: true keeps the other side). To look into the cut, put the eye on the removed side.
To look closely at one part of a large model, such as a STEP assembly, work in a headless session. These steps change only the open design in memory:
- Open the model with
solidcraft-cli mcp --in assembly.step(or theopentool, which also takesupandnames, see step 4 and STEP import).inspect_designlists the bodies with their names and bounding boxes. - Remove what is in the way with
solid.remove, e.g.{"bodies": ["Cover"]}. - Cut the region out of the rest with
solid.box, e.g.{"corner": [40, 0, -45], "length": 20, "width": 50, "height": 45, "operation": "intersect", "targets": ["Housing", "Plate"]}. Each listed body is trimmed to the box, and one that lies wholly outside the box is removed. The box itself is not kept. - If the model lies on its back (many STEP files have +Y up), open it with
up: "y"(theopentool,doc.open,file.insert_step), which turns the file’s bodies a quarter turn about X (Y becomes Z). One already open is stood Z up withsolid.move, listing every body you keep, e.g.{"bodies": ["Housing", "Plate"], "axis": [1, 0, 0], "angle": 90}. It turns the bodies about the world origin, and a bare angle is in degrees. A turn of +90° about X takes +Y to +Z. - Colour the parts that matter with
appearance.assign, e.g.{"bodies": ["Housing"], "color": "#3a7bd5"}, and hide helper bodies or sketches withbrowser.visibility, e.g.{"items": ["Plate"], "visible": false}. - Cut a section with
inspect.section, e.g.{"plane": {"origin": [50, 30, 0], "normal": [0, 1, 0]}}or{"plane": "XY", "at": 5}. - Take the picture with
screenshot: acamerawhosetargetis the centre of the region and whose eye is on the cut side,source: "model",grid: falseif the region straddles Z 0 (the ground grid would cut through it), andpath,widthandheightas needed. An orthographic picture is 0.8 ×distancehigh, so adistanceof about 1.5 times the region’s size frames it. Look at the image, adjustyaw,pitchanddistance, and shoot again. Headless, the picture is always a render of the model; bridged to the app,source: "model"asks for that render instead of the app’s window.
Compact replies
execute and batch default to verbose: true, preserving the full command results.
For long modelling sessions, set verbose: false at the tool-argument level:
{"name": "execute", "arguments": {"command": "solid.box", "params": {"length": 10, "width": 10, "height": 10}, "verbose": false}}
Feature replies omit the full bodies list while keeping feature, name,
created_bodies, changed_bodies and removed_bodies. A cut can therefore report a
changed body even when no body name was added or removed. Sketch replies keep new
IDs under created and omit only identical top-level points, curves,
constraints and dimensions lists. Other fields and errors are unchanged;
inspection commands still return their complete results. Compaction uses the
engine’s reply directly, without an additional design-inspection request.
Demo: an enclosure built over MCP
examples/demo/enclosure_mcp.py is a small MCP client. It drives a running app (solidcraft --control PORT) through solidcraft-cli mcp --connect and builds an electronics enclosure one
execute call at a time (enclosure_steps.json): a rounded box, shelled open at the top, a
USB port cut through a side wall, four screw bosses, and then a change of the width
parameter that the timeline rebuilds. examples/demo/record.sh records the run with ffmpeg,
with each step’s caption burnt in.
examples/demo/enclosure_ui_mcp.py (v2) builds the same part the way a person would: the
modelling goes through the app’s UI over its control channel. The pointer glides to toolbar
buttons and faces and clicks them, the real dialogs open, the sketches go on the model’s faces,
and the camera follows. The parameters, the sketch dimensions bound to them and two extrudes go
over MCP. With --story v3 (the default) one standoff is patterned 2 × 2 in the Rectangular
Pattern dialog with spacings from the parameters, and width and wall are then changed in the
Parameters dialog. record_ui.sh records it and draws the pointer and the captions.
A typical session
→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"me","version":"1"}}}
→ {"jsonrpc":"2.0","method":"notifications/initialized"}
→ {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"batch","arguments":{"commands":[
{"command":"sketch.create","params":{"plane":"XY"}},
{"command":"sketch.rectangle.two_point","params":{"p0":[0,0],"p1":[40,30]}},
{"command":"sketch.finish"},
{"command":"solid.extrude","params":{"distance":20}},
{"command":"solid.fillet","params":{"edges":[[0,0,10]],"radius":3}},
{"command":"sketch.create","params":{"plane":"XY"}},
{"command":"sketch.circle.center","params":{"center":[20,15],"radius":5}},
{"command":"solid.extrude","params":{"distance":20,"operation":"cut"}}]}}}
← {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"{\"ok\": true, \"completed\": 8, \"results\": [...]}"}]}}
→ {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"measure","arguments":{}}}
← … "total": {"volume_mm3": 22390.6, …}
→ {"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"screenshot","arguments":{"view":"iso"}}}
← {"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"image","mimeType":"image/png","data":"iVBORw0…"},{"type":"text","text":"{\"source\":\"model\",…}"}]}}
→ {"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"export","arguments":{"path":"/tmp/part.step"}}}
The tests in crates/mcp/src/tests.rs run sessions like this (box → fillet → hole, parameters,
batch rollback, hostile arguments) and check the results only through MCP.
Agent chat
Agent chat is a side panel where you talk to a coding agent you already use (Claude Code, Codex, Google Antigravity, OpenCode, Grok Build and others), or to a model behind an OpenAI-compatible or Anthropic-compatible endpoint you choose (Ollama or LM Studio on your computer, OpenAI, Anthropic, any other URL), and have it work on the design that’s open. A coding agent runs on your computer under your own account with that agent: SolidCraft doesn’t sell or proxy a model and needs no key for it. An endpoint’s key, if it needs one, goes into your system keychain, never into SolidCraft’s settings.
Desktop app only.
Turning it on and off
Everything about it is on one page: Preferences → AI.
- Show AI features (on by default) is the master switch. Turned off, SolidCraft shows nothing of it: no AI button, no shortcut, no menu entry, no panel, and nothing is looked for or started. The page stays, so you can turn it back on.
- Show Assistant button in application bar (on by default): the Assistant icon in the application bar row, which stays there however wide the window is. Turn it off to keep only the shortcut and the menu entry.
- Connect the last-used agent when SolidCraft starts (off by default): each launch starts the agent you connected last (the default one if you never connected), in the background, once SolidCraft has looked for the installed agents. The panel’s header shows Starting…, then Ready, or Connection failed with Retry in place of Connect (the agent is missing, could not start, or ended while starting); the conversation says why. A design opened in place of the empty one SolidCraft starts with keeps the agent connected, as long as nothing was asked in that empty design’s chat.
- Open the Assistant panel when SolidCraft starts (off by default): the panel (or its own window) is shown at each launch.
Neither applies when SolidCraft is started with --control for automation.
With AI features on, the panel opens from:
- the Assistant icon in the application bar;
- Ctrl+Shift+A (Cmd+Shift+A on a Mac), which also works from the chat’s own text box;
- View settings (the last button of the navigation bar under the view) → Agent chat.
Nothing runs until you need it: SolidCraft looks for installed agents (a quick --version
each, in the background) when the panel or the AI page first opens, and starts an agent only
when you send a message or press Connect (or at launch, when you turned that on above).
The panel
In the floating layout, Assistant fills the viewport height in the shared rounded panel, with space around its edges. The compass and navigation bar move into the remaining space beside it. It overlays the model without changing the viewport projection; clicks and scrolling over the panel stay in the chat. The docked layout retains its side panel.
- Docking: on the left, Assistant is the first panel, before vertical history and the browser. The drag highlight shows the actual destination bounds. Drag the panel’s header (the dotted grip and the title) to the other half of the window and let go. The side it would land on lights up while you drag. You can also pick Left or Right under Preferences → AI → Panel. SolidCraft remembers the side.
- Width: drag the panel’s inner edge. SolidCraft remembers it.
- Its own window: the pop-out button in the header (a window with an arrow) moves the chat into a window of its own, named after the design, which you can move anywhere, including another screen. The window’s own close button closes the chat, which opens there again next time; Dock (the same place in its header) puts it back in the side panel. You can also pick Own window under Preferences → AI → Panel. SolidCraft remembers where the window was and how big.
- Minimise (the line in the header) folds the panel into a small floating strip (a side strip in the docked layout) that keeps the chat icon, the agent’s state, and a count of anything waiting for your answer. Click the icon to unfold it. Close (×) hides the panel. Neither stops the agent; Stop does.
- The agent switcher under the title picks the agent for this conversation; switching stops
the one running. Next to it, when the agent has modes of its own (Claude Code has Default,
Accept Edits, Plan…), a second drop-down sets the agent’s mode. When the agent reports its models (an ACP agent’s
model list, or an endpoint’s
/models), a model drop-down shows the one in use and changes it; SolidCraft remembers the last choice for each agent and puts it back on the next session. An endpoint’s agent also keeps it as its default model. An agent that reports no models shows no drop-down. When the models come from more than one provider (Hermes and OpenCode name each modelProvider · model, or sayProvider: …in its description), a provider drop-down comes first and the model drop-down lists only that provider’s models, without the provider in front. It opens on the provider of the model in use; picking another provider changes nothing until one of its models is chosen. Models with no provider named are listed under Other.
Messages use right-aligned user bubbles and plain assistant text. Consecutive tool calls share an activity group: click a row to inspect its output; failed calls have an error tint. Approval cards retain the agent’s permission choices, and SolidCraft’s own approvals offer Deny, Always allow and Allow once. The composer’s permission menu uses the same Ask / Edits / All policy as Preferences, for the selected agent, and shows the actual changes used against the configured turn limit. Queue, Send now and Stop keep their existing behavior.
Files and pictures
A message can carry up to eight files, to show the agent what you mean: a concept picture, a sketch, a brief, a part to fit around.
- + next to Send has Choose a file… (a file dialog for the types below) and Paste a picture (the picture on the clipboard).
- Drop files on the chat. Files dropped on the panel (or its folded strip) attach to the next message, and the app doesn’t open them too. While files are dragged over the chat, it says so. The window gives no position while something is dragged, so the drop is the chat’s when the pointer was last seen over the chat; + always works.
- The files show as chips above the text box: a picture’s thumbnail, the name, and × to take the file off. A picture is read off the window thread, so a large one never stops the window: its chip shows a spinner meanwhile, and Send waits for it. Send sends files with no text.
- Pictures (PNG, JPEG, GIF, WebP) go to the agent as they are when they are at most 2048 px on the long side and 3.75 MB (a picture’s base64 then stays under 5 MB, so eight still fit in a prompt). Otherwise they are scaled down to 2048 px and re-encoded: as PNG when that fits, as JPEG when the picture has no transparency, halved until it fits; a GIF goes as its first frame. BMP and TIFF go as PNG. HEIC can’t be read: export it as JPEG or PNG first. An agent that takes no pictures gets none, and the message says which were left out.
- Text files (UTF-8, up to 256 KB: Markdown, plain text, JSON, CSV, YAML, TOML, XML, SVG…) go in the message’s text, whole as far as the message’s 100,000-character cap allows: past it a file is cut (and says how much is missing) or named only.
- CAD files (STEP, IGES, FreeCAD, Rhino, STL, 3MF, OBJ, glTF) are imported into the design at once, as components of their own. A file joins the message once its import is in (its chip shows a spinner meanwhile, and Send waits), and the message tells the agent it was added; a failed import says why and the agent is told nothing of it. Taking a part’s chip off the message leaves the part in the design (Undo removes it).
- Every file is named in the message. In the conversation the attached pictures show under it as thumbnails (hold the pointer over one for a bigger preview, click it to open the viewer, where the file’s name is shown and ‹ › step through the message’s pictures, together with the picture of the view); the other files are listed by name. Pictures in any of the formats above are decoded in the background, with a spinner until they are in. A file that can’t be attached says why.
Files stay with their message: one queued while the agent works keeps its files, and one sent again after a sign-in sends them again. When several queued messages go as one prompt, their files go with it, at most eight; the rest are named in the chat and not sent. New chat clears the files waiting on the chips too.
Typing while the agent works
Each design tab has its own conversation, agent session, draft, attachments and queued messages. A new tab starts with an empty chat. Switching away stops the current turn and keeps its completed changes in that design’s undo history. Returning restores the conversation; queued messages wait for Send. Closing a design closes its conversation. Conversations are kept only while the tabs remain open.
The input stays enabled during a turn. Enter queues the message: it shows in the conversation as a Queued bubble that you can Edit or remove (×) until it is sent. Several queued messages go as one prompt, in order, when the turn ends normally.
- Send now (on the bubble, or Ctrl+Enter / Cmd+Enter in the input) stops the current step, waits for the agent to say it stopped, then sends the queue as the next prompt, with a short note that the agent’s previous step was interrupted. What the interrupted message changed undoes as one step, like any other message.
- Stop (the square next to the input, or Esc in the input) stops the agent and keeps the queue: the bubbles wait for Send.
- New chat (the header button, Ctrl+Shift+L / Cmd+Shift+L with the chat focused) stops what runs, clears the conversation, the queue and the files on the chips, and starts a fresh session on the same agent, keeping the agent and its mode. It asks first when a message is running or queued, or files are waiting. The design’s undo history is untouched.
Agents
Preferences → AI → Agents lists every agent the chat can use, each with its kind (a program or an endpoint), version or model, whether it looks signed in, Test, Default (the one a new conversation starts with), its own permission setting, and Remove for the ones you added.
Found by themselves
SolidCraft looks for agents that speak the Agent Client Protocol
on your PATH and in the usual install folders a desktop app’s PATH often misses (npm’s
global folder, nvm, Volta, bun, ~/.local/bin, ~/.grok/bin, Homebrew, %APPDATA%\npm). For each one it
shows its version (from --version, given three seconds) and whether a login was found.
Version checks and agent sessions also use those folders on their PATH, so adapters can find
runtimes such as Node when SolidCraft is opened from Finder or the Dock. Existing absolute
PATH entries keep their order; no shell startup files are run.
SolidCraft only checks whether the agent’s own login file (~/.claude/.credentials.json,
~/.codex/auth.json, ~/.gemini/oauth_creds.json, …) or one of its key variables exists. It never
reads the file and never starts the agent to find out. On macOS Claude Code keeps its login in
the keychain, so SolidCraft doesn’t guess there. A found login file may be expired: Test or a
chat connection verifies it. A successful ACP connection shows signed in; an authentication
rejection shows not signed in. These results apply to that agent until a newer connection
result or Refresh, which checks the installed agents again. An unrelated connection error
doesn’t change the last known login state.
| Agent | What to install |
|---|---|
| Claude Code | npm install -g @agentclientprotocol/claude-agent-acp (the ACP adapter for Claude Code) |
| Codex | npm install -g @agentclientprotocol/codex-acp |
| Google Antigravity | Google’s ACP server, a separate download from Google’s agy command (which has no ACP mode): take the zip for your system from the ACP registry, unzip it and put agy_acp_server.par (agy_acp_server.exe on Windows) on your PATH. The zip also holds localharness_external: leave the two together. It signs in itself (browser). |
| OpenCode | npm install -g opencode-ai (opencode acp) |
| XAI Grok | Grok Build installer, then grok login (grok agent stdio speaks ACP) |
| GitHub Copilot CLI, Qwen Code, goose | listed with their install command when missing |
The panel lists agents that aren’t installed with their install command and a Copy button. Refresh looks again after you install one. Test starts an agent briefly, asks it for a session and ends it again.
Flatpak
The Flatpak sandbox has no home-folder or network access by default. Agents installed
through nvm or ~/.local/bin can therefore work in a terminal while SolidCraft reports
that no agent was found. For Claude Code and its ACP adapter, run:
flatpak override --user ai.storyteller.solidcraft --filesystem=~/.nvm:ro --filesystem=~/.local/bin:ro --filesystem=~/.local/share/claude:ro --filesystem=~/.claude --filesystem=~/.claude.json --share=network
These permissions apply to SolidCraft for your user account:
~/.nvm:roexposes Node and globally installed npm adapters read-only.~/.local/bin:roexposes locally installed executables read-only.~/.local/share/claude:roexposes Claude Code’s native installation read-only, including the targets of links in~/.local/bin.~/.claudeand~/.claude.jsonallow Claude Code to read and update its sign-in and configuration files.--share=networklets the adapter reach its service. It also gives SolidCraft network access, including making an enabled control server reachable from the host.
Restart SolidCraft after applying the override. Other agents or installation folders may need different filesystem permissions.
XAI Grok
Install Grok Build and run grok login in a terminal to sign in with your Grok account.
In SolidCraft, open AI → Refresh, select
XAI Grok, then Connect. You can make it the default under
Preferences → AI → Agents.
SolidCraft reuses Grok Build’s sign-in in ~/.grok/auth.json; it checks only whether that file
exists and does not read or copy its tokens. If sign-in is needed, the panel offers Grok’s own
sign-in methods. You can also run grok login again and reconnect. Grok controls model access
and usage limits for your account. This connection does not require an API key in SolidCraft.
Grok Build also supports XAI_API_KEY for users who choose API authentication.
Add agent…
- Installed agent: your own entry for an installed agent, for example with other arguments.
- Custom command: any other program that speaks ACP on stdin and stdout, such as
my-agent --acp. SolidCraft starts it directly, not through a shell. - OpenAI-compatible API: a preset (Ollama
http://localhost:11434/v1, LM Studiohttp://localhost:1234/v1, OpenAI, Mammouth AIhttps://api.mammouth.ai/v1) or a custom URL, the model name, and an optional key. - Anthropic-compatible API: a preset (Anthropic) or a custom URL, for an endpoint that
speaks the Anthropic Messages API (
{base}/v1/messages). This covers Anthropic itself and providers that offer an Anthropic-compatible endpoint, such as DeepSeek and Qwen. - The key goes into the system keychain (Keychain on macOS, Credential Manager on Windows, the Secret Service on Linux) and is only ever sent over HTTPS or to this computer. Test asks an OpenAI-compatible endpoint for its models; an Anthropic-compatible one gets a short message, because not every such endpoint lists models (Qwen’s has no model list). For these agents SolidCraft runs the conversation itself, with the same tools, limits and questions as for a coding agent, and no cap on model requests per message (Stop ends it). Request timeout is how long one model request may take: 300 s by default, from 30 s to an hour. A slow local model (a large model on modest hardware, or a long message with many tool results) may need more; raise it here or in the endpoint’s entry under Agents, where a change applies from the next request. A request that runs out of time ends the message with the limit it hit and where to raise it.
For Ollama, choosing a model checks its reported capabilities in the background. If its
capabilities list lacks tools, a warning below the model picker explains that the model can
only chat and suggests choosing one with tool support. Results are cached in memory per base URL and
model, with a limit of 128 entries. SolidCraft recognises Ollama through /api/version at the
endpoint’s origin or the Ollama preset. Failed checks and older servers without a capabilities
field show no warning. The check never blocks sending a message.
Local models
Choose a model that supports native tool calling to read or change the design. With Ollama,
look for tools in the model’s capabilities; a model that only supports chat cannot operate
SolidCraft. Other OpenAI-compatible endpoints also need a model with native tool support.
If tools were available but a completed turn made no tool calls or changes, the panel shows a short notice when the reply was empty, or when an explicit edit request received only text. Ordinary answers to questions do not show it. Edit requests are recognised conservatively from English opening words such as “make”, “rename”, or “can you move”; other phrasing may stay quiet. Cancelled and failed turns keep their existing messages. Text describing a call is never run.
An agent inside WSL (Windows)
An agent installed inside WSL keeps its own settings, skills and sign-in there. Add it as a
Custom command that starts it through wsl.exe, for example:
wsl.exe -d Ubuntu -e bash -lc claude-agent-acp
bash -lc runs your login shell’s setup, so the agent is found where your PATH puts it (npm,
nvm, Volta). When the command’s program is wsl or wsl.exe, SolidCraft gives the agent its
working folder and the solidcraft-app server’s program as WSL shows them (C:\Users\… becomes
/mnt/c/Users/…). The server itself still runs on Windows and reaches the app at 127.0.0.1, so
this works with WSL’s default networking. If you moved WSL’s drive mounts away from /mnt
(automount.root in /etc/wsl.conf), those paths won’t match and the agent can’t start the server.
Signing in
If a coding agent isn’t signed in, it says so and the panel shows Sign in to the agent with the agent’s own ways to sign in:
- A command for a terminal, e.g.
claude-agent-acp --cli auth login --claudeai, with a Copy button. Run it in a terminal, sign in there, then press I’ve signed in: connect again. - A button for agents that sign in by themselves (Codex’s ChatGPT login opens your browser).
- A variable to set for agents that take a key from the environment. The panel has no field for a coding agent’s key, on purpose.
Whatever you typed before signing in is sent once the agent is ready.
What the agent can do
The agent reaches the open design only through SolidCraft’s own tools (the
MCP server), which SolidCraft hands it when the chat starts. That server is named
solidcraft-app, so an MCP server you set up yourself under the name solidcraft (as in
client configuration) doesn’t take its place:
- Read the design: list commands, inspect features, sketches and parameters, measure, take pictures of the view.
- Change the design: run any modelling command, alone or in a batch. Changes show in the window as they happen.
- One undo per message: everything the agent changes while answering one message becomes a single undo step, named after your message. The conversation shows N changes, one undo step with Undo these changes while nothing else has happened since.
- Tool cards: each tool call shows as a card with its state; click it to see what it returned.
- Formatted text: the agent’s answers show as Markdown: headings, lists, quotes, code blocks
(with Copy), bold, italic,
codeand links (web links only; each link is a button that opens it in your browser). What you type, and the agent’s thoughts, stay plain text. - Attach view: tick it to send a picture of the current view with your message, for agents that take pictures. The picture is taken when the agent starts on the message and then shows as a small thumbnail under it. Hold the pointer over the thumbnail for a bigger preview that stays while you do; click it to open the viewer (the message’s pictures one at a time, with ‹ and › or the arrow keys, closed with Esc or a click outside). A long message scrolls inside its box, so Send and the buttons beside it stay in view.
- Attach selection: tick it to send what is selected in the model with your message, so the agent knows what “this edge” or “that feature” is. The box shows how many things are selected, counted as you clicked them (the two halves of a rim you clicked once are one). Faces and edges go with the names and numbers the agent’s tools take and with their size and place (so they can be found again after an edit), features with their name and kind, bodies with their name. The agent gets them as text after your message; the conversation shows your message as typed with the things as chips below it, grouped (Elements, Features, Bodies) with a count; hover a chip for what the agent was told about it. At most 64 things go with one message; the rest are counted (“+N more”), not dropped silently. The selection is read when you send, also for a message that waits in the queue.
- Pick context: press it to choose what the message is about on purpose. A picker opens with three lists, Elements (faces, edges, vertices), Features (steps of the timeline) and Bodies; click things in the model, the timeline or the browser to put them in the list that is active (click a list to make it active; click a picked thing again to take it out). OK puts them as chips above the message box, each with a ×; they go with the next message like an attached selection (and are shown the same way), and the box is empty again afterwards. Cancel keeps what was picked before.
Permissions
Preferences → AI → Permissions decides what the agent may do without asking, for every agent; each agent can have its own setting in its row.
- Ask every time: every change to the design and every file action waits for Allow, Always allow or Deny in the panel.
- Auto-allow model edits (the default): modelling commands run at once; saving, opening, exporting, importing, starting a new design, any command given a file name, and undoing past the start of your message still ask.
- Allow everything: nothing asks. The page warns you: the agent can then save, overwrite and replace your design.
Always allow remembers that command for that agent. The page lists what you allowed this way, per agent, with Reset.
These settings cover SolidCraft’s tools. A coding agent’s own tools (its shell, its file edits) follow the agent’s own mode, which you pick in the panel’s header. When an agent asks before using one of SolidCraft’s tools, SolidCraft answers for you, since its own gate checks the call, and notes it in the chat as Auto-allowed. With Allow everything, or after Always allow, the agent’s other questions are answered the same way and noted too.
What counts as “a command given a file name”: a parameter that holds a file or folder (file,
dir, output, …), or path and source when the value reads like a file name (a separator, ~,
a leading dot or a known extension), or any value of those in a command that exports, imports,
saves, opens or inserts. A sweep’s or pattern’s path (curve ids) and an occurrence’s path
(ids) are geometry and run without a question.
What it can’t do
- Files wait for you unless you chose Allow everything. Cancel or Stop declines everything that’s waiting, and a question nobody answers is declined after four minutes. The MCP server’s folder limits still apply after you allow: the design’s folder, the recovery folder and the server’s own scratch folder.
- An optional limit per message. There’s none by default. Untick Unlimited on the AI page to cap the changes the agent may make per message (it starts at 24). Reading the design doesn’t count. At the limit, it’s told to stop and report what’s left; send another message to let it carry on. Stop always works.
- No crash reports, whatever the permissions. Crash reports can hold other designs and file paths.
- Not the rest of the app. It can’t click the interface, change settings, quit, or drive the chat itself.
- Its own tools stay off where we can turn them off. SolidCraft declares no file system and no terminal to the agent, runs it in an empty working folder of its own, and asks the Claude Code adapter to turn off its shell, file and web tools for the session.
When the panel’s agent is stopped, another agent is picked, or SolidCraft quits, the agent and every program it started (the CLI it wraps, its MCP server) are stopped.
Privacy
Nothing goes anywhere except to the agent you run, or the endpoint you added. SolidCraft starts a coding agent as a program on your computer and talks to it over its standard input and output. What the agent sends to its own service, under your account and that service’s terms, is between you and that service, exactly as when you use the agent in a terminal. SolidCraft never sees, stores or passes on a coding agent’s password, token or key. An endpoint’s key is kept in the system keychain and sent only to that endpoint. SolidCraft’s own connection to the agent’s tools uses a random per-launch token in a file only you can read, which never passes through the agent.
Trying it without an account
A scripted test agent is in the source tree. It makes a block, rounds it and measures it, with no model behind it:
cargo build --release -p solidcraft-ui-egui --example acp_test_agent
Then add it under Preferences → AI → Add agent… → Custom command with the path of
target/release/examples/acp_test_agent. Put “save” in your message to see an Allow / Deny card,
or “ask” to see the agent ask its own permission.
Benchmarking the Assistant
solidcraft-cli bench-assistant runs the eleven tasks of the
standard model test (v1, #4851) against a
desktop app that is already running with a control port, and checks each result on the design
itself. Use it to see how well a local model drives SolidCraft before you post its results.
solidcraft --control 9271
# In another terminal:
solidcraft-cli bench-assistant --connect 127.0.0.1:9271 \
--base-url http://localhost:11434/v1 --model qwen2.5:7b \
--json results.json --markdown results.md
Each task replaces the open design with a new one, so save your work first. The command adds the endpoint as an API agent named “bench”, starts a new chat with All permissions for every task, sends the prompt once, and waits for the turn to end. It runs only the setup the test prescribes (the box some tasks start from) and does none of the modelling itself. When it finishes it stops the agent and puts your permission setting back.
- The control token comes from
--token,SOLIDCRAFT_CONTROL_TOKEN, or the app’s token file, in that order. --tasks 1,2,5runs only those tasks,--repeat Nruns the test N times, and--timeout-s(default 300) is each turn’s deadline; a turn that runs past it is stopped and fails as “timeout”.- A failed task gets one of the issue’s failure categories (“wrote the call as text”, “wrong or unknown command”, “wrong arguments”, “the endpoint refused tools”, “an empty reply”, “only talked”, “gave up after errors”, or “hit the request limit”), or “wrong result” when the calls worked but the design is not what the task asks for.
--markdownwrites the issue’s comment template filled in; add the quantization, download size, and runtime before you post it.--jsonkeeps each task’s failed rules, conversation, and design before and after. Both files are rewritten after every task.
Local models with the Assistant
You can use a model running on your computer to work on the open design in SolidCraft’s desktop Assistant. See Agent chat for the panel’s controls.
What you need
- Ollama, LM Studio, or another OpenAI-compatible server running on your computer.
- A model that supports tool calling: this lets it ask SolidCraft to read or
change the design. On Ollama’s model page, look for the tools tag, or run
ollama show <model>and look fortoolsunder capabilities. Tool support alone does not guarantee good results; see the table below. - Enough graphics memory (VRAM) for the model and its context (the conversation and tool instructions). See the table below for sizes.
Set it up
- Start your server and load or download the model you want to use.
- In SolidCraft, open Preferences > AI > Add agent… and choose the OpenAI-compatible API. Choose Ollama, LM Studio, or a custom URL.
- Enter the server’s base URL, such as
http://localhost:11434/v1for Ollama, and the model name. Use Test, then add the endpoint. - Open the Assistant and select your endpoint. Choose the model in the model list if the server provides one.
- Choose permissions in Preferences or the chat’s permission menu: Ask asks before changes and file actions; Edits allows model edits but still asks before file actions and replacing the design; All allows actions without approval, including saving, overwriting, and replacing the design.
Which models work?
These results were measured with the standard test (v1) in #4851. The table in that issue is the up-to-date one.
| Model | Size / quant | Passed (v1) |
|---|---|---|
| Muse Glimmer 30B | Unsloth UD-Q3_K_XL, 12.4 GB | 11/11 |
| Qwen3.8 27B | Unsloth UD-Q4_K_XL | 11/11 |
| gemma4 (latest, 7.5B) | Q4_K_M, 6.1 GB | 11/11 |
| gemma4:12b | Q4_K_M | 10/11 |
| gpt-oss:20b | MXFP4, 12.8 GB | 10/11 |
| qwen3:14b | Q4_K_M, 8.6 GB | 8/11 |
| qwen3:8b | Q4_K_M | 7/11 |
| qwen2.5:7b | Q4_K_M, 4.4 GB | 4/11 |
| qwen2.5-coder:7b | Q4_K_M, 4.4 GB | 0/11 (writes every call as text) |
| gemma3:4b | Q4_K_M | 0/11 (no tool support: HTTP 400) |
| deepseek-r1:14b | Q4_K_M | 0/11 (never calls a tool) |
For about 8 GB of graphics memory, start with gemma4 (the default tag, 6.1 GB). With
16 GB, Muse Glimmer 30B or gpt-oss:20b also work well.
Troubleshooting
- The model replies with JSON or code, but nothing happens. It is not calling tools well. Try another model from the results above.
- “HTTP 400 … does not support tools.” The model lacks tool support. Choose one with the tools capability.
- Empty replies. The context may be too small for the tool instructions.
Increase the server’s context size (
num_ctxin Ollama), then try a new chat. - Slow answers. The model may be too big for your VRAM and spilling work to the CPU. Try a smaller model or a smaller quantization.
Measure a model yourself
Follow the standard test in #4851 to compare your model with these results. It gives the exact prompts, starting designs, pass rules, and a template for sharing results.
Contributing to SolidCraft
Agents: start from the router in AGENTS.md, which points to a short skill in skills/ for each kind of work. This page stays the full human guide.
SolidCraft is an open-source parametric 3D CAD app written in pure Rust. You sketch on a plane, constrain the sketch, turn profiles into solids, and change a parameter to watch the timeline rebuild. It runs as a desktop app (Windows, macOS, Linux), in the browser (wasm + WebGPU/WebGL2), headless from a CLI, and as an MCP server that AI agents can model with.
Help is welcome, whether that’s a one-line fix or owning a whole area. This page covers how to build it, how the code is laid out, the few rules that aren’t negotiable, and where help is needed most.
Before choosing a task, check the command inventory and roadmap for implemented capabilities and current priorities.
Questions, ideas or want to say what you’re working on? Join the Discord.
Kernel work is paused
We’re evaluating a new geometry kernel. Until that’s decided, please don’t open issues, pull
requests or discussions about kernel work: booleans, fillets, chamfers, shell, offsets, sweeps and
lofts, threads and holes, STEP/IGES import and export, tessellation, or anything else that changes
crates/kernel/. The pinned issue on the tracker explains it. New ones get the kernel-paused
label (.github/workflows/kernel-paused.yml) and a maintainer answers them. Crashes and data loss
are still taken as issues, and everything outside the kernel carries on as normal.
Get it running (about 10 minutes on a fast machine)
You need stable Rust 1.95 or newer (required by egui/eframe 0.36). For the web
build you also need rustup target add wasm32-unknown-unknown and trunk
(documentation).
git clone https://github.com/tangency-labs/solidcraft && cd solidcraft
cargo run --release -p solidcraft -- --sample # desktop app with a sample part
cargo xtask test # unit, engine and UI-scenario tests
cargo xtask ci # everything a PR must pass (see below)
SolidCraft is the product, repository, Cargo package and executable name.
Build the desktop app with cargo build --release -p solidcraft, or the CLI with
cargo build --release -p solidcraft-cli. Run the CLI with
cargo run --release -p solidcraft-cli -- commands (or target/release/solidcraft-cli commands
after building). The old name appears only in the compatibility shims listed in
docs/compatibility.md; don’t add new uses of it.
Web: cd apps/solidcraft-web && trunk serve --release, then open the printed URL (?sample
opens the sample part, ?webgl forces WebGL2). Saving and opening files in the browser needs
HTTPS or localhost, because it uses the Origin Private File System.
Windows from Linux: cargo xwin build --release --target x86_64-pc-windows-msvc -p solidcraft.
NixOS: nix develop
NixOS has no /usr/lib, so nothing is on LD_LIBRARY_PATH and a plain cargo run finds none of
the libraries the app loads at run time: the Wayland ones winit opens the window through, the EGL
loader wgpu renders through, and libdbus-1, which rfd’s file dialogs reach xdg-desktop-portal
through (a dialog then never appears and the pick returns no path):
ERROR solidcraft: winit EventLoopError: ... The wayland library could not be loaded
ERROR eframe::native::run: Exiting because of error: WGPU error: Failed to create surface for any enabled backend: {}
ERROR rfd::backend::xdg_desktop_portal::portal::libdbus: Can't connect to a portal: libdbus-1.so not found
nix develop (the flake.nix in the repository root) supplies Wayland,
X11, EGL, Vulkan-loader and D-Bus runtime libraries. It also exposes the NixOS
driver manifests in /run/opengl-driver/share, so Vulkan can discover the system
drivers. Enable hardware.graphics.enable on NixOS; other Linux distributions
may need a matching nixGL wrapper.
The default development shell retains the Rust/C toolchain already on PATH;
nix develop .#rust supplies the pinned toolchain and builds in target/nix.
To launch, use nix run .#solidcraft -- --sample. To install the app’s terminal
commands, desktop entry and generated icons, use nix profile install .#solidcraft.
nix build alone creates a build result; it does not install a launcher in your
desktop menu. The installed solidcraft command runs the wrapped app; solidcraft-cli is included as
well. See
Nix installation and troubleshooting for remote-flake commands,
desktop integration and how to distinguish an old executable on PATH.
How the code is laid out
The architecture page has the layer table. The short version:
crates/geom: vectors, planes, meshes, measures.crates/sketch: our own constraint solver, plus profile finding.crates/kernel: the B-rep kernel boundary. It wraps truck, and it’s the only crate allowed to touch truck. Fillets, chamfers, shell/offset, Delete Face, Split Face, STEP/IGES read and write, and face provenance are our own code here. Patched copies of three truck crates live invendor/and are excluded from our fmt/clippy rules.crates/doc: the document. It holds parameters and expressions, the feature timeline and its incremental rebuild, components and occurrences, joints, appearances, and persistent naming.crates/io: design files and mesh formats (3MF, STL, OBJ).crates/engine: the session and the command registry. Everything the user can do is a command, and the toolbar, dialogs, scripts, CLI, control channel and MCP all run the same commands.crates/ui-egui: the desktop/web front end (egui + wgpu). It’s swappable; nothing below it depends on egui.crates/mcp,apps/solidcraft,apps/solidcraft-cli,apps/solidcraft-web.
cargo xtask layers fails the build if a lower layer depends on a higher one.
Rules that aren’t negotiable
- Independent. SolidCraft is independent. Behaviour is designed from public
documentation, general CAD conventions and user feedback. Reference tests use analytic values
and open-source tools. We don’t drive, script, automate or extract data from commercial CAD
products, and we never copy another product’s code, icons, artwork, help text, tooltips,
templates or materials. Command ids are our own (
solid.fillet). - No GPL/LGPL/AGPL code, not even reading it. That rules out OpenCASCADE, FreeCAD
(including planegcs), SolveSpace, CadQuery/OCP, LibreCAD and QCAD. SolidCraft is
MIT OR Apache-2.0, and its solver and blends are written from the maths. (Running an
open-source application to get reference values for a test is fine; reading its source is
not.) Permissive
dependencies are fine. Note which one you’re adding and why in the PR. THIRD-PARTY-LICENSES.txt
lists it once the change lands: the Generated files workflow rewrites it on
main. Nightly/manual CI also scans advisories, bans and sources with cargo-deny; see the local command and coverage limits. - Every asset is original or openly licensed and has a row in
ATTRIBUTION.md. Icons are drawn in code (
crates/ui-egui/src/icons.rs).cargo xtask assetsenforces this. - Never crash. People trust a CAD app with their work.
- Non-test code never uses
unwrap,expect,panic!or indexing on input-derived data. There’s nounsafeoutsideapps/solidcraft/src/open_documents.rs(macOS file-open events),apps/solidcraft/src/native_crash.rs(Windows native crash capture) andcrates/threedx-client/src/macos.rs(the 3Dconnexion driver’s client API on macOS), and the compiler denies it. - An unsupported case returns an error (“not supported yet: …”); it never panics.
- Every call into truck goes through
solidcraft_kernel::guard. - Every crash fix comes with a regression test.
- Non-test code never uses
- Everything is a command, with a test. A new feature means a
CommandSpecincrates/engine/src/cmd/with input and output schemas (see Adding a command; a test warns about a command without them, and will fail it once required), engine tests that measure the result (volume, area, face/edge/vertex counts against a value you can derive), and for UI work a scenario (below). - One set of camera controls. Every page or canvas that shows a view (the modelling
viewport, the Render preview, and any new page such as a flat pattern or drawing) calls
navigation::handle(crates/ui-egui/src/navigation.rs) for its camera input, withNavCapssaying whether it can orbit (off for flat 2D pages) and pick. New canvases must usenavigation::handle; never read drags, the wheel or pinch to move the camera yourself. Thehand_rolled_camera_input_is_rejectedtest fails if a page does.
Labels, parameter docs, messages and docs use the terms in the glossary: one term for each idea. A change that adds a new idea adds its term there.
AGENTS.md has the long form of these rules. It’s written for AI agents, but it’s the most complete description of the house style.
Tests
| What | Where | Run |
|---|---|---|
| Unit and engine tests | next to the code, or *_tests.rs files | cargo xtask test (or cargo test --workspace --profile ci) |
| UI scenarios | crates/ui-egui/tests/scenarios/*.json | part of cargo test |
| Reference tests | cargo xtask oracle | tests/oracle/ (see below) |
A geometry change that changes the robustness goldens re-records them in the same pull request:
ROBUSTNESS_GOLDENS_BLESS="why it changed" cargo test --profile ci -p solidcraft-engine --lib robustness_goldens::
(see docs/robustness.md); CI runs every seed on pull requests that touch geometry.
docs/mutation-testing.md shows how to find kernel behaviour these
black-box suites miss.
UI scenarios drive the real app headless: start a tool, click a plane or a sketch point, press keys, then assert on the result.
[
{"start": "sketch.create"},
{"click": {"plane": "XY"}},
{"start": "sketch.rectangle.two_point"},
{"click": {"sketch": [0, 0]}},
{"click": {"sketch": [40, 30]}},
{"expect": {"errors": 0}}
]
A new .json file in that folder is picked up automatically. To record a bug before it’s fixed,
put {"pending": "why"} in the scenario, and remove it in the PR that fixes the bug.
A failing test without its fix can’t merge as-is: it would turn main’s CI red for everyone.
Either include the fix in the same PR, or mark the test #[ignore = "fails until #123 is fixed"]
(it can then merge right away), and remove that line in the PR that fixes the bug.
Use the ci profile. CI runs the tests with --profile ci: light optimisation, line tables,
no LTO, and debug assertions off (truck has debug-only assertions that reject valid geometry). It
builds several times faster than --release. If a test fails only in a plain debug build, say so
in the PR. cargo xtask test builds the tests with it and then runs every test binary at once
(cargo test runs them one after another); arguments after -- go to each binary, e.g.
cargo xtask test -- shell. Set SOLIDCRAFT_TEST_JOBS to a positive integer to opt into a
shared bound on top-level test/side commands for cargo xtask test and cargo xtask ci.
For example, SOLIDCRAFT_TEST_JOBS=2 allows two commands at once; the sequential wasm/doc
worker consumes one of those slots. Every binary and side check still runs after failures.
Without the variable the existing concurrency is unchanged. With it set, each test binary
inherits RUST_TEST_THREADS and ACTION_MATRIX_JOBS of max(1, cpus / jobs) — the machine’s
parallelism shared between the binaries in flight — unless you set either one explicitly;
explicit caller overrides always win. Cargo’s build-job setting remains an independent caller
override; this limit does not change test filters, timeouts or baselines.
Tests run beside each other on a busy machine, so a test must not depend on wall-clock timing.
Ready pull requests test what they touch. CI maps changed files to workspace packages and
runs Clippy, the test build and tests for those and their dependents. The
package selector and CI workflow define
which changes require native checks; documentation, packaging, other-workflow and bot-script-only
changes run the repository checks only, on pull requests and on main. Workspace-wide changes,
merge-queue groups, nightly runs and Run workflow select the full workspace. A push to main
reuses a successful merge-queue run for the same commit, and runs only the repository checks
when no code changed since the last push to main that passed; otherwise it runs the full
suite. Doc tests, reference tests and the Windows jobs run on main and nightly only.
CI shards the tests. One job builds them into a cargo-nextest archive; two shards run on
pull requests and four during full validation. Every test runs in its own process: don’t rely
on state left by another test.
Pull requests skip a few heavy tests (one to twelve minutes each), which run in full validation
(the merge queue, nightly, manual runs and pushes to main not already tested by the queue).
Check that validation after merging. They are listed in
.config/nextest.toml and the testing page:
solidcraft-engine:tests::hostile_params_never_panic, everything intests::robustnessand the seeds oftests::robustness_goldens(seed_00…seed_95; pull requests that change the kernel, truck or the goldens run all but the slowest,SLOW_SEEDSinci.yml),pattern_suppress_tests::enclosure_arc_extinction_keeps_a_real_sharp_inner_lip_after_resize_and_reopen,pattern_suppress_tests::enclosure_datums_follow_wall_height_and_board_depth_with_material_proofs,cmd::plastic::tests::enclosure_snap_fit_follows_resized_front_wall, theatomic_commandscontract testssolidcraft-kernel:tests::boolean_identity_on_tangent_primitives,tspline::tests::hostile_tsplines_never_panic, and theboolean_propsproperty tests (these run only in the nightly “Property tests” job: their inputs are random, so main skips them too)solidcraft-ui-eguiscenarios:acceptance_sensor_enclosure
The action matrix tries every ordinary action on every face, edge, vertex and body of a
corpus of designs against a checked-in ratchet (docs/action-matrix.md).
A pull request that makes an action pass should record it with cargo xtask action-matrix --bless;
one that makes a passing action fail fails the sample every CI run checks.
The reference tests live in tests/oracle/, each a recipe.json
in our own format with an expected.json beside it. SolidCraft rebuilds the recipe and compares
body count, volume, area, centre of mass and bounding box (and, where there is a reference.stl,
the surface itself). Expected values are either worked out in closed form
(tools/oracle-analytic/) or computed by building the same recipe in FreeCAD, run as a separate
program (tools/freecad-oracle/); each file says which. The generated
reference test report has the current case count and results. The reference-test
CI job runs cargo xtask oracle during full validation; pull-request runs skip it. A case that
fails because of a known bug names its issue in expected.json ("known_failure": "#123");
never change an expected value to match SolidCraft’s output.
Changing the design file format
A design file says which format it is in ("format": "solidcraft/N"). If your change saves
something new in a design (a field, a feature type, a new kind of name), or makes older designs
mean something else, it needs a format change. Add one file, and edit nothing shared:
- Create
crates/doc/src/format/pending/<what-it-is>.rs(lowercase, dashes). Start it with a//!note: what is saved, what older designs get without it, and that the last release asks for an update instead of meeting it. - If older designs must be rewritten as they open, put
pub fn step(v: &mut Value) -> crate::Result<()>in the same file. Pending steps run in file-name order, after the older formats’ steps. - Don’t touch
FORMAT,JSON_STEPS, the history incrates/doc/src/format.rs, another change’s file, or anyv<N>/folder: those are fixed once released.
That’s all. The build counts the files: while any are pending it writes the next format number,
which no release reads, so a release refuses a design that has your change instead of losing it.
A file also lists the pending changes it has (format_pending), so a later development build
runs only the steps that file is missing. Two pull requests that each add a change merge without
a conflict. crates/doc/src/format/pending/README.md has an example.
At release time the maintainer runs scripts/fold-format-pending.sh, which moves the pending
files into crates/doc/src/format/v<N>/ and fixes that number for good (see
docs/releasing.md). Never add a file to a v<N>/ folder afterwards.
Lists many changes add to
Some files gain an item in many pull requests at once. They are kept so that two changes that each add an item only meet when their items are neighbours, which spares everyone a conflict and a fresh CI run:
- Message catalogs (
assets/locales/*.json): one message per line, sorted by id. Add your message anywhere and runcargo xtask lists --fix;cargo xtask lists(incargo xtask ciand the Lists workflow) fails on an unsorted catalog or an id that appears twice. modlines: one per line, in the sorted group they belong to (cargo fmtkeeps each group sorted). Add a new module into the existing group, not as a new group at the end.- Commands without schemas (
crates/engine/src/schema/unconverted.txt): sorted, one id per line, and it only shrinks. Converting a command removes its line; a new command is never added to it. - Languages (
crates/ui-egui/src/i18n.rs): one per line, English first, then by id (a test checks it). - Generated files:
THIRD-PARTY-LICENSES.txtanddocs/features.mdare rewritten onmainafter each merge by the Generated files workflow (cargo xtask licences,cargo xtask features). Don’t regenerate or edit them in a pull request. - Design format: one file per change, never an edit to a shared number (above).
- New UI strings and attribution rows: one new file per change,
assets/locales/pending/<slug>.jsonandattribution/pending/<slug>.md, instead of editing the catalogs or the ATTRIBUTION.md table;cargo xtask fold-pendingfolds them onmain(see the README in each folder).
Sending a change
- Work is tracked in GitHub issues, labelled
by area (
area:kernel,area:sketch,area:doc,area:ui,area:infra). For anything bigger than a bug fix that has no issue yet, open one first. Claim the issue with a draft PR before you start, so two people don’t build the same thing:- Check nobody has it:
gh pr list -R tangency-labs/solidcraft --state open --search "#N". - Branch from the latest
main, commitgit commit --allow-empty -m "claim: #N", push, and open a draft PR titled<summary> (#N), e.g.Fillet: variable radius along a chain (#123). - The lowest-numbered open PR for an issue wins. If yours lost, close it (or offer to help on the other one).
- The bot records the claim when the PR is opened: it assigns you, adds
in progressand says so on the issue. It reads#Nin the PR title andFixes #Nin the body. If someone already holds the issue, or an older open PR references it, it says so on your PR instead. - Want to stake a claim before you have a branch? Comment
/claimon the issue (below). - Push work in progress at least daily. A draft with no pushes for 72 hours may be taken over: comment on it first, then open your own claim.
- Mark the PR ready for review when it is done.
- Check nobody has it:
- Branch from
mainand keep the PR to one topic. - Run
cargo xtask cibefore pushing. It runs fmt, the asset, licence and layer checks, clippy (-D warnings), then the tests with the wasm build check alongside. A PR has to pass it. Heavy CI runs after review: a pull request gets the formatting and repository checks and a quick compile (cargo check) when you push. Once a maintainer has reviewed it they add theapproved-for-cilabel when the PR is ready to merge, which runs the full CI (build, tests, goldens, Clippy); every push after that runs the full set again. Maintainers merge approved PRs in groups by area, so PRs that touch the same code meet a conflict at most once. - Reference the issue (
Fixes #12). In the PR, say what you tested and how. For geometry work, include the measured numbers (and the expected ones).
Commenting /claim assigns you, adds the in progress label, and replies with its automatic cleanup
deadline. It frees a claim with no linked PR after 72 hours, or a claim with a PR after 14 days
without pushes. These automatic cleanup deadlines are separate from the daily progress and
72-hour takeover policy above. A draft PR that references the issue claims it the same way.
/unclaim releases the bot claim early (only you or a maintainer can). Issues a maintainer has
taken are never freed automatically.
Help reviewers see what the words mean. For observable CAD, UI or model behavior, please include actual descriptive screenshots or a short recording in the issue or PR. This evidence is required for changes or bugs that can be demonstrated in the app, including kernel, geometry, assembly and backend work. Coding agents should run a small reproduction and capture the evidence themselves; please don’t ask a human for evidence the agent can produce. Show the actual failure or result, preferably before and after, with brief steps and expected/actual behavior. An arrow or annotation can help identify the detail that matters.
Flowcharts and Mermaid diagrams cannot replace evidence of visible behavior. Diagrams are useful for architectural reasoning when actual screenshots cannot illustrate the point. Changes with no useful observable behavior do not need screenshot or video evidence. Use a small example without private designs or sensitive details; attach the media or link to an accessible recording rather than committing generated review artifacts.
To show a picture without committing it, attach the media to the PR (it goes to the PR’s own attachment storage and renders inline in the comment). A gist is the better home for a log or another text file, and you link it; a gist does not render a picture inline. The review artifacts stay out of the diff that way, and nothing has to be pushed to a branch of its own.
Attaching works in the browser for anyone who can comment: drag the file into a comment on the pull request. A command-line agent without a browser session usually cannot attach a file (the upload needs write access to this repository). Say so in the pull request, and the person running the agent or a maintainer can attach it.
Keep an eye on your PR’s status, but don’t push to it only to catch up with main (no merge of
main, no Update branch button, no rebase for that alone): every push restarts the whole CI
run for everyone waiting behind it, and main‘s own run catches what lands in between. When you
push a change anyway, or GitHub reports a conflict, rebase onto the latest main (push your
branch with --force-with-lease) or merge main in once, and resolve conflicts without losing
others’ changes. Don’t re-run CI on an unchanged commit either: a real failure fails again.
Re-run only a job that runner trouble cancelled, with “Re-run failed jobs”. While a task remains
active, follow an open PR through its status changes; at handoff, clearly identify anything
still awaiting merge.
Much of SolidCraft so far was written by AI coding agents (Claude) working in parallel, each owning one area, with a human setting direction and reporting bugs. Their working notes are in AGENTS.md. Human contributors are just as welcome. Use whatever tools you like, as long as the change meets the rules above.
Two quick checks run on every pull request (.github/workflows/pr-checks.yml, not required): it
fails when a lower-numbered open PR by someone else already closes the same issue (the label
not-a-duplicate skips it), or when the description lacks the Changes a default: no / Changes a default: yes — <what>, old behaviour kept behind preference <name> line (docs-only and bot PRs are exempt).
Labels and triage
The triage bot labels every new issue and PR (.github/workflows/triage.yml). It only adds labels;
correct them freely and it won’t put them back.
- PRs get
area:*from the paths they change (crates/kernelandcrates/geom→area:kernel,crates/ui-eguiand the apps →area:ui,.github/,scripts/,docs/→area:infra, and so on). - Issues get
bug(in-app reports and the bug template),enhancement(feature requests),priority:P1for crashes, and anarea:*from the title or the reported command. Until it has a priority, an issue carriesneeds-triage; setting a priority removes it. - Maintainers comment
/triage area:kernel P1(any existing label;P0–P3for a priority, which replaces the old one;-labelremoves one) to sort an issue or PR. - Short bug reports without steps to reproduce get
needs-infoand one comment asking for the steps (and the version, when it’s missing). An app report that arrived empty is asked to paste the report the app copied to the clipboard. The reporter’s reply, or an edit that adds the steps, removes the label; an issue that stays inneeds-infofor 14 days is closed, and can be reopened any time. Feature requests, app reports with their details, and maintainers’ issues are never asked. needs-triageis the queue: open issues with it.
Where help is wanted most
Kernel speed (slow fillets, booleans, STEP): please don’t rewrite the current kernel code for speed; it is being replaced by solidcore (progress). Report slow cases with a file under the kernel-perf label, and put kernel effort into solidcore instead. See #1597.
See the help wanted and
good first issue labels.
In order of how much they’d help users:
- Fillet and blend robustness. Fillets are our own code (truck has no fillet operation). Constant, variable-radius and chord fillets work, and so do corners where fillets of different radii meet, including three rounds meeting at a vertex. Next are setback corners, fillets along curved edges between curved faces, and fillets that consume neighbouring faces.
- Boolean robustness. A random-design fuzzer currently gets about half of its designs through without a kernel error. Each failing seed is a reproducible bug.
- Drawings, CAM and mesh tools. These are out of scope for now (see ROADMAP.md), but anyone who wants to own one is welcome.
Updating the generated feature list
After adding or renaming commands, run cargo xtask features. The registry is the
source of truth for docs/features.md; do not hand-edit the generated rows.
The generator sorts commands by ID and omits global and per-section counters,
so additions to unrelated areas no longer rewrite shared summary lines.
If two branches still conflict in this file (for example, adjacent additions in
the same area), first resolve the command registry and source conflicts, then run
cargo xtask features again and stage the regenerated file. Do not use Git’s
union merge driver: it can silently retain stale or duplicate command rows.
The generator tests cover stable ordering and additions that leave existing lines
unchanged. The initial switch to sorted rows is a one-time larger diff.
Licence
By contributing you agree that your contribution is licensed as MIT OR Apache-2.0, like the rest of SolidCraft. SolidCraft isn’t affiliated with any CAD vendor; product names are trademarks of their owners (see NOTICE).
Contributing command guides and resources
The command reference combines the live command registry with reviewed Markdown guides. A guide appears inside its command’s entry, before the collapsed automation syntax. You can improve one entry without rewriting the whole manual. The reference’s Practical guides list automatically links to every available guide; adding a guide does not require editing an index or Rust code.
Add or improve an entry
- Find the command in the reference. Its ID is in Automation details.
- Copy the guide template
to
docs/book/command-guides/<command-id>.md, or edit its existing file. For example, Extrude lives insolid.extrude.md. - Explain what the tool does, what must exist or be selected first, the dialog’s choices in a table, a short example, and useful DOs and DON’Ts. Use plain language rather than copying the raw automation parameters.
- Add related commands and any checked community resources, then open a pull request. Documentation changes are reviewed like code.
Do not edit the generated src/reference/commands.md: rebuilding replaces it.
The generator rejects guide filenames whose command is absent from the registry.
Names, groups, shortcuts and automation parameters continue to come from that
registry; handwritten explanations remain editable independently.
Use fourth-level headings (####) inside an entry so the command remains the
parent section. Write image and page paths relative to the generated reference
page, docs/book/src/reference/commands.md, not to the input guide file.
For example, ../generated/sheet-metal.png and ../guide/sheet-metal.md.
Related commands
Write a command link as {{#command sheet.hem}}. The generator inserts its
current name and links directly to that command. Unknown or missing command IDs
and unfinished directives fail the book build, helping contributors catch stale links.
Normal Markdown links can point to guide pages or external resources.
Community resources
You can add a focused tip, a short demo, a model or an illustrative screenshot to the entry’s resource table. Explain what the reader learns from each link; include author and licence information. Links are reviewed, not automatically collected from the web. A resource need not cover the whole command.
Use original SolidCraft examples or openly licensed resources with permission to share. Do not use designs, screenshots, help text or artwork extracted from commercial CAD products, or GPL/LGPL/AGPL code. Prefer small public-safe examples and remove private designs, paths and account information from evidence.
For a new file asset, follow assets/AGENTS.md and add its attribution in a new
attribution/pending/<change>.md file. A reproducible recipe in
docs/book/scripts/ can be rendered by the book builder; generated images stay
out of Git. Existing examples demonstrate both inline pictures and resource links.
Preview and review
Run cargo xtask book with mdBook installed, inspect the command entry and its
links, and check the example in SolidCraft. Confirm the required selection,
units, defaults and limitations against the implementation. Show reviewers an
actual before/after screenshot of the rendered entry. Repository quality gates
still apply before a commit.
Copyable template
One sentence explaining what this command does and when someone would use it.
#### Before you start
- Explain the required body, sketch, region, edge, face or point in ordinary words.
- State whether a selection is required and what cannot be used instead.
#### Options
| Choice in the dialog | Meaning | Example or reason to choose it |
| --- | --- | --- |
| Replace with a real option | Explain the effect without copying parameter syntax. | A useful value with units. |
#### Try it
1. Start from a small reproducible design.
2. Name the selection, command, settings and expected visible result.
#### Tips and common mistakes
- **Do** explain a useful habit and its benefit.
- **Don't** describe an unsupported selection as if it worked.
#### Examples and community resources
Add only rows for resources you have checked. Replace placeholders before
submitting; do not publish an empty resource table.
| Kind | Resource and what it teaches | Author and licence |
| --- | --- | --- |
| Tip | Link to a focused explanation. | Credit and permission to reuse. |
| Demo | Link to a short recording or reproducible recipe. | Credit and licence. |
| Model | Link to an original SolidCraft design or an openly licensed model. | Credit and licence. |
| Screenshot | Link to or embed an actual SolidCraft result, with descriptive alternative text. | Credit and licence. |
#### See also
Use `{{#command solid.extrude}}` with a real related command's ID and explain
why it is related. The generator supplies its current label and a direct link.
Architecture
SolidCraft is a Cargo workspace in layers. A crate only depends on crates in lower layers, and
cargo xtask layers fails the build otherwise.
| Layer | Crates | Role |
|---|---|---|
| L0 | geom | vectors, planes, profiles, meshes and their measures |
| L1 | sketch, kernel, render | constraint solver and profiles; the B-rep kernel boundary (truck); view math and the headless scene description |
| L2 | doc | parameters, expressions, the feature timeline and its incremental evaluation |
| L3 | io | design files, STL/OBJ/STEP/3MF export, STEP and 3MF/STL/OBJ/glTF import features |
| L4 | engine | the session and the command registry (everything is a command) |
| L5 | gpu, ui-egui, mcp | compute-shader evaluation of display tessellation plans; the swappable desktop front end; the MCP server (headless or bridged to the app) |
| apps | solidcraft, solidcraft-cli | desktop app, headless CLI |
The web app, apps/solidcraft-web, is the desktop front end compiled to wasm with
trunk. xtask holds the repository’s own tooling: cargo xtask ci,
layers, assets, features, oracle and book.
A few rules follow from the layering:
- Only
crates/kerneltouches truck. Everything above it sees SolidCraft’s own types: bodies, faces, edges, meshes and measures. See The kernel boundary. - The document knows nothing about commands or the UI.
crates/docholds the parameters, the feature timeline and the component tree, and evaluates them into a model. - Everything the user can do is a command in
crates/engine. See The command engine. - The front end is swappable. Nothing below
ui-eguidepends on egui. The CLI and the MCP server run the same engine without a window. Headless pictures (solidcraft-cli snapshot, MCP screenshots,ui.render, thumbnails) are drawn on the GPU with the viewport’s own pipelines (crates/gpu,capture): the engine describes the scene (view::scene) and the host installs the renderer (engine::capture). The tiers, in order: a hardware GPU, a software adapter (Mesa lavapipe or llvmpipe, WARP), and lastcrates/render’s CPU rasterizer, which draws no surface analysis, highlights or legends.SOLIDCRAFT_GPU=autowalks all three,hardwareandsoftwarestay on their tier (an error without one),cpuforces the rasterizer; every picture’s reply names itsrenderer(gpu,gpu-software,cpu) and a CPU picture carries a warning.solidcraft-cli gpulists the adapters and the tier the policy lands on.
SolidCraft UI kit
crates/ui-egui/src/ui_kit/ contains the tokenised controls used by feature dialogs,
preferences, manager cards, prompts, context menus and the sketch palette. Import through
crate::ui_kit::{...}. Most controls are crate-visible; the container API is public so platform apps use the same hosts.
Stage bars
StageBar composes two clipped, horizontally scrollable rows inside the shell’s
shared floating Container (Surface::Chrome). Its 36 pt first row contains numbered
stage chips with the current workspace accent, shared primary/normal buttons and
the existing command-search editor. The second row fills the remaining card height.
The feature supplies labels, enabled reasons and command dispatch; the kit owns
chip geometry, row spacing, typography and colour. Stage selection is navigation;
the kit never invents completion badges. The shared compact workspace card is 104 × 88 pt; its icon, label and caption are
centred vertically. The stage bar is 88 pt high with an 8 pt gap from that card.
Its 36 pt header and 52 pt tools band share a 1 pt divider. Tools use 8 pt side
padding, icon-text segments, icon-label actions and an information chip, all with
theme::Tokens colours in both themes. Render retains its Inspector controls
outside the bar.
Panel::workspace gives paired floating docks the same bounded vertical span and
limits each dock to half the available width with an 8 pt gap. Content clips inside
the allocated host, so a small window cannot grow a dock into the stage bar or Shots
strip. Docks without usable space are omitted. Render’s default outer widths are 266 pt
for Browser and 305 pt for Inspector, retaining the shared minimum/maximum resize
rules. Its compass is 110 pt; the 48 pt Shots strip clears the docks by 8 pt.
Composition and rules
-
Every outer host uses
ui_kit::Containerwith a semanticSurface. The shell publishes the layout once per frame;Container::floating()andframe()own adaptive placement policy and chrome.Panel,Window,ConfirmandMenucompose it. Raw egui hosts and outer shadows are rejected recursively, including platform apps. The mandatorycargo xtask ui-containersgate runs in fast CI and scans new crates/apps automatically, including macro bodies and alias chains. See the container audit and migration inventory. -
Colours, typography, dimensions and spacing come from
theme::Tokens. -
Sliders only via
ui_kit::slider;Slideroptions change behaviour, never appearance. Raw sliders and duplicate track/thumb painters are forbidden in the UI and apps. -
scope(ui, add)supplies form styling.row(ui, label, add)aligns labels and controls atcontrol_height(28 pt). Wide manager rows use a 104 pt label basis with a 10 pt gutter, derived from the compact 70 pt basis and spacing tokens. Compact rows retain their 8 pt gutter. Labels widen from the appropriate basis to at most 1.6 times that width and never exceed half the row. -
expr_row,hint_row,text_rowandchoice_rowcomposerowwith the existing expression, hinted expression, plain text and dropdown controls. They preserve row order, labels, focus IDs and completion; callers keep Enter/change ownership and explicit dropdown IDs.count_rowcomposesrowwithcount_fieldfor pattern quantities. -
Panel::workspaceowns grip-based left/right placement and bounded width resizing for floating workspace cards, including in Compact ribbon layouts. It reuses the 40 pt Panel/Window header and padded body, with no second top inset below the ribbon. Persistent workspace cards omit the close action.Panel::bottom_stripusesContainer::bottom_strip_rect, the same placement as Design History.Container::navigation_cardsupplies the shared Navigation surface for anchored HUDs. Register measured card rectangles and reserved edges withshell::register_panelso fitting and pointer shielding agree. Feature modules provide content, never outer chrome. -
Panelowns bounded windows, feature/palette composition, scrolling bodies, pinned actions, compute status and keyboard routing.wide()uses three card widths.feature_frameandpalette_frameretain docked/floating surfaces.FeatureOptions::apply_labeladds Apply;Panel::actionssupplies custom labels. WithDialogButtons::Toppublished (Preferences › Interface › Dialog buttons),featureputs the actions in the header row throughHeaderActionsinstead of a footer: the same buttons, enabled states, keyboard and handles, each as wide as its bold label, with Cancel standing in for the close button and the compute status in the title’s place.body_rowscaps a manager’s scrolling body independently of its position; short viewports still keep the header and actions visible. -
Windowcomposes the shared Panel frame with a 40 pt icon/title/close header, padded content and optional 48 pt footer.show_with_leftpins a left action beside the right-aligned buttons.show_pagesaccepts declarativeWindowPageentries (icon, i18n label key, optional group heading, stable handle, scroll policy and draw callback). Its 188 pt soft page list has 32 pt icon/label rows, selected tint/ink and a divider. Preferences has nine grouped pages; legacy control-channel page names remain aliases.page_rowsopts into a taller body while preserving viewport bounds.actions,anchor/placed,cards,fieldsandkeep_open_on_escaperetain existing window behavior. Empty action labels omit the footer. -
Confirmis an input-blocking modal using the same card frame and footer controls. It shows a 36 pt icon disc in warning, danger or info tone, a title, caller-supplied message and optional monospacedetail, above a 52 pt footer. Glyphs use the construction ramp for warning, the shared shaded danger treatment for danger, and base blue for info; the disc keeps its tint.actions(primary, cancel, left_ghost)places the optional third action at the left. Danger usesform_errorwith a contrast-selected on-colour; other primaries use blue. Enter invokes the selected answer (primary by default) when valid; Esc cancels. It retains close, answer and message scenario handles. The oldPromptelement is removed. -
Callers supply data and enabled state, then dispatch existing commands. Editors leave their established completion, Enter, Tab and commit ownership unchanged.
-
Every text input uses the kit’s shared
text_inputpolicy: symmetric horizontalform_inset, zero vertical margin and centred galley alignment. Font metrics therefore centre both mono and proportional text within the allocated field, including invalid and attached Library fields, without per-caller offsets. Canvas placement editors retain their native line-height plusspace_4hit target so nearby sketch picks are not intercepted; alignment stays shared. -
Scenario handles and accessible labels are API. Consolidated options preserve the former geometry, wrapping, native metadata and publication keys.
Preference compositions use settings_heading, shared disclosure settings_section headings and
settings_pair for responsive controls beside one specimen. option_card supplies the
shared illustrated radio control. settings_form and settings_block_hint define aligned label grids; theme_rail keeps the import tile fixed beside a scrollbar-free scrolling row. PreferenceFigure owns theme swatches, orbit pivots,
quality, anti-aliasing, contact shading and up-axis figures. layout_preview follows the
four workspace choices without opening another viewport. Scale, palette, autosave, mouse
and live-axis specimens share tokens and the gallery; live bars show actual calibrated
input and introduce no repaint timer.
Elements
Module names below describe implementation locations, not caller import paths.
Every inline element/variant has one entry in the light/dark gallery::ELEMENTS
table. Panel/Window/Confirm compositions also have dedicated geometry and interaction tests.
| Module / element | Use and options | Main tokens |
|---|---|---|
assistant: assistant_header, assistant_controls, assistant_badge, assistant_bubble, assistant_prose, assistant_tool_frame/row/detail/status, AssistantToolState, assistant_approval/actions, assistant_composer/footer, assistant_input, assistant_send, assistant_empty, assistant_banner, AssistantTone | Shared Assistant presentation: 40 pt header, 22 pt state pill, right-aligned user messages limited to 86% width, unboxed assistant prose, bordered tool groups and command detail, tinted approval heading, rounded focused composer with one unframed growing editor and pinned footer, centred setup copy and semantic notices. Closures preserve existing permission choices, protocol routing, queue actions and scenario handles. | form_header_height/close_size, font_label/small/body, dialog_body/footer/rule, control_fill/border/radius, form_ink/dim/primary/success/error, accent_soft/selected_ink, warning, radii and spacing |
history: HistoryChip, HistoryStyle, history_drop_bar, history_marker, HistorySeam, history_seam | One painter family for bottom chips, 38 pt vertical icon chips and single-line 26 pt name rows (the browser’s shared TREE_ROW_HEIGHT). Selected items use the active tab tint/ink/border; all variants retain multi-tone feature artwork using one shared kind-to-accent rule (Sketch green, Surface orange, Mesh yellow, Form purple, solids base blue); rolled-back items have 42% opacity; a reference row (a sketch listed under another feature, #6489) has its name in secondary text. Error/warning badges, suppression strikes, horizontal/vertical drop bars and rollback line/hollow circle are shared. No connector lines. | control_height, space_2/3/4/6/8/10/12/14/16/24, radii, typography, TabAccent, timeline_item, control_border, text/dim, form_error/fill |
search: CommandSearch, MenuItem::search_row | Ribbon search composition: one persistent ribbon editor expands from 28 to 40 pt and to 520 pt wide with its right edge anchored, magnifier and S/Esc keycap, 38 pt two-line menu row with 26 pt icon tile, accent bold substring, path, shortcut/kind keycap, counts, JSON detail box and footer. The ordinary 30 pt menu row cannot fit a second line; this variant keeps its enabled/selected states and scenario IDs. Ranking and dispatch stay with the caller. | form_card_width × 2 − space_8 (520 pt), control_height, space_2/3/4/6/8/10/12/14/16/24, radius_xl/control_radius, control_fill, dialog_body/footer/rule/border, form_ink/dim/primary/error/error_fill/success/success_fill, selected_ink, accent_soft, typography |
menu: Menu, MenuItem | Shared popup, 30 pt rows, 28 pt icon column, clipped labels, right-aligned mono shortcuts, submenu chevrons, checked/unchecked and document swatches, inert 40% disabled rows and danger ink; headings and separators; ribbon drag/pin targets retained | dialog_body/border/rule, radius_xl, control_height/radius, form_ink/dim/primary/error, accent_soft, typography and spacing |
radial: radial_menu | Eight rounded annular sectors, parallel 4 pt gaps, 9 pt circular corner joins, tab-accent artwork and hover, centre label/key; existing hold/flick/release dispatch | dialog_body/border, form_ink/dim, TabAccent, icon palette, font/spacing tokens |
panel: Window, WindowPage | Shared card frame, header, optional page list, bounded content and footer with a left action | form_header/footer_height, form_card_width, form_label_width, control_height, dialog_footer/rule, selected_bg/ink, spacing |
confirm: Confirm, Tone | Modal warning/danger/info, title/message, mono detail and ghost/cancel/primary actions | warning, form_error/fill, accent_soft, form_primary, on_accent, tab_on_fill_dark, font_subtitle, spacing |
metrics: row_height, table_row_height, header_height | Shared control/header heights | control_height, space_8, form_header_height |
layout: scope, row | Form styling and aligned label/control columns | form_label_width, form_gap_y, control_*, font_label, dialog_label, space_4/8 |
caption | Dim caption aligned beneath a control | font_small, text_dim, label-column tokens |
figure_rows, FigureSlot | A caller-painted figure in the label column beside a stack of captioned expression fields (the Hole dialog’s section); the caller gets each field’s rect and hover/focus state to draw leaders and light the part it sizes. Fields keep their dialog:<label> handles; the block publishes dialog:figure:<id> and its picture dialog:figure_picture:<id> | control_height, form_label_width, font_small, form_dim, space_2/4, label gap |
section, section_count, divider | Persisted small-caps disclosure with chevron, optional count badge and trailing hairline; full-width rule | control_height, dialog_rule, font_caption, tracking, spacing |
list_row | Picked item with a remove target | control_height, font_label, icon/spacing tokens |
text, Text | Hint, note, report, inline or cell text; see variants below | font_small/label, text/dim/error, control_height, space_4 |
text_lines | Virtualized read-only licence/report lines, horizontal overflow | font_small, text, control_height |
fields: field, hint_field, focused_field | Expression editors, optional empty hint or retained focus; returns accepted Enter | control_height/fill, form_inset, font_label |
count_field, Count | Expression editor for a whole-number count (a pattern’s quantity) with up/down step arrows at its right edge. Count gives the limits and the evaluator (the design’s parameters); an arrow steps the evaluated count by one within them, replacing a plain number or adjusting an expression’s trailing + n, and is dimmed when it can’t step. Handles dialog:<row> up / down; returns accepted Enter | field tokens, control_border, control_radius, selected_bg, form_ink/dim, space_4 |
text_field, focused_text_field | Plain name editor, optionally retaining focus | field tokens |
multiline_text_field | Three-line plain-text row with stable focus ID | field/row tokens |
password_field | Stable secret editor; excludes the secret from scenario/accessibility metadata | field tokens |
message_field | Growing message editor; owner retains scroll and Enter routing | field tokens |
cell_editor, bounded_cell_editor, search_field, library_cell | Stable-ID inline/search editors; bounded variant caps Unicode characters; library_cell attaches a Library dropdown inside the editor border | field tokens, text/error |
text_area | Bounded multiline JSON/data editor | field tokens |
dropdown, dropdown_menu | Indexed choices or caller-supplied menu controls | control_height, text_dim, form tokens |
searchable_dropdown, SearchOption | Filtered choices, custom-value action and pending message | dropdown tokens |
selection_field, Selection | Selection chip with explicit active and filled state, in three looks: the active input (where the next pick goes) has a form_primary border and selected_bg tint; a filled inactive one a quiet neutral tint, a stronger neutral border and its count in form_ink; an empty inactive one the plain control with a dim prompt | selected_bg/ink, form_primary/ink/dim, control_*, form_inset, space_2/8/24 |
value_pair | Paired captions/expressions; optional independent field names | row/field tokens, space_8 |
fraction_field, scalar_field | Locale-aware bounded dimensionless values; scalar fields fill their control column | form control/font tokens |
number_field | Integer/decimal field with caller range, drag speed and suffix | form control/font tokens |
length_field, angle_field | Length in design units, angle in degrees | form control/font tokens |
slider + Slider options | One token-painted track/thumb over native slider/value interaction; optional step, caller formatting, whole-step manipulation and hidden value editor | control_height, space_24/8/4/2, form control/font tokens |
placement_field | Stable focus ID; drawing tool owns Enter/Tab validation and completion | control_fill, font_body, text, control_height, spacing |
color_field, color_popover | Opaque document RGB swatch and kit popover; global U8/F segments, copy, gamma-byte or linear RGB fields, painter-drawn HSV square/hue and current strip; outside click dismisses | control_*, form_*, border, on_accent, space_* |
choice: segmented, Segments | Equal text choices; states supplies mixed selection, compact clips instead of falling back to a dropdown, primary uses accent/on-accent for the selected segment | control_*, selected_bg/ink, font_label, space_4/8 |
segmented_icons, IconSegment | Equal icon choices and mixed states | segment tokens, icon palette |
checkbox, checkbox_indeterminate, checkbox_row, checkbox_grid, options | One box/tick painter for inline, mixed, full-row, grid and menu choices | space_14/3/2, radius_sm, control_fill/border, form_primary/ink, hover, on_accent |
text_emphasis | Bold/italic with translated accessible names | form control/font tokens |
tool_choices, ToolChoice, toggle_icon | Wrapped command modes or independent icon toggles | sketch mode/icon tokens, accent |
list_picker | Selectable row with caller identity and native checked state | control_height, form selection tokens |
popup_list, PopupOption | Focus-neutral suggestions; owner retains keyboard routing | space_2, form_label_width, control_height, form tokens |
color_swatch, swatch_grid, color_thumbnail | 30 pt draggable samples, 6 pt grid step gap, selected outer ring and checker pattern under transparent colours; caller payloads retained; local-library thumbnails use 16 pt | control_height/radius, border, form_primary, control_fill, space_2/6/8 |
assistant_entry, AssistantEntryState | Application-bar chat icon with quiet, ready, busy, auth, stopped and approval count marks; caller owns state and target | app_bar, agent_ok/busy, warning/error, font_badge |
actions: button, Button | Secondary action: Normal, Small, or Enabled(bool) | native sizing, form font/control tokens |
primary_button | Accent action outside a footer | accent, on_accent, form tokens |
icon_button_at | Icon action inside a supplied rectangle | form_icon_inset, text/dim, icon palette |
button_pair, PairButton | Equal secondary actions, optional icons and selected state | form_button_height, control/selection/icon tokens |
footer, Footer, Action, HeaderActions | OK/Cancel with optional Apply, compute status or selected action; HeaderActions puts the same buttons in a header row | form_footer/button_height, dialog_footer/rule, control_*, accent, spacing |
keyboard | Esc cancels, unowned Enter submits when valid | none |
action_menu, command_menu_button | Nested dense action menu or ribbon overflow action retaining its icon, shortcut and enabled state | form control/font tokens, space_16, icon palette |
table: table, table_custom, Column, Table::headers, Table::row, TableRow::cell, Table::add_prompt | Left-aligned headers/cells, horizontal overflow, selected/hover rows and empty state. table_custom supplies header actions through headers. Column::flex shares remaining width by weight, freezing columns at their control minimums before redistributing the remaining space; Column::icon_only hides the heading while retaining its automation identity. add_prompt is the final slim dashed line and requests a regular draft row from its owner; Column::new(label, width) uses label-column multiples, min_content_width(points) reserves measured control content; ContentWidth::name measures strong configuration names | label-column tokens, control_height, dialog_rule/footer, selected_bg, spacing |
removable_header, plus_header, bin_button, active_pill | Hover-only column removal, anchored Add Column menu with insertion brackets, icon-only immediate deletion and active-row status. Bins preserve dialog:button:Actions:Delete and :×; header removal preserves dialog:button:LABEL:×. | control_*, form_*, selected_bg, spacing |
compact_button, summary_row | Borderless stars and chevrons with enabled/selected states; named two-line rule summary beside an enable checkbox and compact actions. | form_close_size, control_*, form_*, typography and spacing |
label_actions | Clipped row name beside compact actions | control_height, form font/spacing tokens |
status: status_header, StatusHeader | Supplied icon/name/status and constraint meter | font_label, text/dim, accent, sketch/header tokens |
progress | Clamped determinate progress | space_3/6, dialog_rule, accent |
History contents are selected from measured panel width. A 56 pt thin strip shows icons
and four vertical transport controls: beginning, back, forward and end. The minimum named
width is 130 pt; it adds horizontal transport and the same six-dot window grip as Assistant.
When the remaining header space fits its measured text, History appears between grip
and transport. No names-mode flag
or command selects these layouts. The seam snaps between 56 and 130 pt; widths from 130
through 624 pt remain continuous and persist as timeline_left_width.
Drag the grip or title to dock along the bottom, at the full-height left edge, or beneath
the browser. Either card’s right edge resizes their shared width. This position divides
available column height equally. Folding either card leaves its header visible and gives the remainder to
the other. If both are folded, their headers sit together at the top of the column.
Expansion and collapse share a smooth cubic transition capped at 120 ms, respecting
the interface’s instant-animation setting. Their collapse controls share the same active-tab blue hover tint. Browser resizing also resizes History.
The view.history_layout command validates width, docking and collapse requests without
altering the design or opening dialogs. Floating mode preserves viewport projection.
The seam handle overlays the strip’s right edge without taking layout space. Its grip is
visible at rest; the arrow appears only on hover. Click, double-click the edge, or use
Enter/Space on the focused handle to resize between thin and minimum named widths. Drag
freely above the named minimum; resizing draws no dotted guide or name label. Header/title dragging
and the edge width gesture are separate from feature/folder and rollback dragging.
The rollback marker is 12 pt high.
Its 2 pt line uses the active tab fill and ends in a 10 pt hollow circle with a 2 pt accent
border and the card/panel surface inside. The circle overlaps the line end by 1 pt. The bottom
strip uses the same painter, rotated. Folder selection, range selection, context menus,
sketch-pattern children, drag reordering and command JSON retain their existing behavior and handles.
history_icons, history_names, history_marker and the history_seam_rest/hover/drag states
have light/dark gallery references. The CPU
board test captures both strip widths with expanded and collapsed browsers
(SOLIDCRAFT_HISTORY_EVIDENCE).
text(ui, text, options) supports the existing text behaviors and the new error band:
| Option | Layout and scenario publication |
|---|---|
Text::Hint(key) | Wrapping 12 pt hint; rect and handle dialog:hint:KEY |
Text::Note(key), Text::MonoNote(key) | Wrapping small dim text (optional monospace) with vertical padding; rect dialog:note:KEY |
Text::Error(key) | Wrapping full-width error band with tinted background and padding; retains dialog:report:KEY |
Text::Report(key, error) | Native wrapping label, optional error colour; rect/handle dialog:report:KEY |
Text::Heading(key) | Semibold body heading; retains dialog:report:KEY |
Text::NameCell(strong) | Medium/semibold clipped clickable name; retains dialog:cell:TEXT |
Text::MonoCell, Text::Value(key, error) | Monospace unit/value cells; clipped to one row with full tooltips. Value/error cells retain dialog:report:KEY. |
Text::Inline | Native truncated label; rect/handle dialog:inline:TEXT |
Text::Cell | Fixed-height clipped clickable cell with tooltip; rect/handle dialog:cell:TEXT |
Examples:
#![allow(unused)]
fn main() {
segmented(ui, &mut mode, &["One", "Two"], Segments::default());
selection_field(ui, "Select", Selection { active: true, filled: false });
value_pair(ui, "Position", [&mut x, &mut y], ["X", "Y"], Some(["Distance X", "Distance Y"]));
button(ui, "Add", Button::Enabled(valid));
footer(ui, "OK", "Cancel", valid, Footer { apply: Some("Apply"), ..Default::default() });
text(ui, report, Text::Report("validation", invalid));
}
Icon surfaces that explicitly choose a palette ramp use icons::paint_accented.
None selects base blue, while unknown names retain the icon’s semantic accent.
Only active roles change: ink/line outlines, passive faces, guides and knockouts keep
normal artwork colours. Existing icon painters retain their semantic-accent behaviour.
Danger menu items and danger confirmation glyphs both use icons::paint_danger.
It additionally maps passive-1/2/3 faces to constraint active-1/2/3, retaining the
shading steps, ink outlines, handle and transparent slat details. The cache distinguishes
this whole-glyph treatment from ordinary accent selection and monochrome ink masks.
Radial icons follow the active tab (Sketch, Surface, Mesh, Form; other tabs use base blue)
in both normal and hovered sectors; the hovered background retains its tab tint.
Extending and checking the kit
Add one implementation, document its tokens/options here, re-export it through
mod.rs, and add one table entry in gallery::ELEMENTS. Keep implementation-only
helpers private. Add interaction tests when the element introduces an action.
The gallery checks geometry, label alignment, handles and effective paint bounds in
both themes. Field/footer/panel tests retain focus, completion and action coverage.
ui_kit_guard_tests scans every UI feature module recursively, rejects raw control constructors
and methods (including aliases), and rejects removed legacy helpers. The raw-control
allowance table is empty and guarded against additions. Standard colour/font guards,
Clippy, wasm, i18n, assets, icons and layers still apply.
See remaining coverage boundaries for specialised surfaces that remain outside the kit and why.
Popup presentation and draft rows
Panel and Window use 28 pt controls, a 40 pt title band (14 pt semibold title,
19 pt command icon and 26 pt close/fold targets), and a 48 pt footer with
28 pt actions. Table rows are control_height + space_8 (36 pt), independent
of control height. Bodies use 12 pt insets. Width limits and scroll boundaries
remain owned by Panel/Window/Confirm. Titles retain the supplied case.
The form_ink, form_dim, form_primary, form_error and form_error_fill
colour keys match the approved light cards. Dark values follow existing text,
accent and error palette roles; every role is exported by theme files and measured
by the colour-vision checks. Invalid fields include both error text and a tinted
surface. Long variable-height lists can use Window::placed with a top-left pivot to
keep their painted controls and scenario targets aligned as they grow.
Completion lists use the approved 230 pt minimum width (derived from card and
spacing tokens), show kind labels on the right, selected details beneath
the list, and the existing keyboard hint.
Parameters and Configuration Table use this one table grid, including Favorites
and each model group. The grid has space_8 horizontal insets within the row
highlight, shared by headers and cells, plus space_8 column gaps. Flexible widths
are measured inside the scroll viewport and rounded by cumulative column edges;
fitting content does not acquire a horizontal scrollbar from independent rounding
or inherited scroll margins. Explicit minimum widths still allow genuine overflow.
The viewport and measured content width publish dialog:table:TABLE:viewport and
dialog:table:TABLE:content_width for geometry checks. Hover uses the clipped row
rectangle, including interactive children, while preserving child click ownership.
Their add prompts publish dialog:table:TABLE:add, use control_height - space_4
height, a dashed control_radius outline and a pointing-hand cursor.
Clicking creates a regular unsaved row, focuses its name and exposes the existing
field controls and bin. Enter or valid focus loss commits through the existing
commands; abandoning a nameless row drops it. Configuration Enter with an empty
name retains ConfigurationN naming and copies the active row. Parameter validity
is published as param:new:valid; invalid expressions cannot commit on focus loss.
The natural scrolling body height is published as dialog:body:content_height,
so geometry tests distinguish content size from the viewport cap.
After a successful parameter add, a fresh empty draft retains Name focus for
continuous keyboard entry; leaving that empty row drops it.
Parameter draft fields retain param:new:name/unit/expression/comment.
Configuration drafts publish config:new:name; existing config:cell:ROW:COLUMN,
activation, rule and preview handles remain. The header plus retains
dialog:menu:Add Column, including the existing menu nesting. The old separate
Add forms and column-removal band have been removed.
Crash Reports uses report_list (bordered bounded scroll), report_header (kind,
count pill and mono UTC time), path_chip (clipped mono path and adjacent action),
empty_box (dashed empty state), and icon_label_button (secondary icon/text,
optional danger ink). These use dialog_rule/footer, control_*, form_*,
font_label/small and spacing tokens. The report header retains the combined-title
cell alias; bins retain the previous Delete handle. Report actions remain immediate.
list_frame supplies the same border to virtualised library rows.
The list frame owns its border and horizontal padding; the report viewport adds
no inherited scroll margin. Report list measurements publish
dialog:list:KEY:viewport and dialog:list:KEY:content_width. Reports use vertical
scrolling; the horizontal rule beneath each report remains a divider.
Appearance uses a 340 pt card derived from card/label/spacing tokens, with the
104 pt label basis where space permits, 10 pt body gaps, and a count on In This Design.
Colour popovers retain dialog:color:ROW and add color:format:U8/F,
color:copy, color:channel:R/G/B, color:sv, color:hue, color:current
and the color:popover rectangle. Format changes update egui’s global numeric
colour presentation without changing RGB. Linear fields edit linear RGB, not
normalised gamma bytes. HSV state preserves hue at zero saturation/value and
sub-byte precision during drags. Gradient colours are document colour data;
all interface chrome uses existing paired light/dark tokens.
Sliders only via ui_kit::slider, everywhere in crates/ui-egui/src and apps.
The element owns its form styling even when called from a viewport menu.
Slider options select behaviour, never appearance: range and values remain in caller
units, step preserves stepping, format preserves suffixes/display mapping,
integer rounds manipulation and show_value: false hides the editor. Disabled
sliders use ui.add_enabled_ui; tooltips attach to the returned response. Preferences
UI scale still applies only after the pointer is released. There are no alternative
slider helpers, raw egui::Slider callers, or custom track/thumb painters outside
the kit. The source guard scans both trees without an allowance table; the gallery
covers continuous, percent/stepped, integer/no-editor and disabled states in both themes.
Numeric fields use the existing kit scalar_field, length_field, angle_field
or number_field rather than constructing a raw egui::DragValue at a caller.
Parameters retains its bounded header drag offset while the manager is open,
including frames without a regular feature dialog. The shared Panel bound reserves
header and footer space at the bottom edge. Import and both export actions use
icon_label_button with the existing import/export arrow icons and unchanged
command routing and scenario identities. The gallery includes all three actions.
The Add Column target is form_close_size - space_4 (22 pt) square: idle paint is
only form_dim ink; hover adds the selected accent fill and insertion guides.
HSV gradients use a rounded mesh silhouette sampled along control_radius arcs;
the current-colour strip uses the same rounded radius. Render tests sample all
four corner pixels and opaque interiors of all three surfaces at 1× and 2× in
both themes.
Checkboxes only via ui_kit::checkbox, everywhere in crates/ui-egui/src and
apps. checkbox_row, checkbox_grid and menu checkbox rows share its single
box painter and paint_check_glyph path. The box is space_14 square with
radius_sm corners, a control_border outline and control_fill background.
Checked/mixed boxes use form_primary and on_accent; hover uses hover and
an accent border, focus adds a space_2 outset ring, and disabled controls inherit
egui’s disabled opacity. Geometry never depends on caller font or native icon size.
checkbox_indeterminate represents a mixed selection with a bar and activates to
checked. Space/Enter, accessible checked state, tooltips and scenario handles are
retained. Exclusive choices use existing kit segments/dropdowns/list pickers;
there are no radio controls. The recursive guard rejects native checkbox/radio
constructors (including aliases), raw toggle methods and duplicate check glyph
painters across both trees without allowances. Offscreen checks compare actual
box and tick geometry/pixels across form, grid, palette and menu call sites at
1×/2× in both themes.
Disclosure chevrons point down when open (content visible) and right when
closed (content hidden). Only ui_kit::disclosure draws them, using the original
chevron / chevron_right SVGs at form_section_icon_size, without animation
(as in kit section headers). Sections, nested groups, Browser trees, card headers,
window folds and the Assistant strip all follow this rule. Keep existing scenario
handles, including dialog:collapse, and translated Collapse/Expand tooltips.
Dropdown arrows always point down; submenu arrows point right. Playback, reorder,
window management and overflow controls are not disclosures. The source guard scans
UI, apps and compiled asset glyphs without caller allowances; render tests inspect
the actual icon textures used by sections, card folds and window folds.
Per-tab accent tokens
TabAccent::for_tab supplies three independent theme colours: color (fill) for
pills, active-tool borders and the history marker; ink for small text and selected
rows; soft for tinted selection/tool backgrounds. These are palette entries, not
blends. Browser rows and selected ribbon labels use ink directly.
| Tab id | Fill token | Ink token | Soft token |
|---|---|---|---|
MODEL / default | accent | selected_ink | accent_soft |
SKETCH | sketch_accent | sketch_ink | sketch_soft |
SURFACE | surface_fill | surface_ink | surface_soft |
MESH | mesh_fill | mesh_ink | mesh_soft |
FORM | form_fill | form_tab_ink | form_soft |
SHEET METAL | sheet_fill | sheet_ink | sheet_soft |
RENDER | render_fill | render_ink | render_soft |
form_tab_ink is separate from the existing form_ink used for form controls.
Sketch reuses its existing fill in both themes. Surface and Form fills match their
existing accents in light mode but need independent dark values; Mesh and Render
fills also differ from their existing mesh_accent and render_accent values.
All existing tokens, including Model’s fill, ink and tint, retain their values.
The Model/default mapping preserves its existing appearance rather than adopting
the proposed Model colours from the board.
The new palette entries (fill / ink / soft) are:
| Tab | Light | Dark |
|---|---|---|
| Sketch | #057F45 / #057F45 / #EDF7F1 | #128D45 / #46BE78 / #1F3B2B |
| Surface | #A45000 / #A45000 / #FBEBD8 | #B26A14 / #E0914A / #45331E |
| Mesh | #E0B200 / #8A6D00 / #FFF8DC | #C6A800 / #D4B726 / #41391A |
| Form | #7349CD / #7349CD / #EEE8FA | #8A6FCC / #B39BEE / #372F4F |
| Sheet Metal | #1E6F96 / #1E6F96 / #E1F0F7 | #2A7FA8 / #4FB3D9 / #1F3A47 |
| Render | #A8326E / #A8326E / #F8E6EF | #C8588F / #E074AE / #402336 |
Each key is supported by solidcraft-theme/1 import/export, including
tab_on_fill_dark (#000000 in both themes). TabAccent::on_fill uses on_accent
(white in the built-in palettes) when it reaches 4.5:1, otherwise that dark ink.
The fallback is black because the board’s dark ink does not reach 4.5:1 on several
of the approved fills. It only styles tab pills; global primary actions still use
on_accent. Built-in palette tests require WCAG contrast of at least 4.5:1 for
ink on the card and panel, ink on soft, and pill text on fill in both themes.
Only Mesh separates ink from fill in the light theme.
Context menu adapters retain item IDs, enabled logic and command parameters. Menu
owns popup elevation (10 pt corners, 1 pt border), headings, separators, adjacent
submenus and bounded arrow navigation. Enter opens a submenu or chooses its row;
Right opens, Left returns, and Esc dismisses the menu. Appearance includes document
swatches and Remove override through appearance.assign; its original action still
opens the full editor. Workspace retains its descriptive cards and scenario handles
on the shared surface. Ribbon rows reuse MenuItem::paint while owning drag, focus
and pin interactions. History context menus and ribbon overflow use kit popup chrome.
Rename uses the shared caption, field policy and Cancel/OK actions with its stable
editor ID. No additional translation keys or colour roles are needed.
The menu gallery includes normal, hovered, open, disabled, checked/unchecked,
swatch and danger rows, a submenu and rename. Radial geometry tests measure the
actual side endpoints at both radii and sample all four rounded corners. Optional
CPU-rendered menu evidence can be produced without a window with
SOLIDCRAFT_MENU_EVIDENCE=/tmp/menus cargo test -p solidcraft-ui-egui --lib menu_board_in_both_themes.
Desktop interaction and visual acceptance still require a reviewer capture.
Command search uses the ribbon field itself (sc_sbox_text); ribbon:search,
shortcut:search and palette:search all identify that same field. Opening gives
it the accent focus ring, cursor and Esc keycap. The field and the attached results
sheet unfold in 120 ms with egui cubic ease-out, capped by egui’s animation time
so instant settings remain instant. Esc, click away and command dispatch reverse
the animation. Only the visible sheet height participates in layout and hit testing;
result content is clipped while unfolding. The sheet’s ribbon:search_popup /
palette:outer handles exclude the field. Its footer (ribbon:search_footer)
fills the bottom strip edge to edge, with rounded bottom corners and a 1 pt top
rule. Ranking, command mode and keyboard routing remain with the caller.
Generate CPU-rendered evidence of all five search states and the actual ribbon’s
closed/opening/open/closing transition in both themes with
SOLIDCRAFT_SEARCH_EVIDENCE=/tmp/search cargo test -p solidcraft-ui-egui --lib command_search.
Compact workspace composition
Container::gap and Container::frame supply the layout policy for every adaptive
surface. Compact has no gaps or shadows between edge-bound panels; floating uses
the shared card spacing and frame. Panel bounds draggable compact sheets to the workspace. The shared frame rounds
all corners away from the right dock and only the left corners while right-docked;
the footer inherits that frame’s bottom corners. Both layouts retain drag offsets. Browser uses the same content renderer and width in both modes,
with a square, flush ribbon join in Compact. Non-workspace surfaces keep their roles.
DockEdges declares touching edges. The container measures content-hugging cards
before rounding their exposed corners, and aligns touching outlines onto a single
border strip without changing logical panel allocations. The application bar has
an independent surface role.
The saved Prefs::layout is changed through view.layout; ui.set may still make a
transient automation override.
File/start presentation
FileView owns File surfaces and controls. It accepts translated strings, existing
icon ids and cached thumbnail textures; callers retain commands, document state,
persistence, thumbnail jobs and recovery decisions.
page,contentandrailown the page background, responsive insets and full-height rail fill/clip.navigationalso supports the narrow navigation menu. Callers supply layout rectangles, scrolling and entry order.nav_rowaccepts a stable id, label, shortcut, selection and enabled state.back_toaccepts a translated{name}template, paints the document name semibold and reuses the mirrored chevron and outlined keycap.separatorsupplies grouped rules. Disabled rows cannot activate.title,subtitle,detailandsectionown typography.sectionaccepts a token-based heading/body gap:space_12for New,space_10for Recent/Samples. Callers retain semantic section order and the largerfile_section_gap.gridprovides responsive columns with 14 pt gutters and no trailing row gap.action_tilesupplies an 84 pt card with 44 pt original artwork and vertically centered label/subtitle.tile_min_widthinforms responsive columns.recent_listowns the rounded border and zero row gap.recent_rowaccepts aRecentRow, paints internal separators, medium names and workspace dots, and retains title/path at narrow widths. Callers handle unavailable-file actions.sample_grid(ui, id, &[SampleCard])measures each row’s tallest content and stretches cards to equal bottoms without clipping descriptions. Responses stay in sample order, with stable{id}:{index}ids. Artwork inputs remain unchanged.recovery_banneracceptsRecoveryMessagesegments for a bold title, normal message, monospace filename and normal suffix. Actions align right or wrap below at narrow widths. Discard is optional for summaries; the return value is(recover_response, optional_discard_response).recovery_reviewuses the same geometry with per-design Discard. Callers provide truthful metadata.
The exported file_* colour tokens provide readable light/dark foreground and
background pairs, including File-specific secondary text. Thumbnails retain the
neutral file_image_tint; icon and text colours do not change their pixels.
Spacing, dimensions, fonts and radii come from theme::Tokens.
Gallery and geometry tests cover these components. To capture the actual File
view using public temporary fixtures, set SOLIDCRAFT_FILE_EVIDENCE to an output
directory and run file_view_application_evidence_in_both_themes_and_widths with
--ignored. It produces desktop and 420 pt narrow PNGs in both themes.
button_at(ui, id, rect, label, Button::Primary/Normal) places the same shared
native button used by button inside a bounded, centred layout. It preserves
stable identity, enabled state and exact caller-owned geometry. Recovery actions
use this component rather than custom button painters. The recovery well renders
the existing restore artwork through its construction accent ramp.
Theme corner radii
Theme files may override radius_sm, radius_md, radius_lg, radius_xl,
control_radius and sketch_mode_radius in an optional radii object (integer
UI points, 0–32). Missing entries retain the base palette’s shape. Light and dark
variants of a family should use identical values. Import/export and preferences
retain these overrides; colour vision modes leave them alone.
Use Tokens for decorative UI corners, including hover, selection, focus,
image thumbnails, popups and nested surfaces. Small legacy accents derive their
radius from the small token so the default appearance stays unchanged. Expanded
outlines use Tokens::outset_radius so square corners stay square. Container
continues to own outer surface chrome and square dock contacts. Functional
geometry (circles, model selection boxes, plot clipping, manipulator handles
and the artwork inside icons) retains its own shape.
Theme border hierarchy
Three structural levels distinguish outer chrome, major section rules and minor dividers. Separate solid container and control roles keep enclosing boxes independent of divider patterns.
| Role | UI audit / assignment |
|---|---|
| Primary | Ribbon/workspace cards, Browser/History, feature and palette sheets, system windows, menus/popups, navigation/status surfaces |
| Secondary | Ribbon tab/content rule, Browser/History heading rule, feature/window header and footer, Preferences page/navigation division, disclosure section rules, Assistant header/composer division, File navigation sections |
| Tertiary | Ribbon category dividers, menu groups, report/table rows, neutral History items, File cards/thumbnails, ordinary inherited egui separators |
| Container | Settings previews, information cards, list/detail wells, Assistant content cards, shortcut keycaps |
| Control | Neutral fields, buttons, checkboxes, segmented choices and search inputs |
Focus, selection, active commands, errors, recovery warnings and status keep their existing semantic strokes. Icon artwork, example mouse diagrams, plots, model edges, manipulator geometry and the compass are content; structural border roles do not recolour them.
solidcraft-theme/1 optionally accepts borders. Each role has a width (finite 0–4
logical UI points; zero hides the stroke) and a color (#rrggbb or #rrggbbaa).
primary_inset also requires gap (finite 0–12 points of clear distance inside the
primary border). This companion is painted inside panel/ribbon/window/menu chrome,
without extra input targets or layout space; tiny surfaces omit it. Compact contacts
remain square and collapse adjoining primary strokes. Other ornament, such as the
circular compass and native widget-owned tooltip content, does not acquire an inset.
"borders": {
"primary": {"width": 1, "color": "#ffffff"},
"secondary": {"width": 1, "color": "#e5efff", "pattern": "dotted"},
"tertiary": {"width": 1, "color": "#102748"},
"container": {"width": 1, "color": "#a8c8ed"},
"control": {"width": 1, "color": "#a8c8ed"}
}
Those are Blueprint’s settings: a single primary outline and dotted secondary rules. Secondary roles optionally accept pattern: solid (default) or dotted; this pattern follows horizontal/vertical section rules. Container outlines remain solid, as do the other roles. Themes without border settings retain their previous appearance.
SolidCraft 95 uses silver surfaces and directional two-band bevels: light top/left
edges and dark bottom/right edges for raised panels and buttons, reversed for
recessed fields and pressed buttons. Inputs use the field colour independently
of the neutral button face. Disabled controls fade their bevel; focus, selection
and validation keep their semantic indicators. Solid grey rules divide sections.
Optional borders.bevel enables this shared container/control treatment. It accepts
width (finite, greater than 0 and at most 2 points per band), highlight, light,
shadow and dark_shadow (hex colours). Missing bevel settings preserve flat
rendering exactly. The bands paint inside existing rounded or square geometry
without changing hit targets or allocation. Ghost controls remain borderless.
Missing roles preserve each surface’s existing width and colour, including overrides in
colors; exporting keeps omitted roles absent so reimport cannot flatten those legacy
surface differences. Dark/Light and the Gruvbox families share border geometry.
Imports and direct preference updates reject unknown roles/settings, invalid colours,
non-finite/out-of-range numbers and missing required settings. Invalid hand-edited saved
border settings safely fall back to legacy styling. The optional field requires no format
version change. There is no general border editor in Preferences; theme file import/export
and the existing preference/control workflow own this extension.
Theme icon and tab colours
Theme files optionally supply icon_palette SVG roles and semantic ramps. The
shared renderer snapshots that palette into the icon look and cache identity;
callers continue to request semantic icon IDs and surface-specific monochrome ink.
See the shipped-theme colour audit for the schema,
workspace assignments, accessibility rules and intentionally monochrome themes.
Material previews and look chains
sphere_thumbnail(ui, id, Sphere { color, kind, opacity }, size) paints a lit
sphere using a fixed CPU mesh and the egui painter, without a texture or GPU.
FinishKind covers Matte, Satin, Gloss, Metal and Glass. Colour is document data;
highlights, shadows, checker and UI chrome use Tokens. Glass shows a four-by-four
checker and defaults to 35% opacity; other finishes default to opaque. Explicit
opacity is clamped to 0–1; non-finite opacity uses the finish default. Sizes are
rounded and bounded to 1–96 logical points (non-finite sizes become 1).
A shared per-egui-context FIFO cache retains at most 64 meshes, keyed by RGBA, finish, byte opacity and bounded size. Hits reuse shading without changing eviction order. Every mesh has 637 vertices and 1,152 triangles, independently of input. Theme highlight/shadow changes clear the cache. No file assets are added.
look_chain(ui, id, labels, looks, active) draws Face → Body → Component → Material
as four connected steps. Pass translated labels, [Option<Sphere>; 4] and one
LookStep; empty slots use a dashed circle and plus, while filled slots reuse the
sphere cache. Exactly one step has a Tokens::render_accent ring. It returns
Option<LookStep> on activation; callers own commands and selection. Disabled
controls do not activate. Handles/rects are {id}:face, :body, :component,
:material; {id} is the full allocation. Long labels clip within their step.
These are content widgets; their enclosing surface still belongs to Container.
Gallery entries show all five sphere finishes, a mixed filled/empty look chain with Body active, and an empty chain with Face active in both themes.
For CPU-rendered review images, run
SOLIDCRAFT_MATERIAL_WIDGET_EVIDENCE=/tmp/material-widgets cargo test -p solidcraft-ui-egui ui_kit::gallery::material_widgets_board_in_both_themes.
Extensible libraries: materials, appearances and components
The first local scalar-library consumer implements only copied colour/opacity and density/look definitions. Its separate v1 catalog is not the complete resource-pack/provenance/update system proposed below. Texture/environment descriptors remain coordinated with the maintainer’s renderer work.
Proposal for #785, #786 and #787. This page describes planned contracts and acceptance gates, not an implemented pack format or new commands. It does not bring the deferred Simulation or Render workspaces into scope.
Reuse the existing paths
Physical materials already have built-in densities and design-local definitions
(material.create, material.edit, material.delete, material.list, material.assign).
They drive component.properties, survive reopening and participate in configurations.
Appearances have a built-in color/opacity library and body, face and component overrides.
parts.library, parts.insert and parts.resize already generate seven families of
fasteners, pins and bearing envelopes as editable components.
Extend those routes. A shared catalog supplies identity, discovery and provenance; each entry kind retains its own payload and application rules. A physical material is not a texture, and a reusable component is not executable plugin code.
Portable, independently extensible packs
Use one versioned, offline pack envelope for three kinds: physical material, appearance and component family. Both project contributors and users can author packs without rebuilding SolidCraft. Start with a directory and JSON manifest, not a new archive format or an online marketplace. The exact schema belongs in the bounded-loader PR, accompanied by machine-checked examples; the following are its design requirements:
| Record | Required meaning |
|---|---|
| Pack | Schema version, stable namespaced ID, immutable revision, display name, author, source and license metadata |
| Entry | ID scoped to its pack, immutable revision, kind, label, category/tags, typed payload and per-entry provenance |
| Resource | Relative path, content digest, byte count, media role, author, source, license and included license text where required |
| Dependency | Explicit entry/revision reference; bounded acyclic resolution within explicitly selected packs |
| Selection | Pack ID + entry ID + exact revision; labels and filesystem paths are not identities |
Pack revisions and schema versions are different: the former identifies content, the latter describes how to read it. Changing content under an existing revision is an error. Unknown schema versions, entry kinds and required capabilities produce a clear refusal; they are never silently interpreted as an older format. Optional extensions need an explicit namespace and preservation rules before being accepted.
Provide three independent scopes:
- Bundled: reviewed with SolidCraft and read-only in the library editor.
- Project: packs explicitly opened for the current project/team; never discovered by silently executing or trusting files beside a design.
- User: local packs the user creates, imports, edits or exports. Native file access belongs in the app; browser users import/export through the web storage/file workflow.
Discovery order is deterministic. Duplicate labels can coexist, with their source shown. Duplicate qualified identities/revisions with different contents are errors, not overrides chosen by scan order. Editing a bundled entry uses Save a copy into a user namespace. Updating a pack is explicit and leaves existing design instances unchanged.
Designs own their selected definitions
On assignment/insertion, copy the definition, chosen revision, parameters, provenance and required resource bytes into the design’s existing container/resource mechanism. Keep the source identity for inspection and an eventual explicit Update action. A saved design must open, rebuild and render the supported appearance without the original pack, network or another user’s home directory. Installing a pack never changes a design or undo history.
Assignment/insertion/update is a normal engine command, atomic and undoable. Preview is non-mutating. A failed import, unresolved dependency, name collision or unsupported recipe leaves the document and undo/redo stacks intact. Existing material names and appearance precedence retain their legacy meaning; introduce stable IDs through a documented migration rather than reinterpret old name-based assignments. Design-format changes need their own version/migration and reopen tests, separate from the pack schema version.
Physical properties (#785)
Keep physical material and appearance selection independent, with an optional default look. Convert imported density units explicitly to the existing g/cm³ representation; geometry continues to use millimetres and radians. Never infer units from magnitudes. Properties need source, edition/date, grade, processing condition and applicable temperature/moisture when relevant. Unknown properties stay unknown, not zero or guessed values.
Start with density and mass integration through the existing commands. Reserve typed extensions for elastic constants, yield/ultimate strength, thermal properties and orthotropic axes, but do not expose these as FEA-ready until their model, units and validation are implemented. A generic wood density is not an engineering material grade. User-measured data can be stored with its own provenance and edited without changing the bundled library.
Wood and sheet appearances (#786)
Begin with body-local mapping, an explicit physical repeat size and a user-editable grain frame. Suggest the longest local body-bound axis once, with X/Y/Z order for equal extents; store the chosen frame rather than recompute it on every resize. Manual rotation and scale survive parameter rebuilds, instance placement, undo and reopening. Shared component instances transform the same definition-local mapping with their placement. Non-finite or degenerate frames refuse; automatic choice remains a suggestion.
Visual grain does not rotate the body, its geometric bounding box or a manufacturing stock frame. Structural grain direction and grain-aware nesting/cut lists are distinct features, coordinated with #137. An anisotropic shading effect also does not imply mechanical anisotropy.
After basic wood mapping, add sheet-specific top/bottom/edge looks: MDF, chipboard, plywood and compact laminate, then melamine, laminate, linoleum and veneer finishes. Appearance layers initially describe presentation only. Physical finish thickness or mass must be explicit modelling/material inputs, never an invisible side effect of paint. Face/edge assignments require durable identities and must refuse a lost association rather than paint a nearby replacement face.
Keep texture role, color space, normal-map convention and physical scale explicit. Coordinate with the render capability contract: the existing color/opacity viewport is not proof of PBR or texture support. A renderer must report what it supports; unavailable maps use a documented preview fallback with a visible notice. Export either preserves supported mapping/resources or reports loss.
Standard and user component families (#787)
Keep the existing fastener insertion/resize path. Add original parameterized BAR, SHS, RHS and CHS recipes first, with explicit length units, parameter bounds, section frames and insertion/connection frames. Validate wall thickness against the outer size and state whether corners are sharp or rounded. Do not label approximate geometry as a certified standard section. UB/UC need actual flange/web radii and tapers; identifying a standard does not grant redistribution rights to its published size tables.
A family revision defines a bounded parameter schema, permitted recipe and optional reviewed size table. A size designation identifies a row, not an arbitrary filename. Changing size rebuilds the same component identity and preserves supported connections. Initial recipes must use a small allowlist of ordinary modelling operations. No embedded native code, shell commands, scripts, arbitrary command IDs or network hooks. Reject cycles, excessive expansion and unsupported operations before mutating the design.
Custom assemblies follow single components: bounded dependencies, named connection frames, explicit shared/independent instance semantics and one-step insertion undo. Store selected parameters and definition snapshots so missing or changed catalogs cannot silently substitute another part. Geometric envelope models, including existing bearing envelopes, must remain distinguishable from detailed/manufacturing-ready models.
Sources and licensing
SolidCraft is MIT OR Apache-2.0. Code and generated recipes must follow the clean-room
rules; never copy commercial CAD materials, templates, geometry, icons or catalogs.
Third-party assets retain their own licenses. Every bundled resource needs its author,
source, license and ATTRIBUTION.md row, even if attribution is optional under CC0.
Record hashes and retrieval dates when admitting actual files. A manifest’s claimed
license is provenance to review, not automatic evidence of permission.
Sources checked on 2026-10-13; no external assets or datasets are bundled by this proposal:
| Source | Suitable use / admission condition |
|---|---|
| ambientCG license | Downloadable assets and material preview renders are CC0. Candidate wood/sheet texture source; choose and attribute individual files. |
| Poly Haven license | Assets are CC0. Site text, logos and other site content have separate rights; API access has separate terms. Do not copy provider implementation code. |
| Wikidata structured-data license | Structured entity/property data are CC0; other page text has different terms. Candidate for individual referenced quantities and identifiers, not unverified engineering-grade defaults. Preserve the underlying measurement references, qualifiers and units. |
| USDA Wood Handbook, revised 2021 | Reference for moisture, physical and mechanical wood properties. Verify the specific table’s rights, species, units and conditions before importing data; no wholesale dataset approval is implied. |
| NIST data/publication rights and repository terms | Candidate property references only. Uploaded data use provider-selected terms; public availability is not a blanket redistribution license. Review each dataset. |
| Khronos PBR overview | Public rendering conventions for texture roles and shading properties; a design reference, not a source of CAD material data. |
| Original SolidCraft recipes / user measurements | Preferred initial component recipes and illustrative procedural previews; preserve authorship and label illustrative versus measured values. |
Prefer CC0 resources for the starter texture pack and original recipes for the starter component pack. Defer uncertain or restrictive data rather than scrape supplier sites, copy standards publications or assume a downloadable CAD file can be redistributed.
Phases and acceptance gates
Each implementation PR references these issues without closing them prematurely.
| Phase | Deliverable | Required evidence before calling it complete |
|---|---|---|
| 0 | This proposal, source review and roadmap | Maintainer-reviewable contract; distinguish existing functions from planned ones; no runtime or format changes. |
| 1 | Shared bounded manifest parser, resolver and pack validation/import/export | Project and user manifests use the same schema; deterministic conflicts, invalid versions/IDs, missing resources/dependencies, cycles, path traversal and budget exhaustion fail atomically; native + wasm pass. |
| 2 | Material and appearance catalog integration and browser UI | Create/copy/edit/import/export from the UI; existing commands used; analytic box mass before/after assignment; configurations, undo and offline cold reopening; actual screenshots. |
| 3 | Wood mapping and a small reviewed starter pack | Rotate/scale grain on a public-safe block and placed instance; no flip on resize; reopen without pack; rendering capability/fallback tests and screenshots; attribution for each shipped asset. |
| 4 | Parameterized section families and reviewed standard size data | BAR: area = width × height; sharp RHS/SHS: outer area minus inner area; CHS: π(outer_diameter² − inner_diameter²)/4. Check volume = area × length, hollow membership, insertion frame, resize, connections, undo and offline reopening; screenshots. |
| 5 | Custom assemblies, sheet face/edge layers and expanded catalogs | Bounded nested insertion, shared/independent semantics, durable face associations, explicit updates and loss reporting; measured tests and screenshots for each workflow. |
After phase 1, material/appearance integration and section-family work can advance independently. Wood mapping depends on actual renderer support, not on section families. Phase 5 is several separate PRs, each following its relevant earlier workflow, rather than one change coupling assembly insertion to face painting.
Keep parsing/resource I/O in io, durable selected definitions in doc, application
and validation commands in engine, renderer-specific interpretation in render/its
adapters, and discovery/editing UI in ui-egui. Lower layers do not depend on the UI
or platform file dialogs. Reuse container resource deduplication; avoid a second blob store.
Before allocating/decoding, enforce manifest byte/entry/string/dependency limits, total resource bytes, image dimensions/decoded bytes and recipe expansion bounds. Paths must be relative, normalized and confined to the selected pack, including symlink handling. Phase 1 must publish exact limits and test their boundaries. Do not auto-fetch missing files or install executable extensions. Explicit import/export is enough for the first delivery.
Run cargo xtask ci for each implementation arc and check remote PR results. Geometry
tests use analytic values; UI and observable CAD changes include actual app evidence.
The three issues remain open until their individual workflows pass these gates.
Local material libraries
The local library transports design-owned appearances and physical-material definitions. Portable catalog v2 preserves opaque PBR parameters and validated engineering cards, including SI values, material-local axes, source records and per-property conditions. It reuses the existing commands, file pickers and library contract. The catalog has its own version; no additional design-format change is needed.
Desktop workflow
- Apply a named colour/opacity look using Appearance, or define a physical material using Physical Material → New / Edit or the material commands. An appearance does not change physical density.
- In Appearance → Local library, choose Export design library…. The snapshot contains explicit body/face/component appearance overrides and design-local physical materials, including their default looks. It does not export imported kernel paint, unassigned built-in catalog entries or an unapplied colour-picker draft.
- Open that library in another design, search its appearance presets, select one and choose Apply library appearance. The normal body/face/component targets are reused. OK after this immediate application does not add a second undo step.
- To import a physical definition in the same Local library section, enable Physical material definitions, select an entry and review its density/card. Name on import makes a collision explicit: choose another name instead of overwriting existing data. Import material into design copies the definition only. Cancel or OK after copying keeps the import and does not assign either a material or an appearance to any body. Assign the copied material later in Physical Material. Each import is one undo step.
- Save the design. Its selected definitions are ordinary copied design data; the source library can be moved or removed without changing the saved result.
Export does not edit the design, its dirty state or history. An existing file is refused unless Replace existing library file is explicitly selected. Export is a snapshot of the design definitions, not an automatic merge with entries in an existing catalog. Editing a copied look/material uses the normal commands and does not edit its source.
Copied look labels are inert data even when equal to a starter.* recipe ID.
Reopening Appearance or applying the copied look to another target preserves its
color, opacity and PBR response. Library body and face targets use the same world
selections as ordinary assignment, including displaced component instances;
the outer command maps them into the source component exactly once.
The combined render integration uses main’s merged #1127 asynchronous picker.
Model edits and tab changes wait for its answer while drawing/resizing continues.
Overwrite consent is captured before opening; export snapshots the design when
library.export runs after choosing the destination. Document identity is checked,
and an old library answer cannot replace another Appearance editor. Cancellation
does not read or write a library. The earlier #1114 candidate is no longer a runtime
dependency of the refreshed consumer.
The desktop panel applies appearances and imports physical definitions. library.apply
also supports explicit body assignment in the same atomic command; imported definitions
appear in the existing Physical Material list and can be edited there.
The browser can export a library through the existing download service. Interactive local
library opening is a desktop feature in this first slice. Browser engine commands can
consume a bounded catalog already supplied through VFS; wasm compilation does not prove
an interactive browser importer or download workflow.
Commands
All programmatic calls use JSON and never open a file picker:
{"command":"library.export","params":{"path":"my-library.solidcraft-library.json"}}
{"command":"library.inspect","params":{"path":"my-library.solidcraft-library.json"}}
Inspection/export returns a catalog with qualified entry IDs and exact revisions. Use
the returned values when applying; do not replace an unknown revision with a guessed one:
{"command":"library.apply","params":{
"path":"my-library.solidcraft-library.json",
"entry":"user.local/appearance-<returned-id>",
"revision":"<returned-64-character-revision>",
"targets":{"bodies":["Body1"]}
}}
Appearance targets can be bodies, components or faces: [{body, point: [x,y,z]}].
Points are world-space millimetres. At most 1,024 total targets are accepted. Missing
objects and invalid targets fail; the importer does not invent a nearby replacement.
A material entry can be copied without targets or assigned to body targets. The optional
name explicitly selects a new physical-material name on import. An identical existing
definition is reused; a conflicting definition or built-in name is refused. No density,
look or configuration cell is silently overwritten. Creation and assignment share one
outer undo transaction through the existing material commands. Mass remains the existing
volume × density calculation; a generic illustrative density is not an engineering grade.
library.export also accepts id (default user.local), name (default My library),
provenance, overwrite (default false) and format (default solidcraft-library/2).
Explicit solidcraft-library/1 refuses PBR or engineering-card loss before publishing,
including with overwrite enabled. Only explicit assignments/design-local
definitions are inspected, without evaluating geometry. Duplicate exact definitions are
deduplicated; different values with the same display name remain distinct entries.
Portable catalog v2
solidcraft-library/2 extends the same envelope and definition kinds with optional
pbr: {metallic, roughness, dielectric_ior} on appearance values and optional
physical_card on material values. PBR uses the bounded opaque contract and requires
opacity exactly one. Cards use the existing SI admission and validation rules.
Sources and conditions are copied as supplied data, not certification. Card density
never replaces assigned mass density. An independent default PBR look and a physical
card may coexist in a material.
Applying an appearance is an exact replacement: v1 scalar values and v2 values without PBR clear the target’s prior PBR. V2 values with PBR retain their parameters. Display labels are inert, including labels equal to built-in preset IDs: importing an owned value never selects a different recipe. Undo/redo and native save/reopen preserve the copy after removing the source file.
Absent richer fields are omitted from serialization. Existing v1 scalar bytes, content revisions and identities remain exact; pinned appearance bytes and a material revision cover the writer contract. Changing only the envelope version leaves entry revisions unchanged. PBR and complete card data participate in revisions when present. V1 wire input rejects even null v2-only fields. Older readers refuse v2.
Scalar catalog v1 compatibility
The independent JSON format marker is solidcraft-library/1. It has id, name and an
entries array. Each entry has id, revision, provenance and a typed definition:
appearance:value: {name, color: [r,g,b], opacity}; sRGB bytes and finite opacity 0–1.material:value: {name, density_g_cm3, appearance?}; finite density above 0 and at most 1,000 g/cm³, with an optional appearance value of the same scalar shape.
Every object rejects unknown fields. Unsupported kinds (including texture/environment
payloads) and versions fail explicitly, rather than dropping fields or pretending that a
colour preview is a textured material. Catalog IDs are 1–64 ASCII bytes, beginning with a
lowercase letter and continuing with lowercase letters, digits, ., _ or -. An entry
ID is <catalog-id>/<local-id> with the same local-ID rules; paths and labels are not IDs.
An entry revision is lowercase SHA-256 of compact v1 serde JSON [provenance, definition].
Derived struct field order, omission of absent optional fields, UTF-8 escaping and f64
round-trip number formatting are part of this writer contract; this is not arbitrary JSON
canonicalization. Prefer library.export or the Rust Entry::new/content_revision API
when authoring revisions. A future incompatible writer changes the catalog version. The
default export IDs contain a kind and 16 hex revision characters; collisions are rejected,
never used to overwrite a different definition. Explicitly authored stable local IDs can
retain their identity across revisions. Application requires the selected exact revision.
Changing another entry does not change the selected definition’s identity.
Provenance contains a required license, optional author and optional source_url.
It is supplied metadata, not verified permission or a publisher signature. URLs are never
fetched. The default LicenseRef-User-Provided does not claim that user data is openly
licensed. No external art/data is bundled in this slice. Catalog provenance/revisions stay
in the catalog: values are copied into existing Look/UserMaterial records without a
persistent catalog-origin/update receipt. Card source records and property conditions
belong to the card and are copied exactly. Resource embedding and explicit update
links remain separate work.
Bounds and failure behavior
The reader accepts at most 4 MiB encoded JSON and 1,024 entries. A bounded raw preflight preserves strict v1 wire fields before typed decoding. Entry decoding stops at the count cap with fallible vector growth. Names are trimmed, nonempty and at most 128 UTF-8 bytes; provenance license/author/source URL limits are 128/256/2,048 bytes. Control characters, invalid identifiers, duplicate identities, bad revisions and nonfinite scalar values fail. Export inspects at most 100,000 source references, deduplicating into at most 1,024 entries. Export bounds retained unique definitions before whole-catalog encoding, reserving 4 KiB for the envelope and counting each entry’s pretty bytes plus nested indentation. This conservative admission may refuse a near-limit compact catalog. Physical imports also obey the design’s 1,000-material cap. These are encoded/work limits, not a process-memory or latency guarantee.
Native reads are bounded regular-file reads; browser reads check VFS size before cloning. Native no-clobber writes reuse atomic new-file publication; explicit replacement is atomic without reading/backing up an arbitrarily large old target. All library command paths obey the active MCP/control file scope before reading or publishing, including overwrite. Browser VFS publication is synchronous and reuses the host’s download service. No automatic discovery, asset fetch, relative-resource resolution, executable recipe or plugin is introduced.
Failed application preserves the document and undo/redo history. The source file is read and checked before copying; a stale selected revision needs an explicit reload/reselection. The copied design remains independent after successful assignment, undo/redo and cold reopening. This library has no dependency on the pending render stack and does not change the document format. User-visible evidence and actual native/wasm results are recorded in the PR; geometry/robustness checks remain separate and no reference baseline is weakened.
Scope
The catalog carries the document’s scalar/opaque Look and engineering-card values.
It does not transport GPU2 runtime overrides, refractive glass, clearcoat, texture
resources, mappings, image files or environment maps. The six starter glass recipes
remain recipes; v2 does not turn an IOR or alpha preview into refraction. Unknown
resource kinds and fields fail explicitly. A runtime GPU-only look must not be
presented as captured by this design-definition export.
The command engine
Every user-visible action is a command: a CommandSpec registered in
crates/engine/src/cmd/, with a dotted, lower-case id (solid.extrude, sketch.create,
timeline.rollback). The toolbar, dialogs, marking menu, S box, scripts, the CLI, the control
channel and MCP all run commands through Session::execute(id, params). Commands take JSON
parameters and never open dialogs; a dialog is a front-end form that builds the parameters.
#![allow(unused)]
fn main() {
CommandSpec::new("solid.fillet", "Fillet", fillet)
.at("SOLID", "MODIFY") // toolbar tab and panel ("" = not on the toolbar)
.icon("fillet") // drawn in code, assets/icons/ui/source/icons.rs
.key("F") // default shortcut
.params("edges: [[x,y,z] | {body, index}]; radius: expr; …"),
}
run is fn(&mut Session, &Value) -> Result<Value>, and enabled is
fn(&Session) -> Result<(), String>; the error string is the disabled-button tooltip. The
params string is the one-line documentation shown by solidcraft-cli commands, MCP’s
list_commands and the command reference.
A command that changes the design is undoable by default: the session snapshots the document
before it runs, and a failing command leaves the document unchanged. Feature commands add a
feature to the timeline and let the document evaluate it; they validate their inputs first
(units, ranges, references) and return BadParams instead of building a broken feature.
Only the registry’s own dotted ids name commands (matched ignoring case); any other id fails
with unknown command, which names up to three close ids: a typo’s (solid.extrud), or one
ending in the same word (file.open: did you mean doc.open?).
Adding a command
- Add a
CommandSpecto the right file incrates/engine/src/cmd/and implementrun. - Give it input and output schemas: a parameters type (its contract) and a reply type in the
module for its command file under
crates/engine/src/schema/(schema/features.rsforcmd/features.rs, and so on; create it for the file’s first contract), built from the shared field types inschema/types.rs. Register them withchecked!(…),.contract(…)and.output(…), assolid.extrudedoes, and add the command to that module’sschemas_and_the_key_check_agreetest, with error-message cases and a reply test. The parameters type documents each parameter (keep the.params(…)prose too, for now), anddescribe_commandserves both schemas to agents and scripts. A test (every_command_has_input_and_output_schemas) warns for a command without them, and will fail it once the rule becomes required; the commands that predate the rule are listed inschema/unconverted.txt, which only shrinks. Command schemas walks through it, with the shared types and a checklist. - Write engine tests that measure the result: volume, area, and face/edge/vertex counts against values you can derive by hand.
- The hostile-input test (
hostile_params_never_panicincrates/engine/src/tests.rs) runs your command automatically with every documented parameter set to junk. It must return errors, never panic. - For UI work, add a scenario (see Test harnesses).
Command schemas
Every command has an input schema and an output schema (JSON Schema 2020-12), generated by
schemars from two Rust types:
- a contract: the parameters type, checked in front of the handler (see
crates/engine/src/schema/mod.rs); with the session’s check level at warn or strict, a misspelt key or a wrong-typed value is reported or refused instead of silently ignored; - a reply type: what a successful call returns.
engine.describe and the MCP tool describe_command serve both, so agents and scripts read a
command’s exact parameters instead of guessing from prose.
When the check fails
The tests in crates/engine/src/schema/coverage_tests.rs say what to do. A command without
schemas is only a warning for now (the test prints the list); it will become required once the
PR backlog is down, so give new commands both schemas now. Set SOLIDCRAFT_SCHEMA_RULE_STRICT=1
to make the missing-schema check fail today.
- “
x.yhas no input or output schema” (or “no input schema”, “no output schema”): a new command. Give it what it lacks, as below. Don’t add it toschema/unconverted.txt: that list holds only the commands that predate the rule. - “
x.yhas its schemas now: remove its line from unconverted.txt”: you converted an old command. Delete its line. - “
x.yis a planned! placeholder, which needs no line in unconverted.txt”: delete its line. - “unconverted.txt names commands that don’t exist”: a command was removed or renamed. Delete its line.
- “unconverted.txt must be sorted, each id once”: keep the list in byte order
(
LC_ALL=C sort). - “
x.yran with an unknown key in strict” or “x.yshould refuse with …”: it has a contract, but itsrunisn’tcrate::checked!("x.y", <its handler>)(step 5): the wrapper is missing or names another command’s id.
Writing a contract
1. List the keys the handler reads
The contract’s keys are exactly the keys the handler reads, no more and no fewer. Read the
handler and everything it passes p to:
- direct reads:
p.get("k"),params::expr(p, "k"),req_expr,str_,num,bool_,vec3,string_listand the like; features::add_feature(every feature command) readsname,body_name,body_namesandparticipants: take them withFeatureCommon. Whatparticipantschanges depends on the feature, and for some it changes nothing in the result: check where the feature is evaluated (crates/doc/src/eval.rs).participantstakes its description fromFeatureCommon, shared by every feature, so say what it does for this one elsewhere: where it and another key override each other (a primitive’stargetsgives way to it; an extrude’stargetsoverrides it), in that key’s description; where it has no effect, in the summary or in the description of the key it might seem to change (bounding solid’sbodies). A feature that makes no body (scale) declaresnameandparticipantsitself instead, asbody_nameandbody_nameswould do nothing. It keepsparticipantsbecausetimeline.redefinepasses a feature’s own back, though for a scale it changes nothing either; its own description says so. For the primitives (box, cylinder, sphere, torus, cone)add_featurealso readstargets; a new primitive must join theprimitivematch inadd_feature_in, and its contract declarestargets. The commands that combine bodies readoperation(and usuallytargets) in their own handlers;- keys read around the command:
frames::to_localreadsbody(andface_body) for commands that take picks; live, for a command with.live(…)(todaysketch.dragandsketch.edit_text): a call withlive: truegoes to the live runner, whichchecked!doesn’t wrap, so live calls aren’t checked yet. Leave those two on the list until the engine checks the live path too. A new command that needs a live runner still gets its contract (declareliveas an optionalbool); say in its pull request that its live calls aren’t checked.
A key the handler reads only in some situations (thin-wall keys with thin: true) is still a
field; its description says when it applies. Contracts don’t check combinations of keys
(sketch with face); the handler does.
2. Put it in the module for its command file
Contracts for cmd/features.rs live in schema/features.rs, for cmd/inspect.rs in
schema/inspect.rs, and so on, each with its tests in a file next to it
(schema/features_tests.rs). For a command file’s first contract, create the module and add its
pub mod line, sorted, under “The contracts, by command file” in schema/mod.rs. A new module
starts like this (keep the imports it uses):
#![allow(unused)]
fn main() {
//! Contracts and result types for the commands in `cmd/wedge.rs`.
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::types::{FeatureCommon, LengthExpr, OperationParam, PointExpr3, StringList, feature_common, present};
// …the parameters and reply types…
#[cfg(test)]
#[path = "wedge_tests.rs"]
mod tests;
}
3. Write the parameters type
#![allow(unused)]
fn main() {
/// Add a wedge primitive.
#[derive(Debug, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct WedgeParams {
/// Size along X.
pub length: LengthExpr,
/// Size along Y.
pub width: LengthExpr,
/// Height at the thick end, along Z.
pub height: LengthExpr,
/// The wedge's minimum corner. Default: the origin.
#[serde(default, deserialize_with = "present", skip_serializing_if = "Option::is_none")]
#[schemars(with = "PointExpr3")]
pub corner: Option<PointExpr3>,
/// How the wedge combines with existing bodies. Default: new.
#[serde(default)]
pub operation: Option<OperationParam>,
/// The bodies a join, cut or intersect works on. Default: every body in the feature's
/// component, not its sub-components (an intersect removes those the wedge doesn't meet).
/// Ignored when `participants` lists bodies.
#[serde(default)]
pub targets: Option<StringList>,
#[serde(flatten, deserialize_with = "feature_common")]
pub common: FeatureCommon,
}
}
- Descriptions are the documentation. The struct’s doc comment is the command’s summary and
each field’s doc comment is its description in the schema, so put nothing else on those
lines. Say the unit or form, the default (
Default: …) and when the key is ignored. Write for someone who has only the schema. Check what each value does where the feature is evaluated (crates/doc/src/eval.rsand the kernel), not only how the handler reads it: a limit the evaluation enforces (a torus’sminormust be less than itsmajor) or a key it ignores belongs in the description. A scale’sfactor, for one, has no effect whenfactorsis given, though it must still evaluate. - Required or optional follows the handler: a key it can’t do without is a plain field;
any other is
#[serde(default)] Option<T>. null: the common readers (str_,num,bool_,expr,string_list) readnullas absent, which a plain#[serde(default)] Option<T>matches. Readers that parsep.get(k)themselves (features::feature_sketch,features::profiles, the pick lists) refusenull; for those keys use#[serde(default, deserialize_with = "present", skip_serializing_if = "Option::is_none")]with#[schemars(with = "T")]. The key may then be absent but notnull, the schema has nonullbranch, andskip_serializing_ifstops schemars publishingdefault: null, which the schema itself would refuse.- Use a shared type for every value it fits (below) rather than
Stringorf64: shared types accept exactly what the handlers’ readers accept, give the handlers’ messages, and publish the matching schema.
4. Write the reply type
A feature command whose handler returns add_feature’s reply unchanged returns
FeatureResult (in schema/features.rs): use it, don’t copy it. A command that returns
anything else, including a feature command that reshapes the reply (solid.thread returns
{feature, name, thread}), gets its own reply type:
#![allow(unused)]
fn main() {
/// What `wedge.measure` returns.
#[derive(Debug, Serialize, JsonSchema)]
pub struct WedgeMeasureResult {
/// The wedge's volume, in mm³.
pub volume: f64,
/// In the warn parameter-check mode: what the call's parameters got wrong.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub param_warnings: Vec<String>,
}
}
- Build the handler’s reply from the type (
serde_json::to_value(reply)), asadd_feature_inbuildsFeatureResult, rather than ajson!literal next to it. Then the schema can’t drift from what the command returns. - Warn mode adds
param_warningsto a successful reply that is a JSON object, so an object reply type keeps that field. A reply that isn’t an object (a list) is returned unchanged and has no such field.
5. Register them
In the command file, next to its COMMANDS:
#![allow(unused)]
fn main() {
use crate::schema::features::FeatureResult;
use crate::schema::wedge::WedgeParams;
use crate::schema::{TypedContract, output_schema_for};
const WEDGE_CONTRACT: TypedContract<WedgeParams> = TypedContract::new();
// in COMMANDS:
CommandSpec::new("solid.wedge", "Wedge", crate::checked!("solid.wedge", prim_wedge))
.at("SOLID", "CREATE")
.icon("wedge")
.contract(&WEDGE_CONTRACT)
.output(output_schema_for::<FeatureResult>)
.params("length, width, height: expr; corner?: [x,y,z] …"),
}
.contract and .output publish the schemas, and checked! checks each call against the
contract that its id publishes, in front of the handler, so what is checked is always what is
published. The handler itself doesn’t change: it still reads the raw parameters. Keep the
.params(…) prose: the command list, the palette and describe_command’s params still show
it, until it is generated from the schemas.
6. Test it
In the module’s tests file:
schemas_and_the_key_check_agree: addassert_keys(input_schema_for::<WedgeParams>(), &[…])with the keys, sorted,FeatureCommon’s included. The keys then change only on purpose.strict_keeps_todays_errors: for calls the handler already refuses (a malformed value, an unknown spelling), add cases toassert_strict_keeps_todays_errors: strict must give the same message as the handler. Leave out the cases its documentation lists as different on purpose, such as a missing required key (strict says`length` is required, the handler something else).- A reply test: run the command and check the reply matches its output schema with
conforms(replies_match_the_output_schemainschema/features_tests.rsshows how; its misspelt-key call checks that a reply carrying warnings still matches). Run every form the command takes (each alternative key, such ascornerorcenter, eachoperation, one name and a list of names) in Warn, and check those replies have noparam_warnings: Warn runs a call the contract refuses anyway, so a matching reply doesn’t show the contract accepts the call. A reply that isn’t an object never carriesparam_warnings; run those calls in Strict, where a refused call is an error. In the same file,converted_primitives_and_body_commands_reply_with_feature_resultshows the Warn and no-param_warningschecks across several commands.
schema_defaults_are_valid checks the default of each top-level parameter on its own; a
nested type’s defaults are yours to check.
7. Converting a command that already exists
Do steps 1 to 6, remove its line from schema/unconverted.txt, then check the contract against
every caller the tests have: run the whole suite with the audit recorder and summarise it.
mkdir -p audit
SOLIDCRAFT_PARAM_AUDIT=$PWD/audit cargo test --workspace --no-fail-fast --features solidcraft-engine/param-audit
python3 scripts/param_audit.py audit
The recorder doesn’t create the directory, and it writes nothing if it can’t, so check the summary’s first line says at least one file. Each line under “SUCCEEDS TODAY, STRICT WOULD REFUSE” is a call that works today and that strict would refuse: a form the contract is missing (fix the contract) or a caller’s mistake, such as a misspelt key (fix the caller). Put the summary in the pull request.
Shared types
In crates/engine/src/schema/types.rs. Each accepts exactly what its reader accepts, most by
calling that reader.
| Type | For | Matches |
|---|---|---|
LengthExpr, AngleExpr | a number or an expression ("w / 2", "30 deg") | params::expr |
NumberExpr | a number or an expression with no unit (a ratio, a factor) | params::expr |
Point3, Point2 | [x, y, z] / [x, y] or {x, y, z} of numbers, in mm | params::vec3, params::vec2 |
PointExpr3 | a placement point whose coordinates may be expressions | features::point_param |
IdOrName | a feature by id or name (extrude’s sketch) | features::feature_sketch |
SketchRef | a sketch by id or name, with features::sketch_id’s message | features::sketch_id |
StringList | one name or a list of names | params::string_list |
OperationParam | new, join, cut, intersect and their spellings | Operation::parse |
FaceRef | a face by a point on it or its name | face::face_ref |
FaceRefs | the faces list of shell, draft, offset face, split face and face.delete (not solid.replace_face, whose messages differ) | features::face_points |
EdgeRef, EdgeRefs | an edge by a point on it or {body, index}; fillet’s and chamfer’s edges | features::edge_points |
ProfilesParam | which sketch profiles | features::profiles |
ScaleFactors | scale’s factors: three NumberExprs (strict words a bad item differently; see its doc) | features::scale |
AxisDir | X, Y or Z, or a non-zero [x, y, z] / {x, y, z} (cylinder’s, cone’s and coil’s axis) | features::axis_dir_form |
FeatureCommon | name, body_name, body_names, participants, flattened | features::add_feature |
When no shared type fits
Add one to types.rs, next to the reader it mirrors:
- Its
Deserializecalls the handler’s own reader, so “the contract accepts it” means “the handler accepts it”. Don’t re-implement the parsing. Many readers are private to their command file or need the design (features::point_paramtakes theSession). Split out the part that only looks at the JSON as apub(crate)function, have the handler call it, and re-export it fromcmd/mod.rs, asface_ref_formandedge_ref_formare; whether the value finds something in the design stays in the handler. - On failure it gives the handler’s message. Wrap a message that is already a whole
sentence, such as the handler’s
unknown operation `x` (…), inwhole(…), and the check passes it on as it is. Otherwise start it with “must”: the check names the key, so “must be a point [x, y, z] of numbers” becomes"`corner` must be a point [x, y, z] of numbers". (Any other message comes out as"`corner`: …".) - Its
JsonSchemadescribes the same values, with a description. Where JSON Schema can’t say something the reader checks (ignoring case, a byte length), say it in the description. A pattern that allows surrounding whitespace must allow exactly what Rust’strimremoves: build it fromWHITESPACE, asnot_blank_patternandAxisDirdo, rather than writing a regex’s whitespace class by hand (it differs, and the escapes are easy to get wrong).conformsdoesn’t read patterns, so pin the pattern’s text in a test. - Test both: values the reader accepts and refuses are accepted and refused by the type, and
they match the schema (the
conformshelper inschema/tests.rs; seeface_and_edge_refs_match_the_pick_readers). - A handler that reads a key more leniently than any type can (a wrong-typed value read as if the key were absent) gets the stricter type: that difference is what warn and strict report.
Checklist
- The contract’s keys are exactly the handler’s reads, including
add_feature’s (lessbody_nameandbody_namesfor a feature that makes no body), the frame mapping’s andlive. - The struct and every field have descriptions, with defaults and when a key is ignored.
- Shared types wherever they fit; a new type calls the handler’s reader and gives its message.
FeatureResultfor a command that returnsadd_feature’s reply unchanged, else a reply type the handler builds its reply from.checked!,.contractand.outputon theCommandSpec, and the.paramsprose kept.assert_keys, error-message cases, a reply test.- For an existing command: its
unconverted.txtline removed and the audit summary in the PR.
The kernel boundary and truck
crates/kernel is SolidCraft’s B-rep kernel. It wraps
truck (Apache-2.0) for topology, NURBS geometry, booleans,
tessellation and STEP parsing, and it is the only crate allowed to use truck. Callers get
SolidCraft types (Body, face and edge indices, meshes, measures) and a
KernelError instead of truck types and panics.
guard
truck can panic on geometry it doesn’t handle. Every call into it goes through
solidcraft_kernel::guard, which catches the panic and turns it into
KernelError::Internal, leaving the model unchanged:
#![allow(unused)]
fn main() {
pub fn guard<T>(what: &str, f: impl FnOnce() -> Result<T>) -> Result<T>
}
A failing feature shows an error on the timeline; it never takes the app down.
What is our own
truck has no fillets, chamfers, shells or face edits, so these are SolidCraft code in the kernel: constant, chord and variable-radius fillets with corner blends, chamfers, shell and offset, Delete Face, Split Face, Replace Face, sewing, STEP and IGES reading and writing beyond truck’s parser, and face provenance (which input face each output face came from, for persistent naming).
Vendored truck crates
Patched copies of truck-shapeops, truck-stepio and truck-meshalgo live in vendor/,
wired in with [patch.crates-io]. The patches fix robustness bugs found by our tests and the
STEP corpus. vendor/ is excluded from our fmt and clippy rules, and each patch should go
upstream when it can.
Equal cylinders crossing each other (tangent_cylinders.rs) touch at two points, which truck’s
mesh-based intersection cannot trace through. When a boolean of such a pair fails, the kernel
splits both cylinder faces along the line through the touching points and takes the boolean of
the split bodies; the shapeops patch that picks the corner of a face at a vertex several curves
end on makes the arms of the crossing connect.
Some truck assertions only run in debug builds and can fail on valid geometry, so tests and CI
run with --release.
Persistent naming
A fillet stores the edges it rounds, a sketch the face it sits on, a shell the faces it opens. If those were face and edge indices, an upstream edit that adds a face would move the fillet to another edge. SolidCraft stores names, and finds them again every time the timeline evaluates.
Why names follow the kernel’s history
Names used to be worked out from geometry: sample points of each new face were looked up on the faces of the feature’s input, and the majority won; pieces of a split face were numbered by where their centres lay. That guess is right most of the time, and wrong exactly when it matters: after an upstream edit that splits, merges, mirrors or shrinks a face, points can land on a neighbour, centres can swap order, and a fillet or a sketch quietly re-attaches to a different face. Nothing tells the user, which is the worst thing a CAD tool can do.
The kernel now reports, for every operation, what each face and edge of its result came from
(crates/kernel/src/history.rs: a face’s origins and how it changed, an edge’s origin edge or
the faces whose meeting made it). Names follow that history, so they are exact and deterministic,
and when a reference can no longer be found exactly the feature says so instead of guessing.
How a body’s faces are named
crates/doc/src/lineage.rs composes the histories of the kernel operations a feature runs (a
tool built from a sketch, cut from each target, the cut body split into lumps) into one
lineage over operands that can be named:
| Operand | Its faces are named |
|---|---|
| a body of the model before the feature | as they were |
| a moved copy of one (pattern, mirror) | <name>@F<feature>.<instance> |
| the cavity side of a shell grown outward | F<feature>:inner:<name> |
| a body the feature built | by role: F<feature>:side:<sketch curve>, start/top/bottom/end caps, +x… box sides, face:<k> imported faces, link:<digest> faces of a linked design (see below) |
| a copied tool of a patterned feature | F<feature>:<role>@F<pattern>.<instance> |
crates/doc/src/history_names.rs names each face of the result from its origins: a kept or
trimmed face keeps its origin’s name, a fillet or chamfer face is F<feature>:blend:<edge> of the
edge it replaces, a shell’s inner wall F<feature>:inner:<face>. A side’s sketch curve is the
curve the profile segment lies on exactly; nothing is sampled.
- Pieces. Faces that end up with one name are pieces of one face, and are told apart by the
faces around them that the others lack:
F2:top#[F2:side:l4,F4:side:l4]is the piece of the block’s top bounded by its left wall and the slot’s left wall. That survives the slot moving, growing or shrinking. Pieces nothing around them tells apart are numbered (#1,#2): by what they came from when that differs (the two halves of a circle’s wall), else in kernel order and then unverified. - Merged faces. A face made of several (a join flush with the body) takes the name of the face they are all pieces of, else the target’s; it answers to the others’ names too (aliases).
- Edges are named by the faces they separate,
<face A>|<face B>,~kwhen several edges run between the same two faces (numbered by where they lie; a reference by such a number is reported when only geometry confirms it). - Lumps. When a cut leaves a body in pieces, the whole is named first, so pieces of one face in
different bodies are told apart, and the bodies (
Bar,Bar (1)) are assigned in the order of their least face name, not the kernel’s shell order (which an upstream edit can swap; see Older designs).
Names are worked out lazily, on first request, and shared by a body’s clones.
Linked designs
A linked design (Insert Design) comes in as STEP text, which keeps no names: its faces would be
named by their place in the file (face:<k>), and adding a hole to the part can give that place to
another face. So when the link is made or updated, each face is matched to the face of the part it
was written from (the same geometry, so a point well inside one lies on the other) and the Import
feature keeps that face’s name in the part (Feature::link_faces). The face is named
F<import>:link:<digest>, a digest of that name (which may hold anything: the part’s feature
numbers and @, | mean something else in the assembly), and a piece keeps its qualifier with the
faces around it named the same way. A joint or other reference on a linked face therefore stays on
it across Update Links exactly as it would inside the part, and says so when the part no longer has
the face. Every linked face still answers to face:<k>, so references made before keep finding
their face by place; Update Links warns about joints that do.
Where there is no history
A body made by an operation that does not report history yet (some kernel paths return none; see
the kernel’s handoff lists, and the doc features that do not compose it yet: sheet metal, plastic
and surface features, emboss, rib, replace face, the faceted thread cut), or a face whose role
is not known exactly (a loft’s sides, a coil’s or pipe’s generated profile, a hole’s tool), is
named the old way, from geometry, and its names are marked unverified (model.faces reports
exact: false). A reference that resolves through an unverified name while its stored point no
longer lies on the face says so: face ... was found by geometry, not exact history; check it.
How a reference finds its face again
crates/doc/src/name_lookup.rs, in order:
- the face with that name;
- the one face answering to it: merged into it (the feature says it was merged), or the named piece, which the faces listed in its name still bound;
- the pieces of it (it was split, or it is on several bodies): the one nearest the reference’s point, and the feature says it was split;
- nothing: the feature falls back to the geometry it recorded and says the face no longer exists.
An edge name is found as the edge between the two faces it names, each found as above: when exactly one edge runs between them it is that edge, even after a split of one of its faces.
A sketch’s link to a projected edge (sketch.project {edge: [x, y, z]}, and Intersect and
Include 3D Geometry of an edge) keeps the edge’s name beside its point. The link is that edge
wherever a change takes it; of an edge that was split it is the piece nearest the link’s point,
and the sketch says so. When no edge has the name any more the link is lost and keeps its last
geometry, as it does for a face: it does not take whichever edge is nearest now. A link saved
without a name, by a build that kept only the point, is found by the edge nearest its point, as
it always was, and takes that edge’s name when its sketch is next edited.
A command that takes a face as a point can be given a name model.faces lists instead
(face.delete, solid.shell, solid.draft, solid.offset_face, solid.split_face, the faces
of solid.replace_face, the face of sheet.convert). It is found with the same lookup
(naming::locate_one: the face with that name, or the one face answering to it, on the given
body or any body; a name that finds several faces is refused). The command takes a point on that
face (naming::named_face_point: the incentre of the face’s roomiest triangle, moved onto the
surface of a plane, cylinder, cone, sphere or torus), which the feature stores like a picked one,
with the name it then gets as before. Nothing new is stored in the design. model.faces lists
that point for every face.
Profiles picked by their curves
A profile picked by a point or an index is stored by the curve ids of its outer loop
(ProfileSel::Curves), so it follows dimension and parameter edits exactly. Editing the outline
itself can leave no profile bounded by exactly those curves; the feature then falls back to the
best match and warns, instead of failing and taking its body and everything after it along
(profile_refs::fallback_loop):
- all the stored curves are still there and still close a loop on their own, but another curve now cuts the region: the pieces inside that loop that touch it, which together are the region that was picked (islands inside stay holes);
- otherwise the profile whose outer loop shares the most of the stored curves, at least half of them (a side deleted and drawn again, a notch trimmed in); a tie goes to the one with the fewest curves the loop did not have, and a tie left after that is an error, as is a loop that matches nothing.
Loops of offset curves and legacy graph-span multisets keep their exact meaning. Commands still
check a pick with the exact rule, so a curve list that names no profile is refused when given.
A sketch on such an extrude’s cap falls back the same way: its certificate names the loop, and
the cap of the same solid extrude at the same end, from the same sketch and on the same body,
that shares the most (at least half) of its curves stands for it, unless the exact cap is still
on another body (Document::anchored_face_indices).
A hole placed by a position on a face is the exception to “the point nearest the reference’s point”: its position is a place, not a pick. The hole follows its face along its own axis, so it moves with the face when the face moves, and when the face no longer reaches the position it stays where it was placed and the feature says so, instead of sliding to the face’s edge and drilling half a hole there. (Holes at sketch points follow their sketch.)
Older designs
Designs saved before format 14 stored names worked out from geometry, and a few of them name a
face that history names differently (a tube’s outer wall was named after the sketch curve nearest
its centre, its inner circle). Migrating such a design marks those names (?F2:top#2), and they
are found among the names geometry gives, as the build that saved them found them: an older design
evaluates as it was saved. Picking the face again stores its exact name.
Their features also keep naming the pieces they split a body into in the kernel’s shell order
(Feature::shell_piece_order, set by the migration): the body names stored after them (fillet and
chamfer bodies, targets, participants, materials, appearances, joints) mean the pieces in that
order. Features added later name their pieces after their faces.
Not done yet
- Certificates (
FaceSource, the strict attachment identities behind sketches on faces and configured face styles) still travel only where the kernel’s planar-source mapping keeps them; carrying them throughHistory::face_sourceswould let a certified attachment follow a face a later join reshapes, which today is refused on purpose. - Operations without history, listed above, keep the unverified geometric names.
Test harnesses
cargo xtask ci runs everything a pull request must pass: fmt, xtask assets,
xtask licences --check, xtask layers, clippy with -D warnings, the tests with the ci
profile and the wasm build check.
| Harness | Where | What it catches |
|---|---|---|
| Unit and engine tests | next to the code, *_tests.rs | measured results: volume, area, topology counts against derived values |
| Hostile input | hostile_params_never_panic, crates/engine/src/tests.rs | every command × every documented parameter × junk values: errors, never panics |
| Sketch fuzz | crates/engine/src/cmd/sketch_fuzz_tests.rs | random sketches through the solver and profile finder |
| UI scenarios | crates/ui-egui/tests/scenarios/*.json | the real app, headless: clicks, keys, drags, assertions |
| Layout tests | crates/ui-egui/tests/ui_layout.rs, window_widths.rs | panels and dialogs at every window width |
| Design-file corpus | apps/solidcraft-cli/tests/corpus | every saved file version still opens and evaluates the same |
| Kill mid-save | apps/solidcraft-cli/tests/kill_mid_save.rs | a save killed at any moment leaves a readable file |
| Reference tests | cargo xtask oracle → docs/oracle.md | parts rebuilt from tests/oracle/*/recipe.json, compared with analytic or FreeCAD expected values |
Dependency advisories
Scheduled and manually dispatched CI runs have a separate Dependency advisories job; new advisories do not fail unrelated pull requests. To run the same scan locally:
cargo install cargo-deny --version 0.20.2 --locked
cargo deny --locked check --warn unmaintained advisories bans sources
The checked-in deny.toml rejects vulnerabilities and unknown registries/git sources.
Unmaintained crates, yanked versions and duplicate versions warn; licences remain covered
by cargo xtask licences --check. Review advisory failures and update dependencies rather
than adding broad ignores. The first scan prompted an update to quick-xml 0.41.0 for
RUSTSEC-2026-0194 and
RUSTSEC-2026-0195.
Coverage excludes the version matching of local path patches: truck-shapeops,
truck-stepio, truck-meshalgo, matext4cgmath and egui-wgpu in vendor/. Their checked-in
code and patches still need comparison with their permissively licensed upstreams; a green
scan does not establish their security or incorporate an upstream fix automatically.
UI scenarios
A scenario is a JSON list of steps run against the real app with a headless egui context:
[
{"start": "sketch.create"},
{"click": {"plane": "XY"}},
{"start": "sketch.rectangle.two_point"},
{"click": {"sketch": [0, 0]}},
{"click": {"sketch": [40, 30]}},
{"expect": {"errors": 0}}
]
A new file in crates/ui-egui/tests/scenarios/ is picked up automatically. To record a bug
before it’s fixed, add a {"pending": "why"} step; its test is then ignored with that reason, and
the fix removes the step.
CI profile
CI runs the tests with --profile ci: light optimisation, line tables, no LTO and debug
assertions off. Some truck debug assertions fail on valid geometry; the ci profile disables
them while building faster than --release. If a test fails only in a plain debug build, say
so in the PR.
Run cargo xtask test to build with this profile and run the test binaries concurrently,
with doc tests alongside. Arguments after -- go to each test binary, for example
cargo xtask test -- shell. To run the test binaries sequentially instead, use
cargo test --workspace --profile ci. On a loaded machine set SOLIDCRAFT_TEST_JOBS=N to
bound how many test binaries and side commands run at once; each binary then gets
RUST_TEST_THREADS and ACTION_MATRIX_JOBS of max(1, cpus / N) so the binaries in flight
share the machine instead of each defaulting to one worker per CPU. Setting
RUST_TEST_THREADS or ACTION_MATRIX_JOBS yourself overrides that split, and leaving
SOLIDCRAFT_TEST_JOBS unset keeps the original all-at-once behaviour.
Test-level scheduling for full local CI
Install cargo-nextest, then set
SOLIDCRAFT_TEST_RUNNER=nextest when running cargo xtask ci. For example, on PowerShell:
$env:SOLIDCRAFT_TEST_RUNNER = 'nextest'
cargo xtask ci
This optional backend builds the workspace tests once, then schedules individual tests using
the local-ci nextest profile, which inherits ci. The action matrix runs first, then the longest geometry
cases start before the other costly tests, so the ordinary queue does not stall behind an
all-slot reservation. It ignores the default filter,
so the heavy tests below still run, does not stop after a test failure, and does not retry failures.
Tests marked #[ignore] remain ignored, as with the ordinary full local CI.
The default concurrency is at most four tests; NEXTEST_TEST_THREADS overrides it. The action
matrix’s child workers default to the smaller of that budget and the host CPU count;
ACTION_MATRIX_JOBS explicitly overrides this worker count. The local profile reserves CPU
slots for the matrix and keeps its scheduling changes separate from GitHub CI. On Windows,
the solidcraft-gpu and app packages, plus UI offscreen/GPU tests, use a serial group. The Wasm check and doc tests still run alongside,
and the web-offline gate still follows a successful native/Wasm/doc run.
The backend keeps Cargo and binary metadata under target/xtask-nextest/ and reuses it for the
native run, freeing Cargo to run the side gates. Nextest prints each test’s result and duration
as it finishes. SOLIDCRAFT_TEST_RUNNER=libtest (or leaving it unset) retains the original runner;
cargo xtask test and its Cargo/libtest argument forwarding are unchanged. Unknown runner names
fail explicitly.
CI sharding and heavy tests
On GitHub, one job builds the tests into a cargo-nextest archive and four
jobs run a quarter of the tests each (two halves on a pull request; --partition slice:N/4,
settings in .config/nextest.toml), the slowest tests first; doc tests run in their own job.
Pull requests skip a few heavy tests (each takes one to twelve minutes); they run on every push to
main, nightly, and in cargo xtask test:
solidcraft-engine:tests::hostile_params_never_panic, everything intests::robustnessand the seeds oftests::robustness_goldens(seed_00…seed_95; pull requests that change the kernel, truck or the goldens run all but the slowest,SLOW_SEEDSinci.yml),pattern_suppress_tests::enclosure_arc_extinction_keeps_a_real_sharp_inner_lip_after_resize_and_reopen,pattern_suppress_tests::enclosure_datums_follow_wall_height_and_board_depth_with_material_proofs,cmd::plastic::tests::enclosure_snap_fit_follows_resized_front_wall, theatomic_commandscontract testssolidcraft-kernel:tests::boolean_identity_on_tangent_primitives,tspline::tests::hostile_tsplines_never_panic, and theboolean_propsproperty tests (these run only in the nightly “Property tests” job: their inputs are random, so main skips them too)solidcraft-ui-eguiscenarios:acceptance_sensor_enclosure
The action matrix (apps/solidcraft-cli/tests/action_matrix.rs) tries every ordinary action
(sketch, offset plane, press pull, hole, draft, shell, fillet, chamfer, construction geometry, move,
mirror, pattern) on every face, edge, vertex and body of a corpus of designs and compares with a
checked-in baseline: an action that passed there and fails now fails the test. Every run checks a
fixed sample; the nightly “Action matrix” job runs it all. After a fix, record what passes now with
cargo xtask action-matrix --bless. The results and the causes of the failures are in
the action matrix page.
To run exactly what a pull request runs: cargo nextest run --cargo-profile ci --profile ci;
add --ignore-default-filter for the full set. A new test that takes over a minute on CI can
join the list (the default-filter in .config/nextest.toml) if it guards robustness rather
than everyday behaviour.
Control protocol
The control channel is on by default. SolidCraft listens on 127.0.0.1 only, for JSON lines,
so agents can drive the app without a launch flag. Every connection still needs this launch’s
token (below); nothing else on the machine, and nothing off it, can send a command.
| Launch | What listens |
|---|---|
solidcraft --control PORT, or SOLIDCRAFT_CONTROL_PORT=PORT | exactly 127.0.0.1:PORT (the flag wins over the variable), whatever the setting |
| neither, and Preferences > Assistant > “Let AI agents connect (control channel / MCP)” on (the default) | the default port 27878, or the next free one up to 27893 when it is taken (a second window, another program) |
| neither, and the setting off | nothing |
The setting is read at start-up. Old preference files, which don’t have it, load with it on. A
launch with --control or the variable behaves as it always did (the start page stays hidden,
the window doesn’t ask for the foreground), because a scripted launch expects that; the default
channel changes neither for a normal launch.
Finding the port
Each app that opens its channel writes the port to a discovery file beside the token files,
control/current.port (same folders and permissions as below), and removes it at exit if no later
app has replaced it. It holds only the port, never the token. solidcraft-cli mcp with no
arguments reads it, checks that something listens there and bridges to that app; with no app
running, or with --headless, it runs a session of its own. --connect HOST:PORT and the
other arguments still decide first. With several windows open, the most recently started one is
the one found; use --connect for another.
Requests are one line {"id": …, "method": "…", "params": {…}}; replies are described below. Each request is one line {"id": …, "method": "…", "params": {…}}; each reply is one
line {"id": …, "ok": true, "result": …} or {"id": …, "ok": false, "error": "…"}. Requests are
handled on the UI thread between frames; pointer and keyboard input is injected as real egui
events, one pointer event per frame. A connection that sent control.subscribe is also sent
event lines {"event": "…", "params": {…}}, which have no id
(Following the design).
Token
Every connection must authenticate. At start the app makes a random 256-bit token for this launch and writes it, as one line of hex, to a file only the user can read:
| System | Token file |
|---|---|
| Linux | $XDG_DATA_HOME/solidcraft/control/PORT.token (~/.local/share/solidcraft/control/PORT.token) |
| macOS | ~/Library/Application Support/SolidCraft/control/PORT.token |
| Windows | %LOCALAPPDATA%\SolidCraft\control\PORT.token |
On Unix the folder is 0700 and the file 0600; on Windows the file’s inherited permissions are
replaced by a single entry for the user. SOLIDCRAFT_CONTROL_DIR moves the folder. The file is
removed when the app exits, and the next launch writes a new token. The token file’s path is
printed to stderr at start-up. Beside it, <port>.pid holds the app’s process id (and, on Linux,
its start time).
An app that is force-quit can’t remove its files, so a later client may send that launch’s token.
When the app refuses it, solidcraft-cli mcp names the token file it read and when it was
written, and says the file is stale when the process in <port>.pid has exited. Delete the file
or restart SolidCraft. On Windows, an app or client started from inside an MSIX-packaged app (and
the tools it launches) has its AppData redirected: it writes and reads
%LOCALAPPDATA%\Packages\<package>\LocalCache\Local\SolidCraft\control\ before the folder above.
The first request on a connection must carry the token as a top-level "token" member
({"id": 1, "method": "ui.inspect", "params": {}, "token": "…"}); later requests on the same
connection may leave it out. A first line without the right token is answered with
{"ok": false, "error": "unauthorized: …"} and the connection is closed, without running
anything. Tokens are compared in constant time.
solidcraft-cli mcp --connect 127.0.0.1:PORT reads the token file for that port by itself
(--token T or SOLIDCRAFT_CONTROL_TOKEN give one explicitly). For a test harness, or a
sandboxed package whose data folder the client can’t see, start the app with
SOLIDCRAFT_CONTROL_TOKEN set to at least 32 hex characters (for example
openssl rand -hex 32) and the app uses that token instead of making one; the file is still
written. A one-line client in Python:
token = (Path.home() / ".local/share/solidcraft/control/7878.token").read_text().strip()
sock.sendall((json.dumps({"id": 1, "method": "ui.inspect", "params": {}, "token": token}) + "\n").encode())
Limits
The channel is untrusted input. A request line may be at most 16 MiB; a longer one is answered
with {"ok": false, "error": "request too large"} and the connection is closed, as are
connections that send eight lines that are not JSON. At most 16 connections are served at once
(further ones get {"ok": false, "error": "too many connections"}), at most 64 queued requests
run per frame so a flood cannot starve drawing, and a request that the UI thread has not
answered within 60 seconds (3600 seconds for engine.batch) is replied to with
{"ok": false, "running": true, "error": "timeout: … is still running …"}. The same 16 MiB line
limit applies to the MCP server.
A timed-out request has not failed: it is still running in the app, and a change still applies
when it finishes (a long fillet, say), so don’t simply send it again. The timeout’s text says
what it still does: a request that only reads (ui.inspect, document.inspect, an inspection
command) “only reads: nothing changes”; one that only writes a file (file.export,
file.save, file.save_as, file.save_mesh, another export given a path, ui.render, or
ui.screenshot with a path) “will still write the file when it finishes” and leaves the design
as it is; any other request “will still apply when it finishes”. The connection keeps its reply (#1618). Ask for it
with control.pending: {"pending": <id>, "method": …, "done": false} while it runs, then
{"pending": <id>, "method": …, "done": true, "reply": {…}} (its own reply, once), and
{"pending": null} when nothing is outstanding. Until it lands, any other request on that
connection is refused at once with {"ok": false, "busy": true, "error": "busy: …"} and not
run, so nothing is queued to apply unseen behind it. control.pending belongs to the
connection: a new connection has nothing pending. The bridged MCP server does this itself
(mcp.md).
control.pending answers only for its own connection. control.running answers for the whole
app, the same on every connection, at once (it never waits behind a running request, and a
connection may ask it while it is busy): every request the app has been sent and has not
answered yet, oldest first, from any connection, including a timed-out one whose connection has
since closed:
{"id":1,"method":"control.running","token":"…"}
→ {"id":1,"ok":true,"result":{"running":[
{"connection":3,"id":17,"method":"engine.execute","command":"file.export","running_s":312.4,"queued":false},
{"connection":5,"id":2,"method":"engine.batch","steps":12,"running_s":4.1,"queued":true,"self":true}]}}
running_s counts seconds since the request arrived; queued is true while it still waits its
turn behind another. command is the command id of engine.execute (a known one only) and
steps the length of engine.batch or engine.script; parameters are never shown, so file
paths and expressions stay with the connection that sent them. self marks the asking
connection’s own request. {"running": []} when the app is idle.
Any request’s params may carry allow_paths: ["/abs/folder", …]: every file that request
reads, writes or deletes (command paths such as file.export’s path or doc.recovery_*’s
dir, ui.render, ui.screenshot) must then lie inside one of those folders after .. and
symlinks are resolved, or the request fails with … outside the folders this agent may use ….
The app adds its own recovery folder and, when it lies safely inside the home folder and not in
a hidden folder, the open design’s folder (whose hidden files stay out of reach). The bridged
MCP server sends its folders this way on every request (mcp.md). Without
allow_paths file paths are not limited: a client holding the token can do what the user can.
Engine
| Method | Params | Result |
|---|---|---|
engine.execute | command: command id, params: JSON object | the command’s result |
engine.script | commands: [{command, params}, …] | results, stops at the first error |
engine.batch | commands: [{command, params}, …] (at most 10 000), rollback?: bool (default true), timings?, profile?, coalesce_parameters?: bool (default false) | {completed, results, undone}, plus failed: {index, command, error} when a step fails, and with timings timings: [{index, command, ms}] (the failing step included) and total_ms; profile adds each step’s profile: {boolean_ms, tessellate_ms, other_ms}. Runs the steps in one request, so within one frame, each as engine.execute runs it. The first failure stops the batch, and with rollback the design goes back to how it was before it (undone: the steps taken back, rolled_back: whether the design is back as it was, false only when the checkpoint could not restore it). Refused before anything runs, with an error starting engine.batch unavailable, while the agent cursor is shown or when a step loads a file (doc.open, file.insert_step, file.insert_mesh). Send such steps one by one. A step the batch would certainly refuse at its turn is found before the first step runs (#5853): a step without a command, an unknown command id (with the closest ids) or, with strict parameter checking, a call that breaks its command’s contract. The reply is then that step’s failure with completed: 0, undone: 0, not_run listing every step and a note; nothing has run. With coalesce_parameters, each run of consecutive plain parameter edits (parameters.change with only a name and an expression, or with changes) is set as one parameters.change {changes}, so the design rebuilds once for the run. Every step still gets its own result (with coalesced: the run’s length), and the run is one undo step. The run skips the states between its edits: an edit that would be refused on its own, but not once the run’s later edits are in, goes through. A run whose combined edit is refused (a new parameter, a bad expression) runs step by step, so the refused step is named. Answered when done, for up to 3600 s |
engine.commands | — | every command: id, label, tab, panel, icon, shortcut, params doc, enabled |
document.revision | — | {document_id, revision}: the active design’s id and a number that moves whenever the design changes, for programs that follow it (Following the design) |
control.subscribe | — | {document_id, revision}, as document.revision; the connection is then sent a design.changed line whenever they change (Following the design) |
document.inspect | measure?: bool | parameters, timeline (errors, timings), bodies, sketches, selection (assembly joints: engine.execute joint.inspect {joint?}, which reads only: each joint’s two origins with the occurrence, the snap as saved, the feature, face, edge or sketch curve its names refer to, and whether it resolves or why not; kind, offset, angle, flip, values, limits, expressions and motion links) |
Commands never open dialogs when run this way. solidcraft-cli commands prints the same registry.
Following the design
A program that follows the open design (a renderer kept in step, a live link) asks
document.revision, which only reads and does not wait behind design changes (a slow command
computing, a file loading, the agent cursor acting one out), and exports only when the answer
changes (#6646):
{"id":1,"method":"document.revision","token":"…"}
→ {"id":1,"ok":true,"result":{"document_id":"18f3a0c2d4e5b6a7-1","revision":3}}
document_idnames the active design (its tab) while it stays open, through edits, saves and renames. Two open designs have different ids even with the same name, and each design started or opened gets a new one, also one opened or started in the same tab. Ids of one run never match those of another. Treat it as an opaque string.revisionmoves whenever the design changes: an edit, a parameter, undo or redo, a design opened or started, what is hidden or shown (saved with the design), a component moved and not captured yet. It stays the same for reads, a selection, a camera move, a section or an analysis view. Revisions come from one counter for the whole run, so a revision never names two states, and it never goes back: changes undone before the next ask that end where the design was need no new one (its revision still names that design).
Switching tabs changes document_id; back on a tab its revision is the one it had, if nothing
changed it meanwhile. Compare both values, and export when either differs from the last
answer. The same answer comes from engine.execute document.revision (and so from scripts and
MCP execute), which waits its turn behind design changes like any command.
A client need not ask again and again: after control.subscribe on its connection, the app
sends it a line whenever the active design changes (#6985):
{"id":1,"method":"control.subscribe","token":"…"}
→ {"id":1,"ok":true,"result":{"document_id":"18f3a0c2d4e5b6a7-1","revision":3}}
{"event":"design.changed","params":{"document_id":"18f3a0c2d4e5b6a7-1","revision":4}}
{"event":"design.changed","params":{"document_id":"18f3a0c2d4e5b6a7-2","revision":5}}
- The reply is what
document.revisionanswers then (and, like it, does not wait behind design changes); no event comes before it. From there on, eachdesign.changedline carries whatdocument.revisionwould answer, sent when itsdocument_idorrevisiondiffers from the last one this connection was told: an edit, undo or redo, a design opened or started, a tab switched. - Lines are coalesced, not sent per frame: at most about ten a second, and changes made faster (a parameter dragged) are followed by one line with the latest. Once the design stops changing, the last line names it as it is.
- An event has
eventandparamsand noid; every reply still has its request’sid. Each event is a whole line of its own, between replies, never inside one. Ignore events you don’t know. - Only connections that subscribed get events, until they close (or shut their sending side). A
second
control.subscribeon a subscribed connection is refused withalready subscribed; askdocument.revisionfor the design now. Acontrol.subscribethat timed out or failed did not subscribe the connection: send it again.
Slow commands and the window (#1989)
With the preference Preferences > AI > Keep the window responsive while agents run commands
on (ui.prefs {"agent_off_thread": true}; off by default), a design change in the desktop app
that takes longer than about 150 ms (an engine.execute of a
modelling command, an engine.batch of them) computes off the window thread, so the window keeps
drawing and answering while it runs:
- The command is evaluated ahead on a copy of the design, which also makes the display meshes of the bodies it ends with. Then it runs for real on the window thread, reusing every feature the evaluation computed (kept for it, whatever the bounded evaluation cache dropped meanwhile), so the window waits only for the bookkeeping. Its reply, undo step, autosave and crash journal are the same as before. With the agent cursor shown, the evaluation runs while the pointer glides to the button, and the pointer waits there until it is done.
- Meanwhile
ui.*requests,document.inspectandengine.commandsare answered at once (inspection shows the design as it was), also while the agent cursor acts a command out. Later design changes, from any connection, wait their turn and run in the order they arrived. - A person’s edit made meanwhile goes first. The agent’s command is then computed again for the
design as it is and lands after that edit, never in the middle of it. If the design was replaced
(another tab made active, a new file opened), the request fails with
… the design was replaced … nothing was applied. - A chip in the view names the command, with Stop. Stop answers
{"ok": false, "stopped": true, "error": "stopped: …; nothing was applied"}and the requests waiting behind it go on. The computation finishes unseen in the background. file.exportandfile.save_meshchange nothing in the design. They run on a copy of the design as it was when they were asked for, and nothing waits behind them.- Batches with
timingsorprofilestill run on the window thread, so the times they report measure the real work.
Timeouts and control.pending work as described above. A slow command is still answered when it
is done, from a later frame. With the preference off, commands run on the window thread, and the
window waits for each one.
Product images use render.preflight/start/status/cancel/export through engine.execute.
Keep the returned session-scoped job ID for subsequent calls; status does not block or
render. See render jobs for capture, cancellation and export semantics.
While an interactive native file picker is open, ui.inspect reports file_picker_busy: true.
Engine commands, ui.file and ui.home requests wait for its answer; window resizing,
inspection and screenshots remain available.
Every sketch.* command that changes a sketch adds created: {points, curves, constraints, dimensions} to its result (and wires for 3D curves): the ids of what it added, in creation
order, next to whatever else the command returns (curves, sides, corners…). Sketch ids are
stable and never reused, even after a delete (l1, l2, then l3 once l2 is gone; only undo
rolls the numbering back with the design), so they can go straight into later commands:
{"id":7,"method":"engine.execute","params":{"command":"sketch.line","params":{"points":[[0,0],[40,0]]}}}
→ {"curves":["l1"],"created":{"points":["l1.start","l1.end"],"curves":["l1"],"constraints":["k1"],"dimensions":[]},…}
{"id":8,"method":"engine.execute","params":{"command":"sketch.dimension","params":{"entities":["l1"],"value":50}}}
A bare numeric value given to sketch.dimension (from the control channel, scripts or MCP) is
read in the design’s units, exactly as if it were typed into the dimension box: in an inch design
"value": 2 makes the line 2 in (50.8 mm) long. Point coordinates stay in millimetres. To pin a
unit regardless of the design, pass an expression with one: "value": "50 mm" or
"value": "0.5 in". Angles are degrees unless the expression says otherwise.
The viewport also shows an X/Y/Z-aligned dimension chain between the endpoints of the
currently shown distance (centre, minimum or maximum), with each world-axis distance
labelled separately as an absolute value (including zero).
It uses the design’s units and length precision, independently of the view or sketch plane.
Preferences → Graphics → Show X/Y/Z distances controls these extra lines and
labels (default on, saved between runs). Set ui.prefs {"measure_axis_distances": false}
to hide them while keeping the total distance and the dialog’s ΔX/ΔY/ΔZ values. These
overlays remain visible until Measure closes.
Overlay handles measure:label:x, measure:label:y and measure:label:z identify the extra
labels; prefs:measure_axis_distances identifies the checkbox.
UI
| Method | Params | Notes |
|---|---|---|
ui.inspect | — | UI state, pending file picker (file_picker_busy; compatibility alias file_dialog_open, no path), what is hidden (visibility: bodies, origin and construction items, sketches, joints (hidden_joints, joint ids); set it with the browser.visibility command), viewport rect, camera, active tool/dialog, the status line (status: text and whether it is an error), hover, renderer, timings, what the GPU drew last frame (draw: draw_calls, triangles, line_segments, supersampled; null without a GPU) and the graphics adapter, live preview (active, busy, error, ms, bodies it stands in for) |
ui.set | any UiState field (tab, perspective, showGrid, dark, layout, …), and the browser’s view of the design, saved with it: showOrigin (the Origin folder’s eye), showSketches (the design’s Sketches folder, c0/sketches in hiddenFolders), hiddenBodies, hiddenOrigin, hiddenSketches, shownSketches, hiddenJoints, hiddenFolders, hiddenProfiles, shownDims, lockedBodies (whole lists; browser.visibility changes single items) | dark: false switches to the light theme; layout: "floating" (default) or "compact" ("docked" is also accepted) selects the transient shell layout without saving a preference. Scenarios use {"call":"ui.set","params":{"layout":"docked"}} |
ui.view | view: front, back, top, bottom, left, right, iso, home, fit; animate (bool, default false: snap) | |
ui.camera | target?: [x, y, z] (mm), yaw?, pitch?, roll? (radians), distance? (mm), fov?, up?; fit?: true, false or [occurrence id or name…] (root: the bodies outside every component) | the viewport’s orbit camera: it looks at target from distance away; yaw 0 looks along +Y (from the front), pitch is the elevation (±π/2: straight down or up), roll turns the image. Fields given are set first (the others keep their values), then fit frames the listed occurrences (or everything), keeping the direction. The reply, passed back, sets the same view again, so a framing can be stored and replayed. fov and up follow the perspective setting (ui.set) and the up axis preference (ui.prefs) and can’t change here; a refused field changes nothing |
ui.start | command, params? | like a toolbar click: starts the sketch tool or dialog for the command. params sets what the dialog opens on, and nothing runs until OK: joint.drive takes joint (id or name; without it, the joint picked in the browser, else the first joint that moves; a rigid joint opens to say why it has nothing to drive, with an Edit Joint button, handle drive:edit_joint) and values (numbers in degrees or millimetres, or expressions); motion.study takes study (id or name), which opens that study to edit and chart. A command that takes no params is refused before it starts; a param the dialog doesn’t take is refused and the dialog closes |
ui.click | x, y (screen points), button?: left/right/middle, shift?, ctrl?, double? | real pointer input |
ui.move | x, y, shift?, alt?, ctrl?, cmd? | pointer movement without a button; modifiers apply until the next synthetic move/click; a move with omitted modifiers releases them. With nav: "touchpad", Shift pans and Alt/Option orbits. |
ui.drag | x0, y0, x1, y1, button?, shift?, ctrl?, steps?, hold?, hold_end? | press, move, release; a left drag on the model is a box selection (left to right: window, right to left: crossing); with OpenSCAD navigation, plain left-drag on empty space orbits, Shift+left-drag box-selects, and drags starting on sketch geometry edit it; hold keeps the button down that many frames first (a right press-and-hold opens the marking menu, releasing over a direction picks it), hold_end that many frames at the end (a sketch drag shows its snap) |
ui.menu | x?, y?, target?, close? | with a point: open a context menu there as a right-click would (the viewport’s marking menu, or a browser menu with target: {"type": "body", "name"} | {"type": "sketch", "id"} | {"type": "component", "id"}); without: the open menu (radial and items: id, label, shortcut, enabled, icon, placeholder; blank icons on headings/separators have placeholder: false) |
ui.icon_audit | reset?, stop? | reset: true enables collection on the UI thread and clears previous requests; returns {requested: [{name, master, placeholder}]} sorted by requested id. Each read drains names collected since the previous read/reset. Collection stays enabled until stop: true (which returns pending names before disabling it). Includes aliases, variable/table ids and cached paints. |
ui.menuPick | item: id or label | runs an item of the open menu, as clicking it does |
ui.selectOther | x?, y?, pick?, add?, hover?, close? | Select Other: with a point, list everything pickable there (as holding the left button does); pick: select row pick (add: as Shift-click; with a dialog open, into its active input); hover: light a row up; without: the open list (open, at, total: everything under the cursor, rows: label, icon, sel, at most 20 per kind, nearest first) |
ui.rename | text?, commit? (default true) | finishes the rename box a Rename item opened |
ui.selection | the selection, the open dialog’s inputs, the hovered item, and the active sketch’s drawn dimensions (dimensions: id and text centre, dimension_selected, dimension_editing), and finished sketches’ dimensions shown in the model view (shown_dimensions: sketch, id and text centre; a double-click there edits the value) | |
ui.worldToScreen | points: [[x, y, z]…] world points | their screen points |
ui.triad | screen points of the 3D sketch move triad’s handles (x, y, z, plane) | |
ui.sketchToScreen | points: [[x, y]…] in active-sketch coordinates | their screen points (null when behind the camera) |
ui.dialogInput | index | what clicking an input’s Select chip does: picks go to that input |
ui.at | world: [x,y,z] | sketch: [x,y] | plane: "XY" | axis: "Z" | dimension: "d1" (its value text) | handle: "arrow" (a manipulator handle, see scenario::publish_handle) | the screen point (a scenario step’s target; see crates/ui-egui/src/scenario.rs) |
ui.pick | pixels?: [[x,y]…], rays?: [{origin, dir}…], frame?: "window"|"model", width?, height?, edges? (default true), edge_px? (default 6), all?, include_ghosted? | read-only geometry under each pixel or ray (#6304): one entry per input, null where nothing is hit — body, face index, exact world point and outward normal, depth, occurrence/component for placed parts, edge/vertex within edge_px of a pixel, section_cap: true on a cut face. window pixels are physical pixels of a window screenshot; model pixels are pixels of a ui.render of width×height (default 960×600), so it works headless. As view.pick through engine.execute/engine.batch. See crates/ui-egui/src/view_pick.rs |
ui.handles | prefix? | the handles drawn last frame, [{name, x, y}]: toolbar buttons and drop-down items toolbar:<command id> (the button alone: button:<command id>; a drop-down row: dropdown:<command id>, highlighted dropdown_hot:<command id>, its pin dropdown_pin:<command id>; Customize Toolbar’s check boxes customize:<command id>), ribbon tabs tab:<name> (MODEL, SURFACE, ASSEMBLE, INSERT, INSPECT, MANAGE, VIEW, SHEET METAL, WELDMENTS; contextual SKETCH; workspace RENDER), panel labels panel:<tab>:<panel> (also ribbon:section:<TAB>:<SECTION>), document overflow list tab:overflow, workspace column ribbon:workspace, command-search launcher ribbon:search, browser rows row:<label> and fold arrows fold:<label>, manipulator handles; signed-axis tips axis_view:z, axis_view:-y; optional cube facets navigation_cube:top, navigation_cube:top_front_right, controls navigation_cube:control:home (and other actions), plus signed-axis aliases for visible cube faces; Preferences navigator picker prefs:navigator, options prefs:navigator:Signed axes and prefs:navigator:Cube; in the Motion Study dialog, where the chart puts its first and last step motion:chart_start and motion:chart_end, the Check collisions button motion:check and each collision found motion:collision:<n> |
ui.editFeature | feature (id or name) | what double-clicking a timeline item does: rolls back to it and opens its dialog filled in (sketches open in sketch mode) |
ui.scroll | x, y, delta? | wheel zoom at the cursor |
ui.key | key (egui key name, Enter, Escape…), cmd?, ctrl?, shift?, alt?, pressed? | By default, press and release, then release modifiers. With pressed: true or false, send only that edge; modifiers remain as given. cmd retains the legacy primary-modifier alias unless ctrl is explicit: cmd:true, ctrl:false sends physical Command, and both true sends Control+Command. ui.move can change modifiers between edges. |
ui.text | text | typed text |
ui.screenshot | path? | PNG of the window (needs a presented frame: a minimised or fully covered window answers at once with hidden: true and an error; use ui.render) |
ui.render | path, width?, height?, grid?, axes? | Render of the model with the current camera (no window needed), drawn on the app’s GPU device with the viewport’s pipelines (the reply’s renderer: gpu, or gpu-software on a software adapter), in the window’s theme (light or dark; solidcraft-cli snapshot keeps the light colours); cut by the section the viewport shows (inspect.section, an open Section Analysis dialog’s preview, a sketch’s Slice), with the cut face capped. The ground grid and the origin’s axes are drawn as the window shows them (ui.set showGrid and showOrigin, hiddenOrigin); grid and axes (booleans) say otherwise for this render |
ui.confirm | accept?: bool | answers the Delete confirmation (shown when a delete takes other features with it or makes some fail); without accept, what it lists |
ui.messages | clear?: true (empty the list), open?: bool (show or hide the Messages window) | every status message of this run, oldest first (the last 200; a message repeated in a row is one entry with a count): messages [{text, error, at, count}] (at in ms since the Unix epoch), and whether the window is open (#2763) |
ui.documents | action?: new, switch, close, quit; index?; force? (close without asking); answer?: save, dont_save, cancel (answers “save changes?”); open_elsewhere?: read_only, anyway, cancel (answers “open elsewhere” for the first Open waiting); changed_on_disk?: save_copy, overwrite, cancel (answers “changed on disk” before a Save) | the open designs (tabs): names, unsaved flags, the active one, a pending “save changes?” prompt, whether a quit is asking, whether the active design is read_only, the Opens waiting for “open elsewhere” (open_elsewhere) and a waiting “changed on disk” (changed_on_disk) |
ui.file | path; insert?: bool | starts an interactive background Open or Insert; poll ui.inspect.file_busy for completion; the window stays responsive |
ui.home | open?: bool; sample?: index | the start page: show or hide it, open a built-in sample (once built); returns open and the recent designs |
ui.shortcut | command, key?, replace? | a command’s key, or rebinds it ("" clears; a key another command uses is refused unless replace) |
ui.help | item?: about, shortcuts (read only), report (opens a report draft for review), crashes (the Crash Reports window) | the version, commit, build date and diagnostic text |
ui.prefs | any of default_units, nav (fusion, solidworks, inventor, alias, blender, tinkercad, openscad, touchpad), zoom_reverse, pan_reverse_x (default false: horizontal scrolling pans the other way), zoom_to_cursor, smooth_wheel_zoom (default true), trackpad_pan (default true: two-finger scroll pans, except Touchpad always zooms; pinch zooms), orbit_around (view_centre (default: what Pan moves and Fit frames), origin (the world origin stays still on screen), cursor (the model point under the pointer when the drag began); the older orbit_cursor boolean still sets cursor when true), sketch_exit_view (keep (default: the view stays where it is when a sketch ends), previous (back to the view from before the sketch), unless_rotated (back unless the sketch view was turned)), look_at_anim, look_at_speed, middle_double_click (home (default), fit, off), msaa, show_frame_rate (default false: frames per second in the view’s bottom-right corner, counting the frames the view draws anyway; it reads 0 FPS once the view is idle), window_system (Linux: auto (default: XWayland whenever there is an X display, native Wayland on niri), wayland, x11; saved in graphics.json, applies at the next start; SOLIDCRAFT_WAYLAND overrides it), graphics_adapter (exact card name, or empty for Automatic; applies after restart; WGPU_ADAPTER_NAME takes precedence), frame_rate_details (default false: with the frame rate, a frame-time graph of the frames drawn with the triangles over it, a frame-time histogram and what the GPU drew; clicking the readout switches it), length_decimals, angle_decimals, step_values (default false: Up/Down and the wheel step a plain number in a focused value field), theme (dark, light, system or a theme file’s id: a built-in one such as gruvbox-dark, or an imported one; the reply’s theme_ids lists them), viewport_theme (theme (default: the theme’s own), light, dark: the 3D view in the Light or Dark theme’s colours whatever the window uses, #4146), theme_import (a solidcraft-theme/1 file’s JSON or text: adds the theme and turns it on; the reply’s theme_imported is its id), theme_remove (an imported theme’s id), theme_export: true (the reply’s theme_file is the theme on now as a complete file; see colour themes), language (en, ar, ja, de, fr, he, ko, ms, ru, uk, zh; see localization), ui_scale, ui_font (an installed font family or a font file for the interface; empty for the built-in font), up_axis (z, y), offer_stand_up, extrude_auto_to_object (default false: in the Extrude dialog, with a profile picked, a face, vertex or parallel plane picked off its plane switches the extent to To Object; it applies to Extrude dialogs opened afterwards), hole_snaps (default false: a Single hole snaps to its face’s middle and round edges’ centres), hole_moves (default false: a placed Single hole has a drag handle (hole:<n>), and a pick on a face that has a hole moves it there), placement_handles (default false: handles on the preview of a Rest, Boss or Snap Fit that move it across the model and turn it), color_vision (off (default), protan, deutan, tritan: colour vision modes, #778), view_confirm_corner (default false: while a dialog or a sketch is open, a check and a cross at the view’s top right, handles view:confirm:ok and view:confirm:cancel, do what the dialog’s OK and Cancel do, or Finish Sketch and Cancel Sketch; the axis view or cube moves down below them, #6709), history_sketches (in_order (default), under_feature: the History at the left lists a sketch under the first feature built from it, #6489; the timeline at the bottom keeps the creation order), dialog_buttons (bottom (default), top: a feature dialog’s OK, Cancel and Apply sit in its title row instead of under its inputs, #6708; the buttons keep their handles, and dialog:close is then Cancel’s), timeline_origin_bodies (default false: the History on the left or under the browser starts with the active component’s Bodies folder and the Origin, the browser’s rows with their eyes, folds and menus; the bottom History is unchanged), dialog_place (edge (default) or left_column: command dialogs open in the left column in place of the browser and the History, #6707), check_updates (default true; updates), nav_bridge_enabled (default false) and nav_bridge_port (default 8765: a device’s own app on this computer moves the view, the navigation bridge); open (a page: General, Interface, Design, Navigation, Keyboard Shortcuts, Sketch, Graphics, AI, Plugins; Shortcuts, Assistant and Sketch & selection are aliases, and the former Selection page opens Sketch) opens the Preferences dialog there; button: ok, cancel, apply, defaults; stand_up: true stands up a just-opened Y-up file, stand_up: false leaves it as it is (“Leave it”: the offer closes and offer_stand_up stays on); the offer’s buttons are the handles stand_up:stand, stand_up:leave and stand_up:never | sets preferences (kept between runs), returns them all plus dialog (the open page or null), dark, viewport_dark (whether the 3D view is dark) and stand_up_offer |
ui.capture | action?: capture, revert, cancel | answers the Capture Position question (asked when a command starts or the design is saved while components were moved without capturing); without action, what waits |
ui.window | action: minimize, maximize, restore, toggle, close | what the title bar’s caption buttons do (on Windows and Linux the application bar is the title bar); close asks about unsaved designs first |
ui.agent_cursor | show (bool), speed: a number from 1 to 5 (times the normal speed) or "instant" (no animation: the command runs at once and the pointer jumps to where it acted), follow_camera (bool); all optional | the settings. While the cursor is shown, engine.execute first glides to the command’s toolbar button and to each pick, then runs and replies; later requests wait their turn. The pointer shows the command’s localized name, including commands inside a closed toolbar group; an animated batch names each command it visits. It also follows the pointer input sent with ui.move, ui.click, ui.drag and ui.scroll, as each event reaches the app, and a press leaves a ripple. "animate": false on a call skips it |
engine.execute → view.layout | mode: floating or compact | Apply and save the workspace layout without opening a dialog, changing the design, resetting the camera or cancelling its active tool. General preferences expose the same choice; ui.prefs {"layout":"compact"} also saves it. |
engine.execute → browser.expand_all | path?: occurrence ids; component?: id; omit both for the whole tree | Open every Browser row under that scope, including folders that start closed (Origin, Named Views, Features). Display only: not saved in the design, not an undo step. |
engine.execute → browser.collapse_all | same | Close those rows. The design row stays open when the whole tree is collapsed. |
engine.execute → view.history_layout | width?, dock?: bottom, left, under_browser, collapsed? | Resize/dock the history without opening a dialog or changing the design. Contents respond to measured width; browser-stacked history shares its width and splits height equally. One folded header gives the expanded card the rest; two folded headers stay at the top. |
ui.agent_chat | features?, toolbar_button?, connect_at_start? and open_at_start? (connect the agent started last, and open the panel, when SolidCraft starts; both off by default, never with --control), permissions? (ask, edits, all), add_agent? ({name, kind: acp, api or anthropic (api is OpenAI-compatible, anthropic Anthropic-compatible), command or base_url + model + request_timeout_s? (seconds one model request may take, 300 by default, rounded and clamped to 30 to 3600)}; no keys), add_dialog? (installed, custom, api, anthropic, or false to close), default_agent?, agent? (the conversation’s agent, by id), mode? (the agent’s own mode), provider? (which provider’s models the model picker lists, when they come from several), model? (one of its models), open?, side? (left, right), minimized?, popped? (in its own window), width?, picker?, refresh?, connect?, stop?, attach?: a file’s path (a picture, a text file or a CAD file, for the next message; CAD files are imported at once), remove_file? (index of a file on the next message), send?: text (files alone make a message too), attach_view?, attach_selection?, pick? (true: open the context picker), permission?: an option id or "cancel" (the newest question of the agent’s own), approve?: true, false or "always" (the oldest request held for Allow/Deny), cancel? (Stop: the queue stays), send_now? (stop the running step, then send the queued messages), remove_pending? (index), new_chat? (clear the chat, new session), undo? (the last message’s changes) | the chat’s state: features, agents (found and added), currentAgent, state (off, starting, ready, busy, auth, exited, failed: the agent never got ready, and Connect reads Retry), connectAtStart, openAtStart, lastAgent (the agent started last), popped, window (its place and size), agent, modes, mode, models, model, modelWarning (the model capability warning text, or null when unknown or tool support is reported; checked asynchronously and never blocks sending), turnWarning (the latest completed turn’s no-tool notice, otherwise null; cleared on the next turn or new chat), providers (name and count of each, empty with one provider), provider (the one listed), authMethods, toolsPort, pid, waiting, turn, pending (queued messages, with their files), attachments (the files on the next message, by name), loading (pictures still being read for it, by name), stopped, detection and the conversation (items). With features off only the settings apply. Desktop only; see Agent chat |
ui.resize | width, height | |
app.quit | force? (quit without asking about unsaved designs) | quitting (the window closes), asking (a “save changes?” prompt is open) |
ui.workspace {workspace?: "design" | "render"} reads or changes the active workspace.
Entering Render requires finishing the current modelling operation; returning restores
the Design camera. See the integrated Render workspace.
Continuous camera motion
Continuous camera motion is also available through engine.execute with command
view.navigate: pass camera (from ui.inspect) and motion: "pan" | "orbit" | "zoom".
Pan and orbit take delta: [dx,dy] in screen points (each within ±10000); pan also requires
viewport: [width,height] (each 1..1000000). Orbit accepts a world-space pivot with the
viewport size. Zoom takes factor (0.05..20, below 1 zooms in) and an optional world-space
anchor. The returned camera is applied immediately by the UI, without animation,
document edits, undo steps or a success-log entry per movement.
MCP
solidcraft-cli mcp --connect 127.0.0.1:PORT bridges an MCP client (an AI agent) to this
channel; see mcp.md.
Example
{"id":1,"method":"engine.execute","params":{"command":"sketch.create","params":{"plane":"XY"}}}
{"id":2,"method":"engine.execute","params":{"command":"sketch.rectangle.two_point","params":{"p0":[0,0],"p1":[40,30]}}}
{"id":3,"method":"engine.execute","params":{"command":"solid.extrude","params":{"distance":20}}}
{"id":4,"method":"engine.execute","params":{"command":"solid.fillet","params":{"edges":[[0,0,10]],"radius":3}}}
{"id":5,"method":"ui.screenshot","params":{"path":"/tmp/part.png"}}
Preferences → Navigation → View navigator offers Signed axes (the original default) and
Cube. This choice is saved independently of the mouse preset. The control channel selects it
with ui.prefs {"navigator_style":"axes"} or ui.prefs {"navigator_style":"cube"}.
Existing settings without this field keep signed axes. Signed-axis tips choose principal views;
clicking an aligned tip again reverses the view.
Hold Shift during an orbit gesture to snap to the nearest standard or diagonal view. This applies to right-drag, the Orbit navigation tool, and dragging either view navigator. Under the default preset, Shift+middle also orbits with snapping; middle alone pans. Cube faces choose principal views, and corners choose isometric views. The middle of each cube edge belongs to its face. Face names follow the up-axis preference; the small X/Y/Z triad shows world axes. Clicking the same face keeps that view. The inward-pointing arrows orbit by 90 degrees; the curved arrows at the upper right roll the view by 90 degrees. The Home button at the upper left returns to isometric and the lower-right circle fits the model.
The cube is subdued while idle. Hovering its area brightens the cube and navigation bar and reveals the surrounding controls; the picked face or corner gets a pale blue fill and blue outline. Moving away restores the subdued state. Navigation-bar controls also brighten when hovered or when a navigation tool is active.
The cube has six face targets and eight corner targets, with no edge targets. Corner picking regions lie on the six flat faces, preserving sharp corners with no permanent subdivision lines. Hovering a corner highlights its parts on the adjacent visible faces together.
The cube widget occupies a compact 112-point square in the viewport corner. A straight-on cube face is approximately 55 points wide; painting and picking scale together. Face labels are stretched vertically. View clicks ease through intermediate orientations when Navigation animated view changes is enabled (the default), at the configured animation speed.
The cube runs view.navigate through the command engine. Programmatic callers pass camera
and either direction: [x,y,z] in the camera’s up-axis frame or action (left, right, up,
down, clockwise, counterclockwise, home, fit). The result contains camera and fit;
UI callers apply the camera with their animation preference, without editing the design.
Shift-orbit snapping includes 45-degree edge views and isometric corner views, with a lightly damped spring transition and a small boundary margin to prevent flicker.
Command search
ui.palette {text?, open?} opens command search under the ribbon search field; open: false
closes it. ui.start {command: "ui.palette"}, clicking ribbon:search, and S open the same
popup. This is UI routing: engine.execute continues to run commands without opening dialogs.
The query is limited to 4096 Unicode characters, with at most 80 visible results.
shortcut:search and palette:search identify the editor; ribbon:search_popup and
palette:outer identify the popup. Command rows retain menu:<command id>; design rows use
menu:search:design:<index>. Up/Down choose an enabled row, Enter runs it, Tab fills its id or
name, and Esc closes. Typing command.id {"parameter": value} validates JSON live and runs
through the same engine entry as engine.execute. Shift+Enter runs JSON and keeps the query
open. Invalid JSON stays editable and reports the parser’s error. Feature results open their
existing edit tools; parameter results open Change Parameters filtered to that name.
Reference tests
Generated by cargo xtask oracle. Each case in tests/oracle/ is a part described by our own recipe.json. SolidCraft replays the recipe through its command engine and compares the result with expected.json: body count, volume and area (relative tolerance 1e-3 unless the case states another), centre of mass and bounding box (within 1e-3 of the part’s size), face count where the geometry fixes it, and, where the case has a reference.stl, the sampled symmetric (Hausdorff) distance between the two surfaces and the principal second moments of both triangulations. Every rebuilt part is also exported as STEP, validated and read back.
Where the expected values come from (the Source column):
- analytic: closed-form geometry worked out by hand (prisms, cylinders, cones, spheres, tori and Pappus’ theorems, fillets on straight edges with a constant radius or one varying along the edge, sheet-metal bends as annular sectors). The scripts in
tools/oracle-analytic/compute them and give the formulas. - freecad <version>: the same recipe built with FreeCAD’s public Python scripting API by
tools/freecad-oracle/, an independent kernel run as an external program. FreeCAD is not a dependency of SolidCraft. Where the two kernels legitimately build a different shape (for example the corner patch where fillets meet), only volume and area are compared, within the tolerance the case states.
No expected value comes from a commercial CAD product.
83 / 83 cases pass; sources: 62 analytic, 21 freecad. 0 known failure(s) tracked in issues; 0 case(s) without expected values yet.
| Case | Source | Result | Surface distance mm (max / mean) | Method | Notes |
|---|---|---|---|---|---|
| 01-box | analytic | pass | box 40 x 30 x 20 from the origin: V = abc, A = 2(ab + bc + ca) | ||
| 02-cylinder | analytic | pass | cylinder r=15 h=50 on XY: V = pi r^2 h, A = 2 pi r^2 + 2 pi r h | ||
| 03-box-through-hole | analytic | pass | box 60x40x20 minus centred through cylinder r=6: V = abc - pi r^2 c, A = 2(ab+bc+ca) - 2 pi r^2 + 2 pi r c; hole is centred so the centroid stays at the box centre | ||
| 04-revolve-vase | analytic | pass | revolve of a line/3-point-arc profile (XZ, x = radius, z = -sketch y) 360 deg about Z. Pappus/Green: V = 2 pi * int x dA, z_c = int x z dA / int x dA, A = sum over profile edges of 2 pi int x ds (bottom disk r20, frustum r20->25, torus zone of the arc (centre (-16.000000, 33.000000), R=41.109610), frustum r15->18, top disk r18) | ||
| 05-taper-extrude | analytic | pass | square frustum: base 40 at z=0, top a1 = 40 + 230tan10 at z=30 (positive taper grows outward); V = h/3 (A0 + A1 + sqrt(A0 A1)), z_c = h (A0 + 2 sqrt(A0A1) + 3 A1) / (4 (A0 + sqrt(A0A1) + A1)), sides = 4 trapezoids (a0+a1)/2 * h/cos(10) | ||
| 06-symmetric-extrude | analytic | pass | 50 (x) by 20 (z) rectangle on XZ extruded +-15 along Y: box 50x30x20 centred at origin | ||
| 07-fillet-all-edges | analytic | pass | box 40x30x20 with every edge rounded r=3 = inner box (34x24x14) grown by a ball r (Steiner): V = abc + 2r(ab+bc+ca) + pi r^2 (a+b+c) + 4/3 pi r^3, A = 2(ab+bc+ca) + 2 pi r (a+b+c) + 4 pi r^2; faces 6 planes + 12 quarter cylinders + 8 sphere octants | ||
| 08-chamfer | analytic | pass | box 40x30x20 with the 4 top edges chamfered 2: lower box 40x30x18 + top slab with section (36+2u)x(26+2u) at depth u (V, moment by exact polynomial integration); A = bottom + sides(h=18) + top 36x26 + 4 trapezoid bevels of slant 2 sqrt2 | ||
| 09-shell | analytic | pass | 50x40x30 box shelled inward 2 with the top open: box minus cavity [2,48]x[2,38]x[2,30]; A = outer bottom + outer sides + rim + cavity floor + cavity walls | ||
| 10-rect-pattern-holes | analytic | pass | plate 90x70x5 minus 12 through cylinders r=3 at (15+20i, 15+20j), i<4, j<3: V = abc - 12 pi r^2 t, A = 2(ab+bc+ca) + 12(2 pi r t - 2 pi r^2) | ||
| 11-circular-pattern-flange | analytic | pass | annulus R50/r15 h10 minus 6 through cylinders r=5 on a 40 radius: V = pi(R^2-r^2)h - 6 pi rh^2 h, A = 2 pi(R^2-r^2) + 2 pi(R+r)h + 6(2 pi rh h - 2 pi rh^2); 6-fold symmetry keeps the centroid on the axis | ||
| 12-mirror-feature | analytic | pass | block [-30,30]x[0,30]x[0,10] + two bosses r=6 h=15 at (+-15,15) on top (mirror across YZ): V = abc + 2 pi r^2 h, A = 2(ab+bc+ca) + 2 * 2 pi r h | ||
| 13-combine-join | analytic | pass | box [0,40]^2x[0,20] union cylinder r=12 at (40,20) z 0..40: V = box + half-cylinder (x>40, z<20, centroid 40 + 4r/3pi) + full cylinder z 20..40; A = bottom (1600 + pi r^2/2) + box top (1600 - pi r^2/2) + cyl top + 3 full sides + x=40 side minus 24x20 + cyl side (pi r 40 outside half + pi r 20 upper inside half) | ||
| 14-combine-cut | analytic | pass | box [-25,25]^2x[-20,0] minus sphere r=15 at the origin (lower hemisphere removed): V = abc - 2/3 pi r^3, hemisphere centroid z = -3r/8; A = 2(ab+bc+ca) - pi r^2 + 2 pi r^2 | ||
| 15-combine-intersect | analytic | pass | cube [-20,20]^3 intersect sphere R=25: sphere minus 6 disjoint caps of height h=5 (caps cannot overlap: two would need x^2+y^2 > 800 > R^2); V = 4/3 pi R^3 - 6 pi h^2 (3R-h)/3, A = 4 pi R^2 - 6 * 2 pi R h + 6 pi (R^2-20^2) | ||
| 16-sweep | analytic | pass | circle r=3 swept along line 30 (+Z) + tangent R20 quarter arc + line 30 (+X): two cylinders + quarter torus (Pappus: V = pi r^2 R theta, A = 2 pi r R theta; torus centroid at rho = (R^2 + r^2/4)/R * sin(theta/2)/(theta/2) from the bend axis along the bisector) + 2 end disks | ||
| 17-loft | freecad 1.1.4 | pass | 0.013 / 0.0019 (worst at -7.9, -10.8, 31.2) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part.makeLoft | |
| 18-hole-features | analytic | pass | plate 60x40x15 minus: counterbore (r7 x 5 deep + r4 through), countersink (90 deg cone frustum r6->r3 over 3 mm + r3 through), blind r3 hole 10 deep to the shoulder with a 118 deg cone point (height 3/tan59, centroid 1/4 up from the apex); areas: removed top/bottom disks, cylinder walls 2 pi r h, cb floor annulus, cone laterals pi r s | ||
| 19-l-bracket-constrained | analytic | pass | solved L: (0,0)-(60,0)-(60,8)-(8,8)-(8,40)-(0,40); area 60x8 + 8x32, extruded 20: V = A2 h, A = 2 A2 + perimeter*h, centroid of the two rectangles | ||
| 20-slot-constrained | analytic | pass | obround: arc centres (0,0) and (40,0), R6, tangent horizontal lines y=+-6: A2 = 2rL + pi r^2, V = A2 h, A = 2 A2 + perimeter (2L + 2 pi r) h | ||
| 21-draft | analytic | pass | 40x40x30 box, 5 deg draft hinged on the bottom (z=0), pull -Z: square frustum 40 -> 40 + 230tan5; V = h/3 (A0 + A1 + sqrt(A0A1)), sides 4 trapezoids of slant h/cos5 | ||
| 22-split-body | analytic | pass | cylinder r20 z -30..30 split by the plane through the X axis tilted 30 deg (z = y tan30, stays within /z/<=11.6 inside the cylinder): 2 congruent halves (point symmetry), V total = pi r^2 h, A total = cylinder + 2 ellipses pi r (r/cos30) | ||
| 23-bracket-multifeature | freecad 1.1.4 | pass | 0.007 / 0.0010 (worst at 57.3, 49.3, 25.8) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude join, Part makeFillet, Part extrude join, Part extrude cut, Part makeChamfer | |
| 24-user-parameters | analytic | pass | width 60 x depth 40 x height 15 block from the origin with a centred d10 through hole: V = (wd - pi r^2) h, A = 2(wd - pi r^2) + 2(w+d)h + 2 pi r h | ||
| 25-sketch-triangle | analytic | pass | solved triangle (0,0), (50,0), P = t(cos60, sin60) with t = 25 + sqrt(150) from /P-(50,0)/ = 45 (the root nearest the drawn apex); V = (1/2 * 50 * Py) * 5, A = 2 A2 + perimeter*5, centroid = vertex mean | ||
| 26-sketch-belt | analytic | pass | belt: convex hull of circles R20 @ (0,0) and r10 @ (50,0) (external tangents, sin a = (R-r)/d) minus the d8 hole at (50,0), extruded 5 as one body; hull area = (R+r) sqrt(d^2-(R-r)^2) + R^2(pi+2a)/2 + r^2(pi-2a)/2; moments by Green’s theorem over the line/arc boundary | ||
| 27-sketch-trapezoid | analytic | pass | solved trapezoid bottom (-30,0)-(30,0), top (-15,25)-(15,25) minus two d6 holes at (+-18, 8) (symmetric about the axis, 36 apart, 8 above l1); trapezoid centroid y = H(a+2b)/(3(a+b)); V = A2 t, A = 2 A2 + perimeter t | ||
| 28-blind-pocket-fillets | freecad 1.1.4 | pass | 0.009 / 0.0015 (worst at 60.0, 15.1, 13.4) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude cut, Part makeFillet | |
| 29-two-sided-and-partial-revolve | analytic | pass | block [-20,20]x[-25,10]x[0,20] minus the 270 deg revolve (from +X towards +Y) of the 8x8 square r 4..12, z 12..20; the removed section is the annular sector minus the circular segment y > 10 (R^2 acos(d/R) - d sqrt(R^2-d^2), y-moment 2/3 (R^2-d^2)^1.5) that lies outside the block; sector moments (R^3-r^3)/3 (sin, 1-cos); A = box - top opening + floor - front-face notch 2 sqrt(44) x 8 + inner wall r th h + outer wall (th - pi + 2 asin(5/6)) R h + 2 end caps | ||
| 30-fillet-after-shell | freecad 1.1.4 | pass | 0.008 / 0.0025 (worst at 56.3, 35.7, 3.5) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, makeThickness shell, Part makeFillet | |
| 31-fillet-l-all-edges | freecad 1.1.4 | pass | 0.009 / 0.0018 (worst at 7.9, 38.6, 18.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeFillet | |
| 32-boss-plate-fillets | analytic | pass | plate 60x60x8 + boss (r10, z 8..23) with R3 concave base and R2 convex top fillets taken as one solid of revolution: V, z-moment and lateral area from Pappus/Green on its (r,z) profile (quarter-circle fillet arcs); plate top loses the r13 disc | ||
| 33-fillet-tangent-chain | analytic | pass | obround (R8, centres 40 apart) x 10 minus R2 fillet spandrel R^2(1-pi/4) and 1 mm chamfer triangle swept round the whole loop. Swept volumes use Pappus; Z moments account for inward-offset perimeter P(u)=P-2piu: fillet top-depth moment PAd-2pir^4*(19/24-pi/4), A=r^2*(1-pi/4), d=r*(10-3pi)/(3*(4-pi)); bottom chamfer Z moment Pc^3/6-pic^4/12; surfaces use fillet arc centroid inset r-2r/pi | ||
| 34-chamfer-then-fillet | freecad 1.1.4 | pass | 0.009 / 0.0022 (worst at 38.5, 5.6, 19.9) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeChamfer, Part makeFillet | |
| 35-fillet-curved-curved | freecad 1.1.4 | pass | 0.011 / 0.0024 (worst at -9.5, 5.3, 14.6) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude join, Part makeFillet | |
| 36-corner-blend-two-radii | freecad 1.1.4 | pass | 0.012 / 0.0026 (worst at 45.4, 39.2, 26.5) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeFillet | |
| 37-pyramid-apex-blend | freecad 1.1.4 | pass | 0.020 / 0.0029 (worst at -0.0, 0.0, 28.4) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part.makeLoft, Part makeFillet | |
| 38-coincident-cylinders | analytic | pass | union of r10 z0..30 and r10 z20..50 = cylinder r10 h50: V = pi r^2 h, A = 2 pi r h + 2 pi r^2 | ||
| 39-tee-pipe | freecad 1.1.4 | pass | 0.008 / 0.0014 (worst at -9.7, 2.4, 40.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude join, Part extrude cut | |
| 40-box-sphere-torus | analytic | pass | box 60x60x10 (z -10..0) + upper hemisphere r12 + spherical cap h2 below z=-10 (V = pi h^2(3R-h)/3, A = 2 pi R h) - lower half torus R22 r4 (Pappus: V = pi r^2/2 * 2 pi R, centroid -4r/3pi, A = pi r * 2 pi R); top loses disc r12 and annulus 18..26, bottom loses disc r sqrt(44) | faces: bottom, 4 sides, top split by the groove into an annulus (r 12..18) and the outer part, upper sphere, lower cap, torus | |
| 41-tube-radial-holes | freecad 1.1.4 | pass | 0.010 / 0.0012 (worst at 1.9, 19.9, 24.8) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude cut, pattern (tools transformed and re-applied) | |
| 42-overlapping-patterns | freecad 1.1.4 | pass | 0.009 / 0.0010 (worst at -1.0, -10.1, 2.2) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude cut, pattern (tools transformed and re-applied), Part extrude cut, pattern (tools transformed and re-applied) | |
| 43-cut-splits-body | analytic | pass | two boxes 35x20x20 (x 0..35 and 45..80): V = 2abc, A = 2*2(ab+bc+ca) | ||
| 44-shell-l-bracket | analytic | pass | L prism (area 736, perimeter 200) x 20 minus cavity = L shrunk by 2 (SolidCraft’s intersection-join shell convention: inner faces offset by 2 and extended to meet, sharp inner corner at (6,6); area 352) from z=2 to 20. A rounded (arc) join would give V 8368.5487, A 8768.5487, 16 faces | ||
| 45-shell-boss-plate | analytic | pass | plate 60x60x10 + boss r12 h20 minus cavity (box 56x56x8 + cylinder r10 z8..28) under SolidCraft’s intersection-join shell convention (offset faces extended to meet: sharp circular inner edge at r10, z8). A rounded (arc) join would give V 13620.2567, A 14104.4455, 16 faces (torus round r2) | ||
| 46-shell-two-open-faces | analytic | pass | box 50x40x30 minus cavity x3..47, y0..37, z3..30 (3 mm walls, top and front open) | ||
| 47-draft-curved-pocket | freecad 1.1.4 | pass | 0.008 / 0.0019 (worst at 22.1, 21.3, 15.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude cut, PartDesign Draft | |
| 48-pulley | analytic | pass | revolve of the 12-segment (r,z) polygon about Z: V = 2 pi int r dA, Mz = 2 pi int r z dA, A = 2 pi int r ds (Pappus) | ||
| 49-helical-sweep | analytic | pass | true helix coil (screw motion of the axial d4 section, R20, pitch 8, 3 turns): V = turns * 2 pi R * pi a^2; lateral A = turns * 2 pi * int sqrt(r^2 + (p/2pi)^2 (dr/ds)^2) ds (periodic trapezoid, exact to round-off) + two end discs; COM z = pitch*turns/2 | ||
| 50-sweep-tight-bend | analytic | pass | tube r5 along line 20 + R5 quarter bend + line 20: V = pi r^2 L, A = 2 pi r L + 2 pi r^2 (Pappus, valid while bend radius >= r; here equal so the inner side pinches to a line); bend centroid at (R^2 + r^2/4)/R * sinc(pi/4) along the bisector | ||
| 51-loft-rect-circle-offset | freecad 1.1.4 | pass | 0.025 / 0.0030 (worst at -11.7, 15.0, 0.4) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part.makeLoft | |
| 52-circular-pattern-fillet | analytic | pass | disc r40 h8 + 6 x (boss r6 h10 with R2 concave base fillet as a solid of revolution, Pappus/Green on its (r,z) profile) at radius 28; disc top loses 6 discs r8 | ||
| 53-path-pattern | analytic | pass | plate 120x60x5 minus 7 through holes d6 at arc lengths k*17.875031 along the arc through (100,15),(60,45),(20,15) (centre (60,10/3), R 125/3), translated copies | ||
| 54-mirror-body-fillets | analytic | pass | prism h10 of the 60x40 rectangle with four R5 corner rounds and two d8 holes at (+-15,20): V = A h, area = 2A + perimeter*h | ||
| 55-tapped-hole-cosmetic | analytic | pass | plate 40x30x15 minus d6.8 cylinder 12 deep (to the shoulder) and 118 deg drill point cone (h = r/tan59, V = pi r^2 h/3, lateral pi r * r/sin59); cosmetic thread adds no geometry | ||
| 56-holes-on-curved-face | freecad 1.1.4 | pass | 0.009 / 0.0018 (worst at -16.9, 18.5, 0.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, hole tool (cylinder/cone) cut | |
| 57-hole-pattern-sketch-points | analytic | pass | plate 80x50x10 minus 6 counterbored through holes (cb d10 x 5.5, hole d5.5) at x 15/40/65, y 15/35 | ||
| 58-phone-stand | freecad 1.1.4 | pass | 0.007 / 0.0010 (worst at 82.5, 11.7, 6.1) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeFillet, Part extrude cut | |
| 59-enclosure-base | freecad 1.1.4 | pass | 0.008 / 0.0017 (worst at 90.9, 53.0, 6.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeFillet, makeThickness shell, Part extrude join, Part extrude cut | |
| 60-pipe-flange | freecad 1.1.4 | pass | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part revolve, Part extrude cut, pattern (tools transformed and re-applied), Part makeChamfer, Part makeFillet | No reference.stl: over 409600 bytes even at 0.02 mm deflection (11380 facets). | |
| 61-shaft-keyway | freecad 1.1.4 | pass | 0.008 / 0.0019 (worst at 12.2, 2.8, 72.1) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part revolve, Part makeChamfer, Part extrude cut | |
| 62-heat-sink | analytic | pass | base 60x50x5 + 15 fins 1.5x50x25 at x = 2 + 4k: sums of boxes; fin ends coplanar with the base ends | faces: bottom, x=0, x=60, y=0 and y=50 (each merged with the fin ends), 16 base-top strips, 15x3 fin faces | |
| 63-hinge-knuckle | freecad 1.1.4 | pass | 0.008 / 0.0016 (worst at 28.0, 36.9, 4.7) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude join, Part extrude cut | |
| 64-sketch-tangent-arc-chain | analytic | pass | solved sketch: 80x20 rectangle whose top is 4 tangent R12 arcs on 20 mm chords alternating up/down (net area 1600); prism h3: V = A h, area = 2A + perimeter*h, Green’s theorem for centroid | ||
| 65-sketch-gear-tooth | freecad 1.1.4 | pass | 0.008 / 0.0017 (worst at -6.5, 39.5, 0.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Sketcher-solved sketch, Part extrude | |
| 66-sketch-linkage | analytic | pass | solved sketch: hull of circles R12 at (0,0) and R8 at (60,0) (outer tangent lines, cos a = 4/60) minus holes d12, d8 (concentric) and d4 at (30,+-4) (symmetry keeps radii equal); prism h6 | ||
| 67-sm-base-flange | analytic | pass | plate 100 x 60 x t, t = 2.5 (Steel (mm) rule), thickness along +Z: V = 10060t, A = 2(ab+bc+ca) | ||
| 68-sm-edge-flange | analytic | pass | extrusion along x (100) of the YZ section: base y 5..60 x t; 90 deg bend annulus R 2.5..5 about (y 5, z 5); flange y 0..2.5, z 5..20. Setback (R+t)tan45 = 5 (inside bend position, outer-face height datum: outer face of flange reaches z = 20); V = L*A_sec, A = 2 A_sec + L P_sec | trim/setback and height datum are SolidCraft’s documented edge-flange conventions (sheet.rs add_flanges) | |
| 69-sm-tray | analytic | pass | tray: base 90 x 50 x 2.5 (each edge trimmed by the setback 5); four 90 deg bends R 2.5..5 along the trimmed edges (90, 50, 90, 50 long); four walls 2.5 thick, z 5..25, each running on past its bend by ext = (R+t) - (t + gap/sqrt2) = 0.732233047 at both ends; open corners. A = sum of prisms - 2 x joints | corner treatment is SolidCraft’s convention (sheet.rs close_corners: panels extended until their inner corners are the rule gap = t apart; no corner fill, open corner relief below the walls) | |
| 70-sm-flange-45 | analytic | pass | as 68 with a 45 deg bend: setback (R+t)tan22.5 = 2.071068, base y 2.071068..60, bend annulus 2.5..5 over 45 deg about (y 2.071068, z 5), flat leg 15 - setback = 12.928932 turned 45 deg; extrusion along x 100 | SolidCraft convention: flange height (15) is measured along the flange’s outer face from the virtual sharp at the original edge (y 0, z 0), so the outer top corner is at (y, z) = (-15 cos45, 15 sin45). | |
| 71-sm-custom-rule | analytic | pass | U channel, rule t 1.5, R 3, K 0.33 (K unused folded): base y 4.5..45.5 x 1.5; two 90 deg bend annuli R 3..4.5; flanges y 0..1.5 and 48.5..50, z 4.5..20; extrusion along x 80 | the rule edit applies to the whole sheet body (base built before it is 1.5 thick too, as the recipe’s edge picks at z = 1.5 state) | |
| 72-sm-contour-flange | analytic | pass | contour flange, width 50 along +Y, XZ section: plate x 0..35 z 0..2.5; bend R 2.5..5 about (35, 5) (sketch corner = outer virtual sharp, setback R+t); wall x 37.5..40 z 5..27.5; bend R 2.5..5 about (42.5, 27.5) (sketch corner = inner virtual sharp, setback R); plate x 42.5..70 z 30..32.5 | SolidCraft convention: the sketch lines are the sheet face away from the material (material right of travel); thickness grows to the material side | |
| 73-sm-hem | analytic | pass | 68’s part (height 20) plus a flat hem on the flange top edge: 180 deg bend, inner radius = hem gap 0.01, outer 2.51, about (y 2.51, z 20), folded toward +y (inside); hem leg 8 - (0.01 + 2.5) = 5.49: y 2.52..5.02, z 14.51..20; extrusion along x 100 | SolidCraft conventions: hem gap 0.01 mm (rule hem_gap), hem length measured to the outer face of the hem bend (top at z 22.51), hem folds over the flange’s inner face. | |
| 74-sm-unfold-refold | analytic | pass | U channel 80x50 (t 2.5, R 2.5, base y 5..45, flanges z 5..20) with the flat-pattern slot x 35..45, y -12..10 refolded: BA = (pi/2)(R + K t) = 5.654866776, so the slot removes base y 5..10, the whole y=0 bend and the flange from z 5 to 5 + (12 - (BA - 5)) = 16.345133224; x-segments 0..35 / 35..45 / 45..80 | unfold/refold maps flat distance through the K-factor neutral layer (K 0.44) | |
| 75-sm-bracket-holes | analytic | pass | base x 0..75, y 5..40, z 0..2.5 (trimmed 5 by each flange’s setback); bend 1 (R 2.5..5 about y 5, z 5) x 0..75; flange 1 y 0..2.5, z 5..30, x 0..80; bend 2 (R 2.5..5 about x 75, z 5) y 5..40; flange 2 x 77.5..80, z 5..15, y 5..40; minus two d6 through holes at (25,20), (55,20) in the base. A = sum of prism areas - 2 x 4 joint areas (75t, 75t, 35t, 35t) + hole walls - hole discs | SolidCraft convention: the second flange trims the base under the first bend, so bend 1 stops at x 75 while flange 1’s panel runs to x 80 (open corner relief, no fill). | |
| 76-sm-thickness-variant | analytic | pass | final rule thickness 3 (bend radius = Thickness = 3): base y 6..60 x 3; bend annulus R 3..6 about (y 6, z 6); flange y 0..3, z 6..20; extrusion along x 100 | the rule’s thickness edits regenerate the whole sheet body; only the final 3 mm state matters | |
| 77-fillet-variable-linear | analytic | pass | box 40x30x20 with its top front edge (y=0, z=20) rounded at a radius running straight from 2 (x=0) to 5 (x=40): sections are quarter rounds, V = abc - (1 - pi/4) L (a^2 + ab + b^2) / 3; moments and the round’s area integrated along the edge (tools/oracle-analytic/cases_fillets.py) | ||
| 78-fillet-variable-to-zero | analytic | pass | as 77 with the radius running from 4 (x=0) down to 0 (x=40), where the round closes to the corner: V = abc - (1 - pi/4) L a^2 / 3; the round is three-sided and the corner at x=40 stays | ||
| 79-fillet-chord-length | analytic | pass | box 40x30x20, top front edge rounded 4 wide across (chord c): r = c / (2 cos 45 deg) = 2 sqrt 2; V = abc - (1 - pi/4) r^2 L, A = 2(ab + bc + ca) - 2 r L + (pi/2) r L - 2 (1 - pi/4) r^2 | ||
| 80-fillet-taper-to-fit | analytic | pass | plate (top 20 -> 2 wide over x 0..40, 15 high) with its top front edge filleted at 3, fit: taper. The radius is the taper law: a clamped cubic B-spline in u = x/40 on 8 equal spans, control radii min(3, 0.9 room) at the Greville abscissae, room = min(20 - 18u, 15) tan 45 deg (top face width, front face height): 3.0000, 3.0000, 3.0000, 3.0000, 3.0000, 3.0000, 3.0000, 3.0000, 3.0000, 2.4750, 1.8000. Sections are quarter rounds; V, moments and areas integrated along the edge | ||
| 81-fillet-spline-radius | analytic | pass | box 40x30x20 with its top front edge (y=0, z=20) rounded at a radius that is a clamped cubic B-spline in u = x/40, knots 0,0,0,0,0.25,0.6,1,1,1,1, control radii 2, 5, 1.5, 4, 1, 3 (r(0) = 2, r(40) = 3). Sections are quarter rounds; V, moments and areas integrated along the edge, exactly per knot span | ||
| 82-thin-extrude-frame | analytic | pass | square frame: the lines’ 40 mm square walled 3 mm inside and 1 mm outside (mitred corners) is 42 x 42 less 34 x 34, 10 mm tall: V = (42^2 - 34^2) * 10, A = 2 (42^2 - 34^2) + 4 * 42 * 10 + 4 * 34 * 10 | ||
| 83-boundary-loop-thicken | analytic | pass | region = 10 x 10 square + half disc r 5 on its top side: A = 100 + 12.5 pi, centroid by moments (half disc’s at 4r / (3 pi) above its diameter); slab of thickness 2 over it (symmetric about z = 0): V = 2A, area = 2A + 2 (30 + 5 pi) |
Not covered by a reference case
| Feature | Tested by |
|---|---|
| Boss (draft, root fillet, hole, ribs) | post, hole and rib volumes (boss_with_hole_fillet_and_ribs) |
| Lip / Groove | band offsets and groove clearance on a shelled box (lip_and_groove_on_a_shelled_box, groove_takes_the_rule_clearance) |
| Snap Fit | arm and catch volume, hook direction (snap_fit_arm_and_catch) |
| Rest | rectangle, round, hollow and drafted pads (rest_pads) |
| Plastic Rules (manage, assign) | library values, Thickness-based values, rule draft as the boss default (plastic_rules_library_edit_and_assign) |
Features
SolidCraft’s commands by area, generated by cargo xtask features from the command registry (solidcraft-cli commands). Planned commands are marked in their rows. Every command runs the same way from the toolbar, a script, solidcraft-cli exec <id> '<json>', the control channel and MCP; parameters are in the command reference (cargo xtask book). Status and plans: ROADMAP.
Sketch
| Command | Id | Toolbar |
|---|---|---|
| Center Point Arc | sketch.arc.center_point | SKETCH › CREATE |
| Tangent Arc | sketch.arc.tangent | SKETCH › CREATE |
| 3-Point Arc | sketch.arc.three_point | SKETCH › CREATE |
| 3D Arc | sketch.arc3d | – |
| AutoConstrain | sketch.auto_constrain | SKETCH › CONSTRAINTS |
| AutoConstrain from datum | sketch.auto_constrain_from_datum | SKETCH › CONSTRAINTS |
| Auto-Project Edges on Reference | sketch.auto_project | – |
| Blend Curve | sketch.blend_curve | SKETCH › MODIFY |
| Break | sketch.break | SKETCH › MODIFY |
| Break Link | sketch.break_link | – |
| Cancel Sketch | sketch.cancel | SKETCH › FINISH SKETCH |
| Centerline | sketch.centerline | SKETCH › CREATE |
| Chain | sketch.chain | – |
| Distance and Angle Chamfer | sketch.chamfer.distance_angle | SKETCH › MODIFY |
| Equal Distance Chamfer | sketch.chamfer.equal_distance | SKETCH › MODIFY |
| Two Distance Chamfer | sketch.chamfer.two_distance | SKETCH › MODIFY |
| Center Diameter Circle (C) | sketch.circle.center | SKETCH › CREATE |
| 3-Point Circle | sketch.circle.three_point | SKETCH › CREATE |
| 3-Tangent Circle | sketch.circle.three_tangent | SKETCH › CREATE |
| 2-Point Circle | sketch.circle.two_point | SKETCH › CREATE |
| 2-Tangent Circle | sketch.circle.two_tangent | SKETCH › CREATE |
| Conic Curve | sketch.conic | SKETCH › CREATE |
| Sketch Constrainer | sketch.constrainer | SKETCH › CREATE |
| Coincident | sketch.constraint.coincident | SKETCH › CONSTRAINTS |
| Collinear | sketch.constraint.collinear | SKETCH › CONSTRAINTS |
| Concentric | sketch.constraint.concentric | SKETCH › CONSTRAINTS |
| Curvature | sketch.constraint.curvature | SKETCH › CONSTRAINTS |
| Equal | sketch.constraint.equal | SKETCH › CONSTRAINTS |
| Fix/UnFix | sketch.constraint.fix | SKETCH › CONSTRAINTS |
| Horizontal/Vertical | sketch.constraint.horizontal_vertical | SKETCH › CONSTRAINTS |
| MidPoint | sketch.constraint.midpoint | SKETCH › CONSTRAINTS |
| Parallel | sketch.constraint.parallel | SKETCH › CONSTRAINTS |
| Perpendicular | sketch.constraint.perpendicular | SKETCH › CONSTRAINTS |
| Polygon | sketch.constraint.polygon | SKETCH › CONSTRAINTS |
| Symmetry | sketch.constraint.symmetry | SKETCH › CONSTRAINTS |
| Tangent | sketch.constraint.tangent | SKETCH › CONSTRAINTS |
| Normal/Construction (X) | sketch.construction | SKETCH › CREATE |
| Copy Sketch Entities | sketch.copy | – |
| Create Sketch | sketch.create | SOLID › CREATE |
| Cut Sketch Entities | sketch.cut | – |
| Delete Sketch Entities | sketch.delete | – |
| Sketch Dimension (D) | sketch.dimension | SKETCH › CREATE |
| Move Dimension Text | sketch.dimension_text | – |
| Drag Sketch Geometry | sketch.drag | – |
| End Sketch Drag | sketch.drag_end | – |
| Draw Dimensioned Sketch Geometry | sketch.draw.dimensioned | – |
| Drawing Size | sketch.drawing_bounds | – |
| Edit Sketch | sketch.edit | – |
| Edit Text | sketch.edit_text | – |
| Ellipse | sketch.ellipse | SKETCH › CREATE |
| Save As DXF | sketch.export_dxf | – |
| Extend | sketch.extend | SKETCH › MODIFY |
| Fillet | sketch.fillet | SKETCH › MODIFY |
| Finish Sketch | sketch.finish | SKETCH › FINISH SKETCH |
| Finish with AutoConstrain | sketch.finish_auto_constrain | SKETCH › FINISH SKETCH |
| Fit Curves to Mesh Section | sketch.fit_curves_to_section | SKETCH › CREATE |
| Constraint Glyphs | sketch.glyphs | – |
| Include 3D Geometry | sketch.include_3d | SKETCH › CREATE |
| Infinite Line | sketch.infinite | – |
| Insert DXF | sketch.insert_dxf | SKETCH › INSERT |
| Insert SVG | sketch.insert_svg | SKETCH › INSERT |
| Inspect Sketch | sketch.inspect | – |
| Intersect | sketch.intersect | SKETCH › CREATE |
| Intersection Curve | sketch.intersection_curve | SKETCH › CREATE |
| Isoparametric Curve | sketch.isoparametric_curve | SKETCH › CREATE |
| Line (L) | sketch.line | SKETCH › CREATE |
| Midpoint Line | sketch.line.midpoint | SKETCH › CREATE |
| 3D Line | sketch.line3d | – |
| Loose Ends | sketch.loose_ends | – |
| Tie Loose Ends | sketch.loose_ends.tie | – |
| Mirror | sketch.mirror | SKETCH › PATTERN |
| Move/Copy Sketch Entities | sketch.move | SKETCH › MODIFY |
| Move 3D Point | sketch.move3d | – |
| Drag Sketch Point | sketch.move_point | – |
| Offset (O) | sketch.offset | SKETCH › MODIFY |
| Break Offset Association | sketch.offset.break | – |
| Sketch Palette Options | sketch.options | – |
| Paste Sketch Entities | sketch.paste | – |
| Circular Pattern | sketch.pattern.circular | SKETCH › PATTERN |
| Rectangular Pattern | sketch.pattern.rectangular | SKETCH › PATTERN |
| Point | sketch.point | SKETCH › CREATE |
| 3D Point | sketch.point3d | – |
| Circumscribed Polygon | sketch.polygon.circumscribed | SKETCH › CREATE |
| Edge Polygon | sketch.polygon.edge | SKETCH › CREATE |
| Inscribed Polygon | sketch.polygon.inscribed | SKETCH › CREATE |
| Project (P) | sketch.project | SKETCH › CREATE |
| Project To Surface | sketch.project_to_surface | SKETCH › CREATE |
| Center Rectangle | sketch.rectangle.center | SKETCH › CREATE |
| Rounded Rectangle | sketch.rectangle.rounded | SKETCH › CREATE |
| 3-Point Rectangle | sketch.rectangle.three_point | SKETCH › CREATE |
| 2-Point Rectangle (R) | sketch.rectangle.two_point | SKETCH › CREATE |
| Redefine Sketch Plane | sketch.redefine | – |
| Sketch Scale | sketch.scale | SKETCH › MODIFY |
| Center Point Arc Slot | sketch.slot.arc_center | SKETCH › CREATE |
| Three Point Arc Slot | sketch.slot.arc_three_point | SKETCH › CREATE |
| Center Point Slot | sketch.slot.center_point | SKETCH › CREATE |
| Center to Center Slot | sketch.slot.center_to_center | SKETCH › CREATE |
| Overall Slot | sketch.slot.overall | SKETCH › CREATE |
| Snap Inference | sketch.snap | – |
| Solve Sketch | sketch.solve | – |
| Control Point Spline | sketch.spline.control_point | SKETCH › CREATE |
| Control Point Spline (Degree 5) | sketch.spline.control_point_5 | SKETCH › CREATE |
| Fit Point Spline | sketch.spline.fit_point | SKETCH › CREATE |
| Add Spline Point | sketch.spline.insert_point | – |
| 3D Spline | sketch.spline3d | – |
| Spun Profile | sketch.spun_profile | SKETCH › CREATE |
| Text | sketch.text | SKETCH › CREATE |
| Toggle Radius / Diameter | sketch.toggle_diameter | – |
| Toggle Driven Dimension | sketch.toggle_driven | – |
| Transform (G) | sketch.transform | SKETCH › MODIFY |
| Trim (T) | sketch.trim | SKETCH › MODIFY |
| Trim Along Path | sketch.trim.path | – |
Solid modelling
| Command | Id | Toolbar |
|---|---|---|
| Align | solid.align | SOLID › MODIFY |
| Boundary Fill | solid.boundary_fill | SOLID › CREATE |
| Bounding Solid | solid.bounding_solid | SOLID › CREATE |
| Box | solid.box | SOLID › CREATE |
| Chamfer | solid.chamfer | SOLID › MODIFY |
| Coil | solid.coil | SOLID › CREATE |
| Combine | solid.combine | SOLID › MODIFY |
| Cone | solid.cone | SOLID › CREATE |
| Cylinder | solid.cylinder | SOLID › CREATE |
| Draft | solid.draft | SOLID › MODIFY |
| Emboss | solid.emboss | SOLID › CREATE |
| Extrude (E) | solid.extrude | SOLID › CREATE |
| Fillet (F) | solid.fillet | SOLID › MODIFY |
| Hole (H) | solid.hole | SOLID › CREATE |
| Loft | solid.loft | SOLID › CREATE |
| Mirror | solid.mirror | SOLID › CREATE |
| Move/Copy (M) | solid.move | SOLID › MODIFY |
| Offset Face | solid.offset_face | SOLID › MODIFY |
| Circular Pattern | solid.pattern.circular | SOLID › CREATE |
| Pattern on Path | solid.pattern.path | SOLID › CREATE |
| Rectangular Pattern | solid.pattern.rectangular | SOLID › CREATE |
| Pipe | solid.pipe | SOLID › CREATE |
| Press Pull (Q) | solid.press_pull | SOLID › MODIFY |
| Remove | solid.remove | SOLID › MODIFY |
| Replace Face | solid.replace_face | SOLID › MODIFY |
| Revolve | solid.revolve | SOLID › CREATE |
| Rib | solid.rib | SOLID › CREATE |
| Scale | solid.scale | SOLID › MODIFY |
| Shell | solid.shell | SOLID › MODIFY |
| Sphere | solid.sphere | SOLID › CREATE |
| Split Body | solid.split_body | SOLID › MODIFY |
| Split Face | solid.split_face | SOLID › MODIFY |
| Sweep | solid.sweep | SOLID › CREATE |
| Thread | solid.thread | SOLID › CREATE |
| Torus | solid.torus | SOLID › CREATE |
| Web | solid.web | SOLID › CREATE |
Surfaces
| Command | Id | Toolbar |
|---|---|---|
| Blend Surface | surface.blend | SURFACE › CREATE |
| Boundary Surface | surface.boundary | SURFACE › CREATE |
| Extend | surface.extend | SURFACE › MODIFY |
| Surface Extrude | surface.extrude | SURFACE › CREATE |
| Fair Surface | surface.fair | SURFACE › MODIFY |
| Surface Fillet (planned: #769) | surface.fillet | SURFACE › MODIFY |
| Surface Loft | surface.loft | SURFACE › CREATE |
| Match Surface | surface.match | SURFACE › MODIFY |
| Offset Surface | surface.offset | SURFACE › CREATE |
| Patch | surface.patch | SURFACE › CREATE |
| Reverse Normal | surface.reverse_normal | SURFACE › MODIFY |
| Surface Revolve | surface.revolve | SURFACE › CREATE |
| Ruled Surface | surface.ruled | SURFACE › CREATE |
| Stitch | surface.stitch | SURFACE › MODIFY |
| Surface Sweep | surface.sweep | SURFACE › CREATE |
| Thicken | surface.thicken | SURFACE › CREATE |
| Trim | surface.trim | SURFACE › MODIFY |
| Unstitch | surface.unstitch | SURFACE › MODIFY |
| Untrim | surface.untrim | SURFACE › MODIFY |
Construction geometry
| Command | Id | Toolbar |
|---|---|---|
| Axis Through Edge | construct.axis.edge | SOLID › CONSTRUCT |
| Axis Perpendicular To Face | construct.axis.normal_to_face | SOLID › CONSTRUCT |
| Axis Through Cylinder/Cone/Torus | construct.axis.through_cylinder | SOLID › CONSTRUCT |
| Axis Through Two Planes | construct.axis.two_planes | SOLID › CONSTRUCT |
| Axis Through Two Points | construct.axis.two_points | SOLID › CONSTRUCT |
| Plane Along Path | construct.plane.along_path | SOLID › CONSTRUCT |
| Plane at Angle | construct.plane.at_angle | SOLID › CONSTRUCT |
| Midplane | construct.plane.midplane | SOLID › CONSTRUCT |
| Offset Plane | construct.plane.offset | SOLID › CONSTRUCT |
| Perpendicular Plane | construct.plane.perpendicular | SOLID › CONSTRUCT |
| Tangent Plane | construct.plane.tangent | SOLID › CONSTRUCT |
| Plane Through Three Points | construct.plane.three_points | SOLID › CONSTRUCT |
| Plane Through Two Edges | construct.plane.two_edges | SOLID › CONSTRUCT |
| Point Along Path | construct.point.along_path | SOLID › CONSTRUCT |
| Point At Center Of Circle/Sphere/Torus | construct.point.center | SOLID › CONSTRUCT |
| Point At Edge And Plane | construct.point.edge_plane | SOLID › CONSTRUCT |
| Point Through Three Planes | construct.point.three_planes | SOLID › CONSTRUCT |
| Point Through Two Edges | construct.point.two_edges | SOLID › CONSTRUCT |
| Point At Vertex | construct.point.vertex | SOLID › CONSTRUCT |
Sheet metal
| Command | Id | Toolbar |
|---|---|---|
| Convert to Sheet Metal | sheet.convert | SHEET METAL › CREATE |
| Export Bend Table | sheet.export_bend_table | – |
| Export Flat Pattern as DXF | sheet.export_dxf | – |
| Flange | sheet.flange | SHEET METAL › CREATE |
| Create Flat Pattern | sheet.flat_pattern | SHEET METAL › CREATE |
| Fold | sheet.fold | SHEET METAL › CREATE |
| Hem | sheet.hem | SHEET METAL › CREATE |
| Sheet Metal Rules | sheet.manage_rules | SHEET METAL › MODIFY |
| Path Flange | sheet.path_flange | SHEET METAL › CREATE |
| Perforate | sheet.perforate | SHEET METAL › CREATE |
| Refold | sheet.refold | SHEET METAL › MODIFY |
| List Sheet Metal Rules | sheet.rules | – |
| Unfold | sheet.unfold | SHEET METAL › MODIFY |
Plastic parts
| Command | Id | Toolbar |
|---|---|---|
| Assign Plastic Rule | plastic.assign_rule | PLASTIC › SETUP |
| Boss | plastic.boss | PLASTIC › CREATE |
| Lip | plastic.lip | PLASTIC › CREATE |
| Manage Plastic Rules | plastic.manage_rules | PLASTIC › SETUP |
| Rest | plastic.rest | PLASTIC › CREATE |
| List Plastic Rules | plastic.rules | – |
| Snap Fit | plastic.snap_fit | PLASTIC › CREATE |
Components
| Command | Id | Toolbar |
|---|---|---|
| Activate Component | component.activate | – |
| Break Link | component.break_link | – |
| Capture Position | component.capture_position | SOLID › POSITION |
| Find Identical Copies | component.copies | – |
| Paste Bodies | component.copy_bodies | – |
| New Component | component.create | SOLID › ASSEMBLE |
| Cycle Component Color | component.cycle_color | – |
| Delete Component | component.delete | – |
| Extract to File | component.extract | – |
| Create Components from Bodies | component.from_bodies | – |
| Insert Design | component.insert_design | SOLID › ASSEMBLE |
| List Links | component.links | – |
| List Components | component.list | – |
| Part Properties | component.metadata | – |
| Change Part Properties | component.metadata.set | – |
| Move Bodies to Component | component.move_bodies | – |
| Move Sketches to Component | component.move_sketches | – |
| Paste New | component.paste_new | – |
| Component Physical Properties | component.properties | – |
| Relink Source | component.relink | – |
| Rename Component | component.rename | – |
| Replace with Link | component.replace_with_link | – |
| Revert Position | component.revert_position | SOLID › POSITION |
| Unlock Contents | component.unlock | – |
| Update Links | component.update_links | – |
Assembly joints
| Command | Id | Toolbar |
|---|---|---|
| As-Built Joint | joint.as_built | SOLID › ASSEMBLE |
| Cam | joint.cam | SOLID › ASSEMBLE |
| Joint (J) | joint.create | SOLID › ASSEMBLE |
| Delete Joint | joint.delete | – |
| Drive Joints | joint.drive | SOLID › ASSEMBLE |
| Edit Joint | joint.edit | – |
| Gear | joint.gear | SOLID › ASSEMBLE |
| Inspect Joint | joint.inspect | – |
| Joint Limits | joint.limits | – |
| List Joints | joint.list | – |
| Motion Link | joint.motion_link | SOLID › ASSEMBLE |
| Joint Origin | joint.origin | SOLID › CREATE |
| Rack and Pinion | joint.rack_pinion | SOLID › ASSEMBLE |
| Reach Point | joint.reach | – |
| Rigid Group | joint.rigid_group | SOLID › ASSEMBLE |
| Screw | joint.screw | SOLID › ASSEMBLE |
| Solve Joints | joint.solve | – |
| Sweep Joint | joint.sweep | – |
Configurations
| Command | Id | Toolbar |
|---|---|---|
| Activate Configuration | config.activate | – |
| Configuration Column | config.column | – |
| Configure | config.configure | SOLID › CONFIGURE |
| Preview Configuration | config.preview | – |
| Configuration Row | config.row | – |
| Configuration Rule | config.rule | – |
| Configuration Rules | config.rules | – |
| Display Configuration Table | config.table | SOLID › CONFIGURE |
Timeline
| Command | Id | Toolbar |
|---|---|---|
| Add Recorded Feature | timeline.add | – |
| Compute All | timeline.compute_all | SOLID › MODIFY |
| Copy Features | timeline.copy | – |
| Delete (Delete) | timeline.delete | SOLID › MODIFY |
| Feature Dependents | timeline.dependents | – |
| Edit Feature | timeline.edit | – |
| Export Command Script | timeline.export_script | – |
| New Feature Folder | timeline.group | – |
| Inspect Feature | timeline.inspect | – |
| Move Timeline Items | timeline.move_many | – |
| Move Features to Folder | timeline.move_to_group | – |
| Paste Features | timeline.paste | – |
| Edit Feature | timeline.redefine | – |
| Rename | timeline.rename | – |
| Rename Feature Folder | timeline.rename_group | – |
| Reorder Feature | timeline.reorder | – |
| Reorder Features | timeline.reorder_many | – |
| Roll History Marker | timeline.roll_to | – |
| Move Timeline Marker | timeline.rollback | – |
| Suppress Feature | timeline.suppress | – |
| Remove Feature Folders | timeline.ungroup | – |
Inspect
| Command | Id | Toolbar |
|---|---|---|
| Accessibility Analysis | inspect.accessibility | SKETCH › INSPECT |
| Center of Mass | inspect.center_of_mass | SKETCH › INSPECT |
| Clearance | inspect.clearance | – |
| Enable Component Colors | inspect.colored_components | SOLID › INSPECT |
| Compare Bodies | inspect.compare | – |
| Continuity Analysis | inspect.continuity | SURFACE › INSPECT |
| Curvature Comb Analysis | inspect.curvature_comb | SKETCH › INSPECT |
| Curvature Map Analysis | inspect.curvature_map | SKETCH › INSPECT |
| Draft Analysis | inspect.draft | SKETCH › INSPECT |
| Environment Map Analysis | inspect.environment_map | SKETCH › INSPECT |
| Interference | inspect.interference | SOLID › INSPECT |
| Isocurve Analysis | inspect.isocurve | SKETCH › INSPECT |
| Measure (I) | inspect.measure | SOLID › INSPECT |
| Minimum Radius Analysis | inspect.minimum_radius | SKETCH › INSPECT |
| Parameter Sweep | inspect.parameter_sweep | – |
| Section Analysis | inspect.section | SOLID › INSPECT |
| Silhouette | inspect.silhouette | – |
| Zebra Analysis | inspect.zebra | SKETCH › INSPECT |
Files
| Command | Id | Toolbar |
|---|---|---|
| Export | file.export | – |
| Export Current View as DXF | file.export_view_dxf | – |
| Insert Mesh | file.insert_mesh | SOLID › INSERT |
| Insert STEP | file.insert_step | SOLID › INSERT |
| New Design (Ctrl+N) | file.new | – |
| Open with EasyPrint | file.open_in_easyprint | – |
| Save (Ctrl+S) | file.save | – |
| Save As (Ctrl+Shift+S) | file.save_as | – |
| Save As Mesh | file.save_mesh | INSERT › FILES |
| Send to Slicer | file.send_to_slicer | INSERT › FILES |
| Installed Slicers | file.slicers | – |
Documents
| Command | Id | Toolbar |
|---|---|---|
| Open (Ctrl+O) | doc.open | – |
| Recover Design | doc.recover | – |
| Discard Recovered Design | doc.recovery_discard | – |
| Recoverable Designs | doc.recovery_list | – |
Edit
| Command | Id | Toolbar |
|---|---|---|
| Checkpoint | edit.checkpoint | – |
| Redo (Ctrl+Y) | edit.redo | – |
| Repeat Last Command (Shift+R) | edit.repeat | – |
| Restore Checkpoint | edit.restore_checkpoint | – |
| Undo (Ctrl+Z) | edit.undo | – |
appearance.*
| Command | Id | Toolbar |
|---|---|---|
| Appearance (A) | appearance.assign | SOLID › MODIFY |
| Appearance Library | appearance.library | – |
| List Appearances | appearance.list | – |
| Paint Appearance (planned: #5493) | appearance.paint | RENDER APPEARANCE › APPEARANCE |
| Pick Up Appearance (planned: #5493) | appearance.pick_up | RENDER APPEARANCE › APPEARANCE |
| Replace Appearance (planned: #5493) | appearance.replace | RENDER APPEARANCE › APPEARANCE |
assembly.*
| Command | Id | Toolbar |
|---|---|---|
| Bill of Materials | assembly.bom | SOLID › INSPECT |
body.*
| Command | Id | Toolbar |
|---|---|---|
| Rename Body | body.rename | – |
browser.*
| Command | Id | Toolbar |
|---|---|---|
| Collapse All | browser.collapse_all | – |
| Expand All | browser.expand_all | – |
| New Group | browser.group | – |
| Move Browser Items | browser.move | – |
| Order Browser Items | browser.order | – |
| Rename Group | browser.rename_group | – |
| Ungroup | browser.ungroup | – |
| Show/Hide | browser.visibility | – |
canvas.*
| Command | Id | Toolbar |
|---|---|---|
| Calibrate Canvas | canvas.calibrate | – |
| Decal | canvas.decal | SKETCH › INSERT |
| Delete Canvas | canvas.delete | – |
| Edit Canvas | canvas.edit | SKETCH › INSERT |
| Canvas | canvas.insert | SKETCH › INSERT |
| Canvas to Sketch | canvas.to_sketch | – |
contact.*
| Command | Id | Toolbar |
|---|---|---|
| Check Contact | contact.check | – |
| New Contact Set | contact.create | SOLID › ASSEMBLE |
| Disable Contact | contact.disable_all | SOLID › ASSEMBLE |
| Edit Contact Set | contact.edit | – |
| Enable All Contact | contact.enable_all | SOLID › ASSEMBLE |
| Enable Contact Sets | contact.enable_sets | SOLID › ASSEMBLE |
| List Contact Sets | contact.list | – |
diagnostics.*
| Command | Id | Toolbar |
|---|---|---|
| Crash Report | diagnostics.crash_report | – |
| Crash Reports | diagnostics.crash_reports | – |
| Delete Crash Reports | diagnostics.crash_reports.clear | – |
| Crash Test | diagnostics.crash_test | – |
document.*
| Command | Id | Toolbar |
|---|---|---|
| Inspect Design | document.inspect | – |
| Design Properties | document.metadata | – |
| Remove Design Property | document.metadata.remove | – |
| Change Design Properties | document.metadata.set | – |
| Design Revision | document.revision | – |
| Change Design Units | document.units | – |
engine.*
| Command | Id | Toolbar |
|---|---|---|
| List Commands | engine.commands | – |
| Describe Command | engine.describe | – |
explode.*
| Command | Id | Toolbar |
|---|---|---|
| Exploded View | explode.create | SOLID › ASSEMBLE |
| Delete Exploded View | explode.delete | – |
| List Exploded Views | explode.list | – |
| Show Exploded View | explode.show | – |
expr.*
| Command | Id | Toolbar |
|---|---|---|
| Evaluate Expression | expr.evaluate | – |
face.*
| Command | Id | Toolbar |
|---|---|---|
| Delete Face | face.delete | SOLID › MODIFY |
frame.*
| Command | Id | Toolbar |
|---|---|---|
| Frame Member | frame.member | WELDMENTS › CREATE |
| Insert Profile | frame.profile.insert | – |
| Profile Library | frame.profile.list | – |
| Profile Properties | frame.profile.properties | – |
library.*
| Command | Id | Toolbar |
|---|---|---|
| Apply Local Library Entry | library.apply | – |
| Export Design Library | library.export | – |
| Inspect Local Library | library.inspect | – |
material.*
| Command | Id | Toolbar |
|---|---|---|
| Physical Material | material.assign | SOLID › MODIFY |
| New Physical Material | material.create | – |
| Delete Physical Material | material.delete | – |
| Edit Physical Material | material.edit | – |
| Inspect Physical Material | material.inspect | – |
| List Materials | material.list | – |
| Directional Elastic Modulus | material.properties.directional | – |
| Validate Physical Material Data | material.properties.validate | – |
mesh.*
| Command | Id | Toolbar |
|---|---|---|
| Convert Mesh | mesh.convert | – |
model.*
| Command | Id | Toolbar |
|---|---|---|
| List Edges | model.edges | – |
| List Faces | model.faces | – |
| List Threads | model.threads | – |
motion.*
| Command | Id | Toolbar |
|---|---|---|
| Delete Motion Study | motion.delete | – |
| Export Motion Study | motion.export | – |
| Export Motion Animation | motion.export_animation | – |
| Motion Interference | motion.interference | – |
| List Motion Studies | motion.list | – |
| Play Motion Study | motion.play | – |
| Plot Motion Study | motion.plot | – |
| Motion Study | motion.study | SOLID › ASSEMBLE |
| Motion Trace | motion.trace | SOLID › ASSEMBLE |
| Edit Motion Trace | motion.trace.edit | – |
| Keep Motion Trace | motion.trace.keep | – |
occurrence.*
| Command | Id | Toolbar |
|---|---|---|
| Paste (Instance) | occurrence.copy | – |
| Drag Occurrence | occurrence.drag | – |
| Ground | occurrence.ground | – |
| Move Occurrence | occurrence.move | – |
| Occurrence Physical Properties | occurrence.properties | – |
| Move to Component | occurrence.reparent | – |
parameters.*
| Command | Id | Toolbar |
|---|---|---|
| Add User Parameter | parameters.add | – |
| Change Parameters | parameters.change | SOLID › MODIFY |
| Comment Parameter | parameters.comment | – |
| Complete Expression | parameters.complete | – |
| Delete Parameter | parameters.delete | – |
| Export Parameters | parameters.export | – |
| Favourite Parameter | parameters.favorite | – |
| Parameter Graph | parameters.graph | – |
| Import Parameters | parameters.import | – |
| List Parameters | parameters.list | – |
| Move Parameter | parameters.move | – |
| Rename Parameter | parameters.rename | – |
| Parameter Users | parameters.users | – |
parts.*
| Command | Id | Toolbar |
|---|---|---|
| Insert Fastener | parts.fastener | SOLID › INSERT |
| Insert Part | parts.insert | – |
| Standard Parts | parts.library | – |
| Change Part Size | parts.resize | – |
render.*
| Command | Id | Toolbar |
|---|---|---|
| Layered Surface Aging | render.aging.describe | – |
| Render Backends | render.backends | – |
| Render All Shots (planned: #5493) | render.batch | RENDER › BATCH |
| Frame Render Subject | render.camera.fit | – |
| Inspect Render Camera | render.camera.get | – |
| Set Render Camera | render.camera.set | – |
| Cancel Render | render.cancel | – |
| Compare Images (planned: #5493) | render.compare | RENDER › RESULT |
| Lens and Creative Effects | render.effects.describe | – |
| Export Render Image | render.export | – |
| Inspect HDR Source | render.hdr.inspect | – |
| Apply Library Opaque Appearance | render.library.appearance.apply | – |
| Inspect Render Library Resource | render.library.describe | – |
| Apply Built-in HDR Environment | render.library.environment.apply | – |
| Browse Render Library | render.library.list | – |
| Prepare Physical Reference | render.library.physical.prepare | – |
| Inspect Original Surface Map | render.library.preview | – |
| Apply Authored Studio Lights | render.library.studio.apply | – |
| Prepare Authored Studio Lights | render.library.studio.prepare | – |
| Save Surface Binding | render.library.surface.bind | – |
| Prepare Authored Surface Maps | render.library.surface.prepare | – |
| Remove Surface Binding | render.library.surface.unbind | – |
| Find Render Library Targets | render.library.targets | – |
| Solo Light (planned: #5493) | render.lights.solo | RENDER SCENE › LIGHTS |
| Save Optical Binding | render.optics.bind | – |
| Remove Optical Binding | render.optics.unbind | – |
| Check Render Request | render.preflight | – |
| Apply Native Studio | render.preset.apply | – |
| Review Native Studio | render.preset.resolve | – |
| Review Fixed Sampling Budget | render.quality.prepare | – |
| Retain Displayed Render Result | render.result.pin | – |
| Regrade Linear Master | render.result.regrade | – |
| Inspect Render Scene | render.scene | RENDER › SCENE |
| Apply Render Camera | render.settings.apply | – |
| Save Render Settings with HDR | render.settings.capture_hdr | – |
| Render Settings | render.settings.get | – |
| Save Render Settings | render.settings.set | – |
| Duplicate Render Shot | render.shot.duplicate | – |
| Remove Render Shot | render.shot.remove | – |
| Rename Render Shot | render.shot.rename | – |
| Save Physical Render Background | render.stage.backdrop | – |
| Remove Render Stage | render.stage.clear | – |
| Save Render Ground | render.stage.ground | – |
| Render Image | render.start | RENDER › IMAGE |
| Render Progress | render.status | – |
select.*
| Command | Id | Toolbar |
|---|---|---|
| Clear Selection | select.clear | – |
| Select | select.set | – |
selection.*
| Command | Id | Toolbar |
|---|---|---|
| Delete | selection.delete | – |
view.*
| Command | Id | Toolbar |
|---|---|---|
| Ambient Occlusion | view.ambient_occlusion | – |
| Restore Named View | view.apply | – |
| Back View (Ctrl+2) | view.back | – |
| Bottom View (Ctrl+6) | view.bottom | – |
| Delete Named View | view.delete | – |
| Fit (F6) | view.fit | – |
| Front View (Ctrl+1) | view.front | – |
| Graphics Preset | view.graphics_preset | – |
| Grid | view.grid | – |
| Ground shadow | view.ground_shadow | – |
| History layout | view.history_layout | – |
| Home view | view.home | – |
| Isometric View (Ctrl+7) | view.isometric | – |
| Workspace layout | view.layout | – |
| Left View (Ctrl+3) | view.left | – |
| List Named Views | view.list | – |
| Navigate View | view.navigate | – |
| Navigation bar | view.navigation_bar | – |
| Normal to Selection | view.normal_to | – |
| Perspective view | view.perspective | – |
| Rename Named View | view.rename | – |
| Render Stage | view.render_stage | – |
| Right View (Ctrl+4) | view.right | – |
| New Named View | view.save | – |
| Shots Strip | view.shots_strip | RENDER CAMERA › SHOTS |
| Continuous orbit | view.spin | – |
| Navigation tool | view.tool | – |
| Top View (Ctrl+5) | view.top | – |
| Visual style | view.visual_style | – |
STEP import
SolidCraft reads STEP (ISO 10303-21; AP203, AP214 and AP242 geometry) with its own reader in crates/kernel/src/step_in (written from the public standards; no third-party STEP code). A STEP file becomes an Import base feature on the timeline: it holds the file’s B-rep (the STEP text is kept in the design, so the design reopens without the file) and later features — fillets, holes, cuts sketched on its faces, shells — build on its bodies.
- Open: File → Open (
doc.open) accepts.step/.stpin any case and starts a new design;solidcraft part.step;solidcraft-cli eval part.step,solidcraft-cli run script.json --in part.step; the MCPopentool. - Insert into the current design: File → Insert STEP… or SOLID → INSERT (
file.insert_step {path}). - Drag and drop a STEP file on the window: inserted when the design has features, opened otherwise.
Imports are components
Every import (STEP, IGES, 3MF, STL, OBJ; inserted, dropped, opened as a new design, or run as file.insert_step, file.insert_mesh or doc.open from a script, the CLI or MCP) becomes a new component named after the file (bracket.step → component bracket), placed in the active component at the file’s origin. The Import feature belongs to it, so the file’s bodies never land loose next to your own. Opening a file as a design makes the component too, so the browser shows the file’s name. The command’s reply reports it: component (id), component_name and occurrence. With the top level (or any component above the part) active, an extrude, revolve or press-pull from a sketch on a part’s face cuts or joins that part’s body in its own component, wherever the part is placed, and every placement of the part shows the change. It is refused, with the reason, for a part linked to another design and for a face on a further placement of a part placed more than once (activate that placement to change it there).
A STEP assembly keeps its structure: each product under the file’s root becomes a component nested under the import’s, named after the product, at the place the file gives it. IGES groups and subfigures become nested components the same way. A part (or sub-assembly) the file places more than once becomes one component with an occurrence per placement: the Import feature builds the part once and skips the file’s later copies (skip in the feature, format 22), and the occurrences show it everywhere else. Placements that are scaled, mirrored or otherwise non-rigid keep their geometry in place in the parent component. The reply adds subcomponents (how many components were made below the import’s) and rigid_joints.
The Browser reuses body, sketch and construction-plane membership, child lists and resolved placements while the camera moves. Large assemblies keep their tree open without resolving every part and folder again on each frame. Geometry edits, assembly changes and uncaptured or reverted positions refresh those queries; selection, visibility and locking are read live.
Hover picking pauses while the mouse moves the view and resumes when navigation stops. A 3D mouse moving the view leaves it on: the pointer hand is free, so preselection and clicks keep working while the other hand turns the view. Picking uses the current camera and the cached body bounds directly in model order, so finding each body’s bounds does not scan the assembly again.
Hovering an assembly’s Browser row retains its whole-body highlight buffers during rotation, panning and zoom; the GPU transforms those buffers with the camera. The origin and construction widgets resize independently. Whole-body highlight generation scans each mesh once rather than scanning it again for every face. Sketch profile feedback still updates its projected curves and lift toward the eye as the view changes.
The contents of an imported assembly are rigid by default: rigid joints (a rigid group, its glyphs not drawn) hold each part where the file put it in its parent, so moving the import’s component moves everything, and an inner part doesn’t move on its own; joint degrees of freedom count the whole import as one body. To joint the parts one by one, release them with Unlock Contents on the component’s browser menu (component.unlock {component}), one undo step. Undo takes a whole import away in one step.
Designs saved before imports made components keep their Import where it is (no component is made for it) and rebuild as they did.
Read: manifold solid breps (with voids), faceted breps, shell-based surface models; planes, cylinders, cones, spheres, tori, B-spline and rational B-spline surfaces (with knots, Bézier, uniform, quasi-uniform), surfaces of revolution and linear extrusion, trimmed surfaces; lines, circles, ellipses, hyperbolas and parabolas (as exact rational curves, which export writes back as exact rational B-splines), B-spline curves, polylines, trimmed/surface/seam curves; length and angle units (SI prefixes, conversion-based units such as inches); product names (meaningful solid names are kept, placeholders use part names; names below chooses); surface colours; assemblies (next-assembly-usage occurrences placed by item-defined transformations, and mapped items) — the product tree is kept in the feature and becomes the import’s components (above). Files are untrusted: sizes, entity counts and nesting are capped and every kernel call is guarded. What cannot be read becomes a warning on the feature (a face left out gives an open body); not read yet: offset surfaces, pcurve-only edges.
Extruded circular and elliptical faces keep their profile’s parameter seam outside the face trim, or on an explicit seam edge. Narrow faces such as rounded gear tooth tips therefore stay narrow through the full thickness instead of wrapping around the supporting ellipse in the middle.
Files from other CAD systems
Files from different systems differ in which way is up and in what they call their solids. doc.open, file.insert_step and the MCP open tool take two options for STEP and IGES files; without them a file comes in as written.
up:"z"(the default) keeps the file’s axes. Many systems write Y up, so such a part lies on its back in SolidCraft’s Z-up space."up": "y"turns the import’s component a quarter turn about X (+90°: Y becomes Z), as the window’s “Stand it up” offer does, so an assembly’s parts and instances turn together. The import and the turn are one undo step, the reply names the turned occurrence asstand_up, and only the file’s component turns, not what is already in the design. The window offers to stand a part up after a STEP file opens unless the call said which way is up (up: "z"as well as"y") or the file came from SolidCraft, which already writes Z up. A STEP reply’ssystemis the originating system the file’s header names (SolidCraftfor SolidCraft’s own exports). A header that names SolidWorks (in FILE_NAME’s originating system or preprocessor version, or in FILE_DESCRIPTION; for IGES, the start of the global section’s native system ID, which becomes the originating system) says the file has Y up: the reply carriesexporter(SolidWorks) andexporter_up(y), and the window’s offer then says so (“Exported by SolidWorks, which usually saves Y up”). A file is never turned without asking, because the exporter may have saved it Z up; a component’s right-click menu has Turn Up Axis (Y up → Z up, Z up → Y up: a quarter turn about X, one undo step) for fixing it any time. SolidCraft’s own files sayzand are left alone; any other exporter is unknown, so the offer stays.names:"auto"(the default) keeps meaningful solid names and replaces placeholders such asBody1orBody 2 (3)with the nearest named product, then the occurrence name if no product is named."solid"keeps the solid’s name;"product"chooses the part’s name;"instance"chooses the assembly occurrence (Post-2), falling back to the product. If the chosen name is missing, the solid’s name remains. Repeated names are numbered (Post,Post (2)). Names are stored with the Import feature and survive reopening the design. Files without names or assembly metadata retain generated body names and a flat structure.- Vertices off their curves: some systems write an edge’s vertex a little beside the end of its curve, often further off than the accuracy the file declares (
UNCERTAINTY_MEASURE_WITH_UNIT). A vertex up to the file’s own tolerance off, or up to 1/1000 of the model’s size (never more than 1 mm), is read as that end of the edge: the curve’s end is moved onto the vertex. A vertex further off leaves its face out, with a warning that says how far off it is.
{"command": "file.insert_step", "params": {"path": "frame.step", "up": "y", "names": "instance"}}
Imported appearances
Imported body colours and face colours are retained through later features. Body colours remain the fallback under explicit design appearances and materials; face colours follow persistent face names. A removed face has no colour assignment to display on the resulting body.
IGES
IGES 5.3 files (.igs/.iges) open, insert and drop the same way: crates/kernel/src/iges_in.rs (written from the published specification) restates the file as STEP and the STEP reader reads it, so the feature holds STEP text. Read: manifold solid B-reps (186) with their shells, faces, loops, edge and vertex lists; trimmed (144) and bounded (143) surfaces, and bare B-spline patches, sewn within their owning group into a solid by their shared edges (an open body when they do not close); groups (402 forms 1, 7, 14 and 15), subfigure definitions (308) and instances (408), retaining names, nesting and placements; lines, circular arcs, ellipses, composite curves, rational B-spline curves; planes (108, 190), cylinders, cones, spheres, tori, rational B-spline surfaces, surfaces of revolution and tabulated cylinders; transformation matrices; units; colours (314 and the predefined numbers); body names (name properties, else labels). Not read yet: ruled and offset surfaces, curves on surfaces given only in parameter space. Export (file.export to .igs) writes each body as a manifold solid B-rep on the same surfaces a STEP export uses, with its name and colour.
FreeCAD and Rhino
FreeCAD .FCStd and Rhino .3dm files open, insert and drop the same way, in the desktop app, the browser app and the CLI. They are read with cadmpeg (Apache-2.0, crates/io/src/native_cad.rs): cadmpeg detects the format from the bytes, decodes it into its neutral model under resource caps, checks that model and writes it as STEP AP214, which the STEP reader then reads. The feature holds that STEP text, so the design reopens without the original file. The file’s solids come in; its feature history, sketches and parameters do not. Losses cadmpeg reports become warnings on the feature, and a file without an importable solid is refused with a message. cadmpeg runs inside the panic guard. How much of a file is read follows cadmpeg’s support for that format (format support).
Only codecs for open formats are compiled in: the workspace turns off cadmpeg’s default features and enables fcstd, rhino and step (the writer). cadmpeg’s codecs for commercial CAD formats are not built, and neither is its sat codec, which decodes ShapeManager streams with ACIS as one dialect, so ACIS .sat/.sab files are not read yet. crates/io/src/native_cad.rs is the one place cadmpeg is called; a cadmpeg-based STEP reader could be added there later behind a setting, to compare with the STEP reader above without changing it.
Command reference
Browse commands by their names and default keyboard shortcuts. Entries are organized by command group and panel; ungrouped commands follow by area.
For step-by-step instructions, start with the window tour, sketching guide or solid features guide. Practical command guides below explain prerequisites, options and examples in place. Entries without a practical guide link to an overview instead. Contribute a guide, tip, demo or model link.
Expand Automation details for the command ID and parameters used by scripts, the CLI and MCP. In that notation, ? marks an optional parameter and expr means a number or an expression with units.
This catalog is generated from solidcraft-cli commands by cargo xtask book (493 commands).
Practical guides:
SOLID › CREATE
Create Sketch
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: sketch.create
Parameters:
plane: XY|XZ|YZ | construction plane name or id | {origin?: [x,y,z] numbers in mm, default [0,0,0] (not an expression), x_dir, y_dir} (in world coordinates unless frame says otherwise; a named plane is the active component's own) | {face: [world x,y,z], body?: exact world body, index?: face index, path?: complete occurrence ids, fixed?: bool}; new face attachments require a unique certified source (a flat face of a sheet metal body attaches to its panel instead, in the flat pattern's coordinates), fixed:true explicitly freezes the selected plane; offset?: expr, flip?: bool | "x" | "y" (not on a face: sketch the plane from its other side, so the normal and the default extrude direction are reversed; a half turn about the plane's own x or y axis, true picks the one keeping the sketch's y axis upward; offset still moves along the unflipped normal), frame?: world (default) | component (which coordinates an explicit plane is in; the reply's `frame` says which, with a `note` when the active component is moved or turned), project_edges?: bool, name?
Extrude
Command group: SOLID › CREATE
Default keyboard shortcut: E
Turn a flat sketch region into a solid, or add or remove material from an existing body. For example, a rectangle extruded 20 mm becomes a rectangular block.
Before you start
- For a solid extrusion, finish a sketch with at least one closed region. Select the region you want, rather than an individual edge or point.
- You can also select a planar face of an existing body instead of a sketch.
- To add or remove material, an existing body must meet the extrusion. Choose New Body when you want an independent piece.
- Thin Extrude uses sketch curves instead of filled regions; open chains are allowed. This is useful for a wall or a ring.
Options
| Choice | What it does | When to use it |
|---|---|---|
| Profiles | Chooses the filled sketch regions to extrude. A loop inside another loop makes a hole. | Select just the regions you need when a sketch contains several. |
| Distance | Sets how far the shape extends. Values can include units, such as 20 mm, or a named design parameter. | Make a part to a known size. |
| Reverse Direction | Sends the extrusion to the other side of the sketch. | The preview points away from the intended body. |
| Symmetric | Splits the entered total distance equally across the sketch plane. 10 mm extends 5 mm on each side. | Keep the sketch at the middle of a part. |
| Two Sides | Sets a separate distance on each side. | The sketch is between two unequal extensions. |
| Through All | Reaches through the target body. | Cut a hole that must stay open when the body becomes thicker. |
| To Object | Stops at a selected target face, vertex or point. An offset can stop before or beyond that target. | Let the end follow another part of the design. |
| Start | Starts at the sketch, at an offset from it, or from a selected object. | Leave a gap before the extrusion begins. |
| Taper Angle | Slopes the side walls. | Make drafted sides; reduce the angle if the profile’s narrow details cannot support it. |
| Operation | New Body makes a separate piece; Join adds material; Cut removes it; Intersect keeps only the overlap. | Choose the intended result explicitly when the automatic preview is not what you want. |
| Target bodies | Chooses which existing bodies take part in a join, cut or intersection. | Several bodies lie in the extrusion’s path. |
| Thin Extrude | Builds walls along curves; thickness and side control where the material lies. Two-sided walls can have different thicknesses. | Make ribs, walls or rings without drawing both wall boundaries. |
Try it: a 20 mm block
- Create a sketch on the XY plane and draw a closed rectangle.
- Finish the sketch and choose Extrude.
- Select the rectangular region, set Distance to 20 mm, and choose New Body.
- Check the preview’s direction and confirm. You should have one solid block.
- Edit the extrusion in the timeline to change its distance later.
Tips and common mistakes
- Do check the operation before confirming. Automatic operation can join an addition or cut into an existing body; it is not a promise of a separate part.
- Do use Through All for a cut that should follow later thickness changes.
- Don’t expect an open outline to make a filled solid. Close it, or choose Thin Extrude for a wall.
- Don’t assume a symmetric distance is the distance on each side. New symmetric extrusions use the total distance; older saved designs can retain their earlier convention.
- Taper with a two-sided or symmetric extent is not supported. A very steep taper can also be refused rather than creating an invalid result.
Examples and community resources
The bracket starts with a closed L-shaped profile; another extrusion removes the two holes. Its dimensions are controlled by design parameters.
| Resource | What to explore | Author and licence |
|---|---|---|
| Editable bracket recipe | A complete SolidCraft example using an extrusion, a fillet and a cut. | SolidCraft contributors; MIT OR Apache-2.0. |
Contribute a tip, demo, model or screenshot to add a reviewed resource beside this explanation.
See also
Revolve turns a profile around an axis. Sweep carries a profile along a path. Flange creates sheet metal with a thickness rule.
Automation details
Command ID: solid.extrude
Parameters:
distance: expr (or through_all: true); taper?: angle expr (positive: outward, so the far end is larger along the extrusion direction; negative: inward, so the far end is smaller); sketch?: id|name (default: active or last sketch); profiles?: all | [index] | [{curves: [ids]}] (the profile whose outer loop is made of exactly these curves, or pieces of them; found again on every recompute, so it follows parameter changes; islands inside are its holes; an error when no profile or several match) | [{point:[x,y]}] (index and point picks are stored by their curves when that finds the same profile; a pick on a drawn profile that reference geometry, such as the projected outline of the face the sketch is on, cuts into regions takes the whole drawn profile); exact_regions?: bool (each index or point pick is exactly the region it names, as the dialogs send); default: every drawn profile, except that a loop inside another is its hole and a loop inside that hole is material again; face?: [x,y,z] (extrude a planar body face instead of a sketch profile); faces?: [[x,y,z],…] (coplanar faces of one body); face_body?: name (whose face, where faces of several bodies meet at the point); direction?: positive|negative|symmetric (distance is total thickness, split evenly around the start plane); convention?: total_distance|legacy_per_side (default total_distance; legacy preserves older symmetric extents); distance2?: expr (Two Sides: a second distance the other way from the first side, which may be through_all or to; with direction: negative the first side goes against the normal and distance2 along it); start_offset?: expr (along the sketch normal, also with direction: negative); to?: [x,y,z] (To Object: a face, vertex or point; to_offset?: expr past it); from?: [x,y,z] (From Object: start at that face or point); operation?: new|join|cut|intersect|auto (cut into a body, otherwise join existing solids; new in an empty component); targets?: [body] (Join from a face, or from a sketch on a body's face, defaults to that face's body; explicit targets or participants override); name?; body_name?; thin?: true (Thin: a wall along sketch curves instead of the profiles) with curves?: [curve ids] (open chains and closed loops, joined where they meet end to end; default: every drawn curve of the sketch), thickness: expr, side?: center|inside|outside|two_sides (a closed loop's inside is the area it encloses, an open chain's the side it bends toward, a straight line's the left of its first curve as drawn; default center), thickness2?: expr (two_sides: the thickness outside); corners are mitred, crossing walls are one
Revolve
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.revolve
Parameters:
axis: sketch line id | x | y (sketch axes) | X|Y|Z (world) | {type: world, axis: X|Y|Z} (as returned by timeline.inspect) | construction axis name (followed when edited) | {origin, dir} | {edge|face: [x,y,z]} (a straight or circular edge, or a cylinder, cone or torus face: about its axis, followed as the model changes); angle?: expr (default 360 deg); angle2?: expr (Two Sides: the other side's angle); direction?: symmetric (angle split either side, independent of axis direction); convention?: sketch_normal|legacy_axis_start (default sketch_normal; legacy retains older angular starts); to?: [x,y,z] (To Object: turn until that face, vertex or point); sketch?, profiles? | face: [x,y,z] (a planar body face as the profile) and face_body?: name (whose face, where faces of several bodies meet), operation?, targets?, name?, body_name?
Sweep
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.sweep
Parameters:
sketch: profile sketch; profiles?; path_sketch: sketch; path: [curve ids in order]; orientation?: perpendicular (default: square to the path, turning with it) | parallel (as drawn: the profile moves without turning; the path must keep crossing its plane) | [x,y,z] (square to the path, its turn about the path fixed by this direction); twist?: angle expr (the profile turns this much about the path over its length; within 4 turns); scale?: expr (the profile's scale at the end, linear along the path; 0.01…100); rails?: [{sketch?: sketch (default path_sketch), curves: [curve ids in order]}] (guide rails, one or two, each meeting every plane square to the path once along it, from the end nearest the path's start: one rail turns the profile so its point where the rail starts stays on the rail and scales it with the rail's distance from the path; two rails carry it between them, its points where they start staying on both; rails take the default orientation, no twist and no scale, and a path without sharp corners); rail_fit?: scale (default: the profile grows and shrinks evenly) | stretch (only across to the rail, between the rails); operation?, targets?, name?, body_name?
Loft
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.loft
Parameters:
sections: [{sketch, profiles?} | {sketch, point: id} (an apex)] (2…50, in order; matching hole counts, no holes at an apex); rails?: [{sketch, curves: [ids in order]}] (one or two guide rails from the first section to the last, through every section's outline: the loft follows them between the sections; not with sections that have holes); rail_fit?: scale|stretch (default scale: with one rail the sections grow evenly with the rail's distance from their middle, with two with the distance between the rails; stretch: only that way); operation?, targets? (Join excludes hidden bodies from automatic targets; explicit targets or participants may include them), name?, body_name?
Box
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.box
Parameters:
length, width, height: expr; corner?: [x,y,z] | center?: [x,y,z] (numbers in mm or length expressions; default: the origin); operation?, targets?: [body], name?, body_name?
Cylinder
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.cylinder
Parameters:
radius | diameter, height: expr; base?: [x,y,z] (centre of the bottom: numbers in mm or length expressions; default: the origin); axis?: X|Y|Z|[x,y,z] (default Z); operation?, targets?: [body], name?, body_name?
Sphere
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.sphere
Parameters:
radius | diameter: expr; center?: [x,y,z] (numbers in mm or length expressions; default: the origin); operation?, targets?: [body], name?, body_name?
Torus
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.torus
Parameters:
major, minor: expr (radii); center?: [x,y,z] (numbers in mm or length expressions; default: the origin); operation?, targets?: [body], name?, body_name?
Cone
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.cone
Parameters:
radius | diameter, height: expr (the base circle, and the top's distance along the axis; a negative height goes the other way); top_radius? | top_diameter?: expr (the top circle; default 0: a full cone ending in an apex); base?: [x,y,z] (centre of the base circle: numbers in mm or length expressions; default: the origin); axis?: X|Y|Z|[x,y,z] (default Z); operation?, targets?: [body], name?, body_name?
Rectangular Pattern
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.pattern.rectangular
Parameters:
features: [names] | bodies: [names] | components: [names]; dir1: [x,y,z]; count1; spacing1; dir2?, count2?, spacing2?; alignment?, alignment2?: left|center|right (default right; counts include the original); reversed?, reversed2?: bool (Flip)
Circular Pattern
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.pattern.circular
Parameters:
features: [names] | bodies: [names] | components: [names]; axis: X|Y|Z | construction axis name (followed when edited) | {origin, dir} | {edge|face: [x,y,z]} (a straight or circular edge, or a cylinder, cone or torus face: about its axis, followed as the model changes); count; angle? (default 360 deg; partial arcs include both ends); alignment?: left|center|right (default right; original stays fixed); reversed?: bool (Flip; selects the extra side for even centered counts)
Pattern on Path
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.pattern.path
Parameters:
features: [names] | bodies: [names]; path_sketch: sketch; path: [curve ids in order]; count; spacing (between instances) | distance (first to last); orientation?: identical|path; flip?: bool
Mirror
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.mirror
Parameters:
features: [names] | bodies: [names] (combine?: join with the original); plane: XY|XZ|YZ|construction plane name or id|{origin, normal}|{origin, x_dir, y_dir} (origin coordinates may be length expressions; the mirror follows them, or a construction plane)
Hole
Command group: SOLID › CREATE
Default keyboard shortcut: H
Read the solid features guide.
Automation details
Command ID: solid.hole
Parameters:
position: [x,y,z] on a face (numbers, or length expressions the hole follows) | sketch + points: [sketch point ids] (one hole each, perpendicular to the sketch, into the material on whichever side of the sketch plane it is) | sketch alone (every sketch point: standalone points and circle centres); flip?: bool (sketch points: drill to the other side); single?: bool (with one sketch point: a Single hole that follows the point, drilled into the face there); direction?: [x,y,z] (default: into the face, against its outward normal; one that leaves the face is mirrored into it; ignored with sketch points unless single); diameter | thread (a tapped hole: metric major minus pitch or tabulated UNC/UNF, G or Rp cut-tap drill) | clearance: "M6" with fit?: close|normal|loose (ISO 273); depth? (default through all) | to?: [x,y,z] (To Object: drill to that face or point); references?: [{edge: [x,y,z] on a straight edge of the face, distance: expr}] (at most two; the centre goes that far from each edge's line, in the face's plane, and follows the edges; with a numeric position only); type?: simple|drilled|counterbore|countersink; tip_angle?; cb_diameter?, cb_depth?; cs_diameter?, cs_angle?; thread?: "M6" or "1/4-20 UNC" (cosmetic); handedness?: right|left (default right, needs thread for left); thread_depth?: positive length independent of drill depth (default: full useful cylindrical wall; must fit that wall); targets?: [body] (the bodies to drill; holes at the points of a sketch on a body's face default to that body, found along the holes' direction on it alone; explicit targets or participants override, ["*"] every body)
Pipe
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.pipe
Parameters:
path_sketch: sketch; path: [curve ids in order]; diameter: expr; wall?: expr (hollow); operation?, targets?, name?, body_name?
Bounding Solid
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.bounding_solid
Parameters:
bodies?: [names] (default: all); margin?: expr; name?, body_name?
Thread
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.thread
Parameters:
face: [x,y,z] on a cylindrical face; designation?: ISO metric ("M8", "M8x1"; default: the size that fits); length?: expr (default: the whole face); handedness?: right|left (default right); modeled?: bool (default false). Cosmetic: the model is unchanged, model.threads lists it. Modeled: cuts the thread profile into the body; a shaft thicker than the thread is first turned down to its major diameter over the threaded length
Boundary Fill
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.boundary_fill
Parameters:
tools: [bodies]; cells: [[x,y,z] a point inside each cell to fill (a cell: inside some tools, outside the rest)]; operation?: new|join|cut|intersect; remove_tools?: bool
Coil
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.coil
Parameters:
diameter; two of revolutions (or turns), height, pitch; section_size; section?: circular|square; section_position?: inside|center|outside; base?: [x,y,z] (numbers in mm or length expressions; default: the origin); axis?: X|Y|Z|[x,y,z] (default Z); start_angle?; clockwise?: bool; sketch? (a sketch; its plane is the section plane, so its placement sets the radius and start phase), profiles? (default: all; one closed profile without holes); operation?, targets?, name?, body_name?
Rib
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.rib
Parameters:
sketch, curves: [open curve ids, in order]; thickness (across the sketch plane, or across the curves when normal); depth? (default: to the next face); flip?: bool (fill to the other side); direction?: in_plane|normal (grow across the sketch plane, the default, or along its normal)
Web
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.web
Parameters:
sketch, curves: [open curve ids] (one wall each); thickness; depth? (default: to the next face); flip?: bool; direction?: in_plane|normal
Emboss
Command group: SOLID › CREATE
Read the solid features guide.
Automation details
Command ID: solid.emboss
Parameters:
sketch, profiles?; depth; mode?: emboss|deboss; targets?; face?: world point [x,y,z] selecting a planar or complete exterior cylindrical wall (inferred when exactly one supported tangent cylinder matches the selected profiles)
Joint Origin
Command group: SOLID › CREATE
Read the components and joints guide.
Automation details
Command ID: joint.origin
Parameters:
name?, occurrence?: id|name (default: from the pick, or the named face's component), face: [x,y,z] | "<name>" (centre of a planar face) | circle: [x,y,z] | "<name>" (centre of a round end: a point on its wall or rim, the end's centre, or the round face's name as model.faces lists it) | edge: [x,y,z] (a point on an edge of a planar face: the edge's middle, z out of the face, y into it; on?: [x,y,z] a point on the face (default: the larger planar face beside the edge); corner?: [x,y,z] near one end: the corner there, x and y along its two edges into the face; found again by name, so it follows the face as it changes size; z?: [x,y,z] the frame's z instead of the face's normal, along the edge when it runs that way, x chosen from z: an edge or corner picked as it is, kept as a point where it can't be found by name) | point: [x,y,z], z?: [x,y,z]; a circle's z?: [x,y,z] (a round edge's centre with the way out of it: its end by name when that is the frame it shows, else a point there); body?: the named face's body; end?: "<name>" (a circle's end: the face across its rim there; default the end nearer the pick); x?: [x,y,z] (a direction, world) | "<face name>" | {face: [x,y,z]} (the frame's x: a direction, a plane's normal, or the way to another hole's axis; keeps a round origin's turn; a point's x is a direction, laid square to its z, default: chosen from z alone). Faces are kept by name: the origin follows its face, and a circle its end, when the design changes
SOLID › MODIFY
Fillet
Command group: SOLID › MODIFY
Default keyboard shortcut: F
Read the solid features guide.
Automation details
Command ID: solid.fillet
Parameters:
edges: [[x,y,z] point on edge | {body, index}]; radius: expr; body?; type?: constant|curvature (alias g2: curvature-continuous quintic between planar faces along straight edges, one planar end face at each end (including sloped ends); radius sets the equivalent circular setback, not constant curvature; exact fit only)|chord (radius is the width across)|variable (radius at the end nearest start, radius2 at the other; one end may be 0, where the round closes to the corner)|spline (the radius along the edge is a clamped B-spline from the end nearest start: radius is its first control radius, radii the rest, knots its knot vector from 0 to 1; one end may be 0); radius2?; start?: [x,y,z]; radii?: [expr, …]; knots?: [number, …]; mid?: [[position 0…1 from start, radius expr], …] (variable radii between the ends); smooth?: bool (smooth instead of straight between the radii); corner?: rolling|g2 (rolling, the default: the corners where three blended edges meet are pieces of the rolling ball's sphere, tangent to each blend; g2: a patch repeating each blend's surface up to the second order, constant-radius exact fillets only, refused where it cannot be built); fit?: exact|reduce|taper (exact, the default: a radius too large for the geometry around the edges is refused; reduce: where it doesn't fit it is reduced to the largest that does, one radius for edges sharing a face; the smallest used is returned as `radius_applied`, each edge's as `radii_applied: [{edge: [x,y,z], radius}]`, and noted in the feature's warning; taper: along each straight edge between planar faces where it doesn't fit, the radius follows the room there as a smooth spline, down to 0 where a face closes to a point, noted in the feature's warning)
Chamfer
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: solid.chamfer
Parameters:
edges: [[x,y,z] | {body, index}]; distance: expr; distance2?: expr (along the second face) | angle?: expr (from the first face); flip?: bool (which face is first: by default the one facing up most); body?
Combine
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: solid.combine
Parameters:
target: body; tools: [body]; operation?: join|cut|intersect; keep_tools?: bool; new_component?: bool (the result goes into a new component; component_name?)
Scale
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: solid.scale
Parameters:
bodies: [names]; factor: expr | factors: [x, y, z] exprs; origin?: [x,y,z]
Delete Face
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: face.delete
Parameters:
faces: [[x,y,z] points on the faces, or face names from model.faces]; body?; heal?: bool (default true: the neighbouring planes close the gap, as when a rounded edge, hole or boss is taken off; false: the gap stays open and the faces left are a surface body)
Offset Face
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: solid.offset_face
Parameters:
faces: [[x,y,z] points on faces (planar or curved), or face names from model.faces]; distance: expr (positive: outward); body?
Shell
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: solid.shell
Parameters:
faces: [[x,y,z] points on the faces to remove, or face names from model.faces]; thickness: expr; direction?: inside|outside|both; tangent_chain?: bool (also open faces joined smoothly to the picked ones); body?
Draft
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: solid.draft
Parameters:
faces: [[x,y,z] points, or face names from model.faces]; angle: expr; neutral: XY|XZ|YZ|plane name|{origin, normal}; pull?: [x,y,z] (default: neutral plane normal); body?
Split Face
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: solid.split_face
Parameters:
faces: [[x,y,z] points on the faces to split, or face names from model.faces]; body?; and the splitting tool: plane: XY|XZ|YZ|plane name|{origin, normal} — or tool: {body, point} (a face of a body, its surface extended) — or sketch: id or name (its curves, along the sketch's normal)
Split Body
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: solid.split_body
Parameters:
body: name; plane: XY|XZ|YZ|plane name|{origin, normal} — or tool: {body, point} (a face of a body, its surface extended) — or sketch: id, line: curve id (a straight sketch line, along the sketch's normal)
Press Pull
Command group: SOLID › MODIFY
Default keyboard shortcut: Q
Read the solid features guide.
Automation details
Command ID: solid.press_pull
Parameters:
face: [x,y,z] and distance: expr (positive adds material, negative cuts), or edges: [[x,y,z]…] and distance (a fillet)
Move/Copy
Command group: SOLID › MODIFY
Default keyboard shortcut: M
Read the solid features guide.
Automation details
Command ID: solid.move
Parameters:
bodies: [names]; translate?: [x,y,z] (exprs or numbers); axis?: [x,y,z]; angle?: expr; copy?: bool (default false: keeps the original bodies and transforms a new copy instead of moving them in place)
Replace Face
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: solid.replace_face
Parameters:
faces: [[x,y,z] points on planar faces, or face names from model.faces]; target: plane (XY|XZ|YZ|construction plane|{origin, normal}) | target_face: [x,y,z] on a parallel planar face; body?
Align
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: solid.align
Parameters:
bodies: [names]; from: [x,y,z] | "origin" | {snap: vertex|edge_mid|face_center|circle_center, at: [x,y,z]} | from_face: [x,y,z] | from_plane: XY|XZ|YZ|construction or offset plane name or id|{origin, normal}; to: (the same) | to_face: [x,y,z] | to_plane: (a plane as from_plane; the point or face goes onto it along its normal, and the plane may move later: the bodies follow); faces and planes also turn to meet; flip?: bool
Remove
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: solid.remove
Parameters:
bodies: [names] (removed from here on in the timeline)
Compute All
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: timeline.compute_all
No parameter description is provided by the command registry.
Delete
Command group: SOLID › MODIFY
Default keyboard shortcut: Delete
Read the solid features guide.
Automation details
Command ID: timeline.delete
Parameters:
feature: id|name | features: [id|name] | groups: [timeline folder ids] (a folder goes with all its features and nested folders; one undo step; a sketch takes its dependent features with it) → deleted: [ids]; groups?: [folder ids deleted]; failing?: [{feature, error}] (later features that worked and now fail)
Change Parameters
Command group: SOLID › MODIFY
Read the parameters and expressions guide.
Automation details
Command ID: parameters.change
Parameters:
name, expression (number or text), unit?: mm|cm|m|in|ft|deg|rad|""|text (new parameters default to their value's unit; a text parameter's expression is its text, quotes round it optional: sketch texts' words are built from it), comment?, favorite?: bool, new_name?: rename (references follow); or delete: name; or changes: {name: expression, …} (existing parameters, checked together and rebuilt once, all or nothing) → changed?: {name: value} (with changes), recomputed: features rebuilt, restored: features whose earlier result for the same inputs was reused (e.g. a value set back), switched?: [{feature, name, suppressed}] (the configuration rules that switch a feature's suppression and read a parameter whose value changed run in their order, as on activation; driven parameters and restrictions wait for a configuration edit or activation)
Physical Material
Command group: SOLID › MODIFY
Read the solid features guide.
Automation details
Command ID: material.assign
Parameters:
bodies: [names]; material: name (Steel, Aluminum, ABS Plastic, …, or one made with material.create; `material.list`)
Appearance
Command group: SOLID › MODIFY
Default keyboard shortcut: A
Read the solid features guide.
Automation details
Command ID: appearance.assign
Parameters:
bodies?: [names]; faces?: [[x,y,z] on faces] (body?: which body, default the nearest); components?: [names or ids, or occurrence names]; appearance?: library name or stable starter.* ID (appearance.library) | color: "#rrggbb" or [r, g, b] (0–255), opacity? (0–1, default 1); name?: inert copied look label (requires color, mutually exclusive with appearance); pbr?: {metallic:0..1, roughness:0.045..1, dielectric_ior:1..3} (requires color/appearance and opacity 1); a starter ID or registered starter name resolves its recipe before color overrides; omitted pbr retains that response and pbr: null explicitly clears it. Unavailable glass recipes refuse through either selector. Other custom labels with an explicit color retain legacy scalar behavior. Neither appearance nor color (or color: null): clear, so the face follows its body, the body its component, the component its material
SOLID › CONSTRUCT
Offset Plane
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.plane.offset
Parameters:
base: XY|XZ|YZ|plane name, or a planar face {face: [x,y,z], body?, index?, path?}; offset: expr, or through: a point ref (vertex, sketch point, construction point) the plane goes through, parallel to base; name?
Plane at Angle
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.plane.at_angle
Parameters:
base: XY|XZ|YZ|plane name, or a planar face {face: [x,y,z], body?, index?, path?}; axis: X|Y|Z|construction axis name (where it is now)|{origin, dir} | {edge|face: [x,y,z]} (a straight or circular edge, or a cylinder, cone or torus face: about its axis, followed as the model changes); angle: expr; name?
Axis Through Cylinder/Cone/Torus
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.axis.through_cylinder
Parameters:
face: ref (a cylinder, cone or torus face, or a circular edge); name?
Axis Perpendicular To Face
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.axis.normal_to_face
Parameters:
face: ref; point: ref (the axis goes through it, square to the face); name?
Axis Through Two Planes
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.axis.two_planes
Parameters:
a, b: plane or planar face refs; name?
Axis Through Two Points
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.axis.two_points
Parameters:
a, b: point refs (vertices, sketch points, points); name?
Axis Through Edge
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.axis.edge
Parameters:
edge: ref (a straight edge or sketch line); name?
Point At Vertex
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.point.vertex
Parameters:
point: ref (vertex, sketch point, point); name?
Point Through Two Edges
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.point.two_edges
Parameters:
a, b: edge or line refs (where they meet, or come closest); name?
Point Through Three Planes
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.point.three_planes
Parameters:
a, b, c: plane or planar face refs; name?
Point At Center Of Circle/Sphere/Torus
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.point.center
Parameters:
of: ref (circular edge, sketch circle or arc, sphere or torus face); name?
Point At Edge And Plane
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.point.edge_plane
Parameters:
edge: edge or line ref; plane: plane or planar face ref; name?
Point Along Path
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.point.along_path
Parameters:
path: edge or sketch curve ref; t: expr, 0…1 along it (default 0.5); name?
Midplane
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.plane.midplane
Parameters:
a, b: plane or planar face refs (halfway between; bisecting when they meet at an angle); name?
Plane Through Two Edges
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.plane.two_edges
Parameters:
a, b: straight edge or line refs in one plane; name?
Plane Through Three Points
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.plane.three_points
Parameters:
a, b, c: point refs; name?
Tangent Plane
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.plane.tangent
Parameters:
face: ref (cylinder, cone, sphere or torus); at?: [x,y,z] where it touches (default: the face pick); name?
Plane Along Path
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.plane.along_path
Parameters:
path: edge or sketch curve ref; t: expr, 0…1 along it (default 0.5); name? The plane is square to the path there
Perpendicular Plane
Command group: SOLID › CONSTRUCT
Read the solid features guide.
Automation details
Command ID: construct.plane.perpendicular
Parameters:
plane: plane or planar face ref; line: straight edge, sketch line or axis ref; name? The plane holds the line and is square to the plane
SOLID › ASSEMBLE
New Component
Command group: SOLID › ASSEMBLE
Read the components and joints guide.
Automation details
Command ID: component.create
Parameters:
name?, parent?: component id or name (default: the active one); parent_path?: exact parent placement; activate?: bool (default true); grounded?: bool (default: true for the design's first part, false otherwise)
Insert Design
Command group: SOLID › ASSEMBLE
Read the components and joints guide.
Automation details
Command ID: component.insert_design
Parameters:
path: a .solidcraft design → a new component linked to it, holding its solid bodies as placed in that design and a copy of its design properties; name? (default: the design's name); parent?: component id or name (default: the active one); translate?: [x,y,z]; reuse?: bool (default false): insert a free occurrence, reusing an existing link to the same file when available
Joint
Command group: SOLID › ASSEMBLE
Default keyboard shortcut: J
Read the components and joints guide.
Automation details
Command ID: joint.create
Parameters:
type: rigid|revolute|slider|cylindrical|pin_slot|planar|ball, or a relation: parallel|perpendicular|angle|point_on_plane|point_on_line|line_on_plane (ties only some directions or points, made where the parts are; several between two parts add up); a, b: joint origins ({occurrence, face|circle|edge|point: [x,y,z] in world, or face|circle: "<face name>"; z? for a point, end?, x? as joint.origin} | origin name); values?: [angles in deg or expressions, distances]; limits?: [[min, max] | null…]; flip?: bool (default false: the origins are mated, b's z against a's as two faces meet; true: b's z along a's, so two origins given at one point with the same z stay as they are); offset?: mm or an expression; angle?: deg or an expression (an angle relation's tilt from mated; for other joints the default is none, or a half turn where that turns b less, so faces that already meet stay as they are); name?; quiet?: bool (leave out dof and fixed). An expression (text) is kept with the joint and worked out again whenever the design changes, so the joint follows the parameters it names; a joint whose expression no longer works out (a parameter deleted) is left out of the solve with the reason in its errors
As-Built Joint
Command group: SOLID › ASSEMBLE
Read the components and joints guide.
Automation details
Command ID: joint.as_built
Parameters:
type, a: occurrence, b: occurrence (stay where they are); at?: joint origin (default: b's origin) — the joint's frame; one on a or b is kept on its face or hole there, so the joint follows it as the design changes; name?; limits?: [[min, max] | null…]; quiet?: bool
Rigid Group
Command group: SOLID › ASSEMBLE
Read the components and joints guide.
Automation details
Command ID: joint.rigid_group
Parameters:
occurrences: [ids or names] (move together, as they are)
Drive Joints
Command group: SOLID › ASSEMBLE
Read the components and joints guide.
Automation details
Command ID: joint.drive
Parameters:
joint: id|name; value | values: [angles in deg or expressions, distances] (values not given keep theirs; a value driven drops the expression joint.create gave it; clamped to the limits; the other joints of a closed loop follow, and the motion stops where the loop can no longer close → stopped_by_loop); quiet?: bool (leave out dof and fixed) → values: [{name, value, unit: deg|mm, limits}] as joint.list gives them; moved: whether any occurrence moved; reason: why the joint can move nothing (suppressed, its origins lost, or its chain joined to nothing grounded → unconnected: the occurrence named)
Motion Link
Command group: SOLID › ASSEMBLE
Read the components and joints guide.
Automation details
Command ID: joint.motion_link
Parameters:
a, b: joints; ratio (b = ratio · a + offset): b's units per a's (mm per deg for an angle driving a distance); offset?: in b's units (deg for an angle, mm for a distance, or an expression); ia?, ib?: value indices (default 0)
Gear
Command group: SOLID › ASSEMBLE
Read the components and joints guide.
Automation details
Command ID: joint.gear
Parameters:
a, b: joints that turn (revolute), b driven by a; ra, rb: the gears' pitch radii (or tooth counts): mm, an expression, or {circle: [x,y,z], occurrence?}: the round face there, its radius (read again whenever the design changes, so the ratio follows edits); internal?: bool (one is a ring gear: both turn the same way; by default b turns the other way); reverse?: bool; link?: index (replace that motion link) → link, ratio (b's degrees per a's); b keeps where it is, also when the link replaces another (at the current pose)
Rack and Pinion
Command group: SOLID › ASSEMBLE
Read the components and joints guide.
Automation details
Command ID: joint.rack_pinion
Parameters:
a, b: a joint that turns (revolute: the pinion) and one that slides (slider: the rack), in either order, b driven by a; radius: the pinion's pitch radius (mm, an expression, or {circle: [x,y,z], occurrence?}): the rack travels radius × the turn in radians; reverse?: bool; link?: index (replace that motion link) → link, ratio (mm per degree, or degrees per mm); b keeps where it is, also when the link replaces another (at the current pose)
Screw
Command group: SOLID › ASSEMBLE
Read the components and joints guide.
Automation details
Command ID: joint.screw
Parameters:
joint: a cylindrical joint (its turn drives its travel) | a, b: a joint that turns and one that slides, b driven by a; pitch: travel per turn (mm or an expression); reverse?: bool (a left-hand thread); link?: index (replace that motion link) → link, ratio (mm per degree, or degrees per mm); b keeps where it is, also when the link replaces another (at the current pose)
Cam
Command group: SOLID › ASSEMBLE
Read the components and joints guide.
Automation details
Command ID: joint.cam
Parameters:
a: the cam's joint (revolute); b: the follower's joint, a slider (a translating follower) or a revolute (an oscillating one), b driven by a; profile: {sketch: id|name, index?: the profile (default: the largest)} | {edges: [[x,y,z]…] (points on edges of the cam's bodies forming one closed loop, world), occurrence?}: the cam's outline, in a plane square to its axis, read again whenever the design changes; follower?: knife|roller|flat (default knife; flat: a face square to a slider's line); radius?: the roller's radius (mm, an expression or {circle: [x,y,z], occurrence?}: the roller's round face, which is its tip too); tip?: a joint origin where the follower touches: its knife's point or its roller's centre, such as {circle: [x,y,z]} on the roller (needed for an oscillating follower; a translating one's line runs through it, by default along its slider's axis); flip?: bool (ride the cam from the other side than the one the follower is on now); link?: index (replace that motion link) → link, lift: {min, max, rise, unit} (b's travel over a turn of the cam), reverse (the side it rides from), follower_offset (mm: how far a translating follower's line passes from the cam's axis); b keeps where it is, also when the link replaces another (at the current pose)
Enable Contact Sets
Command group: SOLID › ASSEMBLE
Read the solid features guide.
Automation details
Command ID: contact.enable_sets
Parameters:
enabled?: bool (default true) — driving joints and motion studies stop where bodies in one contact set would pass through each other
Enable All Contact
Command group: SOLID › ASSEMBLE
Read the solid features guide.
Automation details
Command ID: contact.enable_all
Parameters:
every occurrence takes part in contact (one set of all)
Disable Contact
Command group: SOLID › ASSEMBLE
Read the solid features guide.
Automation details
Command ID: contact.disable_all
Parameters:
contact off
New Contact Set
Command group: SOLID › ASSEMBLE
Read the solid features guide.
Automation details
Command ID: contact.create
Parameters:
occurrences: [ids or names] (two or more); name?
Motion Study
Command group: SOLID › ASSEMBLE
Read the components and joints guide.
Automation details
Command ID: motion.study
Parameters:
name?; study? (edit an existing one); steps? (timeline length, default 100); keys: [{joint, index?, points: [[step, value], …]}] (values: degrees for angles, mm for distances, or expressions)
Motion Trace
Command group: SOLID › ASSEMBLE
Read the components and joints guide.
Automation details
Command ID: motion.trace
Parameters:
occurrence: id|name, or an occurrence path [ids] (an instance inside sub-assemblies); point: [x,y,z] world point on it, where it is placed now; study: id|name, or joint: id|name with from and to (degrees for angles, mm for distances, or expressions) and index? (which value, default 0); samples? (2…500, default the study's steps + 1, or 100) - the point's path as the study plays or the joint is driven, closed loops following → points: [[x,y,z]…] world positions, samples (taken), stopped_at (the sample where the motion stopped short; the trace ends there), stopped_by_loop, stopped_by_contact (the design is not changed)
Exploded View
Command group: SOLID › ASSEMBLE
Read the solid features guide.
Automation details
Command ID: explode.create
Parameters:
name?; scale? (automatic: each occurrence pushed away from the assembly's centre by scale × its offset, default 1); moves?: [{occurrence, translate: [x,y,z]}] (instead of automatic)
SOLID › POSITION
Capture Position
Command group: SOLID › POSITION
Read the components and joints guide.
Automation details
Command ID: component.capture_position
Parameters:
keeps pending occurrence moves
Revert Position
Command group: SOLID › POSITION
Read the components and joints guide.
Automation details
Command ID: component.revert_position
Parameters:
occurrences?: [ids] (default all); drops selected pending occurrence moves
SOLID › INSPECT
Interference
Command group: SOLID › INSPECT
Read the solid features guide.
Automation details
Command ID: inspect.interference
Parameters:
bodies?: [names] (default: all, in world placement); budget_s?: seconds (default 240, at most 86400: pairs not finished by then are listed in unchecked as "not checked: the time budget … ran out" and the call returns) → interferences: pairs that overlap with the overlap volume, area, bounding box and centre (a pin a hair off the axis of a hole of its size also has near_coaxial: {offset_mm, length_mm}, its volume the crescent the walls share); unchecked: [{a, b, reason}], the pairs that could not be checked, with the reason, and each mesh body (STL, 3MF or OBJ import: not intersected) as {a: name, b: null, reason: "mesh body: not checked"}, so count: 0 is not read as no overlap (checked counts the solid bodies). Bodies are pruned by bounding box first, then by their display meshes (pairs whose meshes stay apart by more than their chord tolerances, neither inside the other, are clear without a boolean), so any number of bodies is fine; the pairs left are intersected exactly, in parallel on desktop, the slowest first; at most 20000 pairs whose boxes overlap are intersected per call (truncated: true and skipped_pairs: n say when more were left out). Also: pairs_candidate (boxes overlap), pairs_cleared (by the meshes), pairs_intersected, pairs_not_checked, budget_exceeded, threads (worker threads; 0: on the calling thread), boolean_ms and tessellate_ms (kernel time summed over the pairs), slowest: [{a, b, ms, boolean_ms, tessellate_ms}] and ms
Bill of Materials
Command group: SOLID › INSPECT
Read the solid features guide.
Automation details
Command ID: assembly.bom
Parameters:
component?: id or name (default the design); levels?: flat (default: every part at any depth, counted through its subassemblies) | top (the component's direct children, subassemblies as one row each); group_by?: ["part_number"] (default; components without a part number get a row per definition) | ["definition"]; group_children?: true (a component with a part number is one row, the parts placed inside it listed under it as children: [{item, part_number, description, revision, each, count, definition}], not as rows of their own); path?: write a .csv or .json file (children as items 1.1, 1.2…); format?: csv|json (default from the path) → rows: [{item, part_number, description, revision, count, definition, components, properties, children?}] sorted by part number, total_occurrences (children included)
Enable Component Colors
Command group: SOLID › INSPECT
Read the solid features guide.
Automation details
Command ID: inspect.colored_components
Parameters:
enabled?: bool (omitted: toggle); temporary component colours, without changing appearances
Measure
Command group: SOLID › INSPECT
Default keyboard shortcut: I
Read the solid features guide.
Automation details
Command ID: inspect.measure
Parameters:
bodies?: [names] (default all) → closed per body; volume_mm3, center_of_mass, density_g_cm3 and mass_g only for closed, consistently oriented bodies; area, bbox and counts for all; total.open_bodies_skipped counts bodies excluded from volume/mass totals; or items: [1-2 selections] (face/edge: type, body, point; index optional) → length/area/volume; diameter, radius, circumference or arc_length/angle_deg of circles, arcs and cylinders; slot sizes; min (distance_mm, from, to), max, center_distance (centre to centre, or from an axis) and angle
Section Analysis
Command group: SOLID › INSPECT
Read the solid features guide.
Automation details
Command ID: inspect.section
Parameters:
plane: XY|XZ|YZ | construction plane name or id | {origin, normal}; offset?: expr along the plane's normal (XY: +Z, XZ: +Y, YZ: +X); at?: expr, the cut's world coordinate on the named plane's axis (XY: Z, XZ: Y, YZ: X), instead of offset; flip?: bool; hatch?: bool (hatch the cut faces, in each body's colour; default true); or clear: true. Returns the cut's origin and normal, and for a named plane its axis and at
SOLID › CONFIGURE
Configure
Command group: SOLID › CONFIGURE
Read the parameters and expressions guide.
Automation details
Command ID: config.configure
Parameters:
params?: [parameter names]; features?: [feature names]; materials?: [body names]; appearances?: [body names] (columns to add; with no rows yet, a "Default" row takes the current values)
Display Configuration Table
Command group: SOLID › CONFIGURE
Read the parameters and expressions guide.
Automation details
Command ID: config.table
Parameters:
→ columns (param:<name> or suppress:<feature>), rows, the active row and whether the design still matches it
SOLID › INSERT
Insert Fastener
Command group: SOLID › INSERT
Read the solid features guide.
Automation details
Command ID: parts.fastener
Parameters:
the Insert Part dialog's command (same as parts.insert): family, size, length?, at?|point?+direction?, name?
Insert STEP
Command group: SOLID › INSERT
Read the import and export guide.
Automation details
Command ID: file.insert_step
Parameters:
path: STEP, IGES, FreeCAD .FCStd or Rhino .3dm file (its bodies join the design as an Import base feature in a new component named after the file, placed in the active component; an assembly's products become nested components, a repeated part one component with an occurrence per placement, all held as one rigid group until component.unlock → component, component_name, occurrence, subcomponents?, rigid_joints?); name? (the feature's and the component's); up?: z|y (default z: the file's axes are kept; y: the file has Y up, so the import's component is turned a quarter turn about X, Y becoming Z → stand_up: its occurrence); names?: auto|solid|product|instance (default auto: keep meaningful solid names, replace placeholders with the nearest named product, else instance; solid: the solid's own name; product: the part's name; instance: the occurrence's name, else the product's); matrix?: [12 or 16 numbers] (where the new component's occurrence sits in the active component, after `up`: a rigid row-major 3x4 rotation-and-translation or 4x4 matrix, flat or as rows, as occurrence.move takes it; a scale, shear or mirror is refused before the file is read); tolerance? (see occurrence.move)
Insert Mesh
Command group: SOLID › INSERT
Read the import and export guide.
Automation details
Command ID: file.insert_mesh
Parameters:
path: .3mf, .stl, .obj, .glb or .gltf file (its meshes join the design as mesh bodies in a new component named after the file → component, component_name, occurrence; a glTF file's node tree becomes nested components held as one rigid group until component.unlock, a mesh placed again another occurrence, its materials the bodies' appearances, and its metres millimetres); name?; up?: z|y for a glTF file (default y: glTF has Y up, so the import's component is turned a quarter turn about X, Y becoming Z → stand_up: its occurrence; z: the file's axes are kept)
SKETCH › FINISH SKETCH
Finish Sketch
Command group: SKETCH › FINISH SKETCH
Read the sketching and constraints guide.
Automation details
Command ID: sketch.finish
No parameter description is provided by the command registry.
Cancel Sketch
Command group: SKETCH › FINISH SKETCH
Read the sketching and constraints guide.
Automation details
Command ID: sketch.cancel
Parameters:
Leaves the sketch without keeping what changed since it was created or opened for editing: the design goes back to how it was then (a new sketch is removed), as one step that Undo brings back. Refused when the undo history no longer leads back to that point (use Undo instead)
Finish with AutoConstrain
Command group: SKETCH › FINISH SKETCH
Read the sketching and constraints guide.
Automation details
Command ID: sketch.finish_auto_constrain
Parameters:
like AutoConstrain, then Finish Sketch
SKETCH › CREATE
Line
Command group: SKETCH › CREATE
Default keyboard shortcut: L
Read the sketching and constraints guide.
Automation details
Command ID: sketch.line
Parameters:
points: [[x,y] | "l1.end" | {at, ref}…] (x and y each a number or an expression over the design's parameters, "W / 2": such a point is fixed where it comes to and follows the parameters; likewise wherever a point argument also takes a point id), closed?: bool, construction?: bool, ids?: [..], infer?: bool (add horizontal/vertical); connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
2-Point Rectangle
Command group: SKETCH › CREATE
Default keyboard shortcut: R
Read the sketching and constraints guide.
Automation details
Command ID: sketch.rectangle.two_point
Parameters:
p0, p1: opposite corners [x,y]; construction?. Returns sides {bottom, right, top, left} (line ids) and corners {bottom_left, bottom_right, top_right, top_left} (point ids); connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
3-Point Rectangle
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.rectangle.three_point
Parameters:
p0, p1: first edge; p2: point on the opposite edge; construction?: bool; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Rounded Rectangle
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.rectangle.rounded
Parameters:
p0, p1: opposite corners [x,y]; radius: expr (the corner radius, less than half the shorter side); construction?. A 2-point rectangle whose four corners are filleted alike: one radius dimension (`param`), the other arcs equal to it. Returns sides {bottom, right, top, left} (line ids) and arcs (the corner arcs: bottom-left, bottom-right, top-right, top-left); connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Center Rectangle
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.rectangle.center
Parameters:
center, corner; construction?: bool. Returns sides {bottom, right, top, left} (line ids) and corners {bottom_left, bottom_right, top_right, top_left} (point ids); connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Center Diameter Circle
Command group: SKETCH › CREATE
Default keyboard shortcut: C
Read the sketching and constraints guide.
Automation details
Command ID: sketch.circle.center
Parameters:
center: [x,y] or point ref, radius | diameter: number; through?: point ref (a point the circle passes through, kept on it: where the tool's second click snapped); construction?: bool; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
2-Point Circle
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.circle.two_point
Parameters:
p0, p1: diameter ends; construction?: bool; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
3-Point Circle
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.circle.three_point
Parameters:
p0, p1, p2; construction?: bool; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
3-Point Arc
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.arc.three_point
Parameters:
start, end, through: [x,y] or point ref (a referenced through point stays on the arc); construction?: bool; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Center Point Arc
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.arc.center_point
Parameters:
center, start, end (counter-clockwise from start) | center, start, sweep: signed degrees; construction?: bool; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Inscribed Polygon
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.polygon.inscribed
Parameters:
center, radius (to vertices), sides, angle?: deg; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Circumscribed Polygon
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.polygon.circumscribed
Parameters:
center, radius (to edge midpoints), sides, angle?: deg; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Edge Polygon
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.polygon.edge
Parameters:
p0, p1: one edge, sides; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Center to Center Slot
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.slot.center_to_center
Parameters:
p0, p1: arc centres, width; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Overall Slot
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.slot.overall
Parameters:
p0, p1: overall ends, width; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Point
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.point
Parameters:
point: [x,y] (each a number or an expression over the design's parameters: the point is then fixed where it comes to and follows them) or a snap: "mid:<line>" (held at the line's midpoint), "on:<curve>:x,y" (on the curve near x,y), {vertex: [x,y,z]} or {on_edge: [x,y,z]} of the model; id?; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Sketch Dimension
Command group: SKETCH › CREATE
Default keyboard shortcut: D
Read the sketching and constraints guide.
Automation details
Command ID: sketch.dimension
Parameters:
entities: [refs] (a line midpoint is "mid:line_id"), type?: auto|distance|horizontal|distance_x|vertical|distance_y|length|radius|diameter|angle, value?: number or expression (default: current; a bare number is in the design's units, e.g. 2 means 2 in in an inch design; give a unit such as "50 mm" to override), or `name = expression` (e.g. "width = 40"; also `#name = expression`), which adds the user parameter `name` with that expression and dimensions with `name` (refused when the name is taken or reserved; the reply's `defined` names it); text_at?: [x, y]. Two points or a line off the axes: with auto, text_at across from them is the distance between them, above or below them horizontal, beside them vertical. Two midpoints: placing text above or below their bounding box measures horizontally, beside it vertically. Two lines: the angle between them (acute, or 180° − it when the value is nearer that) unless text_at picks a sector. A line and a circle or arc: the distance from the line to its edge (the gap, or how far the edge reaches past a line that cuts it; 0 is tangent); dimension the centre point for the distance to the centre. A point and a circle or arc: the distance from the point to its edge (or how far within it a point inside lies). Two circles or arcs: the gap between their edges (or how far they overlap when they cross, or the narrowest gap between them when one lies inside the other); dimension their centre points for the distance between centres. An arc and one of its end points: the arc's length (type arc_length does it from the arc alone). A dimension the sketch already determines is refused (naming what determines it); if_redundant?: "driven" places it as a driven (reference) dimension instead. scale_sketch?: bool (the dimension tool sends true): when this is the sketch's first length dimension and nothing in the sketch fixes a size (no other driving length dimension, nothing fixed or projected), the whole sketch scales about its origin to the value instead of distorting. Returns measured in unit (mm or deg), and scaled: the factor when the sketch was scaled
Normal/Construction
Command group: SKETCH › CREATE
Default keyboard shortcut: X
Read the sketching and constraints guide.
Automation details
Command ID: sketch.construction
Parameters:
curves: [ids], value?: bool (default: construction unless all of them already are)
Midpoint Line
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.line.midpoint
Parameters:
mid: [x,y], point ref or snap ("mid:<line>", "on:<curve>:x,y", model vertex/edge); end: [x,y]; construction?. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Tangent Arc
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.arc.tangent
Parameters:
start: end point of a line or arc ("l1.end"), end: [x,y]; curve?: the curve to be tangent to (default: the one ending at start). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
2-Tangent Circle
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.circle.two_tangent
Parameters:
curves: [two lines/circles/arcs], radius | diameter: expr, near: [x,y] (which of the solutions). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
3-Tangent Circle
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.circle.three_tangent
Parameters:
curves: [three lines/circles/arcs], near: [x,y] (which of the solutions). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Center Point Slot
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.slot.center_point
Parameters:
center: slot centre, end: one arc centre, width. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Three Point Arc Slot
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.slot.arc_three_point
Parameters:
start, through, end: points on the centre arc, width. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Center Point Arc Slot
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.slot.arc_center
Parameters:
center, start: centre arc start, end: direction of the centre arc end (counter-clockwise) or sweep: degrees (0 < sweep < 360), width; returns centre_arc. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Ellipse
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.ellipse
Parameters:
center, major: end of the major axis [x,y], minor: a point the ellipse passes through on the minor side [x,y] | minor_radius. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Fit Point Spline
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.spline.fit_point
Parameters:
points: [[x,y] or point refs…] (2…500): the spline passes through them, periodic?: boolean (default false); periodic cubic fit closes smoothly through at least 3 distinct samples. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Control Point Spline
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.spline.control_point
Parameters:
points: control points (2…500), cubic. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Control Point Spline (Degree 5)
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.spline.control_point_5
Parameters:
points: control points (2…500), degree 5. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Conic Curve
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.conic
Parameters:
start, end, apex: [x,y] or point refs; rho?: 0…1 (default 0.5: parabola). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Centerline
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.centerline
Parameters:
curves: [line ids], value?: bool (default toggles): centerline line type (not in profiles; an axis)
Text
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.text
Parameters:
text: string (\n breaks lines), or text_expression: the words built from text parameters (parameters.add with unit text) and quoted texts joined by + ("Label + '-L'"): the text keeps it, its words follow the parameters (a parameter edit or a configuration row relabels it) and `exprs` returns it as `text`; at: baseline start [x,y], height?: capital height mm (default 5), angle?: deg; at's x and y, height and angle each take a number or an expression over the design's parameters as a string ("plate_width / 2", "label_size"): the text keeps the expression, follows the parameters, and returns it in `exprs`; an expression naming no parameter is the number it comes to, a bare number in one is in the design's units, font?: an installed font's family or full name, or a .ttf/.otf file path (~ and %VAR% expand; default: the built-in Liberation Sans), bold?, italic?: bool (an installed family's bold or italic face; italic without one is slanted), align?: left|center|right (the lines against `at`, or along the path), letter_spacing?: extra mm between letters, line_spacing?: times the font's line distance (default 1), path?: a curve id of the sketch the baseline follows ("": none; `at` and `angle` then do not place the text; the curve becomes construction, a guide the letters stand on), reverse?: bool (the other way along the path). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Sketch Constrainer
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constrainer
Parameters:
entities: [one or two sketch entity ids]: applies the constraint the geometry nearly has (horizontal/vertical, parallel, perpendicular, collinear, coincident, concentric, equal, tangent)
Project
Command group: SKETCH › CREATE
Default keyboard shortcut: P
Read the sketching and constraints guide.
Automation details
Command ID: sketch.project
Parameters:
refs: [{edge: [x,y,z], body?} | {face: [x,y,z], body?} (its edge loops) | {body: name} (silhouette) | {vertex: [x,y,z], body?} | {sketch, curve} | {sketch, point} | "origin" | {axis: X|Y|Z} | {plane: XY|XZ|YZ|name}]. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Intersect
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.intersect
Parameters:
refs: [{body}, {face: [x,y,z], body?}, {edge: [x,y,z], body?}, {sketch, curve}]: section curves (or points) with the sketch plane, linked. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Include 3D Geometry
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.include_3d
Parameters:
refs: like Project (edges, vertices, sketch curves and points); curves out of the sketch plane are flattened onto it. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Project To Surface
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.project_to_surface
Parameters:
curves: [{sketch, curve}…] (curves of other sketches), face: [x,y,z] point on the target face, body?: projected along the active sketch's normal onto the face, as linked 3D curves. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Intersection Curve
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.intersection_curve
Parameters:
a, b: {body} | {face: [x,y,z], body?}: where they meet, as linked 3D curves. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Spun Profile
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.spun_profile
Parameters:
body: name, axis: a line of the active sketch (curve id) | X|Y|Z: the body's outline revolved about the axis, in the sketch plane. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Isoparametric Curve
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.isoparametric_curve
Parameters:
face: [x,y,z] point on the face (the curve passes through it), body?, direction?: u|v|[x,y,z] (default u): linked 3D curve. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Fit Curves to Mesh Section
Command group: SKETCH › CREATE
Read the sketching and constraints guide.
Automation details
Command ID: sketch.fit_curves_to_section
Parameters:
body: name (a mesh or solid body): its section with the sketch plane, fitted with lines and arcs, as normal (unlinked) geometry. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
SKETCH › CONSTRAINTS
Horizontal/Vertical
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.horizontal_vertical
Parameters:
line: id | points: [a, b] (point ids or {midpoint: line_id}; accepted midpoints reuse or create a point with a Midpoint constraint); mode?: horizontal|vertical
Coincident
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.coincident
Parameters:
a, b: points, a point and a curve or sketch axis (x / y: the sketch's own axes, unless a curve has that id; an axis becomes a linked construction line on it, reported in created), or two distinct lines (joins their closest endpoints), or two circles/arcs (the second lies on the first: concentric and equal, `constraints` lists both); any of them may be a model edge {edge: [x,y,z], body?} or vertex {vertex: [x,y,z], body?}, projected into the sketch first (reported in created)
Tangent
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.tangent
Parameters:
a, b: curves or sketch axes (x / y: the sketch's own axes, unless a curve has that id; an axis becomes a linked construction line on it, reported in created); any of them may be a model edge {edge: [x,y,z], body?} or vertex {vertex: [x,y,z], body?}, projected into the sketch first (reported in created)
Equal
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.equal
Parameters:
a, b: lines or circles/arcs; or curves: [2 to 200 lines or circles/arcs], each made equal to the first, in one step; any of them may be a model edge {edge: [x,y,z], body?} or vertex {vertex: [x,y,z], body?}, projected into the sketch first (reported in created)
Parallel
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.parallel
Parameters:
a, b: lines or sketch axes (x / y: the sketch's own axes, unless a curve has that id; an axis becomes a linked construction line on it, reported in created); any of them may be a model edge {edge: [x,y,z], body?} or vertex {vertex: [x,y,z], body?}, projected into the sketch first (reported in created)
Perpendicular
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.perpendicular
Parameters:
a, b: lines or sketch axes (x / y: the sketch's own axes, unless a curve has that id; an axis becomes a linked construction line on it, reported in created); any of them may be a model edge {edge: [x,y,z], body?} or vertex {vertex: [x,y,z], body?}, projected into the sketch first (reported in created)
Fix/UnFix
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.fix
Parameters:
entity: point or curve | entities: [points and curves]; fixed?: bool (default: fix them all unless all are fixed, then unfix)
MidPoint
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.midpoint
Parameters:
point, line | a, b in pick order: a point and a line (either way round), or two lines whose midpoints meet (the first stays, the second moves); any of them may be a model edge {edge: [x,y,z], body?} or vertex {vertex: [x,y,z], body?}, projected into the sketch first (reported in created)
Concentric
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.concentric
Parameters:
a, b: circles/arcs; any of them may be a model edge {edge: [x,y,z], body?} or vertex {vertex: [x,y,z], body?}, projected into the sketch first (reported in created)
Collinear
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.collinear
Parameters:
a, b: lines or sketch axes (x / y: the sketch's own axes, unless a curve has that id; an axis becomes a linked construction line on it, reported in created); any of them may be a model edge {edge: [x,y,z], body?} or vertex {vertex: [x,y,z], body?}, projected into the sketch first (reported in created)
Symmetry
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.symmetry
Parameters:
a, b: two points or two curves (lines mirror their ends, circles/arcs their centres); line: symmetry line id, or x / y for the sketch's own axes (a curve with that id wins; an axis becomes a linked construction line on it, reported in created); or point (or a point id as line, "origin" too): symmetric about that point, each pair joined by a construction line it is the midpoint of; any of them may be a model edge {edge: [x,y,z], body?} or vertex {vertex: [x,y,z], body?}, projected into the sketch first (reported in created)
Curvature
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.curvature
Parameters:
a, b: two curves sharing an end point (curvature continuous, G2)
Polygon
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.constraint.polygon
Parameters:
lines: [line ids] of a closed chain: equal sides with every corner on one circle (a regular polygon)
AutoConstrain
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.auto_constrain
Parameters:
tolerance?: mm (default 0.01), angle_tolerance?: deg (default 1), dimensions?: bool (default true): infer constraints, then dimension until fully constrained
AutoConstrain from datum
Command group: SKETCH › CONSTRAINTS
Read the sketching and constraints guide.
Automation details
Command ID: sketch.auto_constrain_from_datum
Parameters:
datum: point ref (default origin); like AutoConstrain with positions measured from the datum
SKETCH › MODIFY
Blend Curve
Command group: SKETCH › MODIFY
Read the sketching and constraints guide.
Automation details
Command ID: sketch.blend_curve
Parameters:
a, b: end points of two curves ("l1.end", "a2.start"), continuity?: g1|g2|g3|g4 (default g1: a tangent spline; g2…g4 match the reference curves' curvature, curvature rate and 4th derivative as well, as one Bézier of degree 5, 7 or 9). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Fillet
Command group: SKETCH › MODIFY
Read the sketching and constraints guide.
Automation details
Command ID: sketch.fillet
Parameters:
point: corner shared by two curves ("l1.end") | points: [corners] (all rounded alike: one radius, the rest equal) | a, b: two lines or arcs meeting at a corner; radius: expr. A corner that dimensions or constraints measure to stays as a virtual sharp corner on both curves' extensions (`virtual_corners`; the moved ones in `moved_to_virtual_corner`). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Equal Distance Chamfer
Command group: SKETCH › MODIFY
Read the sketching and constraints guide.
Automation details
Command ID: sketch.chamfer.equal_distance
Parameters:
point | a, b: a corner of two lines; distance. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Distance and Angle Chamfer
Command group: SKETCH › MODIFY
Read the sketching and constraints guide.
Automation details
Command ID: sketch.chamfer.distance_angle
Parameters:
point | a, b: a corner of two lines; distance: along a, angle: deg from a. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Two Distance Chamfer
Command group: SKETCH › MODIFY
Read the sketching and constraints guide.
Automation details
Command ID: sketch.chamfer.two_distance
Parameters:
point | a, b: a corner of two lines; distance: along a, distance2: along b. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Offset
Command group: SKETCH › MODIFY
Default keyboard shortcut: O
Read the sketching and constraints guide.
Automation details
Command ID: sketch.offset
Parameters:
curves: [ids], distance: number in mm or length expression (creates an editable dimension; positive = left of the chain, or toward `side`), side?: [x,y], chain?: bool (default true: the connected chain); direction?: one_side|symmetric (default one_side) or symmetric?: bool (the same distance each way; no `side`, whose job it takes over), distance2?: positive length expression (Two Sides: an independent distance the other way, as for `solid.extrude`). Each side is its own offset association, listed in `sides`; flip?: bool reverses the primary side; `param`/`param2` name the driving dimensions. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Trim
Command group: SKETCH › MODIFY
Default keyboard shortcut: T
Read the sketching and constraints guide.
Automation details
Command ID: sketch.trim
Parameters:
curve: id, at: [x,y] (the piece to remove: between the nearest intersections around it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Extend
Command group: SKETCH › MODIFY
Read the sketching and constraints guide.
Automation details
Command ID: sketch.extend
Parameters:
curve: id, at: [x,y] near the end to extend (a line runs on to the nearest curve, or the previewed reach when nothing is in the way; an arc grows along its circle and closes into a whole circle when nothing stops it)
Break
Command group: SKETCH › MODIFY
Read the sketching and constraints guide.
Automation details
Command ID: sketch.break
Parameters:
curve: id, at: [x,y] (split at intersections) | points: [[x,y]…] (1…1000 positions projected onto the curve; duplicates are merged). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Sketch Scale
Command group: SKETCH › MODIFY
Read the sketching and constraints guide.
Automation details
Command ID: sketch.scale
Parameters:
entities: [curve/point ids], base: [x,y] or point ref, factor: number
Transform
Command group: SKETCH › MODIFY
Default keyboard shortcut: G
Read the sketching and constraints guide.
Automation details
Command ID: sketch.transform
Parameters:
entities: [curve/point ids], factor?: number (scale), angle?: degrees (turn), center?: [x,y] or point ref (what scales and turns stay put; default [0,0]), translate?: [dx,dy] (applied last); scales, turns and moves in one step
Move/Copy Sketch Entities
Command group: SKETCH › MODIFY
Read the sketching and constraints guide.
Automation details
Command ID: sketch.move
Parameters:
entities: [ids], translate?: [dx,dy], angle?: deg, center?: [x,y], copy?: bool, rotate_constraints?: bool (default true; false keeps constraints unchanged); a rotation turns the constraints tied to the sketch axes when all their geometry moves (horizontal and vertical relations and dimensions swap at odd quarter turns and stay at half turns, line angles turn with the line) and removes the horizontal and vertical ones that cannot hold at other angles; picking a text outline or linked point transforms its whole text source once, and copies remain editable text. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
SKETCH › INSERT
Insert DXF
Command group: SKETCH › INSERT
Read the sketching and constraints guide.
Automation details
Command ID: sketch.insert_dxf
Parameters:
path | text: ASCII DXF (blocks expanded); at?: [x,y] offset, scale? | width? | height?: mm, the drawing's size before rotation, aspect kept (win over scale); angle?: degrees, about the drawing's origin (or its centre); align?: "origin" (default: `at` is where the file's origin goes) | "center" (`at` is where the drawing's middle goes and it turns and scales about it); tolerance?: mm within which ends join (default 0.0001); plane?: (when no sketch is active, a new sketch on it; default XY). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Insert SVG
Command group: SKETCH › INSERT
Read the sketching and constraints guide.
Automation details
Command ID: sketch.insert_svg
Parameters:
path | text: SVG; at?: [x,y] offset, scale? | width? | height?: mm, the drawing's size before rotation, aspect kept (win over scale); angle?: degrees, about the drawing's origin (or its centre); align?: "origin" (default: `at` is where the file's origin goes) | "center" (`at` is where the drawing's middle goes and it turns and scales about it); tolerance?: mm within which ends join (default 0.0001); plane?: (when no sketch is active, a new sketch on it; default XY). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Canvas
Command group: SKETCH › INSERT
Read the sketching and constraints guide.
Automation details
Command ID: canvas.insert
Parameters:
path | data (base64 PNG or JPEG); plane?: XY|XZ|YZ|{face: [x,y,z]}|… (default XY); center?: [x,y] on the plane, or at?: [x,y,z]; width?: mm (default 100); angle?: deg; opacity?: 0…1 (default 0.5); flip?; name?
Decal
Command group: SKETCH › INSERT
Read the sketching and constraints guide.
Automation details
Command ID: canvas.decal
Parameters:
path | data; face: [x,y,z] on a planar face (the image is centred there); width?: mm; angle?: deg; opacity?: (default 1)
Edit Canvas
Command group: SKETCH › INSERT
Read the sketching and constraints guide.
Automation details
Command ID: canvas.edit
Parameters:
canvas: id or name; center?, width?, angle?: deg, opacity?, flip?, visible?, name?
SKETCH › INSPECT
Curvature Comb Analysis
Command group: SKETCH › INSPECT
Read the sketching and constraints guide.
Automation details
Command ID: inspect.curvature_comb
Parameters:
curves?: [sketch curve ids] (sketch?: id|name, default active) | edges?: [[x,y,z] points on body edges] | faces?: [[x,y,z] points on faces] with count?: isoparametric curves each way (default 4; combs along the face's iso grid); density?: teeth per curve (default 40), scale?: comb length per unit curvature (default auto)
Minimum Radius Analysis
Command group: SKETCH › INSPECT
Read the sketching and constraints guide.
Automation details
Command ID: inspect.minimum_radius
Parameters:
curves?: [sketch curve ids] (sketch?) | edges?: [[x,y,z]…] | faces?: [[x,y,z]…] (default: every face of every body); the smallest radius of curvature of each and where it is
Isocurve Analysis
Command group: SKETCH › INSPECT
Read the sketching and constraints guide.
Automation details
Command ID: inspect.isocurve
Parameters:
faces: [[x,y,z]…] points on faces; count?: curves each way (default 8): the faces' isoparametric curves
Center of Mass
Command group: SKETCH › INSPECT
Read the sketching and constraints guide.
Automation details
Command ID: inspect.center_of_mass
Parameters:
bodies?: [names] (default all): the centre of mass (uniform density) of each and of all together
Zebra Analysis
Command group: SKETCH › INSPECT
Read the sketching and constraints guide.
Automation details
Command ID: inspect.zebra
Parameters:
stripes?: number across the view (default 12); clear?: true turns the analysis off
Draft Analysis
Command group: SKETCH › INSPECT
Read the sketching and constraints guide.
Automation details
Command ID: inspect.draft
Parameters:
pull?: [x,y,z] direction (default Z); angle?: degrees (default 1); clear?: true turns the analysis off
Environment Map Analysis
Command group: SKETCH › INSPECT
Read the sketching and constraints guide.
Automation details
Command ID: inspect.environment_map
Parameters:
clear?: true turns the analysis off
Accessibility Analysis
Command group: SKETCH › INSPECT
Read the sketching and constraints guide.
Automation details
Command ID: inspect.accessibility
Parameters:
direction?: [x,y,z] the tool comes from (default Z, from above); clear?: true turns the analysis off
Curvature Map Analysis
Command group: SKETCH › INSPECT
Read the sketching and constraints guide.
Automation details
Command ID: inspect.curvature_map
Parameters:
radius?: mm shown red (default: a tenth of the model's size); clear?: true turns the analysis off
SKETCH › PATTERN
Mirror
Command group: SKETCH › PATTERN
Read the sketching and constraints guide.
Automation details
Command ID: sketch.mirror
Parameters:
entities: [curve/point ids], line: mirror line id (symmetry constraints are added). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Circular Pattern
Command group: SKETCH › PATTERN
Read the sketching and constraints guide.
Automation details
Command ID: sketch.pattern.circular
Parameters:
entities: [ids], center: [x,y] or point ref, count: 2…500 (number or unitless expression, retained through edits), angle?: total deg, number or expression (default 360); alignment?: left|center|right (default right); reversed?: bool (default false). Quantity includes the original. Center balances copies around it; reversed flips the direction and extra side for even totals. Partial arcs span angle with angle/(count-1) spacing; full turns use angle/count. Adds associative pattern constraints; a coordinate center creates a constrainable point. Returns pattern and center ids. pattern?: existing pattern id to edit (omitted values retained; surviving instance ids retained). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Rectangular Pattern
Command group: SKETCH › PATTERN
Read the sketching and constraints guide.
Automation details
Command ID: sketch.pattern.rectangular
Parameters:
entities: [ids], count, spacing; count2?, spacing2?; counts: positive integer numbers or expressions, at most 500 instances total; spacings: length numbers or expressions; direction?, direction2?: sketch line ids (live references); dir?, dir2?: [x,y] vectors (defaults: sketch x, perpendicular to direction 1); alignment?, alignment2?: left|center|right (default right). Signed spacings reverse each direction. reversed?, reversed2?: bool (default false); reverses placement and the extra side of even centered totals. Quantities include the original. Adds associative pattern constraints. Returns pattern id. pattern?: existing pattern id to edit (omitted values retained; surviving instance ids retained). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
SURFACE › INSPECT
Continuity Analysis
Command group: SURFACE › INSPECT
Read the solid features guide.
Automation details
Command ID: inspect.continuity
Parameters:
body?: name (default: the visible bodies); edges?: [x,y,z] points or {"index": i} (default: every edge of the body; a pick must match one body); samples?: 3…257 stations along each edge, ends included (default 33); gap_tolerance_mm? (default 0.001), angle_tolerance_deg? (default 1), curvature_tolerance_per_mm? (default 0.01); add?: true keeps the seams already shaded and adds these (measuring an edge again replaces it); clear?: true turns the analysis off. → each edge's worst gap (G0), normal angle (G1) and curvature jump (G2) as {value, at: [x,y,z], edge_fraction}, its verdict (smooth, curvature_jump, sharp or gap) and a summary; the seams are shaded in the viewport
SURFACE › CREATE
Boundary Surface
Command group: SURFACE › CREATE
Read the solid features guide.
Automation details
Command ID: surface.boundary
Parameters:
Either a loop of one sketch's curves — sketch?: id|name (default: the sketch being edited); curves: [2 to 4 sketch curve ids in loop order] (each one ends where the next begins and the last where the first does; two: the patch spans between them and leaves each boundary along the tangent across it; three or four: the flat patch of the loop, three collapsing the side opposite the first curve to the corner there); continuity?: g0|g1|g2|g3 | [one per curve] (default g1: how much of the surface across each boundary the patch repeats — a sketch curve's own plane, so g1 and up hold exactly and g0 lets the middle bow); crown?: number -0.9…3 (default 0: how full the middle is, without touching a boundary; two curves only); samples?: 8…512 stations per boundary (default 25) — or a curve network — u: [curve] (two or more one way: the surface lofts through them), v?: [curve] (crossing every u-curve once: two each way give a Coons patch, more a Gordon surface through every curve; a curve that misses another by more than 0.01 mm is refused, naming the pair), each curve "id" (a curve of `sketch`) | {sketch?: id|name, curves: [ids meeting end to end]} (planar or 3D sketch curves) | {body: name, edges: [[x,y,z]] (points on a chain of its edges), continuity?: g0|g1|g2 (default g0; g1 the surface leaves tangent to the face beside a surface's open edges, g2 with its curvature too; on curves of one direction only)} | {point: [x,y,z]} (a side collapsed to a point: a three-sided patch); operation?: new|join (default new; join sews the surface into the body of its first edge curve, a solid when it closes the gap). name?, body_name? (a surface body)
Patch
Command group: SURFACE › CREATE
Read the solid features guide.
Automation details
Command ID: surface.patch
Parameters:
sketch?: id|name and profiles? (planar patches of sketch regions) | edges: [[x,y,z]] (points on a closed loop of a body's edges: one face on them, planar when the loop is, else a smooth B-spline surface) and body?; continuity?: g0|g1|g2 (edges only, default g0: how the patch meets the faces beside the loop — g1 their tangent planes, g2 also their curvature across the edges) or [{edge: [x,y,z], g: g0|g1|g2}] (those edges so, the rest g0) or {all, edges}; points?: [[x,y,z]] (edges only, up to 64: points the patch passes through); crown?: number −0.9…3 (default 0: only a fitted patch, whose middle becomes fuller by this many times the loop's size — the bump vanishes on the loop, so the face still meets the picked edges and the continuity across them; a loop that is planar and g0 is the exact plane whatever the crown; with points, crown needs a g1 or g2 edge); name?, body_name?
Thicken
Command group: SURFACE › CREATE
Read the solid features guide.
Automation details
Command ID: surface.thicken
Parameters:
bodies: [surface names]; thickness: expr (negative: against the normal); symmetric?: bool; operation?: new|join|cut|intersect; targets?
Surface Extrude
Command group: SURFACE › CREATE
Read the solid features guide.
Automation details
Command ID: surface.extrude
Parameters:
sketch?: id|name; curves?: [curve ids] (open or closed chains of curves meeting end to end) and/or profiles? (their boundaries; the default when no curves are given); distance: expr; direction?: positive|negative|symmetric; distance2?: expr; start_offset?: expr; name?, body_name? (a surface body: walls only, no caps)
Surface Revolve
Command group: SURFACE › CREATE
Read the solid features guide.
Automation details
Command ID: surface.revolve
Parameters:
sketch?: id|name; curves?: [curve ids] and/or profiles? (as surface.extrude); axis: sketch line id|x|y|X|Y|Z|{type: world, axis: X|Y|Z}|construction axis name|{origin, dir} | {edge|face: [x,y,z]} (a straight or circular edge, or a cylinder, cone or torus face: about its axis, followed as the model changes); angle?: expr (default 360 deg); angle2?: expr (the other side); symmetric?: bool; name?, body_name? (a surface body, no caps; a solid where a full turn closes it)
Surface Sweep
Command group: SURFACE › CREATE
Read the solid features guide.
Automation details
Command ID: surface.sweep
Parameters:
sketch?: id|name; curves?: [curve ids] and/or profiles? (as surface.extrude); path_sketch: sketch; path: [curve ids in order] (a path with corners, or 3D curves, gives ruled walls mitred at the corners); orientation?, twist?, scale?, rails?, rail_fit? (as solid.sweep); name?, body_name? (a surface body, no caps)
Surface Loft
Command group: SURFACE › CREATE
Read the solid features guide.
Automation details
Command ID: surface.loft
Parameters:
sections: [{sketch, curves?: [curve ids], profiles?} | {body, edges: [[x,y,z]]}] (2…50 in order; each one chain: a profile without holes, or curves meeting end to end; all open or all closed; a body edge chain takes the face the chain bounds as the surface the end repeats); continuity?: [g0|g1|g2|g3, g0|g1|g2|g3] (each end, default g0: how much of the surface at the first and last section the loft repeats — the tangent plane, the curvature, or how that curvature changes); crown?: [number, number] (default 0: how full each end leaves, -0.9 flattest to 3 fullest); rails?, rail_fit? (as solid.loft; sketch sections only, both ends g0); name?, body_name? (a surface body: ruled between two sections, smooth through three or more; no caps)
Offset Surface
Command group: SURFACE › CREATE
Read the solid features guide.
Automation details
Command ID: surface.offset
Parameters:
faces: [[x,y,z]] (points on the faces); body?; distance: expr (signed, along the faces' normals; 0 copies the faces); chain?: bool (each pick's whole smooth chain of faces); tolerance?: expr (how far a fitted face may stray, default 0.01 mm). Planes and cylinders move exactly; other faces are fitted. A new surface body (a solid where its faces close)
Ruled Surface
Command group: SURFACE › CREATE
Read the solid features guide.
Automation details
Command ID: surface.ruled
Parameters:
body; edges: [[x,y,z]] (points on a chain of its edges, open or closed); distance: expr (how far the strip reaches); mode?: normal|tangent|angle|direction (default normal: along the normal of the face beside each edge; tangent: continuing it); angle?: expr (angle mode: from `direction`, else from the face's normal, turned away from the face; 90 deg from the normal is tangent); direction?: [x,y,z] (direction mode: the rulings; angle mode: the reference, e.g. a pull direction for a draft); face?: [x,y,z] (a point on the face the directions come from; default: the face beside the most picked edges); flip?: bool (the other side); corners?: mitre|round (sharp corners of the chain; inside corners are always cut back). Lines and arcs give exact planes, cylinders and cones, other edges a fitted ruled B-spline. A new surface body
Blend Surface
Command group: SURFACE › CREATE
Read the solid features guide.
Automation details
Command ID: surface.blend
Parameters:
from, to: {body, edges: [[x,y,z]]} (points on the edges of one chain of each body: two picks on the same edge is that one edge, more chain up); continuity?: [g0|g1|g2|g3, g0|g1|g2|g3] (each end, default g1: how much of the surface at the picked edges the blend repeats — the tangent plane, the curvature, or how that curvature changes); bulge?: [number, number] (default 0: how full each end leaves, -0.9 flattest to 3 fullest); align?: [[x,y,z], [x,y,z]] (a point each end leaves towards, when the faces at the picks do not say); seam?: [[x,y,z], [x,y,z]] (a point on each chain its stations start from: two closed chains otherwise pair the way that keeps the blend from twisting; an open chain starts at the end nearer its point; a closed chain blends only to a closed chain); stitch?: bool (sew the blend onto its two bodies where the boundaries meet, replacing them); name?, body_name?
SURFACE › MODIFY
Stitch
Command group: SURFACE › MODIFY
Read the solid features guide.
Automation details
Command ID: surface.stitch
Parameters:
bodies: [names] (surfaces sewn into the first; a solid when they close); tolerance?: expr (default: tight)
Extend
Command group: SURFACE › MODIFY
Read the solid features guide.
Automation details
Command ID: surface.extend
Parameters:
body: surface name; edges: [[x,y,z]] (points on boundary edges to push out); distance: expr (unless up_to); mode?: natural (the surface itself continued: exact for planes and surfaces of revolution, a B-spline's end spans continued; the default) | tangent (a ruled run-off face along each edge, G1) | curvature (a run-off face keeping the surface's curvature across the edge, G2); up_to?: plane (XY|XZ|YZ|plane name|{origin, normal}) | {body, point} (a face, its surface extended) | "bounds" (natural: back to the surface's own bounds, undoing a trim); neighbours?: bool (default true: the edges next to an extended edge are lengthened to meet it; false: the extension's sides run square to the edge)
Trim
Command group: SURFACE › MODIFY
Read the solid features guide.
Automation details
Command ID: surface.trim
Parameters:
body: surface name; plane: XY|XZ|YZ|plane name|{origin, normal} or tool: {body, point} (a face, its surface extended) or curves: {sketch, curves?: [curve ids] (chains of curves meeting end to end; default every curve), direction?: normal|[x,y,z] (swept onto the surface along the sketch's normal, the default, or this direction)}; keep: [x,y,z] (a point on the side to keep) or [[x,y,z], ...] (points on the regions to keep: the pieces that hang together once cut). Mutual trim: with: another surface (one face each; leave out plane, tool and curves), keep_with: [x,y,z] or [[x,y,z], ...] (what to keep of it); each is cut where the other's surface (extended) meets it; extend?: bool (a surface that stops short of the other is extended to meet it); stitch?: bool (the kept pieces sewn into `body`; `with` is then gone)
Untrim
Command group: SURFACE › MODIFY
Read the solid features guide.
Automation details
Command ID: surface.untrim
Parameters:
face: {body, point}; holes?: all|[[x,y,z]] (points on the inner loops to remove; default: all); outer?: bool (restore the outer boundary to the surface's natural bounds: a free-form surface's own, an analytic surface's span of the face); in_place?: bool (untrim a surface body's face where it is; a solid's face is always copied out). A surface body; the face keeps its name
Unstitch
Command group: SURFACE › MODIFY
Read the solid features guide.
Automation details
Command ID: surface.unstitch
Parameters:
body; faces?: [[x,y,z]] (points on the faces to take out, each its own surface body; the rest stay together under the body's name; default: every face its own surface body). A solid comes apart into surfaces
Reverse Normal
Command group: SURFACE › MODIFY
Read the solid features guide.
Automation details
Command ID: surface.reverse_normal
Parameters:
body (a surface); faces?: [[x,y,z]] (points on the faces to turn over; default: all of them)
Fair Surface
Command group: SURFACE › MODIFY
Read the solid features guide.
Automation details
Command ID: surface.fair
Parameters:
body: surface name; faces?: [[x,y,z]] points on the faces to fair (default: every face); strength?: number (0..1 of the full fairing step, default 1); iterations?: number (1..1000 steps, default 1); keep_edges?: bool (default true: hold the boundary row and the row next to it, so a face keeps meeting its neighbours in position and in tangent); tolerance?: expr mm (how far a control vertex may move, default 0.001 of the body's size). Moves the interior control vertices of each B-spline face to flatten its curvature variation while the boundary stays put: the face keeps its edges and the body its shape within the tolerance, and the warning in the reply carries the energy before and after; name?
Surface Fillet
Command group: SURFACE › MODIFY
Read the solid features guide.
Automation details
Command ID: surface.fillet
Parameters:
planned: a, b: {body, point} (two faces of separate bodies that meet or cross; the points pick the side the ball rolls on); radius: expr; trim?: both|first|none
Match Surface
Command group: SURFACE › MODIFY
Read the solid features guide.
Automation details
Command ID: surface.match
Parameters:
body: surface name (the body whose face is rebuilt); faces?: [[x,y,z]] (points on that face, default: the body's first); target: body name (the body the seam must meet); edges: [[x,y,z]] (points on one edge chain of the target: the rebuilt boundary takes its place); order?: g1|g2|g3 (default g2: position and tangent plane, the curvature too, or how that curvature changes along the seam). The far boundary stays where it was and the body keeps its name
SHEET METAL › CREATE
Flange
Command group: SHEET METAL › CREATE
Create a sheet metal base, bend an existing edge into a wall, or build a contour from an open sketch. The sheet metal rule supplies the material thickness and bend settings so the folded and flat versions describe the same part.
Before you start
| Flange type | What you need to select | Result |
|---|---|---|
| Base | A closed region in a finished sketch. | A new flat sheet metal body. |
| Edge | One or more supported edges of an existing sheet metal body. | Bent walls along those edges. A sketch is not required for this mode. |
| Contour | An open chain of straight sketch lines. | A sheet following that outline, extended by the chosen distance. |
Choose or create a Sheet Metal Rule before building the part. To work from an ordinary solid plate, use Convert to Sheet Metal first. An arbitrary solid edge does not become a sheet metal edge just by selecting Flange.
Options
| Choice | Meaning |
|---|---|
| Type | Base, Edge or Contour. The selection determines which input is appropriate. |
| Rule | Sets thickness, bend radius, K-factor and the gaps/reliefs that keep bends manufacturable. Change thickness in the rule, not as a flange height. |
| Height | Sets the edge flange’s height. Check the height reference and preview when matching a drawing dimension. |
| Angle | Sets how far the edge bends; 90° makes a right-angle wall. |
| Per-edge angle and height | Lets selected edges in one feature have different dimensions. Empty values use the common setting. |
| Position | Places the flange relative to the selected edge. The preview shows which material remains inside or outside the original outline. |
| Flip | Bends toward the other side of the sheet. |
| Corner Relief Shape and size | Use the rule’s setting or override relief between edge flanges of this feature. These overrides are not available for base or contour flanges. |
| Distance | Sets the length of a contour flange’s extrusion along the open sketch. |
Try it: two sides of a tray
- Set a sheet rule with 1.5 mm thickness and 1.5 mm bend radius.
- Draw and finish an 80 × 50 mm rectangular sketch.
- Choose Flange, select the rectangle’s region and make a Base flange.
- Choose Flange again, select an edge and set its height to 20 mm.
- Add another 20 mm flange on the opposite edge. Check the bend directions before confirming each feature.
- Use Create Flat Pattern to inspect the developed part and bend lines.
Tips and common mistakes
- Do use one edge flange feature for adjoining walls whose shared corners should close together. Flanges made as separate features keep their own ends.
- Do inspect warnings and the flat pattern; a relief that cannot fit may be left out with a warning.
- Don’t confuse height with sheet thickness. The rule owns thickness.
- Don’t select a sketch point for an edge flange: choose an edge of the sheet. For a base flange, select a closed sketch region instead.
Examples and community resources
| Resource | What to explore | Author and licence |
|---|---|---|
| Editable sheet metal demo | The rule, base sketch and two edge flanges used in this illustration. | SolidCraft contributors; MIT OR Apache-2.0. |
Contribute a tip, demo, model or screenshot, explaining the edge or region that must be selected.
See also
Hem returns an edge back toward the sheet. Fold bends a sheet along a chosen line. Sheet Metal Rules controls thickness and bend settings. Create Flat Pattern shows the developed part.
Automation details
Command ID: sheet.flange
Parameters:
base: sketch + profiles? (rule?, flip?) | contour: sketch + curves (open chain of lines) + distance (rule?, flip?, reverse?) | edge: edges: [[x,y,z] on top/bottom edges, or {point: [x,y,z], angle?, height?} for an edge with its own angle and height] + height (along the flange, to its far edge), angle? (default 90 deg), radius? (default: rule), height_from?: outer|inner|tangent (where the height is measured from: the outer virtual sharp (default), the inner virtual sharp, or past 90 deg the line square to the flange touching the bend's outside (tangent; up to 90 deg the outer sharp)), position?: inside|outside|middle|material_outside|tangent (where the bend starts: the outer virtual sharp on the edge (default), the bend starting at the edge (bend_outside is the same), half way, the inner virtual sharp on the edge, or the bend's outer face tangent to the edge's face (below 90 deg the bend's end on that face)), flip?, body?, corner_relief_shape?: notch|round|rectangular|tear and corner_relief? (size across): the relief at the corners these flanges make (default: the rule's); then?: [{angle?, height, flip?}] further bends (at most 16), each a flange on the far edge of the one before, flip: true turns that bend the other way (edge only); type?: base|edge|contour (default from the inputs); name?, body_name? (the new body). Thickness, K-factor and bend radius come from the rule (sheet.manage_rules); passing them here is refused, and so is the corner relief on a base or contour flange
Path Flange
Command group: SHEET METAL › CREATE
Read the sheet metal guide.
Automation details
Command ID: sheet.path_flange
Parameters:
edge: [x,y,z] on a top or bottom edge of the sheet; sketch + curves: an open chain of sketch lines drawn on a plane across that edge, from the edge out (each line one flange as long as the line, to the outer face from the virtual sharp; each corner one bend, turning the way the lines turn); radius? (default: rule); body?. One feature, one body
Perforate
Command group: SHEET METAL › CREATE
Read the sheet metal guide.
Automation details
Command ID: sheet.perforate
Parameters:
diameter, pitch: expr; margin?: expr (clear of edges, cut-outs and bends; default: the diameter); pattern?: staggered (60°, default) | square; body? - a perforation field over the whole flat sheet: holes in the flat pattern (perforations), the DXF (layer PERFORATION) and the measured volume, not cut into the B-rep (#732)
Hem
Command group: SHEET METAL › CREATE
Fold a sheet metal edge back through a half turn to form a return. Use a hem when you need a doubled-over edge, rather than an upright wall.
Before you start
- You need an existing sheet metal body and a supported free sheet edge. Make a base flange or convert a suitable solid plate first.
- Select the edge, not a sketch line, face or point. Hem does not require a new sketch. A bend along a line inside the sheet uses Fold instead.
- Leave room for the return so it does not run into another wall or return.
Options
| Choice | Meaning |
|---|---|
| Edges | The sheet edges to turn back. Check that the preview changes the intended edge. |
| Length | The return’s length, measured to its outer face. It is not the thickness of the sheet. |
| Gap | Controls the inner radius of the return bend. Leave it empty to use the sheet rule’s hem setting. |
| Flip | Turns the return toward the other side of the sheet. |
Try it: a returned edge
- Create an 80 × 50 mm base flange in 1.5 mm sheet.
- Choose Hem and select a free 80 mm edge of the sheet.
- Set Length to 8 mm and Gap to 1.5 mm.
- Check which side receives the return; use Flip if necessary, then confirm.
- Inspect Create Flat Pattern to see the material needed for the bend.
Tips and common mistakes
- Do check the bend in the preview: an edge returning toward another feature needs enough clearance.
- Don’t draw a sketch line to tell Hem where to bend. Its input is an existing sheet edge; use Fold for a bend line through a panel.
- Don’t use Hem for an ordinary right-angle wall. That is an edge Flange.
Examples and community resources
| Resource | What to explore | Author and licence |
|---|---|---|
| Editable hem demo | The sheet rule, base flange and 8 mm return used in this illustration. | SolidCraft contributors; MIT OR Apache-2.0. |
Contribute a tip, demo, model or screenshot, explaining the selected edge and dimensions.
See also
Flange makes a base sheet or an angled wall. Fold bends along a line in the sheet. Sheet Metal Rules sets the default hem gap. Create Flat Pattern shows the developed part and bend lines.
Automation details
Command ID: sheet.hem
Parameters:
edges: [[x,y,z] on sheet edges]; length (to the outer face); gap? (inner radius, default: rule); flip?; body?
Fold
Command group: SHEET METAL › CREATE
Read the sheet metal guide.
Automation details
Command ID: sheet.fold
Parameters:
line: sketch + curve (a sketch line on the sheet's base face; followed when the sketch changes) or points: [[x,y,z], [x,y,z]]; angle? (default 90 deg); radius? (default: rule); position?: centerline|start|end|mould (where the line sits in the bend, default centerline); fixed?: [x,y,z] on the side that stays (default: the larger side); flip? (fold the other way); body?
Convert to Sheet Metal
Command group: SHEET METAL › CREATE
Read the sheet metal guide.
Automation details
Command ID: sheet.convert
Parameters:
body; face: [x,y,z] on its large flat face (a plate of even thickness; recesses in one face up to half the thickness deep, such as an engraved label, become pockets of the sheet), or that face's name from model.faces; rule?; bends?: [[x,y,z] on sharp edges] (a solid or a shelled body: `face` is the fixed face and the flat faces reached from it across these edges become the sheet; a shell keeps its thickness, a solid takes the rule's inward; edges between two of those faces that are no bends are cut open with the rule's gap; a point in the middle of a chamfer between two faces, or on a bend modelled with its radius (a cylinder between two flat faces tangent to it, either skin), makes it one bend of the rule's radius at their virtual sharp; the faces' holes become cut-outs and their engravings, up to half the thickness deep, pockets)
Create Flat Pattern
Command group: SHEET METAL › CREATE
Read the sheet metal guide.
Automation details
Command ID: sheet.flat_pattern
Parameters:
body? → flat size, outline loops and cut-outs (flat coordinates, as point lists), cutout_shapes (the same cut-outs exactly, in the same order: {circle: {center, radius}} for a round hole, else {segments: [{line: [a, b]} | {arc: {center, radius, start_deg, sweep_deg}} | {polyline: [...]}]}), pockets (with their own cutout_shapes), bend lines and the bend table
SHEET METAL › MODIFY
Unfold
Command group: SHEET METAL › MODIFY
Read the sheet metal guide.
Automation details
Command ID: sheet.unfold
Parameters:
body? (default: the last sheet body) — all bends
Refold
Command group: SHEET METAL › MODIFY
Read the sheet metal guide.
Automation details
Command ID: sheet.refold
Parameters:
body? (default: the last unfolded sheet)
Sheet Metal Rules
Command group: SHEET METAL › MODIFY
Read the sheet metal guide.
Automation details
Command ID: sheet.manage_rules
Parameters:
name (new or existing); thickness?, k_factor?, bend_radius?, relief_width?, relief_depth?, corner_relief?, hem_gap?, gap? (expressions; `Thickness` is the rule's; gap: between flanges that meet at a corner, whose ends are closed toward each other at any angle); corner_relief_shape?: notch|round|rectangular|tear (what is cut where two bends meet at a corner of the base: round and rectangular a relief `corner_relief` across; tear none, each bend's end is cut straight from the corner to its flange's end; default notch); angle_display?: bend|included (how the DXF bend notes read the bend angles: the bend angle, default, or the included angle left between the bend's sides, 180° less it; the bend table gives both, and a flange's `angle` is always the bend angle); active?: bool; delete?: bool
WELDMENTS › CREATE
Frame Member
Command group: WELDMENTS › CREATE
Automation details
Command ID: frame.member
Parameters:
sketch: the frame sketch (id or name; 2D or 3D); lines?: [curve ids] (one member each; default: every straight line drawn in the sketch: not construction, not projected or otherwise linked, not of no length); profile: {kind: bar|round_bar|rect_tube|round_tube|angle|channel|i_beam, …its dimensions: each a number in the design's units or a length expression over its parameters ("tube_wall", "2 in")} (frame.profile.list gives each kind's dimensions and an example; dimensions left out take defaults from the profile's size) | profile_sketch: a sketch whose profile is the section, in its own x and y around its origin, with profiles?: picks as solid.extrude takes them (default: its material, nested loops alternating, so two circles make a tube); anchor?: origin|center|centroid|top_left|top|top_right|left|right|bottom_left|bottom|bottom_right (the profile point that sits on each line, default origin); angle?: angle expr (the profile turned about the line; default 0: its y as near world up as the line allows, a vertical member's x along world X); mirror?: bool (the profile mirrored across its y axis); name?; body_name? (what the members are named after, then numbered: `Rail`, `Rail (2)`…; default the profile's description, `Rect Tube 40x40x3`, or the profile sketch's name); names?: {line id: name} (names to keep, as Edit Feature keeps them). One body per line, from its start to its end; each member keeps its name when other lines are added or deleted. Returns the feature and the bodies it made
PLASTIC › CREATE
Boss
Command group: PLASTIC › CREATE
Read the plastic parts guide.
Automation details
Command ID: plastic.boss
Parameters:
position: [x,y,z] on a face; direction? (default: out of the face); diameter, height; hole_diameter?, hole_depth? (default: to the face); draft? (angle), fillet? (root radius); ribs? (count), rib_thickness?, rib_length? (out from the wall), rib_offset? (below the top); body?
Lip
Command group: PLASTIC › CREATE
Read the plastic parts guide.
Automation details
Command ID: plastic.lip
Parameters:
face: [x,y,z] on the rim face of a shelled body; width, height; type?: lip|groove; gap? (groove clearance); side?: inside|outside (which rim edge); body?
Snap Fit
Command group: PLASTIC › CREATE
Read the plastic parts guide.
Automation details
Command ID: plastic.snap_fit
Parameters:
position: [x,y,z] on a face; direction? (default: out of the face); hook: [x,y,z] (the catch's direction); length, thickness, width, catch_depth, catch_length; base_height? (extends the base below the face); new_body?: bool (make a separate body instead of joining); body?
Rest
Command group: PLASTIC › CREATE
Read the plastic parts guide.
Automation details
Command ID: plastic.rest
Parameters:
position: [x,y,z] on a face (the rest's centre); direction? (default: out of the face); width (round: the diameter); length? (a rectangle, along `along`); along?: [x,y,z]; height; draft? (angle; default: the body's plastic rule); thickness? (wall of a hollow rest); body?
PLASTIC › SETUP
Manage Plastic Rules
Command group: PLASTIC › SETUP
Read the plastic parts guide.
Automation details
Command ID: plastic.manage_rules
Parameters:
name (library or new rule; a new one starts from `from`, default ABS (1.5mm)); from?; material?; thickness?, nominal_radius?, clearance?, knife_edge?, reveal_height?, thickness_variation?, draft?, max_thickness?, min_thickness?, min_draft? (expressions; `Thickness` is the rule's); active?: bool (rule for bodies without one); delete?: bool (a design rule; a library rule goes back to the library values)
Assign Plastic Rule
Command group: PLASTIC › SETUP
Read the plastic parts guide.
Automation details
Command ID: plastic.assign_rule
Parameters:
bodies: [names]; rule: name (empty: no rule) — plastic features on these bodies take its draft and clearance as defaults
Other commands: sketch
Edit Sketch
Automation details
Command ID: sketch.edit
Parameters:
sketch: id or name
Draw Dimensioned Sketch Geometry
Automation details
Command ID: sketch.draw.dimensioned
Parameters:
command: sketch creation command, params: object, dimensions: object of role to numeric or expression value, or `name = expression` (also `#name = expression`), which defines the user parameter `name` as sketch.dimension does (the reply's `defined` lists the names); creates geometry and all driving dimensions atomically in one undo step. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Delete Sketch Entities
Automation details
Command ID: sketch.delete
Parameters:
entities: [curve, point or constraint ids]
Drag Sketch Point
Automation details
Command ID: sketch.move_point
Parameters:
point: ref, to: [x,y]
Drag Sketch Geometry
Automation details
Command ID: sketch.drag
Parameters:
entity: point or curve, from: [x,y] where it was grabbed, to: [x,y], live?: bool: points and the material point grabbed on a line move toward `to` within the design constraints. When exact placement is impossible, bounded feasible steps minimize cursor distance. Splines slide by to - from; a circle or arc grabbed on its edge takes the radius to `to`. `with?`: [point or curve ids] moves the rest of a selection with the entity as one translation (circles keep their radii). Long moves retain the solution branch; arcs cannot sweep through their starts. Returns `reached`: progress projected onto the requested move (0..1), `at`: the grabbed position for the next frame, and `retry`: budget-limited progress can continue in another frame. `live: true` shares a work budget across held-solve decomposition and cursor correction; ordinary recovery and the final model evaluation retain their existing limits, not a wall-clock deadline. Later features stay stale until `sketch.drag_end`; `dof` stays from before the drag
End Sketch Drag
Automation details
Command ID: sketch.drag_end
Parameters:
constraint?: {command: a sketch.constraint.* id, params}: ends a drag made of `sketch.drag` `live: true` frames: adds the constraint it let go on (not kept if it over-constrains the sketch), then evaluates the whole model once; returns the constraint's result and the sketch's solved state and degrees of freedom
Move Dimension Text
Automation details
Command ID: sketch.dimension_text
Parameters:
dimension: constraint id or parameter name; at: [x,y] text centre (sketch coordinates) | reset: true (default place)
Sketch Palette Options
Automation details
Command ID: sketch.options
Parameters:
sketch?: id|name (default active); any of show_profile, show_points, show_dimensions, show_constraints, show_projected, show_construction, grid, snap, slice, sketch_3d: bool. Returns them all
Chain
Automation details
Command ID: sketch.chain
Parameters:
curve: id: the curves joined to it end to end (what a double-click selects)
Solve Sketch
Automation details
Command ID: sketch.solve
Parameters:
reports the solver status and degrees of freedom
Add Spline Point
Automation details
Command ID: sketch.spline.insert_point
Parameters:
curve: fit point spline id, at: [x,y]: a fit point is inserted where the curve passes nearest to it, between its neighbours (the curve still runs through it and all its old points; constraints on them stay); end?: start|end: `at` becomes the spline's new first or last point instead, and the curve runs on to it (refused when that end is joined to other geometry). Control point splines take no new points yet. Returns point: the new point's id
Infinite Line
Automation details
Command ID: sketch.infinite
Parameters:
curves: [line ids], value?: bool (default toggles): infinite line type (#1359): construction geometry drawn across the whole view through the line's two points; off, a normal line
Edit Text
Automation details
Command ID: sketch.edit_text
Parameters:
link: the text's link id (or entity: one of its curves), text? or text_expression? (as in sketch.text; typed words replace the expression), at?, height?, angle?: deg (numbers or expressions as in sketch.text; a number replaces an expression, and so does moving the text), font?: family or file path (empty: the built-in font), bold?, italic?: bool (an installed family's bold or italic face; italic without one is slanted), align?: left|center|right (the lines against `at`, or along the path), letter_spacing?: extra mm between letters, line_spacing?: times the font's line distance (default 1), path?: a curve id of the sketch the baseline follows ("": none; `at` and `angle` then do not place the text; the curve becomes construction, a guide the letters stand on), reverse?: bool (the other way along the path) (each kept when left out); or edits: [{link or entity, text?, at?, …}]: several texts changed together, the design rebuilt once (all or nothing; returns edits: each one's result); live?: bool: one frame of an interactive change (the sketch is solved, the features after it stay as they were until `sketch.drag_end`). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Toggle Driven Dimension
Automation details
Command ID: sketch.toggle_driven
Parameters:
constraint: dimension id, driven?: bool (default toggles); a driving dimension gets a parameter with its current value
Toggle Radius / Diameter
Automation details
Command ID: sketch.toggle_diameter
Parameters:
constraint: a circle's or an arc's radius or diameter dimension id; diameter?: bool (default: the other one); sketch?: id|name (default: the one being edited). The circle stays the same size: its parameter is halved or doubled (a plain value is rewritten, an expression becomes `(e) / 2` or `2 * (e)`); refused when anything else uses that parameter
Constraint Glyphs
Automation details
Command ID: sketch.glyphs
Parameters:
sketch?: id|name: every constraint with where to draw its glyph (sketch coordinates)
Snap Inference
Automation details
Command ID: sketch.snap
Parameters:
at: [x,y], from?: [x,y] (previous click), radius?: mm (snap distance, default 1): what a click there would snap to and infer
Drawing Size
Automation details
Command ID: sketch.drawing_bounds
Parameters:
path | text: SVG or DXF (by `format`: "svg" | "dxf", default by the file name, else SVG); returns min, max, width and height in the file's own size
Save As DXF
Automation details
Command ID: sketch.export_dxf
Parameters:
sketch?: id|name (default active); path?: file to write (else the DXF text is returned)
Loose Ends
Automation details
Command ID: sketch.loose_ends
Parameters:
sketch?: id|name (default active): the ends of profile curves (not construction, projected or text) that can make a profile fail. open: [{point, curve, at}] ends that touch no other profile curve (no profile runs through them); untied: [{point, curve, at, touches: {point}|{curve}}] ends that touch another curve only by position (the profile closes now, but nothing keeps it closed when a dimension changes)
Tie Loose Ends
Automation details
Command ID: sketch.loose_ends.tie
Parameters:
sketch?: id|name (default active); points?: [point ids] (default: every untied end): ties each untied end to what it touches, coincident with another curve's end or on another curve. Returns constraints: [ids added], and the untied ends left
3D Line
Automation details
Command ID: sketch.line3d
Parameters:
points: [[x,y,z]…] world points (2 or more): one 3D line per segment, joined end to end (needs 3D Sketch on). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
3D Spline
Automation details
Command ID: sketch.spline3d
Parameters:
points: [[x,y,z]…] world fit points (2 or more): a smooth 3D curve through them (needs 3D Sketch on). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
3D Arc
Automation details
Command ID: sketch.arc3d
Parameters:
start, through, end: [x,y,z] world points: an exact circular arc through them, from start through through to end (needs 3D Sketch on). Stored exactly (centre, radius, angles), so path consumers can treat it as a true arc. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
3D Point
Automation details
Command ID: sketch.point3d
Parameters:
point: [x,y,z] world point (needs 3D Sketch on). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Move 3D Point
Automation details
Command ID: sketch.move3d
Parameters:
wire: drawn 3D curve or point id, index?: fit point (default 0), to: [x,y,z] | by: [dx,dy,dz]; points joined to it move too
Break Offset Association
Automation details
Command ID: sketch.offset.break
Parameters:
curve: derived offset curve id (retains geometry and makes its points editable)
Trim Along Path
Automation details
Command ID: sketch.trim.path
Parameters:
points: [[x,y]…] (2…2000 positions: a path drawn across the sketch). Every piece the path crosses is removed as `sketch.trim` removes the piece at that crossing, in the order the path crosses them; a piece crossed again after it is gone is not trimmed twice; projected geometry, infinite lines, offset outputs and pieces trim refuses are passed over (why in `skipped`). dry_run?: bool (report without changing the design). One undo step → trimmed: [each trim's result], removed: [[[x,y]…]] (each removed piece as a polyline). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Break Link
Automation details
Command ID: sketch.break_link
Parameters:
link?: link id (j1) | entities?: [curve or point ids]: linked geometry becomes normal sketch geometry
Auto-Project Edges on Reference
Automation details
Command ID: sketch.auto_project
Parameters:
value?: bool (default toggles): project a face's edges when a sketch is created on it
Redefine Sketch Plane
Automation details
Command ID: sketch.redefine
Parameters:
sketch: id or name; plane: as sketch.create; frame?: as sketch.create; author_path?: exact owner occurrence ids; explicitly rebinds or freezes the plane while preserving geometry and dimension names
Copy Sketch Entities
Automation details
Command ID: sketch.copy
Parameters:
entities?: [curve or point ids] (default: the selected sketch geometry); the constraints and dimensions among them come along
Cut Sketch Entities
Automation details
Command ID: sketch.cut
Parameters:
entities?: [curve or point ids] (default: the selected sketch geometry): copies them as sketch.copy does, then deletes them (one undo step)
Paste Sketch Entities
Automation details
Command ID: sketch.paste
Parameters:
at?: [x, y] (sketch point the copy's centre goes to) | translate?: [x, y]; without either, offset from the original in its own sketch and at the same coordinates in another; sketch?: target (default: the active sketch)
Inspect Sketch
Automation details
Command ID: sketch.inspect
Parameters:
sketch?: id|name (default active)
Other commands: canvas
Calibrate Canvas
Automation details
Command ID: canvas.calibrate
Parameters:
canvas: id or name; a, b: two points on the canvas plane [x,y]; distance: their true distance (mm): scales the image about a
Delete Canvas
Automation details
Command ID: canvas.delete
Parameters:
canvas: id or name
Canvas to Sketch
Automation details
Command ID: canvas.to_sketch
Parameters:
canvas?: id or name (default: the last canvas); name?: the new sketch's name. Traces the canvas image (dark drawing on a light background, or the reverse) into a new sketch on the canvas plane, lying exactly on the image: lines, splines and holes
Other commands: mesh
Convert Mesh
Automation details
Command ID: mesh.convert
Parameters:
body?: mesh body name (default: the selected mesh body); tolerance?: length expression (default 0.05 mm): reconstruct a closed constant-section mechanical mesh as editable planar and cylindrical faces, with a parametric timeline feature in the mesh body's owning component (the active component must be that component or an ancestor; otherwise activate the named component first); the active component is unchanged; name?
Other commands: sheet
List Sheet Metal Rules
Automation details
Command ID: sheet.rules
Parameters:
→ rules with their expressions and values, and the active one
Export Flat Pattern as DXF
Automation details
Command ID: sheet.export_dxf
Parameters:
body?, path?, curves?: exact|arcs (default exact: an ellipse as ELLIPSE, splines, text outlines and other Béziers as SPLINE; arcs: tangent arcs and lines within 0.001 mm, for machines that read only those), text?: cut|etch (default cut: letters that text cut through the sheet are cut contours on layer TEXT; etch: single contours on layer ETCH and the sheet keeps its material there; text machined to a depth is always on ETCH), bend_notes?: bool (default false: a note beside each bend line on layer BEND_NOTES, `B1 UP 90%%d R1`, its angle the included angle when the body's rule has angle_display: included), note_height?: mm (default 3), bend_lines?: bool (default true; false leaves BEND_UP and BEND_DOWN out, for a cutter that would cut them), layers?: roles|single (default roles, the layers below; single: every entity on layer 0), per_body?: bool (a file for every sheet body, `<path>-<body>.dxf`, or their DXF texts without a path → files: [{body, path | dxf, bends}]; not with `body`) → DXF (AutoCAD 2000, mm): layers OUTLINE (the outer contour), CUT (what is cut out inside it), TEXT or ETCH (letters), BEND_UP and BEND_DOWN (dashed centre lines), BEND_NOTES, POCKET_<BOTTOM|TOP|INTERNAL>_Z<from>_TO_<to>_MM (one per side and depth) and PERFORATION; inner contours come before the outline
Export Bend Table
Automation details
Command ID: sheet.export_bend_table
Parameters:
body?, format?: csv|json (default csv), path? → bend table in mm and degrees, including rule and flat centre-line coordinates; angle_deg is the bend angle and included_angle_deg the angle left between the bend's sides (180° less it); a bend that cut-outs split has a row for each piece
Other commands: plastic
List Plastic Rules
Automation details
Command ID: plastic.rules
Parameters:
→ rules with their expressions and values, the active rule and the rules assigned to bodies
Other commands: frame
Profile Library
Automation details
Command ID: frame.profile.list
Parameters:
Returns kinds: [{kind, label, required, optional, example}]: the built-in profiles, each SolidCraft's own parametric definition
Profile Properties
Automation details
Command ID: frame.profile.properties
Parameters:
profile: {kind: bar|round_bar|rect_tube|round_tube|angle|channel|i_beam, …its dimensions: each a number in the design's units or a length expression over its parameters ("tube_wall", "2 in")} (frame.profile.list gives each kind's dimensions and an example; dimensions left out take defaults from the profile's size); density?: kg/m³ (adds mass_per_m, kg/m). Returns profile (every default filled in), area (mm²), centroid [x,y] (mm, from the bounding box centre), ixx, iyy, ixy (mm⁴ about the centroid), width, height
Insert Profile
Automation details
Command ID: frame.profile.insert
Parameters:
profile: {kind: bar|round_bar|rect_tube|round_tube|angle|channel|i_beam, …its dimensions: each a number in the design's units or a length expression over its parameters ("tube_wall", "2 in")} (frame.profile.list gives each kind's dimensions and an example; dimensions left out take defaults from the profile's size); at?: [x,y] (sketch mm, default the origin); anchor?: origin|center|centroid|top_left|top|top_right|left|right|bottom_left|bottom|bottom_right (the profile point placed on `at`: its origin, the default and a built-in profile's centre, its bounding box's centre, its centroid, or a corner or edge middle of its bounding box); angle?: radians, counter-clockwise; fixed?: bool (default true: the curves are locked, so solving the sketch keeps the shape); sketch?: id|name (default: the sketch being edited). Returns the section as frame.profile.properties does. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
Other commands: component
Cycle Component Color
Read the components and joints guide.
Automation details
Command ID: component.cycle_color
Parameters:
component: id or name; saves the next identification colour and enables Component Colors; shared by every instance, separate from appearances
Create Components from Bodies
Read the components and joints guide.
Automation details
Command ID: component.from_bodies
Parameters:
bodies: [names] → one new component per body, named after it
Activate Component
Read the components and joints guide.
Automation details
Command ID: component.activate
Parameters:
component: id or name (0 or "root" for the design); occurrence?, path?: exact placement used to author shared geometry
List Components
Read the components and joints guide.
Automation details
Command ID: component.list
Parameters:
compact?: bool (counts of bodies, features, sketches and construction planes instead of their names; occurrences leave out identity transforms; children as occurrence ids); bodies?: bool (with compact: body names, not their count)
Component Physical Properties
Read the components and joints guide.
Automation details
Command ID: component.properties
Parameters:
component?: id or name (default active); descendants?: bool (default true). Measures the first placement, or all placements for root, including pending moves; material-aware mass and center of mass in world coordinates; volume, density, mass and center of mass only for closed, consistently oriented bodies; total.open_bodies_skipped counts open bodies excluded from volume/mass totals
Rename Component
Read the components and joints guide.
Automation details
Command ID: component.rename
Parameters:
component, name
Delete Component
Read the components and joints guide.
Automation details
Command ID: component.delete
Parameters:
component: deletes it, its subcomponents, occurrences and features
Unlock Contents
Read the components and joints guide.
Automation details
Command ID: component.unlock
Parameters:
component: removes the rigid joints that hold its contents together (an imported assembly's rigid group), so its parts move and take joints one by one → joints (the ids removed)
Move Bodies to Component
Read the components and joints guide.
Automation details
Command ID: component.move_bodies
Parameters:
bodies: [names], component; component_path?: its placement to place them by (default: its only one, else the active one; a component placed more than once gets them in every instance); from_path?: the placement of their component they move from (default: its only one, else its first, where its bodies have their own names)
Paste Bodies
Read the components and joints guide.
Automation details
Command ID: component.copy_bodies
Parameters:
bodies: [names, as the Browser shows them: a body seen in another placement of its component (`Cube (B:2)`) is copied from there]; component?: where the copies go (default: the active one); component_path?: its placement to place them by (default: its only one, else the active one; a component placed more than once gets them in every instance). Each copy stays where its body is in the world; the copies' names come back as `bodies`
Move Sketches to Component
Read the components and joints guide.
Automation details
Command ID: component.move_sketches
Parameters:
sketches: [ids or names], component
Paste New
Read the components and joints guide.
Automation details
Command ID: component.paste_new
Parameters:
component: independently copy its nested definition graph and history, materials and styles; occurrence?, path?: selected source placement; parent?: component the copy goes in (default: the source placement's; not the component or one inside it); in_place?, parent_path?: as occurrence.copy; translate?: [x,y,z]. Reassigned external bodies become base geometry; retained external links and omitted boundary joints are reported
Update Links
Read the components and joints guide.
Automation details
Command ID: component.update_links
Parameters:
component?: id or name (default: every linked component) — reads the linked designs again and replaces their bodies where they changed; → updated, unchanged, warnings (also joints whose faces are gone or that no longer solve)
Relink Source
Read the components and joints guide.
Automation details
Command ID: component.relink
Parameters:
component: id or name of a linked component; path: a saved .solidcraft design — replaces the source and linked bodies for all its occurrences, preserving identities and placements; returns path, previous_path, warnings, joint_errors and joint_conflicts
Break Link
Read the components and joints guide.
Automation details
Command ID: component.break_link
Parameters:
component: id or name of a linked component — keeps its bodies as they are and forgets the design file
Extract to File
Read the components and joints guide.
Automation details
Command ID: component.extract
Parameters:
component: id or name of a component that is not linked; path: the .solidcraft design to write → its history, subcomponents and their joints move to that design, and the component becomes linked to it (as component.insert_design makes it); returns path, file, feature, bodies, notices (what stays behind), warnings and copies (components that build the same bodies: see component.replace_with_link)
Find Identical Copies
Read the components and joints guide.
Automation details
Command ID: component.copies
Parameters:
component: id or name of a linked component → copies: [{component, name}], the components not linked whose own history builds the same bodies
Replace with Link
Read the components and joints guide.
Automation details
Command ID: component.replace_with_link
Parameters:
component or components: [ids or names] of components that are not linked; with: a linked component, or path: a .solidcraft design (its link here, else a new linked component) → each component's history goes and its placements become placements of the linked component; refused when its bodies are not the same as the link's; returns component, name, replaced and warnings
List Links
Read the components and joints guide.
Automation details
Command ID: component.links
Parameters:
→ linked components: component, name, path, file (resolved), found (null when the file is on a network location not allowed, or names a device: it is not looked for), and then host (the network location) and reason
Part Properties
Read the components and joints guide.
Automation details
Command ID: component.metadata
Parameters:
component: id or name (0 or "root": the design's own properties) → component, name, part_number, description, revision, metadata (as saved, other tools' keys too), properties: {name: text}
Change Part Properties
Read the components and joints guide.
Automation details
Command ID: component.metadata.set
Parameters:
component: id or name (not the root: use document.metadata.set); part_number?, description?, revision?: text (empty or null removes); values?: {key: value} (SolidCraft's keys, as document.metadata's properties lists them, or a reverse-DNS key holding an object); properties?: {name: text, …} (free properties; empty or null removes). All or nothing, one undo step → component, metadata, properties, changed: [keys]
Other commands: occurrence
Occurrence Physical Properties
Read the components and joints guide.
Automation details
Command ID: occurrence.properties
Parameters:
occurrence: id or name; path?: [occurrence ids] (required if nested under multiple instances); descendants?: bool (default true). Physical properties of this placement in world coordinates, including pending moves; volume, density, mass and center of mass only for closed, consistently oriented bodies; total.open_bodies_skipped counts open bodies excluded from volume/mass totals
Paste (Instance)
Read the components and joints guide.
Automation details
Command ID: occurrence.copy
Parameters:
component (or occurrence): what to place again; path?: validates selected occurrence; parent?: component (default: the same parent); translate?: [x,y,z]; in_place?: bool (default false: the selected transform within the parent; true: where the selected placement is in the world, translate in world axes; a parent placed more than once is placed by parent_path?, else the active placement, and every instance gets the copy). Copies the selected effective transform, including pending moves
Move Occurrence
Read the components and joints guide.
Automation details
Command ID: occurrence.move
Parameters:
occurrence; path?: exact placement; space?: local|world (default local); translate?: [x,y,z]; axis?: [x,y,z], angle?: radians or "30 deg", origin?: [x,y,z]; matrix?: [12 or 16 numbers] (instead of translate/axis/angle/origin: a rigid move as a row-major 3x4 rotation-and-translation or 4x4 matrix, flat or as rows; one that is a rotation only up to rounding is orthonormalised, a scale, shear or mirror is refused); tolerance?: largest accepted |RᵀR − I| (default 1e-9, at most 1e-3); capture?: bool (default true; false keeps it pending until Capture Position); move_grounded?: bool (default false: a grounded occurrence refuses; true moves it and it stays grounded). Nested occurrence definitions remain shared
Drag Occurrence
Read the components and joints guide.
Automation details
Command ID: occurrence.drag
Parameters:
occurrence or path: exact placement; at: [x,y,z] world point on it; point: [x,y,z] where it goes. Moves the placement by point - at (with any chain of joints it is in that is joined to nothing grounded) and captures its position; other pending moves stay pending. Shared definitions move in all placements. Grounded parts and parts joints hold to something fixed are refused (joint.reach moves those)
Ground
Read the components and joints guide.
Automation details
Command ID: occurrence.ground
Parameters:
occurrence; grounded?: bool (default toggles)
Move to Component
Read the components and joints guide.
Automation details
Command ID: occurrence.reparent
Parameters:
occurrence; path?: exact placement; parent: component id or name (0 or "root" for the design); parent_path?: the parent's placement to place it by (default: its only one, else the active one; every instance of the parent gets it). The placement keeps its world position; a move that would break a feature is refused
Other commands: joint
Reach Point
Read the components and joints guide.
Automation details
Command ID: joint.reach
Parameters:
occurrence: id|name; point: [x,y,z] (where its point goes, world); at?: [x,y,z] (its point, in the world where it is now; default: the centre of its bodies' box); z?: [x,y,z] (the way its axis then points); axis?: [x,y,z] (that axis, in the world now; default: its own z); joints?: [ids or names] (only these move toward the point; the other joints of a closed loop they are in follow); quiet?: bool; continuous?: bool (a frame of a drag: the answer says continuous, and the app's command line doesn't echo it). Moves the values of the joints on its chain from where they are to the nearest pose, within their limits, through motion links, keeping closed loops closed as joint.drive does (contact sets are not checked) → reached; error_mm (and error_deg with z): how close it got; point: where its point is; values: [{joint, name, values}] of the joints that moved, as joint.list gives them; moved; stopped_by_limit: [{joint, value, limit: min|max}]: the limits it rests on short of the point; stopped_by_loop: {joint}: the closed loop that stopped it (its chain alone gets closer); reason: why nothing can move
Joint Limits
Read the components and joints guide.
Automation details
Command ID: joint.limits
Parameters:
joint; index?: value index (default 0); min?, max? (omit both to clear)
Edit Joint
Read the components and joints guide.
Automation details
Command ID: joint.edit
Parameters:
joint; type?, flip?, offset?, angle? (a number, or an expression the joint then follows), suppressed?, name?
Delete Joint
Read the components and joints guide.
Automation details
Command ID: joint.delete
Parameters:
joint: id|name (or origin: name, link: index)
List Joints
Read the components and joints guide.
Automation details
Command ID: joint.list
Parameters:
→ joints (type, occurrences, values, limits; expressions: {offset?, angle?, values?} when they were typed as expressions), origins, links, degrees of freedom, conflicts, floating: [{occurrences, joints}] (joint chains joined to nothing grounded: the solver places none of them, so driving them moves nothing)
Inspect Joint
Read the components and joints guide.
Automation details
Command ID: joint.inspect
Parameters:
joint?: id|name (default: every joint) → joints: [{id, name, type, suppressed, visible, flip, offset (mm), angle (deg), values: as joint.list gives them, dofs, expressions?: {offset?, angle?, values?}, a, b: {occurrence, occurrence_name, component, component_name, path (occurrence names from the top, `/` between), snap (as saved: type, pick, face, end, edge, corner, x, point, z, in the component's coordinates), references: [{role: face|end|x|edge|corner, name, role_in_feature?: (top, side:c1…), feature?: {id, name, type} (the feature that made the face; error when it is gone), sketch?: {id, name, curve, curve_type} (a face swept from a sketch curve), faces?: [an edge's two faces], unverified?: a name saved before exact names}], resolves, frame?: {origin, z, x} in the world, error?: why the origin is lost}, links: [{index, role: driver|driven, …as joint.list}], errors, conflicts}] (reads only: nothing changes)
Solve Joints
Read the components and joints guide.
Automation details
Command ID: joint.solve
Parameters:
re-place the occurrences from the joints → conflicts, errors, dof
Sweep Joint
Read the components and joints guide.
Automation details
Command ID: joint.sweep
Parameters:
joint: id|name; from, to: its value the sweep runs between (degrees for angles, mm for distances, or expressions); index? (which value, default 0); step? (between samples, in the joint's unit; default: fine enough that no part passes through another between two); refine?: mm (the first contact is bisected until no part moves more than this between the last clear value and the touching one; default 0.01); occurrences?: [ids or names] (checked against each other; default every occurrence), with against?: [ids or names] (the parts that stay: each of `occurrences` only against these), or contact_sets?: true (the pairs of the design's contact sets); report_gaps?: bool (the nearest gap at every sample, default false); apply?: bool (leave the design where the sweep stopped; default false: the design is not changed) → stopped_at (the last value clear of contact, or where a loop or limit stopped it; null: it reached `to`), stopped_by: {a, b, at: [x,y,z] (where they touch, world)} | "loop" | "limit" | null, samples: [{value, min_gap_mm?, pair?}], min_gap? (report_gaps: {part: {mm, at_value, to}}, the nearest each part came to another), unit, gap_method ("mesh": distances between the tessellations), gap_tolerance_mm (how far those can be off: 0 when every face is flat), pairs (checked), touching_at_start: [[a, b]] (pairs already touching at `from`, such as parts joined face to face or a door on its frame: these stop it only once they pass into each other deeper than they start), applied. The parts are tested at the samples and through the bisection; the motion is on a copy (closed loops follow, contact sets do not stop it)
Other commands: inspect
Clearance
Automation details
Command ID: inspect.clearance
Parameters:
bodies?: [names] (default: every solid body, in world placement, but those in `against`; mesh bodies are not measured); against?: [names] (only pairs with one body in `bodies` and the other here); min_volume?: mm³ (default 0.01: smaller overlaps count as contact); clearance?: mm (default 0: report pairs closer than this); nearest?: n (each body of `bodies` gets its n nearest others); skip_within?: "occurrence" (pairs of bodies of one occurrence) | "component" (pairs of bodies inside one placed component, its subcomponents included) | null; tolerance?: mm (default 1e-6: a gap below it is contact, 0) → overlaps: [{a, b, volume_mm3, area_mm2, bbox, center}]; near: [{a, b, distance_mm, from, to, overlap_mm3?: a contact's shared volume below min_volume, exact?: false when measured on the meshes}], nearest by body (the same for pairs that overlap: 0); nearest: {body: [{body, distance_mm}]}; unchecked: [{a, b, reason}] (pairs whose overlap could not be worked out; each mesh body, STL/3MF/OBJ import, is not measured and is listed as {a: name, b: null, reason: "mesh body: not checked"}, also when named in `bodies` or `against`; the result's `bodies` counts the solid bodies only); pairs_checked; pairs_reused (of those, measured by an earlier call: neither body changed or moved since); pairs_pruned (ruled out by their boxes); ms. Distances are measured on the exact faces and edges, the meshes only rule pairs out
Parameter Sweep
Automation details
Command ID: inspect.parameter_sweep
Parameters:
parameter: name; values: [number | expression] (at most 200) | from, to: numbers, steps? (default 10, at most 199: steps + 1 values) → for each value, in a copy of the design (the design itself is not changed): {value, ms (the rebuild), ok, failed: [{feature, name, error}], warned: [{feature, name, warning}] (features that build at the current value and fail or warn at this one), joints_lost: [{joint, name, origin?: a|b, why}], joints_flipped: [{joint, name, origin: a|b, degrees}] (an origin's z turned more than 90° on its own part from where it is now), sketches_flipped: [{sketch, name, degrees}] (the sketch plane's normal turned more than 90°), sketches_failed: [{sketch, name, failing, folded}] (a solve that fails, or keeps a triangle's turn or an offset's side only by flipping it)} (or {value, ms, ok: false, error} where the parameter takes no such value); summary: {values, ok, slowest_ms, failed, joints_lost, joints_flipped, sketches_flipped, sketches_failed} (how many values show each)
Compare Bodies
Automation details
Command ID: inspect.compare
Parameters:
a, b: body names (default: the two selected bodies, or the design's only two), in world placement; tolerance?: mm (0.001 to 10, default 0.01): surfaces closer than this agree; spacing?: mm (default 0.5): the largest piece of a face sampled as one point, coarser for a body that would need more than max_points? (default 8 million, up to 50 million) samples; max_regions? (default 50); color?: true | {only_in_a?, only_in_b?: "#rrggbb"} (colours the faces that hold a region, red where only a has material and green where only b has: the faces mostly beyond the tolerance, else the one with the largest share; an undo step) → a, b: volume_mm3, area_mm2, center_of_mass, bbox; difference: b − a; deviation: a_to_b, b_to_a (max_mm, mean_mm, at, area_beyond_mm2); regions, largest first: side (only_in_a | only_in_b), volume_mm3 (integrated along lines through the region: resolution), area_mm2, max_deviation_mm, at, center, bbox, faces_a, faces_b (face indices, as model.faces lists them); identical; verdict; warnings (a body whose surface does not enclose what it measures, lines left out). Works from the two surfaces, without a boolean of the bodies, so near-identical bodies compare too
Silhouette
Automation details
Command ID: inspect.silhouette
Parameters:
view?: front|back|left|right|top|bottom|iso (default front); or camera?: {target: [x,y,z], yaw, pitch, distance, fov?} (mm, radians; fov 0 means parallel); bodies?: [world body names] (default visible bodies, named hidden bodies included); rows?: mask height in pixels (default 240, clamped to 16..1024) → tightly framed run-length mask, box_mm, mm_per_px, right/up, projection, body and triangle counts
Other commands: contact
Edit Contact Set
Automation details
Command ID: contact.edit
Parameters:
set: id|name; occurrences?; name?; suppressed?: bool; delete?: bool
List Contact Sets
Automation details
Command ID: contact.list
Parameters:
→ sets, enabled, all
Check Contact
Automation details
Command ID: contact.check
Parameters:
→ pairs in contact sets that pass through each other now
Other commands: motion
Play Motion Study
Read the components and joints guide.
Automation details
Command ID: motion.play
Parameters:
study: id|name; step (0…steps) — sets the joints to the study's values there (contact sets stop them); quiet?: bool (positions of only the occurrences that moved) → values: [{joint, values: [{name, value, unit}]}], positions, moved: whether any occurrence moved, unmoved: [{joint, name, reason}] (keyed joints that can move nothing, such as a chain joined to nothing grounded)
Export Motion Study
Read the components and joints guide.
Automation details
Command ID: motion.export
Parameters:
study; samples? (default steps + 1); path? (.json or .csv) → occurrence positions per step (the design is not changed)
Export Motion Animation
Read the components and joints guide.
Automation details
Command ID: motion.export_animation
Parameters:
study: id|name, or joint: id|name with from and to (degrees for angles, mm for distances, or expressions) and index?, or view: id|name (an exploded view, coming apart from the assembled placement) with back? (default false; true: back together too, exploded at the middle frame of an odd count); path: a .gif file, or a folder for PNG frames (frame-0001.png, frame-0002.png, …); samples? (frames, 2…1000; default the study's steps + 1, 100 for a joint, 41 for a view or 81 with back); width?, height? (16…4096 pixels, default 800 × 500); camera?: {target, yaw, pitch, roll?, distance, fov?, up?} (ui.camera's reply; fields left out keep the default view: view.apply's camera, or the home view framing the model, for a view both assembled and exploded); fps? (1…50, default 20); grid?, axes? (default true: the ground grid and the origin's axes) - draws the study as it plays, the joint as it is driven or the view as it explodes, with the headless renderer → path, format, frames, width, height, fps (GIF), bytes, renderer, stopped_at, stopped_by_loop, stopped_by_contact (the design is not changed, nor its placements)
Keep Motion Trace
Read the components and joints guide.
Automation details
Command ID: motion.trace.keep
Parameters:
occurrence: id|name, or an occurrence path [ids] (an instance inside sub-assemblies); point: [x,y,z] world point on it, where it is placed now; study: id|name, or joint: id|name with from and to (degrees for angles, mm for distances, or expressions) and index? (which value, default 0); samples? (2…500, default the study's steps + 1, or 100) - the point's path as the study plays or the joint is driven, closed loops following; name? (default Motion TraceN); auto_update?: bool (default true); point_ref?: joint origin snap - keeps the trace as a root 3D sketch spline. Associative curves follow mechanism and driving-input edits; false keeps a fixed curve. point_ref retains a geometry snap; otherwise point is retained in component coordinates → sketch, name, curves, points
Edit Motion Trace
Read the components and joints guide.
Automation details
Command ID: motion.trace.edit
Parameters:
sketch: id|name; joint? with from and to, or study?; index?; samples? (2…500); auto_update?: false to detach. Edits a saved associative trace's driving conditions in one undo step
Plot Motion Study
Read the components and joints guide.
Automation details
Command ID: motion.plot
Parameters:
study: id|name; joints?: [id|name] (default: every joint that moves); occurrence? and point? (a point of a part to follow, as motion.trace takes them); samples? (3…1000, default the study's steps + 1); duration?: seconds the whole study takes, 0.001 or more (velocities and accelerations then per second, else per step) → step: [the study step of each sample], time?: [s], joints: [{joint, name, index, dof, unit, value, velocity, acceleration, velocity_unit, acceleration_unit}] (degrees for angles, mm for distances; angles unwrapped), point?: {occurrence, position: [[x,y,z]…], velocity, acceleration (vectors), speed, acceleration_norm, unit, velocity_unit, acceleration_unit}, samples (taken), stopped_at, stopped_by_loop, stopped_by_contact. The study plays as motion.play plays it, each sample driven from the one before, on a copy (the design is not changed); velocities and accelerations are finite differences between samples (central, one-sided at the ends)
Motion Interference
Read the components and joints guide.
Automation details
Command ID: motion.interference
Parameters:
study: id|name; occurrences?: [ids or names] (two or more, checked against each other; default every occurrence), with against?: [ids or names] (then each of `occurrences` is checked only against these, such as an arm against the parts it must miss), or contact_sets?: true (the pairs of the design's contact sets); samples? (2…1000, default the study's steps + 1) → collisions: [{a, b, first_step, last_step, steps: [[from, to]…] (the step ranges where they overlap), depth_mm (how far into each other, the most at deepest_step), volume_mm3 (their overlap there; null when it was not cheap to work out)}], clear, pairs (checked), samples (taken), truncated (the work budget ran out; checked_to: the last step checked), stopped_at, stopped_by_loop. The study plays as motion.play plays it but contact sets do not stop it, on a copy (the design is not changed). Parts are tested at the samples only: one that passes wholly through another between two samples is missed
List Motion Studies
Read the components and joints guide.
Automation details
Command ID: motion.list
No parameter description is provided by the command registry.
Delete Motion Study
Read the components and joints guide.
Automation details
Command ID: motion.delete
Parameters:
study
Other commands: explode
Show Exploded View
Automation details
Command ID: explode.show
Parameters:
view: id|name (or none: back to the assembled placement) — shown only; the design keeps its placements
List Exploded Views
Automation details
Command ID: explode.list
No parameter description is provided by the command registry.
Delete Exploded View
Automation details
Command ID: explode.delete
Parameters:
view
Other commands: config
Configuration Column
Read the parameters and expressions guide.
Automation details
Command ID: config.column
Parameters:
param? | feature? | material? | appearance? (target name); delete?: bool
Configuration Row
Read the parameters and expressions guide.
Automation details
Command ID: config.row
Parameters:
name; from? (row to copy; default current values); values?: {column key: expression, suppression, material name, appearance name or Look object}; rename?; delete?: bool
Activate Configuration
Read the parameters and expressions guide.
Automation details
Command ID: config.activate
Parameters:
row: name — sets its parameter values and suppressions
Preview Configuration
Read the parameters and expressions guide.
Automation details
Command ID: config.preview
Parameters:
row: name — validates a rebuild without changing the design or history
Configuration Rule
Read the parameters and expressions guide.
Automation details
Command ID: config.rule
Parameters:
rule?: {id,name,enabled,condition,effect}; rules?: [rule…] (added or replaced together, then the rows checked once); id? + delete?; id? + index? (ordered move)
Configuration Rules
Read the parameters and expressions guide.
Automation details
Command ID: config.rules
Parameters:
ordered typed rules, rows and active state
Other commands: parts
Standard Parts
Automation details
Command ID: parts.library
Parameters:
family? → families (name, standard, sizes, lengths) and, with family, each size's dimensions (mm)
Insert Part
Automation details
Command ID: parts.insert
Parameters:
family (socket_head_cap_screw | hex_bolt | hex_nut | washer | set_screw | dowel_pin | bearing); size ("M6", or "608" for bearings); length? (mm; screws, bolts, set screws, pins; default a standard length near 2.5 d); at?: [x,y,z] on a hole's rim or wall (the part seats on the hole's face with a rigid joint) | point?: [x,y,z] with direction?: [x,y,z] (no joint); name?
Change Part Size
Automation details
Command ID: parts.resize
Parameters:
component: id|name of an inserted part; size?; length? — rebuilds it (joints keep it seated)
Other commands: body
Rename Body
Automation details
Command ID: body.rename
Parameters:
body: its name, name: the new name (features that use the body follow)
Other commands: browser
New Group
Read the window tour.
Automation details
Command ID: browser.group
Parameters:
folder: bodies|sketches|construction; component?: id (default 0); name?; items?: [keys] (they leave other groups)
Ungroup
Read the window tour.
Automation details
Command ID: browser.ungroup
Parameters:
group: id (its items go back to the folder)
Rename Group
Read the window tour.
Automation details
Command ID: browser.rename_group
Parameters:
group: id, name
Move Browser Items
Read the window tour.
Automation details
Command ID: browser.move
Parameters:
folder, component?, items: [keys]; group?: id (into it; omit to take them out of groups); before?: key (position in the group)
Order Browser Items
Read the window tour.
Automation details
Command ID: browser.order
Parameters:
folder, component?, order: [keys] (the folder's items in display order)
Show/Hide
Read the window tour.
Automation details
Command ID: browser.visibility
Parameters:
visible: bool; items?: [sketch id or name | body name | construction name | joint name | O|X|Y|Z|XY|XZ|YZ]; or joints?: [joint id or name] (their glyphs; they still constrain and move parts); or features?: [feature id or name] (the bodies they made; path?: [occurrence ids] for only that placement's); or folder?: bodies|sketches|construction|origin|joints|all (all of it; showing also shows its browser folders and groups, and `all` takes in the occurrence hides too); or tree_folders?: [browser folder or group key: c0/bodies, o:4/sketches, c0/g7, c0/joints, c0/canvases] (the folder or group itself: its items keep their own state); or paths?: [[occurrence ids]] (exact placements and descendants), isolate?: bool (requires paths and visible:true). Saved with the design and undone like any other change (the camera is not)
Expand All
Read the window tour.
Automation details
Command ID: browser.expand_all
Parameters:
path?: [occurrence ids] (that placement and its descendants); component?: id (that component's own folders); omit both for the whole tree, including folders that start closed (Origin, Named Views, Features). Display only: open/closed rows are not saved in the design and are not an undo step → open, open_by_default, closed_by_default
Collapse All
Read the window tour.
Automation details
Command ID: browser.collapse_all
Parameters:
path?: [occurrence ids]; component?: id; omit both for the whole tree (the design row stays open so the first level remains visible). Display only; not an undo step → open, open_by_default, closed_by_default
Other commands: document
Change Design Units
Automation details
Command ID: document.units
Parameters:
units: mm|cm|m|in|ft (display and unit-less parameters; values keep their meaning)
Design Properties
Automation details
Command ID: document.metadata
Parameters:
→ metadata: the design's metadata object as saved (other tools' keys too); properties: each of SolidCraft's design properties (project, part number, revision…), its text or null (absent, or not text)
Change Design Properties
Automation details
Command ID: document.metadata.set
Parameters:
key and value, or values: {key: value, …} (all or nothing, one undo step). SolidCraft's keys, the ones document.metadata's properties lists, take text ("" or null removes); another tool's data goes under its reverse-DNS key such as com.example.tool (an object; null removes) → metadata, changed: [keys]
Remove Design Property
Automation details
Command ID: document.metadata.remove
Parameters:
key, or keys: [keys] (any key, also one another tool wrote) → metadata, removed: [keys that were there]
Inspect Design
Automation details
Command ID: document.inspect
Parameters:
measure?: bool (include body measurements)
Design Revision
Automation details
Command ID: document.revision
Parameters:
→ document_id (this open design's id: kept while it stays open, another for each design started or opened), revision (moves whenever the design, what is hidden or a component's uncaptured position changes, never on a selection or a view; from one counter for the whole run, so it never repeats). Read-only
Other commands: view
New Named View
Read the window tour.
Automation details
Command ID: view.save
Parameters:
name?; camera: {target: [x,y,z], yaw, pitch, distance, fov?} (replaces a view of the same name)
Rename Named View
Read the window tour.
Automation details
Command ID: view.rename
Parameters:
view: name, name
Delete Named View
Read the window tour.
Automation details
Command ID: view.delete
Parameters:
view: name
List Named Views
Read the window tour.
Automation details
Command ID: view.list
No parameter description is provided by the command registry.
Restore Named View
Read the window tour.
Automation details
Command ID: view.apply
Parameters:
view?: front|back|top|bottom|left|right|iso|home (frames the model) | a named view's name; or camera?: {target: [x,y,z], yaw, pitch, distance, fov?, roll?} (radians, mm; roll defaults to 0, must be finite and is clamped to ±π; up is refused: the up axis comes from the design settings) → camera. The viewport (and the MCP screenshot) moves there. View state: not saved, not an undo step
Fit
Default keyboard shortcut: F6
Read the window tour.
Automation details
Command ID: view.fit
Parameters:
no parameters → frame the active canvas or selection; display only
Home view
Read the window tour.
Automation details
Command ID: view.home
Parameters:
no parameters → home orientation framing the active canvas; display only
Navigation tool
Read the window tour.
Automation details
Command ID: view.tool
Parameters:
tool: orbit|pan|zoom|none → tool; latch a camera tool, none releases it
Continuous orbit
Read the window tour.
Automation details
Command ID: view.spin
Parameters:
on?: bool (omitted: toggle) → on?; continuous orbit until the next click or key
Visual style
Read the window tour.
Automation details
Command ID: view.visual_style
Parameters:
style: shaded_with_edges|shaded|wireframe|shaded_with_hidden_edges → style, index (0..3 in that order)
Grid
Read the window tour.
Automation details
Command ID: view.grid
Parameters:
on?: bool (omitted: toggle) → on?; show the viewport grid
Ground shadow
Read the window tour.
Automation details
Command ID: view.ground_shadow
Parameters:
on?: bool (omitted: toggle) → on?; show the ground shadow
Navigation bar
Read the window tour.
Automation details
Command ID: view.navigation_bar
Parameters:
visible?: bool (omitted: toggle) → visible?; show the navigation bar (window setting)
Render Stage
Read the window tour.
Automation details
Command ID: view.render_stage
Parameters:
stage: look|light|frame|output → {stage}; per-document UI state, never edits or evaluates the design; headless validation only
Shots Strip
Command group: RENDER CAMERA › SHOTS
Read the window tour.
Automation details
Command ID: view.shots_strip
Parameters:
visible: bool → {visible}; Render Shots strip UI preference. Headless echo only, no document edits or undo
Workspace layout
Read the window tour.
Automation details
Command ID: view.layout
Parameters:
mode: floating|compact → validated workspace layout; display only, never opens a dialog
History layout
Read the window tour.
Automation details
Command ID: view.history_layout
Parameters:
width?: 1..1000000; dock?: bottom|left|under_browser; collapsed?: bool → validated layout request; display only, never opens a dialog
Graphics Preset
Read the window tour.
Automation details
Command ID: view.graphics_preset
Parameters:
preset: performance|quality → preset, enabled, display_quality, supersampling, supersampling_adaptive; Quality: AO, Fine, adaptive 2x SSAA; Performance: no AO, Normal, no SSAA
Ambient Occlusion
Read the window tour.
Automation details
Command ID: view.ambient_occlusion
Parameters:
enabled: bool → enabled; apply soft contact shading in the GPU viewport
Perspective view
Read the window tour.
Automation details
Command ID: view.perspective
Parameters:
enabled: bool → enabled; draw the view in perspective, or orthographic when false; a sketch being edited is always shown orthographic
Navigate View
Read the window tour.
Automation details
Command ID: view.navigate
Parameters:
camera: Camera; direction?: [x,y,z] in the camera's up-axis frame; or action: left|right|up|down|clockwise|counterclockwise|home|fit, degrees?: 0..180 (how far left|right|up|down turn; default 90); or motion: pan|orbit|zoom, delta?: [dx,dy] (pixels, each ±10000), free_orbit?: bool (orbit through poles, default false), viewport?: [width,height] (1..1000000, required for pan or pivot), pivot?: [x,y,z], factor?: 0.05..20 (required for zoom), anchor?: [x,y,z] → camera, fit, continuous?
Normal to Selection
Read the window tour.
Automation details
Command ID: view.normal_to
Parameters:
camera?: Camera (default: current session camera or home view) → camera; Look At the first selected planar face or plane, centered on the picked point; repeat while aligned to flip the side; preserves scale and projection
Front View
Default keyboard shortcut: Ctrl+1
Read the window tour.
Automation details
Command ID: view.front
Parameters:
camera?: Camera (default: current session camera or home view) → camera; turns to that standard view as the view cube does, keeping target, distance and projection
Back View
Default keyboard shortcut: Ctrl+2
Read the window tour.
Automation details
Command ID: view.back
Parameters:
camera?: Camera (default: current session camera or home view) → camera; turns to that standard view as the view cube does, keeping target, distance and projection
Left View
Default keyboard shortcut: Ctrl+3
Read the window tour.
Automation details
Command ID: view.left
Parameters:
camera?: Camera (default: current session camera or home view) → camera; turns to that standard view as the view cube does, keeping target, distance and projection
Right View
Default keyboard shortcut: Ctrl+4
Read the window tour.
Automation details
Command ID: view.right
Parameters:
camera?: Camera (default: current session camera or home view) → camera; turns to that standard view as the view cube does, keeping target, distance and projection
Top View
Default keyboard shortcut: Ctrl+5
Read the window tour.
Automation details
Command ID: view.top
Parameters:
camera?: Camera (default: current session camera or home view) → camera; turns to that standard view as the view cube does, keeping target, distance and projection
Bottom View
Default keyboard shortcut: Ctrl+6
Read the window tour.
Automation details
Command ID: view.bottom
Parameters:
camera?: Camera (default: current session camera or home view) → camera; turns to that standard view as the view cube does, keeping target, distance and projection
Isometric View
Default keyboard shortcut: Ctrl+7
Read the window tour.
Automation details
Command ID: view.isometric
Parameters:
camera?: Camera (default: current session camera or home view) → camera; turns to that standard view as the view cube does, keeping target, distance and projection
Other commands: selection
Delete
Automation details
Command ID: selection.delete
Parameters:
items?: [selection items]; components?, occurrences?, canvases?, joints?: [ids or names]; dry_run?: bool → deleted, deleted_joints, would_fail, skipped (one undo step)
Other commands: edit
Undo
Default keyboard shortcut: Ctrl+Z
Automation details
Command ID: edit.undo
No parameter description is provided by the command registry.
Redo
Default keyboard shortcut: Ctrl+Y
Automation details
Command ID: edit.redo
No parameter description is provided by the command registry.
Repeat Last Command
Default keyboard shortcut: Shift+R
Automation details
Command ID: edit.repeat
Parameters:
Starts the last tool or dialog started in the app again, with its defaults (a toolbar click, not a replay of its values). Only the app has a tool to start: called here it is refused, and changes nothing
Checkpoint
Automation details
Command ID: edit.checkpoint
Parameters:
→ {checkpoint: id}: remember the design, active sketch and undo history (restore with edit.restore_checkpoint)
Restore Checkpoint
Automation details
Command ID: edit.restore_checkpoint
Parameters:
checkpoint: id; forget?: bool (true = drop it without going back). Goes back to it, undo history included
Other commands: timeline
Rename
Automation details
Command ID: timeline.rename
Parameters:
feature: id|name, name (references to it follow)
New Feature Folder
Automation details
Command ID: timeline.group
Parameters:
name?; features?: [id|name]; parent?: folder id (features leave any other timeline folder)
Remove Feature Folders
Automation details
Command ID: timeline.ungroup
Parameters:
group: id | groups: [ids] (features remain in the timeline)
Rename Feature Folder
Automation details
Command ID: timeline.rename_group
Parameters:
group: id, name
Move Features to Folder
Automation details
Command ID: timeline.move_to_group
Parameters:
features?: [id|name], groups?: [ids], group?: id (omit or null to move to the timeline root)
Move Timeline Items
Automation details
Command ID: timeline.move_many
Parameters:
features?: [id|name], groups?: [folder ids], group?: folder id, position: insertion slot, active?: bool (true = the moved contents end up before the history marker, false = behind it; other features keep their state); atomically changes folder membership and moves all selected contents as one block
Move Timeline Marker
Automation details
Command ID: timeline.rollback
Parameters:
position: number of features to keep active, or "end" (omit = end)
Roll History Marker
Automation details
Command ID: timeline.roll_to
Parameters:
position: number of features to keep active, or "end" (omit = end) | feature: id|name (the marker goes right after it)
Reorder Feature
Automation details
Command ID: timeline.reorder
Parameters:
feature: id|name, position: its new index, active?: bool (true = it ends up before the history marker, false = behind it; other features keep their state); refused when a feature that worked would fail, unless allow_errors?: bool (default false): then made, and broken: [{feature, error}] lists the features it broke
Reorder Features
Automation details
Command ID: timeline.reorder_many
Parameters:
features: [id|name], position: insertion slot in the current history; selected features keep their relative order; active?: bool (as timeline.reorder's); refused when a feature that worked would fail, unless allow_errors?: bool (default false): then made, and broken: [{feature, error}] lists the features it broke
Edit Feature
Automation details
Command ID: timeline.redefine
Parameters:
feature: id|name, command: the feature's command id, params: its parameters (the feature is rebuilt in place)
Feature Dependents
Automation details
Command ID: timeline.dependents
Parameters:
feature: id|name → features deleted with it, and features that would fail without it
Suppress Feature
Automation details
Command ID: timeline.suppress
Parameters:
feature: id|name | features: [id|name] | groups: [timeline folder ids] (every feature in those folders and their nested folders; one undo step), suppressed?: bool (default toggles: several features are all suppressed unless all of them already are, then all are unsuppressed) → feature: id (or features: [ids]; groups: [ids] when given), suppressed
Edit Feature
Automation details
Command ID: timeline.edit
Parameters:
feature: id|name, set: {fields to change, merged into the feature's definition as timeline.inspect returns it, e.g. {"extent": {"distance": "30"}} (extrude), {"angle": "90 deg"} (revolve); a dotted key names a nested field ({"plane.distance": "8 mm"} = {"plane": {"distance": "8 mm"}}); expressions are text, or a number in mm or degrees} → recomputed: features rebuilt, restored: features whose earlier result for the same inputs was reused
Inspect Feature
Automation details
Command ID: timeline.inspect
Parameters:
feature: id|name → type, definition (the fields timeline.edit's set changes, with their current values), inputs: [{label, param, expression, unit}] (named inputs parameters.change can also set), error, warning
Copy Features
Automation details
Command ID: timeline.copy
Parameters:
features: [ids or names] (in timeline order on paste)
Paste Features
Automation details
Command ID: timeline.paste
Parameters:
translate?: [x,y,z] (moves the copies' geometry); component?: where the copies go (default: the active one), component_path?: its placement (default: the active one, else its only one); the copies go at the marker, each where its original is in the world (copies into their own component keep its coordinates)
Add Recorded Feature
Automation details
Command ID: timeline.add
Parameters:
feature: a feature as the design file and timeline.inspect hold it ({type, name?, id?, …}; the name is kept even when another feature has it, the id is kept and refused when another feature has it, so later steps can refer to them); params?: [{name, expression, unit?, comment?}] (the model parameters its sketch dimensions use); the feature goes at the marker → feature, name, error, warning
Export Command Script
Automation details
Command ID: timeline.export_script
Parameters:
path?: write the script to this file → script: {commands: [...]} that rebuilds the design (solidcraft-cli eval/run), steps, omitted: what the script leaves out (appearances, folders, views, configurations); components and assemblies are not supported yet
Other commands: select
Select
Read the window tour.
Automation details
Command ID: select.set
Parameters:
items: [{type: body|edge|face|feature|sketch_curve|sketch_point|profile, …}], add?: bool
Clear Selection
Read the window tour.
Automation details
Command ID: select.clear
No parameter description is provided by the command registry.
Other commands: parameters
List Parameters
Read the parameters and expressions guide.
Automation details
Command ID: parameters.list
Parameters:
favorites?: bool (only favourites), filter?: all|user|sketch|feature|favorites → parameters (name, expression, unit, value in its unit, source, feature/input, comment, favorite, uses, used_by, error) and cycles
Add User Parameter
Read the parameters and expressions guide.
Automation details
Command ID: parameters.add
Parameters:
name, expression, unit?: as parameters.change (text: a text parameter, its expression the text), comment?, favorite?: bool
Rename Parameter
Read the parameters and expressions guide.
Automation details
Command ID: parameters.rename
Parameters:
name, new_name (expressions, feature inputs and dimensions that use it follow)
Delete Parameter
Read the parameters and expressions guide.
Automation details
Command ID: parameters.delete
Parameters:
name (refused while something uses it; the error lists the users)
Favourite Parameter
Read the parameters and expressions guide.
Automation details
Command ID: parameters.favorite
Parameters:
name, favorite?: bool (default true)
Move Parameter
Read the parameters and expressions guide.
Automation details
Command ID: parameters.move
Parameters:
name, before | after: another user parameter; the user parameter is listed just before or after it
Comment Parameter
Read the parameters and expressions guide.
Automation details
Command ID: parameters.comment
Parameters:
name, comment
Parameter Users
Read the parameters and expressions guide.
Automation details
Command ID: parameters.users
Parameters:
name → users (parameters, feature inputs, dimensions) and the features it drives
Parameter Graph
Read the parameters and expressions guide.
Automation details
Command ID: parameters.graph
Parameters:
→ nodes, edges [user, used] (users include Feature:<name>), cycles, errors
Export Parameters
Read the parameters and expressions guide.
Automation details
Command ID: parameters.export
Parameters:
format?: csv|json (default csv, or from the path), path?: write to a file → text
Import Parameters
Read the parameters and expressions guide.
Automation details
Command ID: parameters.import
Parameters:
text: CSV (header with name, expression, unit?, comment?) or JSON [{name, expression, unit?, comment?}] | path; all or nothing
Complete Expression
Read the parameters and expressions guide.
Automation details
Command ID: parameters.complete
Parameters:
prefix?: the text before the cursor → word, suggestions [{text, kind: parameter|function|unit|constant, detail}]
Other commands: expr
Evaluate Expression
Read the parameters and expressions guide.
Automation details
Command ID: expr.evaluate
Parameters:
expression, kind?: length|angle|unitless (or unit?: mm|in|deg…; text: the text a text parameter would hold, as `text`) → ok, value (mm/rad), display_value, references, error
Other commands: file
New Design
Default keyboard shortcut: Ctrl+N
Read the import and export guide.
Automation details
Command ID: file.new
Parameters:
name?
Save
Default keyboard shortcut: Ctrl+S
Read the import and export guide.
Automation details
Command ID: file.save
Parameters:
path? (default: current file); format?: container|json (default: json for .json, otherwise container); compression?: fast|compact (default: fast); cache?: bool (the evaluated model, so the design opens without rebuilding; default: true); preview?: bool (saved display meshes, default: false); overwrite?: bool (replace the design's file even though another program or computer changed it since it was opened or saved; without it that save is refused)
Save As
Default keyboard shortcut: Ctrl+Shift+S
Read the import and export guide.
Automation details
Command ID: file.save_as
Parameters:
path; format?: container|json (default: json for .json, otherwise container); compression?: fast|compact (default: fast); cache?: bool (the evaluated model, so the design opens without rebuilding; default: true); preview?: bool (saved display meshes, default: false); overwrite?: bool (as file.save, when `path` is the design's own file)
Export
Read the import and export guide.
Automation details
Command ID: file.export
Parameters:
path; format?: stl|stla|obj|step|3mf|3mf-tagged|urdf|json (default from extension; 3mf-tagged: 3MF whose triangles carry their B-rep face, at full precision, for meshing and analysis tools; docs/formats/3mf-face-tags.md; urdf: the whole assembly as a robot, with STL meshes written next to it; docs/urdf.md); bodies?: [names] (JSON and URDF export the complete design); urdf only: mesh_prefix? (before each mesh file name, e.g. package://robot/meshes/), effort? (default 1000), velocity? (default 10); quality?: standard|fine|finest (STL, OBJ and 3MF: how closely the mesh follows curved faces, a chord tolerance of 2e-4, 5e-5 or 1e-5 of each body's size; default standard)
Save As Mesh
Command group: INSERT › FILES
Read the import and export guide.
Automation details
Command ID: file.save_mesh
Parameters:
path; format?: stl|3mf|3mf-tagged (default from the extension, else stl; 3mf-tagged: docs/formats/3mf-face-tags.md); bodies?: [names] (default: all); ascii?: bool (STL); slicer?: id or name from file.slicers, the program path it lists, or a program the user added under Custom… (send the saved file there, through the same steps as file.send_to_slicer: a slicer that has to be started first is waited for; may be deprecated later in favour of file.send_to_slicer or a Send to App workflow, kept for scripts); single_instance?: bool (default true; see file.send_to_slicer); quality?: standard|fine|finest (STL, OBJ and 3MF: how closely the mesh follows curved faces, a chord tolerance of 2e-4, 5e-5 or 1e-5 of each body's size; default standard)
Send to Slicer
Command group: INSERT › FILES
Read the import and export guide.
Automation details
Command ID: file.send_to_slicer
Parameters:
slicer: id or name from file.slicers, or a program the user added under Custom…; bodies?: [names] (default: the selected bodies); wait?: block|return|no (default block: a slicer that drops a file at a cold start is started alone and the call waits up to 10 s for it to be ready, then sends; return: start it and answer {starting}; no: send now); single_instance?: bool (default true: PrusaSlicer, SuperSlicer and Cura load the file in the window that is open); format?: 3mf|stl|3mf-tagged (default 3mf, in millimetres; STL if that can't be written) → saves the bodies in this user's slicer folder and opens the file in the slicer. The file name stays the same for the same design and bodies, so sending again replaces it (use the slicer's Reload from disk)
Open with EasyPrint
Read the import and export guide.
Automation details
Command ID: file.open_in_easyprint
Parameters:
consent: bool (required: true only once the user has agreed to upload the model to Printables' servers; it stays there for 30 minutes and opening it needs a Printables sign-in); bodies?: [names] (default: the selected bodies); format?: 3mf|stl (default 3mf, in millimetres) → uploads the bodies to EasyPrint, Printables' slicer in the browser, and opens it in the browser. 100 MB at most. Works in the desktop app and the browser build; the CLI has no way to upload yet
Installed Slicers
Read the import and export guide.
Automation details
Command ID: file.slicers
Parameters:
→ the slicers found on this machine: [{id, name, program, icon?}]; pass an id, a name or the program path as file.send_to_slicer's or file.save_mesh's `slicer`
Export Current View as DXF
Read the import and export guide.
Automation details
Command ID: file.export_view_dxf
Parameters:
camera: Camera (current view); hidden?: omit|dashed (default omit); tolerance?: 0.0001..1 mm (curve chord tolerance, default 0.01); path?: .dxf file (else returns dxf text). Visible placed bodies and shown sketches' profile curves (no construction geometry, points, dimensions or constraints); 2D LINE/LWPOLYLINE entities on VISIBLE/HIDDEN layers (sketch curves on SKETCH), mm; finished sketches are hidden behind bodies, the active sketch is drawn on top; perspective projected onto the camera target plane; no viewport crop or overlays.
Other commands: doc
Open
Default keyboard shortcut: Ctrl+O
Read the import and export guide.
Automation details
Command ID: doc.open
Parameters:
path: .solidcraft design, .dxf/.svg drawing (opens as a new, unsaved design with an XY sketch, using sketch.insert_dxf/sketch.insert_svg units and defaults), or a .step/.stp/.igs/.fcstd/.3dm/.3mf/.stl/.obj/.glb/.gltf file (STEP, IGES, FreeCAD, Rhino, 3MF, STL, OBJ or glTF; opens as a new design holding the file as a component named after it, as file.insert_step and file.insert_mesh make); up?: z|y for a STEP, IGES, FreeCAD, Rhino or glTF file (see file.insert_step and file.insert_mesh); names?: auto|solid|product|instance for a STEP, IGES, FreeCAD or Rhino file (see file.insert_step); read_only?: bool (a design: saving to its own file is refused, file.save_as keeps the changes elsewhere) → open_elsewhere: {host, user, app, opened} when another SolidCraft has the design open (its lock file is fresh)
Recoverable Designs
Read the import and export guide.
Automation details
Command ID: doc.recovery_list
Parameters:
dir? (default: the recovery folder) → designs autosaved by apps that crashed or closed with unsaved changes
Recover Design
Read the import and export guide.
Automation details
Command ID: doc.recover
Parameters:
id (from doc.recovery_list); dir?; discard?: bool (delete the entry once open) — opens it with its file path, unsaved
Discard Recovered Design
Read the import and export guide.
Automation details
Command ID: doc.recovery_discard
Parameters:
id, or all: true; dir?
Other commands: model
List Edges
Automation details
Command ID: model.edges
Parameters:
body: name
List Faces
Automation details
Command ID: model.faces
Parameters:
body: name → per face: index, name (what face.delete, sheet.convert `face`, solid.shell … take in place of a point), exact, area, centroid (not always on the face), plane_normal, point (a point on the face that picks it), cylinder? ({origin, axis, radius} of a cylindrical face), sheet_face? (a folded sheet's face: `sheet:<body>:<panel>:<side>`, panel base or a flange's tag F<feature>.<k>, side top | bottom | rim.<edge> | bend.inner | bend.outer; taken wherever a face name is, and it holds while sizes change and flanges come and go)
List Threads
Automation details
Command ID: model.threads
No parameter description is provided by the command registry.
Other commands: appearance
Appearance Library
Automation details
Command ID: appearance.library
Parameters:
→ 20 legacy scalar appearances, six opaque PBR finishes and 40 starter recipes with IDs, categories, provenance, requirements and availability; six starter glass recipes are unavailable
List Appearances
Automation details
Command ID: appearance.list
Parameters:
body?: name → assigned appearances; with body, its resolved look and its faces' (face index, look)
Paint Appearance
Command group: RENDER APPEARANCE › APPEARANCE
Automation details
Command ID: appearance.paint
Parameters:
No supported parameters. Always refuses without changing the design or history.
Pick Up Appearance
Command group: RENDER APPEARANCE › APPEARANCE
Automation details
Command ID: appearance.pick_up
Parameters:
No supported parameters. Always refuses without changing the design or history.
Replace Appearance
Command group: RENDER APPEARANCE › APPEARANCE
Automation details
Command ID: appearance.replace
Parameters:
No supported parameters. Always refuses without changing the design or history.
Other commands: material
List Materials
Automation details
Command ID: material.list
Parameters:
→ built-in and design-owned materials (user: true, look, optional physical_card/physical_validation/density_comparison). Read-only; candidate engineering records do not certify sources or supply a solver
New Physical Material
Automation details
Command ID: material.create
Parameters:
name (not a built-in material's); density_g_cm3: (0, 1000]; appearance?: library name (appearance.library) | color?: "#rrggbb" or [r, g, b] (0–255), opacity? (0–1), pbr?: {metallic:0..1, roughness:0.045..1, dielectric_ior:1..3} (opaque only, needs color/appearance): the default look. A named appearance keeps its preset response when pbr is omitted; pbr: null selects scalar shading. Optical edits do not change density. physical_card?: validated SI candidate record (null/omitted means unknown); card density never changes assigned density. Assign it with material.assign
Edit Physical Material
Automation details
Command ID: material.edit
Parameters:
material: a material defined in this design; name? (bodies and configuration cells follow); density_g_cm3?; appearance? | color?, opacity?, pbr? (as material.create; color: null clears the look). physical_card?: validated SI candidate record (omitted retains, null clears). Omitted appearance/color retains the entire current look
Inspect Physical Material
Automation details
Command ID: material.inspect
Parameters:
material: built-in or design-owned name. Returns assigned density, default appearance, optional physical_card, validation and density comparison. Read-only; missing engineering data remains unknown and card density does not change mass
Delete Physical Material
Automation details
Command ID: material.delete
Parameters:
material: a material defined in this design; replace?: material for the bodies and configuration cells that use it (without it, a material in use is refused)
Validate Physical Material Data
Automation details
Command ID: material.properties.validate
Parameters:
card: schema_version 1 engineering material card in SI units. Returns numerical consistency, derived isotropic moduli, missing conditions and sources needing review. Read-only; does not assign/save properties or provide an analysis solver. See docs/physical-material-cards.md
Directional Elastic Modulus
Automation details
Command ID: material.properties.directional
Parameters:
card: schema_version 1 engineering material card; direction: three finite nonzero-vector components in the card's material-local axes. Returns normalized direction, Young modulus in Pa and source/condition review. Read-only constitutive query; no structural solver, persistence or body-axis assignment. See docs/physical-material-cards.md
Other commands: engine
List Commands
Automation details
Command ID: engine.commands
No parameter description is provided by the command registry.
Describe Command
Automation details
Command ID: engine.describe
Parameters:
command: a command id. Returns {id, label, params, input, output} (and param_warnings in the warn parameter-check mode): the command's parameter documentation and, once the command has a typed contract, the JSON Schemas (2020-12) of its parameters and of a successful result (null otherwise). Read-only.
Other commands: library
Inspect Local Library
Automation details
Command ID: library.inspect
Parameters:
path: v1/v2 library JSON; returns validated catalog and exact entry revisions, without changing the design
Export Design Library
Automation details
Command ID: library.export
Parameters:
path: .json; id? (default user.local), name?, provenance?, overwrite? false, format? solidcraft-library/2 (default) | solidcraft-library/1 (refuses PBR/cards). Copies assigned looks and design-local physical definitions; never changes the design
Apply Local Library Entry
Automation details
Command ID: library.apply
Parameters:
path, entry: qualified ID, revision: exact content SHA-256; targets?: {bodies?, faces?: [{body,point}], components?}; name?: physical material name on import. Copies values in one undo step; material conflicts are explicit
Other commands: diagnostics
Crash Reports
Automation details
Command ID: diagnostics.crash_reports
Parameters:
limit?: number (default 20). Lists this computer's crash reports, newest first: {dir, reports: [{id, kind, time, message, command, count, version, path, replayable}]}. kind: command_panic | panic | kernel_panic_recovered | unclean_exit. Read one with diagnostics.crash_report; reproduce it with `solidcraft-cli crashes replay <id>`
Crash Report
Automation details
Command ID: diagnostics.crash_report
Parameters:
id: report id. The whole report: message, location, backtrace, app version and commit, system, the command that crashed and the recent ones, notes, and design_path / replay_path when the crash can be replayed (`solidcraft-cli run <replay_path> --in <design_path>`)
Delete Crash Reports
Automation details
Command ID: diagnostics.crash_reports.clear
Parameters:
id?: one report (default: all). Returns {deleted}
Crash Test
Automation details
Command ID: diagnostics.crash_test
Parameters:
mode: panic | kernel | abort. Checks that crash reports work: panic = an internal error in this command (caught; the design is unchanged), kernel = one inside the geometry kernel (caught), abort = the app ends at once (reported at the next start). Only when the SOLIDCRAFT_CRASH_TEST environment variable is set
Other commands: render
Layered Surface Aging
Automation details
Command ID: render.aging.describe
Parameters:
Empty object. Read-only original Rust/patina/dust/grime/scratch/chip/fading presets and exact GPU aging contract. UI activation opens the shared Aging tab; edits remain a saved render draft, never engineering geometry or material-property mutation.
Lens and Creative Effects
Automation details
Command ID: render.effects.describe
Parameters:
Empty object. Read-only original photographic/creative filter defaults and constraints. UI activation opens shared Effects controls. Saved finishing and exact-master regrading preserve raw EXR bytes; no physical lens or branded-film calibration is implied.
Save Optical Binding
Automation details
Command ID: render.optics.bind
Parameters:
name: GPU setup; set?: complete numerical setup patch; body_index and body_name: exact captured target; expected_revision; optics:{ior,absorption_per_mm:[r,g,b],boundary:closed_mesh|closed_box|thin_sheet,thickness_mm,shadow_transmission,subsurface?:{scattering_per_mm,anisotropy}}; replace?:false. Stores stable definition/feature/occurrence identity in one undo without modifying engineering geometry/materials. Nested media require separate job.options.nested_media and producer certification. Unresolved or incompatible targets refuse atomically.
Remove Optical Binding
Automation details
Command ID: render.optics.unbind
Parameters:
name: saved setup; target: exact saved definition/feature/component/occurrence_path; expected_revision. Removes only that optical assignment in one undo without a device or file access.
Regrade Linear Master
Automation details
Command ID: render.result.regrade
Parameters:
job: exact completed JobId; presentation: {exposure_ev?,tone_map?,finishing?,backdrop?,background?,transparent_background?}. Queues cancellable nondestructive PNG presentation from that retained native FLOAT32 master, not from PNG. Captured geometry/camera/lighting and source EXR remain unchanged; no path tracing, denoising, external EXR import or automatic file writes. Returns a distinct job with source identity and original-master digest. Existing frame retention and export rules still apply.
Set Render Camera
Automation details
Command ID: render.camera.set
Parameters:
camera: complete Render Camera snapshot (target, yaw, pitch, distance, fov; roll defaults to 0 and up to z). Validates the same finite mm/radian bounds as saved render intent, preserves explicit roll/up and changes only transient view state. No model evaluation, setup save, renderer, dirty flag or undo step. Design view.apply retains its separate up-axis policy.
Inspect Render Camera
Automation details
Command ID: render.camera.get
Parameters:
No parameters. Returns the explicitly defined current view camera, or null if the session has no camera override. No camera is fitted, no job is started and no saved setup or engineering document is changed.
Frame Render Subject
Automation details
Command ID: render.camera.fit
Parameters:
No parameters. Fits the current camera to the visible product bounds through the shared Camera::fit contract; changes only view state, not a saved setup, rendering job, stage or engineering geometry.
Review Native Studio
Automation details
Command ID: render.preset.resolve
Parameters:
id: NATIVE-STUDIO-001..025; name; set?: typed GPU setup patch; expected_revision?:u64. Resolves original built-in HDR/rig/passive ground with conservative geometry clearance, returns all replacements and preserved fields without mutation. Unbound SCENE-* concepts are not accepted.
Apply Native Studio
Automation details
Command ID: render.preset.apply
Parameters:
Same reviewed input as render.preset.resolve, expected_revision required, replace_lighting_and_environment:true and replace_stage:true when an existing stage would change. Re-resolves the complete candidate and publishes one undo or no mutation; camera, optical bindings, geometry and engineering materials are preserved.
Review Fixed Sampling Budget
Automation details
Command ID: render.quality.prepare
Parameters:
id: Q-*; variant:fixed_samples_png; acknowledge_omissions:true. Read-only bounded GPU still-budget projection, not full adaptive/convergence/linear-master quality acceptance. Returns dimensions/samples/depth/seed/denoise and explicit omissions. Never switches executor or mutates a shot.
Duplicate Render Shot
Automation details
Command ID: render.shot.duplicate
Parameters:
name, new_name, expected_revision:u64. Copies exact saved setup and resource references in one undo. Existing destinations refuse; captured jobs and camera navigation are unchanged.
Rename Render Shot
Automation details
Command ID: render.shot.rename
Parameters:
name, new_name, expected_revision:u64. Renames exact saved setup in one undo; an existing destination or stale review refuses.
Remove Render Shot
Automation details
Command ID: render.shot.remove
Parameters:
name, expected_revision:u64. Removes only that saved setup in one undo; retains resources, geometry and captured results.
Inspect Original Surface Map
Automation details
Command ID: render.library.preview
Parameters:
id: SURF-*. Returns bounded hash-verified original base-color PNG bytes and source-map fidelity label, not a rendered material thumbnail. No external paths, rendering or document edits.
Prepare Physical Reference
Automation details
Command ID: render.library.physical.prepare
Parameters:
id: named built-in physical profile, name: proposed new material label, acknowledge_reference:true. Read-only loss-aware creation plan using native ValidatedCard admission and one point density; returns source receipts, extended fields and omissions. Does not execute material.create, assign bodies or infer effective-part physics.
Save Render Ground
Automation details
Command ID: render.stage.ground
Parameters:
name: typed raytrace setup, set?: setup patch, margin_fraction?:0.25, gap_mm?:0.1, reflectance_linear?:[0.18,0.18,0.18], replace_stage?:false. Fits one finite passive ground below the visible product once and saves its physical transforms in one undo. Product AABB separation is rechecked on every render.
Save Physical Render Background
Automation details
Command ID: render.stage.backdrop
Parameters:
name: typed raytrace setup, set?: setup patch, kind: floor_wall or cyclorama, reflectance_linear?: [0.18,0.18,0.18], expected_revision: reviewed revision, replace_stage?:false. Fits a finite floor/wall or a 24-strip polygonal cyclorama behind the visible subject using the saved camera azimuth; world +Z up and a minimum one-mm stage envelope. Preserves subject geometry, lighting, output, pose and engineering materials; one undo and rechecked product separation. Not an infinite/analytically smooth/terrain background.
Remove Render Stage
Automation details
Command ID: render.stage.clear
Parameters:
name: setup, set?: setup patch. Removes saved render-only panels atomically; CAD geometry and lighting are unchanged.
Save Surface Binding
Automation details
Command ID: render.library.surface.bind
Parameters:
name: GPU setup; set?: setup patch; surface: same input as render.library.surface.prepare; replace?: false. Pins exact source hashes and definition/feature/occurrence identity with local-mm mapping in one undo. No paths, mesh ordinals or physical material changes.
Remove Surface Binding
Automation details
Command ID: render.library.surface.unbind
Parameters:
name: setup, target: saved definition/feature/component/occurrence_path, set?: setup patch. Removes only that explicit binding in one undo; no device or current geometry required.
Retain Displayed Render Result
Automation details
Command ID: render.result.pin
Parameters:
job: completed JobId, or null to release the displayed-result pin. Retains at most one completed result independently of a newer completion; preserves exact export bytes and original host memory accounting. No design/undo change; preview or foreign jobs refuse.
Find Render Library Targets
Automation details
Command ID: render.library.targets
Parameters:
query?: exact-name substring; offset?:0; limit?:128 (1..128). Returns a bounded page of captured body ordinals/names, revision and scene bounds. Settles the model through ordinary command handling; never returns a large mesh payload.
Browse Render Library
Automation details
Command ID: render.library.list
Parameters:
kind?: surfaces, selene_appearances, hdr, rigs, scenes, materials, lighting, environments, quality, native_scenes, selene_shots or capabilities; query?: bounded search; availability?: all/usable/specification/blocked; offset?:0; limit?:48 (1..128). Returns source status and explicit implemented projections separately. No device, file IO or design change.
Inspect Render Library Resource
Automation details
Command ID: render.library.describe
Parameters:
kind, id: exact authored resource. Returns original specification, runtime projection variants and explicit omissions. A concept is not an executable preset.
Apply Library Opaque Appearance
Automation details
Command ID: render.library.appearance.apply
Parameters:
id: SURF-* or SELENE-AP-*; variant: uniform_opaque (required); bodies: 1..256 exact body names. Applies the original scalar baseline through ordinary appearance assignment in one undo, preserving physical properties. Spatial maps and advanced target effects are explicitly excluded.
Apply Built-in HDR Environment
Automation details
Command ID: render.library.environment.apply
Parameters:
id: HDR-*; name: saved setup; set?: typed CPU/GPU setup patch; rotation_rad?: authored rotation (native origin offset is explicit in the reply); intensity?:1; samples?:16 for CPU; replace_environment?:false. Hash-verifies and embeds original bytes, saves one atomic undo without external files or a render device. An HDR-owned sun cannot duplicate an analytic sun.
Prepare Authored Surface Maps
Automation details
Command ID: render.library.surface.prepare
Parameters:
id: SURF-*; body_index and body_name from render.scene; origin?:[0,0,0]; plane?:xy/yz/zx; scale?:1. Returns an explicit GPU planar map binding with source hashes, original millimetre scale, image-up normal orientation and absolute base-color semantics. No document change; height/displacement and true anisotropy are not included.
Prepare Authored Studio Lights
Automation details
Command ID: render.library.studio.prepare
Parameters:
id: RIG-*; variant: complete for emitter-only rigs, emitters_only for rigs with passive cards; center_mm and subject_extent_mm required. Converts exact rectangular half-edges and solar direction/radius to GPU lighting. Blocked calibration and external-sun rigs refuse. Returns a geometry-clearance review requirement; does not create cards or modify the scene.
Apply Authored Studio Lights
Automation details
Command ID: render.library.studio.apply
Parameters:
rig: render.library.studio.prepare parameters; name; set?: typed GPU setup patch; replace_lighting_and_environment:true required. Saves the explicit rig projection in one undo, removing prior lighting/environment only with that confirmation. Subject clearance still needs review; passive cards are never silently omitted.
Inspect HDR Source
Automation details
Command ID: render.hdr.inspect
Parameters:
path: scoped Radiance HDR file; validates the complete bounded source and returns sha256, width, height, encoded_bytes and primaries without saving or rendering
Save Render Settings with HDR
Automation details
Command ID: render.settings.capture_hdr
Parameters:
name, set: typed CPU/GPU setup patch with hdr/environment unset; path: scoped HDR; expected_sha256?: digest returned by render.hdr.inspect (changed sources refuse); rotation_rad?:0, intensity?:1; samples?:1/4/16/64 for CPU only (default16); assume_linear_srgb?:false for GPU only. Captures immutable bytes and saves resource plus setup atomically in one undo, without a host; returns name/setup/resource metadata, never source bytes
Check Render Request
Automation details
Command ID: render.preflight
Parameters:
setup?: saved name OR settings?: full RenderSetup. A typed job selects raster/cpu_raytrace/gpu_path and its numerical options plus embedded HDR before dispatch; only replace may accompany it. Without job, the legacy runtime controls below remain available. See docs/render-settings.md. integrator?: raster (default) or raytrace; progressive?: false (registered GPU path, CPU integrator/LED options are mutually exclusive); samples?: 1..4096; max_depth?: 1..16; seed?: u32 for progressive; raytrace?: bounded runtime progressive/primary_samples/lights/punctual_lights/area_lights/area_samples/seed/exposure_ev/shadows/reflections/anisotropic_faces/emissions/surface_effects/dielectrics/environment options, only with raytrace. CPU raytrace.budget?: compact (legacy default), studio or workstation; see docs/render-cpu-resource-profiles.md for explicit image/geometry/memory/work limits. CPU raytrace.progressive?: {samples_per_pixel:1..profile limit, seed:u32}; requires primary_samples:1 and selected reflections.samples:1; no area/LED, anisotropy or dielectrics in this initial mode. See docs/render-cpu-progressive.md; environment?: {path: scoped HDR, expected_sha256?: inspected digest, rotation_radians: 0, intensity: 1, samples: 16} OR {resource: saved HDR digest, rotation_radians, intensity, samples}; embedded resources cannot combine with path/hash; explicit sRGB/D65 HDR primaries required. Opaque CPU preview with selected mirror/rough-specular reflections and certified planar anisotropy. HDR with rough reflections or anisotropy requires fully metallic selected bodies and all effective faces; unsupported combinations refuse. No MIS or GI. CPU runtime dielectrics support only certified thin_sheet/closed_box boundaries with explicit IOR and Beer absorption; see docs/render-raytrace-dielectrics.md. CPU emissions are visible radiance only; surface_effects change roughness/shading normals without geometry changes. led_strip?: continuous world-mm LED rectangle recipe; led_samples?: 1/4/16/64 for the whole strip, default16. At most8directional+8punctual+4rectangle/LED sources; shared area N. See docs/render-light-union.md. transparent_background?: false; replace?: false. With progressive:true, raytrace instead accepts GPU dielectrics/clear_coats/textures; environment instead accepts tagged hdr/embedded_hdr/sky with rotation_rad/intensity; file hdr supports expected_sha256, embedded_hdr uses a document resource digest. See docs/render-hdr-resources.md. GPU lens, studio lighting, tone_map, denoise, bounded finishing/backdrop and linear_exr retention are also available. GPU adaptive?: {pilot_samples:16..256,min_samples:16..samples,relative_error:0..1,absolute_error:0.000001..1000000,coverage_error:0.001..0.25} allocates independent production budgets; pilots are discarded, not a certified error guarantee. transmission_samples?:1|2|4 stratifies the first Fresnel branch with extra path cost. volume?:{min_mm:[x,y,z],max_mm:[x,y,z],extinction_per_mm:0..1000,scattering_albedo:[r,g,b],anisotropy:-0.95..0.95} is one finite homogeneous world-mm medium; active media require explicit lighting/environment and refuse glass, denoise and multiple transmission strata. These fields also persist in GPU job.options. See docs/render-materials/TRANSPORT-MASTER-BATCH-2026-10-23.md; see docs/render-transmission.md, render-environment.md, render-runtime-textures.md, render-thin-lens.md, render-studio-lighting.md and render-denoise.md. Optional CPU raytracing requires a raytrace-cpu build and native host, checked before scene capture. Stale geometry is rejected. See docs/render-jobs.md
Render Image
Command group: RENDER › IMAGE
Automation details
Command ID: render.start
Parameters:
Same request as render.preflight. Captures an immutable product scene and queues a bounded image job. Returns its session-scoped job identity, captured scene_generation and progress. Does not change the design. See docs/render-jobs.md
Render Progress
Automation details
Command ID: render.status
Parameters:
job: {session, serial, generation}. Non-blocking progress, error, optional CPU preview metadata {frame_sequence,samples_per_pixel,frame}, and completed frame metadata, optional sampling allocation/work counts and retained-master presentation provenance; no image bytes and no rendering in this call. Preview is separate from the exportable final image.
Cancel Render
Automation details
Command ID: render.cancel
Parameters:
job: {session, serial, generation}. Requests cooperative cancellation; completed jobs remain completed. Late cancelled frames cannot be published.
Export Render Image
Automation details
Command ID: render.export
Parameters:
job: {session, serial, generation}, path: destination matching format?: png (default) or exr, overwrite?: false. EXR requires an opt-in raw linear master retained by that completed GPU job; never converts PNG pixels. Exports a retained completed image atomically; an existing destination is preserved on failure. See docs/render-jobs.md
Render Backends
Automation details
Command ID: render.backends
Parameters:
No parameters. Returns {schema_version: 1, default_backend: builtin, backends: [{id, label, availability, executors: [{id, availability, profiles, limits, scratch}]}]}. Metadata only; does not start rendering or probe a GPU. See docs/render-backends.md
Render Settings
Automation details
Command ID: render.settings.get
Parameters:
name?: saved setup. Without name returns {setups, defaults}; with name returns {name, setup}. Does not evaluate the model or apply a camera. See docs/render-settings.md
Save Render Settings
Automation details
Command ID: render.settings.set
Parameters:
name: setup name, set: object of fields to replace (backend, mode, camera, width, height, format, exposure_ev, environment_rotation, environment_intensity, background, job, surfaces, stage). job?: typed active raster/cpu_raytrace/gpu_path configuration with optional validated embedded HDR reference; omitted preserves, null clears, object replaces the complete job. Creates from defaults and saves without a host, validates atomically, one undo step; identical edits are no-ops. See docs/render-settings.md
Inspect Render Scene
Command group: RENDER › SCENE
Automation details
Command ID: render.scene
Parameters:
No parameters. Authoritative product-scene metadata, without modelling overlays or mesh payloads. Explicitly settles stale geometry; does not render an image. See docs/render-workspace.md
Apply Render Camera
Automation details
Command ID: render.settings.apply
Parameters:
name: saved setup. Applies its existing Camera snapshot including roll/up as view state, without modifying the document or starting a render.
Solo Light
Command group: RENDER SCENE › LIGHTS
Automation details
Command ID: render.lights.solo
Parameters:
No supported parameters. Always refuses without changing the design or history.
Compare Images
Command group: RENDER › RESULT
Automation details
Command ID: render.compare
Parameters:
No supported parameters. Always refuses without changing the design or history.
Render All Shots
Command group: RENDER › BATCH
Automation details
Command ID: render.batch
Parameters:
No supported parameters. Always refuses without changing the design or history.
Glossary
This page sets SolidCraft’s ubiquitous language: one agreed term for each idea, defined once and used the same way in labels, command ids, parameter keys, the design file, the docs, the MCP tools and every translation.
Two rules apply:
- One idea, one term. When this page names a term for an idea, use that term, not a synonym.
- One term, one idea. A word that means two things gets two qualified terms (for example construction entity and construction plane), and each meaning gets its own entry in the message catalogues.
A pull request that adds a new idea (a command, a parameter, a file key, a panel) adds its term
here. A pull request that renames a label, id or key to match this page keeps the old id or key
working as an alias and lists it in docs/compatibility.md.
How an entry reads
Each entry gives the term and a definition: one short sentence in plain technical English that could replace the term in a sentence. It names no product and doesn’t use the term to define itself, in the way ISO 704 and ISO 10241-1 describe for terminology. A note follows, with the context:
- In SolidCraft: the command ids, parameter keys and design-file keys that use the term.
- Don’t use: the other words for the same idea.
- Elsewhere: general names that people coming from other CAD software may know.
- For translators: what the term must not be confused with.
Some of the ids and labels below still use a word this page says not to use. They change in later pull requests, one topic at a time, each pointing at its entry here.
Terms
appearance
How a body, face or component looks on screen and in renders.
An appearance sets colour and surface finish only. It has no effect on mass or strength. Compare material.
- In SolidCraft:
appearance.assign,appearance.library,appearance.list; theappearancesdesign-file key. - Don’t use: look, colour (for the whole appearance), render material.
- Elsewhere: appearance, visual material, colour.
- For translators: not the physical material.
body
One connected solid or surface shape that features create and change.
A design can hold many bodies. A component groups bodies; an occurrence places them.
- In SolidCraft:
body.rename; thebodiesbrowser folder;body_nameparameters. - Don’t use: part, solid (for a surface body too), object.
- Elsewhere: body, solid body, surface body.
- For translators: not a component and not a design.
browser
The panel that lists the design’s components, bodies, sketches, construction planes and joints as a tree.
- In SolidCraft:
browser.group,browser.move,browser.order,browser.visibility, and the otherbrowser.*commands. - Don’t use: model tree, scene browser, parts browser.
- Elsewhere: browser, model tree, feature tree, design tree.
- For translators: not the web browser. A language with its own CAD word for the tree may use it (in French, arborescence), so that “browser” and “web browser” don’t share a word. See web browser.
component
A named container of bodies, sketches, construction geometry and other components that can be placed one or more times.
Each placement of a component is an occurrence. Editing a component changes every occurrence of it.
- In SolidCraft:
component.create,component.activate, and the othercomponent.*commands; thecomponentsdesign-file key. - Don’t use: part, assembly, sub-assembly (for the component itself).
- Elsewhere: component, part, sub-assembly.
- For translators: not an occurrence.
constraint
A rule that fixes the relation between sketch entities, such as coincident, parallel or tangent.
A constraint with a value is a dimension.
- In SolidCraft:
sketch.constraint.coincident,sketch.constraint.parallel, and the othersketch.constraint.*commands; theconstraintparameter key; the sketch’sconstraintsdesign-file key. - Don’t use: relation, condition.
- Elsewhere: constraint, geometric constraint, relation.
- For translators: a joint between occurrences is not a constraint.
construction entity
A sketch entity that guides the drawing but isn’t part of any profile.
A construction entity can be a line, an arc, a circle or any other curve, and it can carry constraints and dimensions like any sketch entity.
- In SolidCraft: the
constructionparameter of the sketch drawing commands; theconstructionfield of a sketch curve; theui.constructioncatalogue entry. - Don’t use: “construction” on its own where it could mean a construction plane.
- Elsewhere: construction line, construction geometry, reference line.
- For translators: not a construction plane, axis or point. The browser’s Construction folder
and this line type still share the
Constructioncatalogue entry, so German, Korean and Turkish show the folder as “construction line”.
construction plane, construction axis, construction point
A plane, axis or point that a feature creates to place other features, and that is not part of any body.
They are listed together in the browser’s Construction folder.
- In SolidCraft:
construct.plane.offset,construct.axis.two_points,construct.point.vertex, and the otherconstruct.*commands; the CONSTRUCT toolbar panel. - Don’t use: “construction” on its own where it could mean a construction entity; work plane.
- Elsewhere: construction plane, reference plane, datum plane (and axis, point).
- For translators: not a sketch’s construction entity.
curve
A sketch entity that is not a point: a line, an arc, a circle, an ellipse, a spline or a conic.
- In SolidCraft: the sketch’s
curvesdesign-file key; curve ids in parameters such aspath. - Don’t use: geometry, edge (for a sketch curve).
- Elsewhere: curve, sketch curve, segment.
- For translators: not an edge of a body.
design
The model that is open in SolidCraft: its timeline, components, bodies, sketches, parameters and settings.
A design is saved as a design file.
- In SolidCraft:
file.new(“New Design”),document.units(“Change Design Units”),document.metadata(“Design Properties”),doc.open,doc.recover. - Don’t use: document, doc, project, file (for the model itself), part.
- Elsewhere: design, document, model, part file, assembly file.
- For translators: not the Project design property and not the sketch Project command.
design file
A file in which SolidCraft saves one design, with the .solidcraft extension.
- In SolidCraft:
file.save,file.save_as,doc.open; thesolidcraft/Nformat tag. - Don’t use: document, project file.
- Elsewhere: design file, model file.
- For translators: “file” is only for what is on disk: opening, saving, importing and exporting.
dimension
A constraint that sets a size, such as a length, a distance, an angle or a radius, by its value.
A dimension can drive the sketch, or be driven by it and only show the value.
- In SolidCraft:
sketch.dimension,sketch.dimension_text,sketch.toggle_driven; a dimension’s id is a constraint id. - Don’t use: measurement (for a dimension), size.
- Elsewhere: dimension, dimensional constraint, driving dimension.
- For translators: not a drawing dimension and not a measured value.
feature
One step in the timeline that creates or changes geometry, such as an extrude, a fillet or a construction plane.
- In SolidCraft:
timeline.edit,timeline.suppress,timeline.reorder, and the othertimeline.*commands; thefeaturesdesign-file key. - Don’t use: operation, step (in labels); feature (for something SolidCraft can do in general: say tool or capability).
- Elsewhere: feature, operation.
- For translators: French uses fonction for a timeline feature, so “AI features” had to become fonctionnalités IA. Translate “feature” only in the timeline sense.
folder
One of the browser’s fixed sections of a component: Bodies, Sketches, Construction, Joints.
A folder is part of every component and can’t be renamed or removed. A container that someone makes is a group.
- In SolidCraft: the
folderparameter ofbrowser.group,browser.orderandbrowser.visibility(bodies,sketches,construction). - Don’t use: folder for a group.
- Elsewhere: folder, node.
- For translators: not a group and not a folder on disk.
group
A named container that someone makes, in the browser or in the timeline, to keep items together.
A group changes only how items are listed. It has no effect on the geometry or the order in which features are computed.
- In SolidCraft:
browser.group,browser.ungroup,browser.rename_group;timeline.group,timeline.ungroup,timeline.rename_group,timeline.move_to_group; thegroupparameter key; thegroupsparameter oftimeline.deleteandtimeline.suppress(a group with all its features); thebrowser_groupsandtimeline_groupsdesign-file keys. - Don’t use: feature folder, folder (for a group).
- Elsewhere: folder, group.
- For translators: not a rigid group (a joint) and not a fixed browser folder.
material
The physical substance of a body, which sets its density and mechanical properties.
A material sets mass, centre of gravity and strength values. How the body looks is its appearance.
- In SolidCraft:
material.assign(“Physical Material”),material.list,material.create, and the othermaterial.*commands; thematerialsanduser_materialsdesign-file keys. - Don’t use: material for a render or visual material.
- Elsewhere: material, physical material.
- For translators: not an appearance.
occurrence
One placed copy of a component inside its parent component.
A component can have several occurrences. Moving an occurrence moves only that copy; editing the
component changes every copy. The browser names occurrences with a number, such as Bolt M20:3.
- In SolidCraft:
occurrence.copy,occurrence.move,occurrence.drag,occurrence.ground,occurrence.properties; theoccurrencesdesign-file key; thepathof occurrence ids. - Don’t use: instance, placement.
- Elsewhere: occurrence, instance, component instance.
- For translators: not a pattern instance.
part number
A design property that holds the identifier an organisation gives a design.
- In SolidCraft: the
part_numberdesign property, shown in Design Properties and set withdocument.metadata.set. - Don’t use: part number for a standard part’s size.
- For translators: see Words with more than one meaning, “part”.
pattern instance
One copy that a pattern makes of its features, bodies or sketch entities.
The first instance is the original.
- In SolidCraft:
solid.pattern.rectangular,solid.pattern.circular,solid.pattern.path,sketch.pattern.rectangular,sketch.pattern.circular; thecountandspacingparameters (“between instances”). - Don’t use: instance for an occurrence; element, item.
- Elsewhere: instance, element, occurrence.
- For translators: not an occurrence of a component.
rollback marker
The marker in the timeline after which no feature is computed.
Moving the rollback marker back shows the design as it was at that step. Features after the marker are kept, not deleted.
- In SolidCraft:
timeline.rollback,timeline.roll_to; themarkerdesign-file key. - Don’t use: timeline marker, history marker, rollback bar.
- Elsewhere: rollback bar, rollback marker, end-of-part marker.
- For translators: “history” is the undo history, not the timeline. French had three names for this marker while English did.
sketch
A set of points and curves, with their constraints, on one plane, that features use as profiles, paths and guides.
- In SolidCraft:
sketch.create,sketch.finish, and the othersketch.*commands; thesketchesbrowser folder. - Don’t use: drawing, profile (for the whole sketch).
- Elsewhere: sketch, 2D sketch.
- For translators: not a drawing (a sheet with views and dimensions).
sketch entity
A point or a curve in a sketch.
- In SolidCraft: the
entitiesparameter of the sketch commands (it takes point and curve ids); the sketch’spointsandcurvesdesign-file keys. - Don’t use: geometry (for sketch content), element, object.
- Elsewhere: sketch entity, sketch geometry, sketch element.
- For translators: “entity” names both points and curves.
standard part
A ready-made component, such as a screw, nut, washer, pin or bearing, that SolidCraft builds from a standard size.
- In SolidCraft:
parts.library(“Standard Parts”),parts.insert,parts.fastener,parts.resize; thepartsdesign-file key. - Don’t use: library part, part (on its own), content.
- Elsewhere: standard part, library part, fastener, catalogue part.
- For translators: see Words with more than one meaning, “part”.
timeline
The ordered list of a design’s features, which SolidCraft computes from first to last.
- In SolidCraft: the
timeline.*commands; thefeaturesdesign-file key, in timeline order. - Don’t use: feature history, history (for the timeline).
- Elsewhere: timeline, history, feature tree.
- For translators: not the undo history.
web browser
A program that shows web pages, such as the one that runs SolidCraft’s web build.
- In SolidCraft: the web build; the
home.in_this_browsercatalogue entry. - Don’t use: browser on its own.
- For translators: not the browser panel. Where a language uses one word for both, use the CAD word for the panel.
Words with more than one meaning
An English word that means two things can be translated correctly for only one of them while both share a catalogue entry. Each meaning below gets its own term and, in time, its own entry.
| Word | Meaning | Use | Where |
|---|---|---|---|
| Construction | a sketch entity that isn’t part of a profile | construction entity | sketch drawing commands, ui.construction |
| the browser folder of construction planes, axes and points | construction plane, axis, point | the Construction folder, construct.* | |
| Instance | one copy a pattern makes | pattern instance | pattern commands |
| one placed copy of a component | occurrence | “Paste (Instance)” on occurrence.copy | |
| History | the undo and redo history | history | Undo, Redo |
| the list of features | timeline | “Feature history” (ui.feature_history), “Roll History Marker” | |
| Part | a standard part | standard part | parts.* |
| a body, an occurrence or the design | body, occurrence, design | catalogue texts that say “part” | |
| a design property | part number | Design Properties | |
| Gap | the space a hem leaves | hem gap | the Hem dialog’s Gap row |
| an open end in the sketch continuity legend | open end | the sketch continuity legend | |
| Clearance | the Clearance command | clearance check | inspect.clearance |
| a minimum distance field | clearance | the Interference and plastic rules dialogs | |
| Flat | a hole’s flat bottom | flat bottom | the Hole dialog’s tip |
| a hem that lies flat on the sheet | flat hem | the Hem dialog’s type | |
| Round | a round relief or corner shape | round (shape) | relief shapes, plastic rests, ruled surface corners |
| a fillet | fillet | solid.fillet | |
| Normal | a sketch entity that isn’t a construction entity | normal (line type) | the sketch palette’s line type |
| the direction perpendicular to a surface | normal (direction) | the ruled surface’s Normal mode | |
| the middle display quality | normal (quality) | ui.display_quality_normal | |
| Quantity | the number of pattern instances | count | the pattern dialogs’ Quantity rows, ui.quantity |
| what the motion plot shows: value, velocity or acceleration | plotted quantity | the motion study’s Quantity row, motion.plot_quantity |
Some translations show the clash today: Japanese and Chinese show a hole’s flat bottom with the word for “unfold” or “plane”, Chinese shows “Round” as “fillet”, and Chinese and Russian show the Normal line type as “normal vector”.
Two commands, one label
A label names one command. Today two pairs share one:
- “Edit Feature”:
timeline.redefineandtimeline.edit. - “Break Link”:
component.break_link(an occurrence’s link to another design) andsketch.break_link(a projected sketch entity’s link to its source).
Search lists both under the same name, and the label lookup gives both the same translation.
A feature’s type
A feature type has one name: its design-file key, such as sheet_flange. The label (Flange) is
its display name. Replies and scripts should use the design-file key; today timeline.inspect
reports a different name (EdgeFlangeFeature), so a script can’t match one against the other.
Open questions for the maintainer
- British or American spelling for labels and docs. Today both are in use (colour and color, centre and center, favourite and favorite); ids and keys use American spelling. #2501 proposes an English (UK) language option, so either base spelling can be read in the other.