SolveCraft
SolveCraft is an open-source parametric 3D CAD application written in pure Rust. It works the way Fusion-style modellers do: 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,
solvecraft-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.
SolveCraft 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 command parity and the Fusion oracle measure how far along it is.
This book has two halves: the user guide for people modelling with SolveCraft, and the developer guide for people working on it. The command reference is generated from the code on every build.
SolveCraft is licensed MIT OR Apache-2.0. It is a clean-room implementation and is not affiliated with Autodesk; Fusion is a trademark of Autodesk, Inc.
Getting started
Desktop
Download the latest build from the releases page, or build it yourself with stable Rust (1.90 or newer):
git clone https://github.com/bherbruck/solvecraft && cd solvecraft
cargo run --release -p solvecraft -- --sample # opens with a sample part
solvecraft part.step opens a STEP, IGES, 3MF, STL or OBJ file as a new design, and
solvecraft design.solvecraft opens a saved design. Windows builds cross-compile from Linux with
cargo xwin build --release --target x86_64-pc-windows-msvc -p solvecraft.
Web
The same app runs in the browser: open SolveCraft on the web (?sample opens the sample
part, ?webgl forces WebGL2 where WebGPU is missing). Designs are saved in the browser’s private
file storage, which needs HTTPS or localhost. To build it yourself:
rustup target add wasm32-unknown-unknown
cd apps/solvecraft-web && trunk serve --release
CLI
solvecraft-cli runs the same commands without a window:
cargo run --release -p solvecraft-cli -- commands # every command, as JSON
cargo run --release -p solvecraft-cli -- run examples/bracket.json --out bracket.step
cargo run --release -p solvecraft-cli -- eval examples/bracket.json # volume, area, faces…
cargo run --release -p solvecraft-cli -- snapshot examples/bracket.json --out bracket.png
cargo run --release -p solvecraft-cli -- exec solid.box '{"length": 20, "width": 10, "height": 5}'
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}}
]}
Units are millimetres and degrees unless an expression says otherwise ("1 in", "30 deg"),
and Z is up.
A tour of the window
- Title bar. On Windows and Linux the application bar is the window’s title bar: File, save, undo and redo, the start page, the theme, Help, and one tab per open design (each with its own undo history, selection and camera).
- Toolbar. The workspace switcher (DESIGN) sits on the left. The tabs (SOLID, SURFACE, SHEET METAL, PLASTIC, UTILITIES, and SKETCH while you sketch) hold panels of commands. Click a panel’s name for all its commands.
- 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. Rows drag into groups and other components.
- Viewport. Click to select, Shift or Ctrl to add, drag for a window or crossing box. The wheel zooms at the cursor, the middle button pans and Shift+middle orbits (the mouse preset in Preferences switches to SolidWorks or Inventor style). The view cube and the navigation bar change the view. Right-click for the marking menu: eight commands around the cursor, plus a menu for whatever is under it.
- Dialogs dock on the right with a live preview. Most features also show drag handles and a value box in the viewport.
- Timeline (bottom). Every feature in order. Double-click to edit, drag the marker to roll back, and drag features to reorder them.
- S box. Press S anywhere for a command search at the cursor. Pin the commands you use most to its top.
Preferences (File › Preferences) hold the theme, the units of new designs, the mouse preset, the visual style and autosave. Help › Keyboard Shortcuts lists every key, and File › Keyboard Shortcuts changes them.
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. Finish Sketch
returns to the model.
Geometry
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.
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.
Constraints and dimensions
The solver is SolveCraft’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, radius, diameter and angle dimensions. Each is a parameter
(d1, d2, …), so it can hold an expression.
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.
Solid features
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), revolve, sweep, loft, box, cylinder, sphere, torus, coil, pipe, rib, web, emboss, hole (simple, counterbore, countersink, tapped; on faces 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, plane at angle |
| Inspect | measure, section analysis, interference, curvature comb and map, zebra, draft, minimum radius, isocurves, centre of mass |
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.
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.
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.
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.
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 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
typed into a feature is read. In an inch design 2 means 2 in. Changing the units never moves
existing geometry.
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, switched with config.activate.
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. SolveCraft asks before the next command if a move wasn’t captured. Ground fixes an occurrence in place.
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. 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.
STEP export writes the component tree as an assembly.
Sheet metal
The SHEET METAL tab 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 (
sheet.flange): a base flange from a sketch profile, a contour flange from an open sketch, or an edge flange from a part edge. - Hem (
sheet.hem) and Fold (sheet.fold) along a sketch line. - Unfold / Refold (
sheet.unfold,sheet.refold) for working on the flat part. - Convert (
sheet.convert) turns a solid of constant thickness into sheet metal. - Create Flat Pattern (
sheet.flat_pattern) shows the flat part with its bend lines and a bend table, and exports it as DXF.
Bends follow the rule’s K-factor, so flat lengths match what a press brake will make. The oracle checks these against parts built in Fusion.
Plastic parts
The PLASTIC tab has a small enclosure subset:
- 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.
Rib and web (on the SOLID tab) complete the set. These features are checked against hand-computed geometry rather than a Fusion oracle.
Import and export
| Format | Read | Write |
|---|---|---|
.solvecraft (JSON design, versioned) | 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 |
| 3MF, STL, OBJ | mesh bodies | yes |
| DXF, SVG | into a sketch | sketches and flat patterns as DXF |
| PNG, JPEG | canvases and decals |
Opening a STEP or IGES 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.
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. Unsaved changes are autosaved to a recovery folder,
and the next launch offers to recover them. Older design files are upgraded on open, and files
from a newer version are refused with a message.
Keyboard shortcuts
Command shortcuts can be changed in File › Keyboard Shortcuts; these are the defaults.
| 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 |
Keys handled by the window itself:
| Key | Action |
|---|---|
| S | the S box: search every command at the cursor |
| 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 |
| Esc | cancel the running command, or clear the selection |
Help › Keyboard Shortcuts in the app lists the current bindings, including any you changed.
SolveCraft and AI agents (MCP)
SolveCraft 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.
Start it
# Headless: an in-process design session, no window (CPU renders for screenshots).
solvecraft-cli mcp
# Headless, starting from a design file or a command script.
solvecraft-cli mcp --in part.solvecraft
# Bridged to the running app, so you can watch the agent model:
solvecraft --control 7878 &
solvecraft-cli mcp --connect 127.0.0.1:7878
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.
Client configuration
Claude Code (project .mcp.json, or claude mcp add solvecraft -- solvecraft-cli mcp):
{
"mcpServers": {
"solvecraft": { "command": "solvecraft-cli", "args": ["mcp"] }
}
}
Claude Desktop (claude_desktop_config.json), bridged to a running app:
{
"mcpServers": {
"solvecraft": { "command": "/path/to/solvecraft-cli", "args": ["mcp", "--connect", "127.0.0.1:7878"] }
}
}
Tools
| Tool | Arguments | What it does |
|---|---|---|
list_commands | filter? | The command catalog: id, label, tab/panel, shortcut, parameter docs, enabled now. |
execute | command, params? | Run any command with JSON parameters (list_commands shows them). Failures leave the design unchanged. |
batch | commands: [{command, params}], rollback? (default true) | 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) and every result before it. |
inspect_design | measure? (default true), sketches? (default true) | Parameters, timeline (type, suppressed, rolled back, error, warning, timing), bodies (volume, area, centre of mass, bbox, face/edge/vertex counts), sketches (curves, points, constraints, profiles, DOF, solve status), selection, undo depth. |
measure | body? | Volume (mm³), area (mm²), centre of mass, bbox and topology counts per body, with totals. |
body_topology | body | Edges (index, midpoint, length, ends) and faces (index, area, centroid, normal). An edge midpoint is the [x,y,z] that picks that edge for fillet and chamfer. |
set_parameter | name, value (number or expression), unit?, comment? | Create or change a user parameter and recompute; reports features that now fail. |
screenshot | view? (iso, top, front, back, bottom, left, right, home, fit), path?, width?, height?, source? (window, model) | A PNG image content block. Headless: CPU render fitted to the model. Bridged: the app window (default) or a model render with the app’s camera. |
export | path, format? (step, 3mf, stl, stla, obj), bodies? | STEP, 3MF or mesh export. |
undo, redo | — | Undo / redo one change. |
new_design | name? | Start an empty design. |
open | path | Open a .solvecraft 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). |
Resources: solvecraft://design (inspect JSON with measurements) and solvecraft://commands
(the catalog).
Errors: an unknown tool or arguments that don’t match the tool’s schema are JSON-RPC
-32602 errors; 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.
batch rollback undoes as many steps as the batch added to the undo history; batches that
themselves call edit.undo, file.new or doc.open are not rolled back exactly.
Demo: an enclosure built over MCP
examples/demo/enclosure_mcp.py is a small MCP client. It drives a running app (solvecraft --control PORT) through solvecraft-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.
Contributing to SolveCraft
SolveCraft is an open-source parametric 3D CAD app written in pure Rust, in the style of Autodesk Fusion. 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, 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.
Get it running (about 10 minutes on a fast machine)
You need stable Rust 1.90 or newer. For the web
build you also need rustup target add wasm32-unknown-unknown and trunk.
git clone https://github.com/bherbruck/solvecraft && cd solvecraft
cargo run --release -p solvecraft -- --sample # desktop app with a sample part
cargo test --workspace --release # unit, engine and UI-scenario tests
cargo xtask ci # everything a PR must pass (see below)
Web: cd apps/solvecraft-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 solvecraft.
How the code is laid out
The README 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/solvecraft,apps/solvecraft-cli,apps/solvecraft-web.
cargo xtask layers fails the build if a lower layer depends on a higher one.
Rules that aren’t negotiable
- Clean-room. We observe Fusion as a black box: its UI, how parts it builds measure, and
what it does with a given input. We never copy Autodesk code, icons, artwork, help text,
tooltips, templates or materials, and we never commit files Fusion produced. Command ids are
our own (
solid.fillet, not Fusion’s internal names). - No GPL/LGPL/AGPL code, not even reading it. That rules out OpenCASCADE, FreeCAD
(including planegcs), SolveSpace, CadQuery/OCP, LibreCAD and QCAD. SolveCraft is
MIT OR Apache-2.0, and its solver and blends are written from the maths. Permissive
dependencies are fine. Note which one you’re adding and why in the PR, and run
cargo xtask licencesso THIRD-PARTY-LICENSES.txt lists it (CI checks that it is current). - 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 nounsafe, and clippy denies it. - An unsupported case returns an error (“not supported yet: …”); it never panics.
- Every call into truck goes through
solvecraft_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/, 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).
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 test --workspace --release |
| UI scenarios | crates/ui-egui/tests/scenarios/*.json | part of cargo test |
| Fusion oracle | cargo xtask oracle | needs the local oracle data (see below) |
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.
Release mode matters. CI runs the tests with --release. truck has debug-only assertions
that can fail on valid geometry, so if a test fails only in debug, say so in the PR.
The Fusion oracle is a set of 76 reference parts, each built in Fusion from a recipe.
SolveCraft rebuilds the same recipe and compares volume, area and topology counts with Fusion’s
measurements; docs/oracle.md has the current results. The recipes and
measurements live outside the repo (plan/, gitignored), because they come from a Fusion
install. If you work on geometry and want to check against it, ask in an issue.
Sending a change
- Work is tracked in GitHub issues, labelled
by area (
area:kernel,area:sketch,area:doc,area:ui,area:infra). Before starting, comment on the issue so it gets thein progresslabel and two people don’t build the same thing. For anything bigger than a bug fix that has no issue yet, open one first. - Branch from
mainand keep the PR to one topic. - Run
cargo xtask cibefore pushing. It runs fmt, clippy (-D warnings), the tests in release mode, the asset and layer checks, and the wasm build check. A PR has to pass it. - 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).
Much of SolveCraft 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.
Where help is wanted most
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.
- Y-up / Z-up. SolveCraft is Z-up only, so most STEP files (from SolidWorks, Onshape, or Fusion’s default) open lying on their backs. Fusion solves this with a “default modeling orientation” preference.
- Imported appearances. Colours from STEP files are lost when a feature changes the imported body. They should become design appearances on import.
- Drawings, CAM and mesh tools. These are out of scope for now (see ROADMAP.md), but anyone who wants to own one is welcome.
Licence
By contributing you agree that your contribution is licensed as MIT OR Apache-2.0, like the rest of SolveCraft. SolveCraft isn’t affiliated with Autodesk. Fusion is a trademark of Autodesk, Inc.
Architecture
SolveCraft 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 CPU rasterizer |
| L2 | doc | parameters, expressions, the feature timeline and its incremental evaluation |
| L3 | io | design files, STL/OBJ/STEP/3MF export, STEP and 3MF/STL import features |
| L4 | engine | the session and the command registry (everything is a command) |
| L5 | ui-egui, mcp | the swappable desktop front end; the MCP server (headless or bridged to the app) |
| apps | solvecraft, solvecraft-cli | desktop app, headless CLI |
The web app, apps/solvecraft-web, is the desktop front end compiled to wasm with
trunk. xtask holds the repository’s own tooling: cargo xtask ci,
layers, assets, oracle, parity, step-corpus and book.
A few rules follow from the layering:
- Only
crates/kerneltouches truck. Everything above it sees SolveCraft’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, andcrates/renderhas a CPU rasterizer so headless screenshots work anywhere.
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, crates/ui-egui/src/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 solvecraft-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.
Ids used before the 2026-10 rename still resolve through crates/engine/src/legacy_ids.rs, so
old scripts, recipes, saved shortcuts and toolbar layouts keep working.
Adding a command
- Add a
CommandSpecto the right file incrates/engine/src/cmd/and implementrun. - 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).
The kernel boundary and truck
crates/kernel is SolveCraft’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
SolveCraft 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
solvecraft_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 SolveCraft 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.
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, and a sketch stores the face it sits on. If those were face
and edge indices, an upstream edit that adds a face would move the fillet to the wrong edge.
SolveCraft stores names instead (crates/doc/src/naming.rs):
- A face of an evaluated body is named after how it was made. A face lying on a face of the
feature’s input keeps that face’s name; a face new to the feature is named by its role:
F<feature>:side:<sketch curve>,F<feature>:startand:end,F<feature>:blend:<k>and so on. - Pieces of a split face get
#1,#2, … in order along the longest axis. - An edge is named by the two faces it separates.
- Pattern and mirror copies are named after their sources plus the instance.
Names are computed lazily, the first time something asks, from the body, the feature that made it and the model just before that feature. Face provenance from the kernel (which input face an output face came from) does most of the work.
When a feature evaluates, each stored reference is resolved by name. If no face has the name any more, the reference falls back to the geometry it recorded (a point on the face, a normal), and the feature shows a warning so the user can check it. A reference that matches nothing is an error, never a guess.
Test harnesses
cargo xtask ci runs everything a pull request must pass: fmt, clippy with -D warnings, the
tests in release mode, xtask layers, xtask assets 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/solvecraft-cli/tests/corpus | every saved file version still opens and evaluates the same |
| Kill mid-save | apps/solvecraft-cli/tests/kill_mid_save.rs | a save killed at any moment leaves a readable file |
| Fusion oracle | cargo xtask oracle → docs/oracle.md | parts rebuilt from recipes, compared with Fusion’s measurements |
| STEP corpus | cargo xtask step-corpus → docs/step-import.md | STEP files from Fusion opened and measured |
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.
Release mode
CI runs the tests with --release, because some truck assertions only exist in debug builds
and fail on valid geometry. If a test fails only in debug, say so in the PR.
Control protocol
solvecraft --control PORT (or SOLVECRAFT_CONTROL_PORT=PORT) listens on 127.0.0.1:PORT for
JSON lines. 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.
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.commands | — | every command: id, label, tab, panel, icon, shortcut, params doc, enabled |
document.inspect | measure?: bool | parameters, timeline (errors, timings), bodies, sketches, selection |
Commands never open dialogs when run this way. solvecraft-cli commands prints the same registry.
UI
| Method | Params | Notes |
|---|---|---|
ui.inspect | — | UI state, viewport rect, camera, active tool/dialog, hover, renderer, timings, live preview (active, busy, error, ms, bodies it stands in for) |
ui.set | any UiState field (tab, perspective, showGrid, hiddenBodies, dark, …) | dark: false switches to the light theme |
ui.view | view: front, back, top, bottom, left, right, iso, home, fit; animate (bool, default false: snap) | |
ui.start | command | like a toolbar click: starts the sketch tool or dialog for the command |
ui.click | x, y (screen points), button?: left/right/middle, shift?, ctrl?, double? | real pointer input |
ui.move | x, y | |
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); 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) |
ui.menuPick | item: id or label | runs an item of the open menu, as clicking it does |
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) | |
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.handles | prefix? | the handles drawn last frame, [{name, x, y}]: toolbar buttons and drop-down items toolbar:<command id>, panel labels panel:<tab>:<panel>, browser rows row:<label> and fold arrows fold:<label>, manipulator handles |
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?, shift? | |
ui.text | text | typed text |
ui.screenshot | path? | PNG of the window (needs a presented frame) |
ui.render | path, width?, height? | CPU render of the model with the current camera (no window needed) |
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.documents | action?: new, switch, close, quit; index?; force? (close without asking); answer?: save, dont_save, cancel (answers “save changes?”) | the open designs (tabs): names, unsaved flags, the active one, a pending “save changes?” prompt, whether a quit is asking |
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 (copies diagnostics) | the version, commit, build date and diagnostic text |
ui.prefs | any of default_units, nav (fusion, solidworks, inventor), zoom_reverse, orbit_cursor, msaa, length_decimals, angle_decimals | sets preferences (kept between runs), returns them all |
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: instant, normal, slow, 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. "animate": false on a call skips it |
ui.resize | width, height | |
app.quit | force? (quit without asking about unsaved designs) | quitting (the window closes), asking (a “save changes?” prompt is open) |
MCP
solvecraft-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"}}
Fusion oracle
Generated by cargo xtask oracle. Each case is a part built in Fusion (recipe and measurements kept locally in plan/fusion/oracle/, not committed). SolveCraft replays the recipe through its command engine and compares body count, volume and area (relative tolerance 1e-3) and face/edge/vertex counts (exact). SolveCraft counts each closed-surface patch once and ignores seam edges and vertices; when body count, volume and area pass but the raw counts differ, Fusion’s counts are normalised the same way from its face, edge and vertex lists (faces cut from one cylinder, cone, sphere or torus and separated only by seams count once; an edge on that surface and no other is a seam and is dropped; a vertex left with two edges joins them) and compared exactly; the note gives both sides’ raw counts.
76 / 76 cases pass (0 need features not implemented yet).
| Case | Result | Notes |
|---|---|---|
| 01-box | pass | |
| 02-cylinder | pass | |
| 03-box-through-hole | pass | |
| 04-revolve-vase | pass | |
| 05-taper-extrude | pass | |
| 06-symmetric-extrude | pass | |
| 07-fillet-all-edges | pass | |
| 08-chamfer | pass | |
| 09-shell | pass | |
| 10-rect-pattern-holes | pass | |
| 11-circular-pattern-flange | pass | |
| 12-mirror-feature | pass | |
| 13-combine-join | pass | |
| 14-combine-cut | pass | |
| 15-combine-intersect | pass | |
| 16-sweep | pass | |
| 17-loft | pass | |
| 18-hole-features | pass | |
| 19-l-bracket-constrained | pass | |
| 20-slot-constrained | pass | |
| 21-draft | pass | |
| 22-split-body | pass | |
| 23-bracket-multifeature | pass | |
| 24-user-parameters | pass | |
| 25-sketch-triangle | pass | |
| 26-sketch-belt | pass | |
| 27-sketch-trapezoid | pass | |
| 28-blind-pocket-fillets | pass | |
| 29-two-sided-and-partial-revolve | pass | |
| 30-fillet-after-shell | pass | |
| 31-fillet-l-all-edges | pass | |
| 32-boss-plate-fillets | pass | |
| 33-fillet-tangent-chain | pass | |
| 34-chamfer-then-fillet | pass | |
| 35-fillet-curved-curved | pass | topology compared seam-normalised: Fusion’s 6/6/5 faces/edges/vertices become 6/5/5 with closed-surface patches counted once and seam edges and vertices dropped (raw SolveCraft/Fusion: faces 9/6, edges 17/6, vertices 10/5) |
| 36-corner-blend-two-radii | pass | |
| 37-pyramid-apex-blend | pass | |
| 38-coincident-cylinders | pass | |
| 39-tee-pipe | pass | |
| 40-box-sphere-torus | pass | |
| 41-tube-radial-holes | pass | |
| 42-overlapping-patterns | pass | |
| 43-cut-splits-body | pass | |
| 44-shell-l-bracket | pass | |
| 45-shell-boss-plate | pass | |
| 46-shell-two-open-faces | pass | |
| 47-draft-curved-pocket | pass | |
| 48-pulley | pass | |
| 49-helical-sweep | pass | Fusion sweeps along a spline fitted through helix points; SolveCraft builds the true helix (a coil), so the size is compared within 0.5% and the topology not at all |
| 50-sweep-tight-bend | pass | area not compared: Fusion reports this tight sweep’s horn torus face at three times its area (π²·R·r quarter-turn value; 740 mm² against 246.7 by Pappus); volume and topology are compared |
| 51-loft-rect-circle-offset | pass | |
| 52-circular-pattern-fillet | pass | |
| 53-path-pattern | pass | |
| 54-mirror-body-fillets | pass | |
| 55-tapped-hole-cosmetic | pass | |
| 56-holes-on-curved-face | pass | topology compared seam-normalised: Fusion’s 7/10/10 faces/edges/vertices become 7/8/8 with closed-surface patches counted once and seam edges and vertices dropped (raw SolveCraft/Fusion: faces 16/7, edges 32/10, vertices 19/10) |
| 57-hole-pattern-sketch-points | pass | |
| 58-phone-stand | pass | |
| 59-enclosure-base | pass | |
| 60-pipe-flange | pass | |
| 61-shaft-keyway | pass | topology compared seam-normalised: Fusion’s 14/22/18 faces/edges/vertices become 14/20/16 with closed-surface patches counted once and seam edges and vertices dropped (raw SolveCraft/Fusion: faces 24/14, edges 54/22, vertices 34/18) |
| 62-heat-sink | pass | |
| 63-hinge-knuckle | pass | known Fusion difference: Gap replayed with the extent Fusion actually used (its through-all is sized as 1.1 × the farthest vertex, so curved tops stop it short; SolveCraft’s through-all goes truly through) |
| 64-sketch-tangent-arc-chain | pass | |
| 65-sketch-gear-tooth | pass | |
| 66-sketch-linkage | pass | |
| 67-sm-base-flange | pass | |
| 68-sm-edge-flange | pass | |
| 69-sm-tray | pass | |
| 70-sm-flange-45 | pass | |
| 71-sm-custom-rule | pass | |
| 72-sm-contour-flange | pass | |
| 73-sm-hem | pass | |
| 74-sm-unfold-refold | pass | |
| 75-sm-bracket-holes | pass | |
| 76-sm-thickness-variant | pass |
No oracle
| Feature | Status | Tested by |
|---|---|---|
| Boss (draft, root fillet, hole, ribs) | no oracle (licence) | post, hole and rib volumes (boss_with_hole_fillet_and_ribs) |
| Lip / Groove | no oracle (licence) | band offsets and groove clearance on a shelled box (lip_and_groove_on_a_shelled_box, groove_takes_the_rule_clearance) |
| Snap Fit | no oracle (licence) | arm and catch volume, hook direction (snap_fit_arm_and_catch) |
| Rest | no oracle (licence) | rectangle, round, hollow and drafted pads (rest_pads) |
| Plastic Rules (manage, assign) | no oracle (licence) | library values, Thickness-based values, rule draft as the boss default (plastic_rules_library_edit_and_assign) |
Fusion builds these only with its Design Extension, which the oracle account lacks, so there is no Fusion part to compare with. They follow Autodesk’s public descriptions of the features and are checked against hand-computed geometry; the plastic rule library values were read from Fusion’s API.
Fusion command parity
Generated by cargo xtask parity from xtask/data/fusion-catalog.tsv (the toolbar commands of Fusion’s Design workspace, ids and names only) and the live command registry (solvecraft-cli commands). A command counts as live when SolveCraft registers the same command id. This measures breadth, not depth; see ROADMAP.md and docs/oracle.md for depth.
In scope (SOLID, SKETCH, ASSEMBLY, SHEET METAL, PLASTIC enclosure subset): 328 / 541 commands live (61%). SOLID + SKETCH: 185 / 291 (64%). Registered commands: 300.
Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE beyond what solids need, PCB, render, animation, simulation, manufacture, drawings, MANAGE, UTILITIES and the rest of PLASTIC. All Design tabs together: 616 / 1175 (52%).
| Tab | Panel | Live | Total | % |
|---|---|---|---|---|
| SOLID | CREATE | 23 | 35 | 66% |
| SOLID | MODIFY | 19 | 32 | 59% |
| SOLID | ASSEMBLE | 12 | 21 | 57% |
| SOLID | CONFIGURE | 2 | 3 | 67% |
| SOLID | CONSTRUCT | 18 | 20 | 90% |
| SOLID | INSPECT | 12 | 25 | 48% |
| SOLID | INSERT | 6 | 11 | 55% |
| SOLID | SELECT | 0 | 15 | 0% |
| SOLID | POSITION | 2 | 2 | 100% |
| SKETCH | CREATE | 41 | 41 | 100% |
| SKETCH | MODIFY | 12 | 12 | 100% |
| SKETCH | CONSTRAINTS | 15 | 15 | 100% |
| SKETCH | CONFIGURE | 2 | 3 | 67% |
| SKETCH | INSPECT | 12 | 25 | 48% |
| SKETCH | INSERT | 7 | 13 | 54% |
| SKETCH | ASSEMBLE | 0 | 1 | 0% |
| SKETCH | SELECT | 0 | 15 | 0% |
| SKETCH | FINISH SKETCH | 2 | 2 | 100% |
| SHEET METAL | CREATE | 15 | 19 | 79% |
| SHEET METAL | MODIFY | 12 | 21 | 57% |
| SHEET METAL | ASSEMBLE | 11 | 20 | 55% |
| SHEET METAL | CONFIGURE | 2 | 3 | 67% |
| SHEET METAL | CONSTRUCT | 18 | 20 | 90% |
| SHEET METAL | INSPECT | 12 | 25 | 48% |
| SHEET METAL | INSERT | 6 | 11 | 55% |
| SHEET METAL | SELECT | 0 | 15 | 0% |
| SHEET METAL | REFOLD | 0 | 1 | 0% |
| SHEET METAL | POSITION | 2 | 2 | 100% |
| ASSEMBLY | INSERT | 4 | 8 | 50% |
| ASSEMBLY | CREATE | 5 | 8 | 62% |
| ASSEMBLY | RELATIONSHIPS | 4 | 11 | 36% |
| ASSEMBLY | MOTION | 3 | 3 | 100% |
| ASSEMBLY | MODIFY | 8 | 11 | 73% |
| ASSEMBLY | CONTACT | 2 | 2 | 100% |
| ASSEMBLY | CONFIGURE | 2 | 3 | 67% |
| ASSEMBLY | CONSTRUCT | 18 | 20 | 90% |
| ASSEMBLY | INSPECT | 12 | 25 | 48% |
| ASSEMBLY | SELECT | 0 | 15 | 0% |
| ASSEMBLY | POSITION | 2 | 2 | 100% |
| PLASTIC | enclosure subset | 5 | 5 | 100% |
Deferred tabs
| Tab | Panel | Live | Total | % |
|---|---|---|---|---|
| SURFACE | CREATE | 9 | 25 | 36% |
| SURFACE | MODIFY | 17 | 26 | 65% |
| SURFACE | ASSEMBLE | 11 | 20 | 55% |
| SURFACE | CONFIGURE | 2 | 3 | 67% |
| SURFACE | CONSTRUCT | 18 | 20 | 90% |
| SURFACE | INSPECT | 12 | 25 | 48% |
| SURFACE | INSERT | 6 | 11 | 55% |
| SURFACE | SELECT | 0 | 15 | 0% |
| SURFACE | POSITION | 2 | 2 | 100% |
| MESH | CREATE | 2 | 5 | 40% |
| MESH | PREPARE | 0 | 4 | 0% |
| MESH | MODIFY | 6 | 30 | 20% |
| MESH | ASSEMBLE | 12 | 21 | 57% |
| MESH | CONFIGURE | 2 | 3 | 67% |
| MESH | CONSTRUCT | 18 | 20 | 90% |
| MESH | INSPECT | 12 | 25 | 48% |
| MESH | INSERT | 5 | 10 | 50% |
| MESH | SELECT | 0 | 10 | 0% |
| MESH | EXPORT | 0 | 1 | 0% |
| MESH | POSITION | 2 | 2 | 100% |
| FORM | CREATE | 2 | 14 | 14% |
| FORM | MODIFY | 5 | 31 | 16% |
| FORM | SYMMETRY | 0 | 6 | 0% |
| FORM | UTILITIES | 0 | 5 | 0% |
| FORM | CONSTRUCT | 18 | 20 | 90% |
| FORM | INSPECT | 12 | 25 | 48% |
| FORM | INSERT | 7 | 13 | 54% |
| FORM | ASSEMBLE | 0 | 1 | 0% |
| FORM | SELECT | 0 | 15 | 0% |
| FORM | FINISH FORM | 0 | 1 | 0% |
| PLASTIC | SETUP | 2 | 2 | 100% |
| PLASTIC | CREATE | 27 | 35 | 77% |
| PLASTIC | MODIFY | 19 | 32 | 59% |
| PLASTIC | ASSEMBLE | 11 | 20 | 55% |
| PLASTIC | CONFIGURE | 2 | 3 | 67% |
| PLASTIC | CONSTRUCT | 18 | 20 | 90% |
| PLASTIC | INSPECT | 12 | 26 | 46% |
| PLASTIC | INSERT | 6 | 11 | 55% |
| PLASTIC | SELECT | 0 | 15 | 0% |
| PLASTIC | POSITION | 2 | 2 | 100% |
| MANAGE | ASSIGN | 0 | 1 | 0% |
| MANAGE | RELEASE | 0 | 2 | 0% |
| MANAGE | BOM | 0 | 2 | 0% |
| MANAGE | PROCESS MANAGEMENT | 0 | 3 | 0% |
| MANAGE | TASK MANAGEMENT | 0 | 3 | 0% |
| UTILITIES | MODELING TOOLS | 0 | 1 | 0% |
| UTILITIES | MAKE | 0 | 1 | 0% |
| UTILITIES | NEST | 0 | 1 | 0% |
| UTILITIES | ADD-INS | 0 | 2 | 0% |
| UTILITIES | UTILITY | 1 | 3 | 33% |
| UTILITIES | INSPECT | 11 | 24 | 46% |
| UTILITIES | CREATE PMI | 0 | 4 | 0% |
| UTILITIES | SELECT | 0 | 15 | 0% |
| UTILITIES | POSITION | 2 | 2 | 100% |
Live (SOLID, SKETCH)
- SOLID › CREATE › Create Sketch
- SOLID › CREATE › Extrude
- SOLID › CREATE › Revolve
- SOLID › CREATE › Sweep
- SOLID › CREATE › Loft
- SOLID › CREATE › Rib
- SOLID › CREATE › Web
- SOLID › CREATE › Emboss
- SOLID › CREATE › Hole
- SOLID › CREATE › Thread
- SOLID › CREATE › Box
- SOLID › CREATE › Cylinder
- SOLID › CREATE › Sphere
- SOLID › CREATE › Torus
- SOLID › CREATE › Coil
- SOLID › CREATE › Pipe
- SOLID › CREATE › Bounding Solid
- SOLID › CREATE › Rectangular Pattern
- SOLID › CREATE › Circular Pattern
- SOLID › CREATE › Pattern on Path
- SOLID › CREATE › Mirror
- SOLID › CREATE › Thicken
- SOLID › CREATE › Joint Origin
- SOLID › MODIFY › Press Pull
- SOLID › MODIFY › Fillet
- SOLID › MODIFY › Chamfer
- SOLID › MODIFY › Shell
- SOLID › MODIFY › Draft
- SOLID › MODIFY › Scale
- SOLID › MODIFY › Combine
- SOLID › MODIFY › Offset Face
- SOLID › MODIFY › Replace Face
- SOLID › MODIFY › Split Face
- SOLID › MODIFY › Split Body
- SOLID › MODIFY › Move/Copy
- SOLID › MODIFY › Align
- SOLID › MODIFY › Delete
- SOLID › MODIFY › Remove
- SOLID › MODIFY › Physical Material
- SOLID › MODIFY › Appearance
- SOLID › MODIFY › Change Parameters
- SOLID › MODIFY › Compute All
- SOLID › ASSEMBLE › Insert Component
- SOLID › ASSEMBLE › New Component
- SOLID › ASSEMBLE › Joint
- SOLID › ASSEMBLE › As-Built Joint
- SOLID › ASSEMBLE › Rigid Group
- SOLID › ASSEMBLE › Drive Joints
- SOLID › ASSEMBLE › Motion Link
- SOLID › ASSEMBLE › Motion Study
- SOLID › ASSEMBLE › Enable Contact Sets
- SOLID › ASSEMBLE › Enable All Contact
- SOLID › ASSEMBLE › Disable Contact
- SOLID › ASSEMBLE › New Contact Set
- SOLID › CONFIGURE › Configure
- SOLID › CONFIGURE › Display Configuration Table
- SOLID › CONSTRUCT › Offset Plane
- SOLID › CONSTRUCT › Plane at Angle
- SOLID › CONSTRUCT › Tangent Plane
- SOLID › CONSTRUCT › Midplane
- SOLID › CONSTRUCT › Plane Through Two Edges
- SOLID › CONSTRUCT › Plane Through Three Points
- SOLID › CONSTRUCT › Plane Along Path
- SOLID › CONSTRUCT › Axis Through Cylinder/Cone/Torus
- SOLID › CONSTRUCT › Axis Perpendicular To Face
- SOLID › CONSTRUCT › Axis Through Two Planes
- SOLID › CONSTRUCT › Axis Through Two Points
- SOLID › CONSTRUCT › Axis Through Edge
- SOLID › CONSTRUCT › Point At Vertex
- SOLID › CONSTRUCT › Point Through Two Edges
- SOLID › CONSTRUCT › Point Through Three Planes
- SOLID › CONSTRUCT › Point At Center Of Circle/Sphere/Torus
- SOLID › CONSTRUCT › Point At Edge And Plane
- SOLID › CONSTRUCT › Point Along Path
- SOLID › INSPECT › Measure
- SOLID › INSPECT › Interference
- SOLID › INSPECT › Section Analysis
- SOLID › INSPECT › Curvature Comb Analysis
- SOLID › INSPECT › Curvature Map Analysis
- SOLID › INSPECT › Draft Analysis
- SOLID › INSPECT › Environment Map Analysis
- SOLID › INSPECT › Isocurve Analysis
- SOLID › INSPECT › Zebra Analysis
- SOLID › INSPECT › Accessibility Analysis
- SOLID › INSPECT › Center of Mass
- SOLID › INSPECT › Minimum Radius Analysis
- SOLID › INSERT › Decal
- SOLID › INSERT › Canvas
- SOLID › INSERT › Insert SVG
- SOLID › INSERT › Insert DXF
- SOLID › INSERT › Insert Mesh
- SOLID › INSERT › Insert Fastener
- SOLID › POSITION › Capture Position
- SOLID › POSITION › Revert Position
- SKETCH › CREATE › Line
- SKETCH › CREATE › Midpoint Line
- SKETCH › CREATE › SketchConstrainer
- SKETCH › CREATE › 2-Point Rectangle
- SKETCH › CREATE › 3-Point Rectangle
- SKETCH › CREATE › Center Rectangle
- SKETCH › CREATE › Center Diameter Circle
- SKETCH › CREATE › 2-Point Circle
- SKETCH › CREATE › 3-Point Circle
- SKETCH › CREATE › 2-Tangent Circle
- SKETCH › CREATE › 3-Tangent Circle
- SKETCH › CREATE › 3-Point Arc
- SKETCH › CREATE › Center Point Arc
- SKETCH › CREATE › Tangent Arc
- SKETCH › CREATE › Circumscribed Polygon
- SKETCH › CREATE › Inscribed Polygon
- SKETCH › CREATE › Edge Polygon
- SKETCH › CREATE › Ellipse
- SKETCH › CREATE › Center to Center Slot
- SKETCH › CREATE › Overall Slot
- SKETCH › CREATE › Center Point Slot
- SKETCH › CREATE › Three Point Arc Slot
- SKETCH › CREATE › Center Point Arc Slot
- SKETCH › CREATE › Fit Point Spline
- SKETCH › CREATE › Control Point Spline
- SKETCH › CREATE › Control Point Spline
- SKETCH › CREATE › Conic Curve
- SKETCH › CREATE › Point
- SKETCH › CREATE › Text
- SKETCH › CREATE › Fit Curves to Mesh Section
- SKETCH › CREATE › Mirror
- SKETCH › CREATE › Circular Pattern
- SKETCH › CREATE › Rectangular Pattern
- SKETCH › CREATE › Project
- SKETCH › CREATE › Intersect
- SKETCH › CREATE › Spun Profile
- SKETCH › CREATE › Include 3D Geometry
- SKETCH › CREATE › Project To Surface
- SKETCH › CREATE › Intersection Curve
- SKETCH › CREATE › Isoparametric Curve
- SKETCH › CREATE › Sketch Dimension
- SKETCH › MODIFY › Fillet
- SKETCH › MODIFY › Equal Distance Chamfer
- SKETCH › MODIFY › Distance and Angle Chamfer
- SKETCH › MODIFY › Two Distance Chamfer
- SKETCH › MODIFY › Blend Curve
- SKETCH › MODIFY › Offset
- SKETCH › MODIFY › Trim
- SKETCH › MODIFY › Extend
- SKETCH › MODIFY › Break
- SKETCH › MODIFY › Sketch Scale
- SKETCH › MODIFY › Move/Copy
- SKETCH › MODIFY › Change Parameters
- SKETCH › CONSTRAINTS › AutoConstrain
- SKETCH › CONSTRAINTS › AutoConstrain from datum
- SKETCH › CONSTRAINTS › Horizontal/Vertical
- SKETCH › CONSTRAINTS › Coincident
- SKETCH › CONSTRAINTS › Tangent
- SKETCH › CONSTRAINTS › Equal
- SKETCH › CONSTRAINTS › Parallel
- SKETCH › CONSTRAINTS › Perpendicular
- SKETCH › CONSTRAINTS › Fix/UnFix
- SKETCH › CONSTRAINTS › MidPoint
- SKETCH › CONSTRAINTS › Concentric
- SKETCH › CONSTRAINTS › Collinear
- SKETCH › CONSTRAINTS › Symmetry
- SKETCH › CONSTRAINTS › Curvature
- SKETCH › CONSTRAINTS › Polygon
- SKETCH › CONFIGURE › Configure
- SKETCH › CONFIGURE › Display Configuration Table
- SKETCH › INSPECT › Measure
- SKETCH › INSPECT › Interference
- SKETCH › INSPECT › Section Analysis
- SKETCH › INSPECT › Curvature Comb Analysis
- SKETCH › INSPECT › Curvature Map Analysis
- SKETCH › INSPECT › Draft Analysis
- SKETCH › INSPECT › Environment Map Analysis
- SKETCH › INSPECT › Isocurve Analysis
- SKETCH › INSPECT › Zebra Analysis
- SKETCH › INSPECT › Accessibility Analysis
- SKETCH › INSPECT › Center of Mass
- SKETCH › INSPECT › Minimum Radius Analysis
- SKETCH › INSERT › Decal
- SKETCH › INSERT › Canvas
- SKETCH › INSERT › Insert SVG
- SKETCH › INSERT › Insert DXF
- SKETCH › INSERT › Insert Mesh
- SKETCH › INSERT › Insert Component
- SKETCH › INSERT › Insert Fastener
- SKETCH › FINISH SKETCH › Finish Sketch
- SKETCH › FINISH SKETCH › Finish with AutoConstrain
Not yet (SOLID, SKETCH)
- SOLID › CREATE › Create Form
- SOLID › CREATE › Derive
- SOLID › CREATE › Automated Modeling
- SOLID › CREATE › Geometric Pattern
- SOLID › CREATE › Boundary Fill
- SOLID › CREATE › Find Features
- SOLID › CREATE › Fluid Volume
- SOLID › CREATE › Create Base Feature
- SOLID › CREATE › Create Linked PCB
- SOLID › CREATE › Derive PCB
- SOLID › CREATE › Create Unlinked PCB
- SOLID › CREATE › Generate Timeline
- SOLID › MODIFY › Edit Face
- SOLID › MODIFY › Silhouette Split
- SOLID › MODIFY › Arrange
- SOLID › MODIFY › Remove Features
- SOLID › MODIFY › Remove Faces
- SOLID › MODIFY › Replace with Primitives
- SOLID › MODIFY › Volumetric Lattice
- SOLID › MODIFY › Volumetric Texture
- SOLID › MODIFY › VolFieldModifyModelCommand
- SOLID › MODIFY › Manage Materials
- SOLID › MODIFY › Compute Unresolved
- SOLID › MODIFY › Convert
- SOLID › MODIFY › Bill of Materials
- SOLID › ASSEMBLE › Duplicate With Joints
- SOLID › ASSEMBLE › Constrain Components
- SOLID › ASSEMBLE › Align Constraint
- SOLID › ASSEMBLE › Angle Constraint
- SOLID › ASSEMBLE › Center Constraint
- SOLID › ASSEMBLE › Tangent Constraint
- SOLID › ASSEMBLE › Joint Constraint
- SOLID › ASSEMBLE › As-Built Constraint
- SOLID › CONFIGURE › Create Configuration Rules
- SOLID › CONSTRUCT › User Coordinate System (UCS)
- SOLID › CONSTRUCT › Perpendicular Plane
- SOLID › INSPECT › Validate
- SOLID › INSPECT › Component Connection Analysis
- SOLID › INSPECT › DOF Analysis
- SOLID › INSPECT › Edit Component Connection Analysis
- SOLID › INSPECT › Fastener Stack Analysis
- SOLID › INSPECT › Find Similar Components
- SOLID › INSPECT › Navigator
- SOLID › INSPECT › Display Component Colors
- SOLID › INSPECT › Display Mesh Face Groups
- SOLID › INSPECT › Assembly Statistics
- SOLID › INSPECT › Change Summary
- SOLID › INSPECT › History
- SOLID › INSPECT › Sustainability Insights
- SOLID › INSERT › Insert T-Spline
- SOLID › INSERT › Insert Derive
- SOLID › INSERT › Insert McMaster-Carr Component
- SOLID › INSERT › Insert a manufacturer part
- SOLID › INSERT › Insert TraceParts Supplier Components
- SOLID › ASSEMBLE › Add To Assembly
- SOLID › SELECT › Select
- SOLID › SELECT › Window Selection
- SOLID › SELECT › Freeform Selection
- SOLID › SELECT › Paint Selection
- SOLID › SELECT › Select By Name
- SOLID › SELECT › Select By Boundary
- SOLID › SELECT › Select By Size
- SOLID › SELECT › Invert Selection
- SOLID › SELECT › Seed And Boundary
- SOLID › SELECT › Component Drag
- SOLID › SELECT › Select Body Priority
- SOLID › SELECT › Select Face Priority
- SOLID › SELECT › Select Edge Priority
- SOLID › SELECT › Select Component Priority
- SOLID › SELECT › Selection Filters
- SKETCH › CONFIGURE › Create Configuration Rules
- SKETCH › INSPECT › Validate
- SKETCH › INSPECT › Component Connection Analysis
- SKETCH › INSPECT › DOF Analysis
- SKETCH › INSPECT › Edit Component Connection Analysis
- SKETCH › INSPECT › Fastener Stack Analysis
- SKETCH › INSPECT › Find Similar Components
- SKETCH › INSPECT › Navigator
- SKETCH › INSPECT › Display Component Colors
- SKETCH › INSPECT › Display Mesh Face Groups
- SKETCH › INSPECT › Assembly Statistics
- SKETCH › INSPECT › Change Summary
- SKETCH › INSPECT › History
- SKETCH › INSPECT › Sustainability Insights
- SKETCH › INSERT › Insert T-Spline
- SKETCH › INSERT › Duplicate With Joints
- SKETCH › INSERT › Insert Derive
- SKETCH › INSERT › Insert McMaster-Carr Component
- SKETCH › INSERT › Insert a manufacturer part
- SKETCH › INSERT › Insert TraceParts Supplier Components
- SKETCH › ASSEMBLE › Add To Assembly
- SKETCH › SELECT › Select
- SKETCH › SELECT › Window Selection
- SKETCH › SELECT › Freeform Selection
- SKETCH › SELECT › Paint Selection
- SKETCH › SELECT › Select By Name
- SKETCH › SELECT › Select By Boundary
- SKETCH › SELECT › Select By Size
- SKETCH › SELECT › Invert Selection
- SKETCH › SELECT › Seed And Boundary
- SKETCH › SELECT › Component Drag
- SKETCH › SELECT › Select Body Priority
- SKETCH › SELECT › Select Face Priority
- SKETCH › SELECT › Select Edge Priority
- SKETCH › SELECT › Select Component Priority
- SKETCH › SELECT › Selection Filters
STEP import
SolveCraft 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;solvecraft part.step;solvecraft-cli eval part.step,solvecraft-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.
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, B-spline curves, polylines, trimmed/surface/seam curves; length and angle units (SI prefixes, conversion-based units such as inches); product names (body names come from the solid’s name, else the product’s); surface colours; assemblies (next-assembly-usage occurrences placed by item-defined transformations, and mapped items) — the product tree is kept in the feature for the components milestone. 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.
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 into a solid by their shared edges (an open body when they do not close); 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.
Fusion corpus
Generated by cargo xtask step-corpus. Each case is a part exported from Fusion as STEP (AP214; kept locally in plan/fusion/oracle/, never committed). SolveCraft opens part.step exactly as File → Open does (an Import base feature) and compares with Fusion’s own measurements: body count, volume and area (relative tolerance 1e-3) and the number of faces read from the file.
76 / 76 files pass.
| Case | Result | Bodies | Volume mm³ (got / Fusion) | Area mm² (got / Fusion) | Faces (got / Fusion) | ms | Notes |
|---|---|---|---|---|---|---|---|
| 01-box | pass | Box | 24000.000 / 24000.000 | 5200.000 / 5200.000 | 6 / 6 | 3 | |
| 02-cylinder | pass | Cylinder | 35339.429 / 35342.917 | 6125.845 / 6126.106 | 3 / 3 | 87 | |
| 03-box-through-hole | pass | Box | 45738.283 / 45738.053 | 9327.809 / 9327.788 | 7 / 7 | 62 | |
| 04-revolve-vase | pass | Vase | 112488.847 / 112499.479 | 13156.093 / 13156.615 | 5 / 5 | 511 | |
| 05-taper-extrude | pass | Frustum | 61814.826 / 61814.826 | 9676.915 / 9676.915 | 6 / 6 | 2 | |
| 06-symmetric-extrude | pass | Block | 30000.000 / 30000.000 | 6200.000 / 6200.000 | 6 / 6 | 1 | |
| 07-fillet-all-edges | pass | Box | 23340.621 / 23340.849 | 4726.220 / 4726.265 | 26 / 26 | 532 | |
| 08-chamfer | pass | Box | 23730.667 / 23730.667 | 5029.352 / 5029.352 | 10 / 10 | 3 | |
| 09-shell | pass | Box | 13632.000 / 13632.000 | 13992.000 / 13992.000 | 11 / 11 | 2 | |
| 10-rect-pattern-holes | pass | Plate | 29803.713 / 29803.540 | 14652.481 / 14652.389 | 18 / 18 | 252 | |
| 11-circular-pattern-flange | pass | Flange | 66758.144 / 66758.844 | 19320.617 / 19320.795 | 10 / 10 | 168 | |
| 12-mirror-feature | pass | Base | 21392.575 / 21392.920 | 6530.944 / 6530.973 | 10 / 10 | 80 | |
| 13-combine-join | pass | A | 45570.295 / 45571.680 | 8634.196 / 8634.336 | 9 / 9 | 43 | |
| 14-combine-cut | pass | A | 42933.034 / 42931.417 | 9706.783 / 9706.858 | 7 / 7 | 402 | |
| 15-combine-intersect | pass | A | 54452.172 / 54454.273 | 7382.532 / 7382.743 | 7 / 7 | 1022 | |
| 16-sweep | pass | Tube | 2584.428 / 2584.724 | 1779.638 / 1779.698 | 5 / 5 | 612 | |
| 17-loft | pass | Loft | 35121.595 / 35125.498 | 6495.373 / 6495.000 | 6 / 6 | 191 | |
| 18-hole-features | pass | Plate | 33890.908 / 33890.710 | 8568.695 / 8568.696 | 13 / 13 | 184 | |
| 19-l-bracket-constrained | pass | LBracket | 14720.000 / 14720.000 | 5472.000 / 5472.000 | 8 / 8 | 2 | |
| 20-slot-constrained | pass | Slot | 2965.428 / 2965.487 | 1774.644 / 1774.690 | 6 / 6 | 15 | |
| 21-draft | pass | Box | 54574.737 / 54574.737 | 8781.998 / 8781.998 | 6 / 6 | 1 | |
| 22-split-body | pass | Cyl, Cyl (1) | 75390.813 / 75398.224 | 12954.440 / 12955.175 | 6 / 6 | 109 | |
| 23-bracket-multifeature | pass | Bracket | 59331.477 / 59331.129 | 18290.767 / 18290.737 | 19 / 19 | 117 | |
| 24-user-parameters | pass | Block | 34822.022 / 34821.903 | 8114.175 / 8114.159 | 7 / 7 | 58 | |
| 25-sketch-triangle | pass | Triangle | 4032.155 / 4032.155 | 2274.099 / 2274.099 | 5 / 5 | 1 | |
| 26-sketch-belt | pass | Belt | 11326.042 / 11326.170 | 5637.347 / 5637.404 | 7 / 7 | 42 | |
| 27-sketch-trapezoid | pass | Trapezoid | 5342.286 / 5342.257 | 3066.961 / 3066.946 | 8 / 8 | 53 | |
| 28-blind-pocket-fillets | pass | Block | 84299.345 / 84299.249 | 16279.560 / 16279.572 | 23 / 23 | 320 | |
| 29-two-sided-and-partial-revolve | pass | Block | 25731.356 / 25731.307 | 6312.589 / 6312.602 | 12 / 12 | 26 | |
| 30-fillet-after-shell | pass | Box | 21745.915 / 21745.876 | 14968.284 / 14968.332 | 27 / 27 | 464 | |
| 31-fillet-l-all-edges | pass | L | 14321.483 / 14321.626 | 5024.004 / 5024.045 | 38 / 38 | 747 | |
| 32-boss-plate-fillets | pass | Part | 33590.273 / 33590.350 | 9929.854 / 9929.864 | 10 / 10 | 1214 | |
| 33-fillet-tangent-chain | pass | Slot | 8237.037 / 8237.122 | 2793.541 / 2793.569 | 14 / 14 | 555 | |
| 34-chamfer-then-fillet | pass | Box | 22892.383 / 22892.421 | 4753.677 / 4753.688 | 18 / 18 | 54 | |
| 35-fillet-curved-curved | pass | Tee | 63169.685 / 63171.443 | 10269.168 / 10269.348 | 6 / 6 | 904 | |
| 36-corner-blend-two-radii | pass | Box | 57799.504 / 57800.240 | 8585.437 / 8585.524 | 18 / 18 | 519 | |
| 37-pyramid-apex-blend | pass | Pyramid | 15465.376 / 15465.575 | 3914.529 / 3914.581 | 18 / 18 | 312 | |
| 38-coincident-cylinders | pass | A | 15706.406 / 15707.963 | 3769.760 / 3769.911 | 3 / 3 | 67 | |
| 39-tee-pipe | pass | Tee | 24095.763 / 24096.237 | 16709.772 / 16710.048 | 7 / 7 | 182 | |
| 40-box-sphere-torus | pass | A | 36287.558 / 36287.433 | 10696.143 / 10696.165 | 10 / 10 | 2393 | |
| 41-tube-radial-holes | pass | Tube | 20243.046 / 20243.981 | 14873.689 / 14873.773 | 22 / 22 | 1409 | |
| 42-overlapping-patterns | pass | Plate | 38488.411 / 38487.064 | 21687.215 / 21686.603 | 43 / 43 | 533 | solid ‘Plate’: imported as an open body (the shell is not closed) |
| 43-cut-splits-body | pass | Bar, Bar (1) | 28000.000 / 28000.000 | 7200.000 / 7200.000 | 12 / 12 | 5 | |
| 44-shell-l-bracket | pass | L | 8384.000 / 8384.000 | 8784.000 / 8784.000 | 15 / 15 | 3 | |
| 45-shell-boss-plate | pass | Part | 13676.354 / 13676.602 | 14156.531 / 14156.602 | 15 / 15 | 69 | |
| 46-shell-two-open-faces | pass | Box | 16044.000 / 16044.000 | 11398.000 / 11398.000 | 10 / 10 | 3 | |
| 47-draft-curved-pocket | pass | Block | 111387.998 / 111387.712 | 17700.336 / 17700.361 | 11 / 11 | 27 | |
| 48-pulley | pass | Pulley | 168508.160 / 168510.841 | 23718.215 / 23718.644 | 12 / 12 | 321 | |
| 49-helical-sweep | pass | Spring | 4733.931 / 4738.099 | 4765.903 / 4767.652 | 3 / 3 | 10552 | solid ‘Spring’: face wrapping around a closed surface kept whole: split line does not reach the rings |
| 50-sweep-tight-bend | pass | Bend | 3758.025 / 3758.443 | – / – | 5 / 5 | 405 | |
| 51-loft-rect-circle-offset | pass | Loft | 61129.957 / 61133.116 | 9412.473 / 9411.655 | 6 / 6 | 251 | |
| 52-circular-pattern-fillet | pass | Hub | 47206.182 / 47206.850 | 14141.988 / 14142.171 | 21 / 21 | 820 | |
| 53-path-pattern | pass | Plate | 35010.499 / 35010.398 | 16463.947 / 16463.894 | 13 / 13 | 130 | |
| 54-mirror-body-fillets | pass | Bracket | 22780.113 / 22780.089 | 6972.831 / 6972.832 | 12 / 12 | 98 | |
| 55-tapped-hole-cosmetic | pass | Plate | 17542.360 / 17542.314 | 4761.590 / 4761.596 | 8 / 8 | 84 | |
| 56-holes-on-curved-face | pass | Cyl | 77941.171 / 77942.388 | 10592.951 / 10593.139 | 7 / 7 | 378 | |
| 57-hole-pattern-sketch-points | pass | Plate | 36767.029 / 36766.712 | 11818.165 / 11818.153 | 24 / 24 | 197 | |
| 58-phone-stand | pass | Stand | 74559.442 / 74559.177 | 29148.741 / 29148.785 | 19 / 19 | 24 | |
| 59-enclosure-base | pass | Enclosure | 34309.038 / 34309.486 | 33795.405 / 33795.610 | 39 / 39 | 286 | |
| 60-pipe-flange | pass | Flange | 323661.027 / 323660.979 | 56984.270 / 56984.564 | 16 / 16 | 993 | |
| 61-shaft-keyway | pass | Shaft | 47521.504 / 47525.946 | 9676.781 / 9677.096 | 14 / 14 | 182 | |
| 62-heat-sink | pass | HeatSink | 43125.000 / 43125.000 | 45725.000 / 45725.000 | 66 / 66 | 8 | |
| 63-hinge-knuckle | pass | Hinge | 4783.029 / 4783.117 | 4110.255 / 4110.287 | 14 / 14 | 118 | |
| 64-sketch-tangent-arc-chain | pass | Wave | 4800.000 / 4800.000 | 3843.701 / 3843.712 | 9 / 9 | 10 | |
| 65-sketch-gear-tooth | pass | Tooth | 3987.831 / 3988.084 | 1945.416 / 1945.485 | 9 / 9 | 8 | |
| 66-sketch-linkage | pass | Link | 8045.410 / 8045.386 | 4308.172 / 4308.175 | 10 / 10 | 129 | |
| 67-sm-base-flange | pass | Body1 | 15000.000 / 15000.000 | 12800.000 / 12800.000 | 6 / 6 | 2 | |
| 68-sm-edge-flange | pass | Body1 | 18972.476 / 18972.622 | 16057.518 / 16057.550 | 14 / 14 | 26 | |
| 69-sm-tray | pass | Body1 | 29665.828 / 29666.234 | 24979.993 / 24980.086 | 46 / 46 | 66 | |
| 70-sm-flange-45 | pass | Body1 | 18450.704 / 18450.777 | 15629.621 / 15629.637 | 14 / 14 | 14 | |
| 71-sm-custom-rule | pass | Body1 | 10053.579 / 10053.717 | 13896.249 / 13896.299 | 22 / 22 | 38 | |
| 72-sm-contour-flange | pass | Body1 | 12097.478 / 12097.622 | 10411.968 / 10412.002 | 22 / 22 | 36 | |
| 73-sm-hem | pass | Body1 | 21334.478 / 21334.723 | 17994.397 / 17994.473 | 22 / 22 | 60 | |
| 74-sm-unfold-refold | pass | Body1 | 15800.086 / 15800.304 | 13610.275 / 13610.326 | 32 / 32 | 84 | |
| 75-sm-bracket-holes | pass | Body1 | 13915.866 / 13916.012 | 12035.934 / 12035.962 | 25 / 25 | 100 | |
| 76-sm-thickness-variant | pass | Body1 | 22520.368 / 22520.575 | 16064.089 / 16064.128 | 14 / 14 | 31 |
Command reference
Generated by cargo xtask book from solvecraft-cli commands (300 commands). Every command runs the same way from the toolbar, a script, solvecraft-cli exec <id> '<json>', the control channel and MCP. Parameters marked ? are optional; expr is a number or an expression with units.
SOLID › CREATE
sketch.createCreate Sketchplane: XY|XZ|YZ | {origin, x_dir, y_dir} | {face: [x,y,z]} (planar face at point), offset?: expr, name?solid.extrudeExtrude (E)distance: expr (or through_all: true); taper?: angle expr; sketch?: id|name (default: active or last sketch); profiles?: all | [index] | [[curve ids]] | [{point:[x,y]}]; face?: [x,y,z] (extrude a planar body face instead of a sketch profile); direction?: positive|negative|symmetric; distance2?; start_offset?; 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, join out of one, else new); targets?: [body]; name?; body_name?solid.revolveRevolveaxis: sketch line id | x | y (sketch axes) | X|Y|Z (world); angle?: expr (default 360 deg); angle2?: expr (Two Sides: the other side's angle); direction?: symmetric (angle split either side); 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), operation?, targets?, name?, body_name?solid.sweepSweepsketch: profile sketch; profiles?; path_sketch: sketch; path: [curve ids in order]; operation?solid.loftLoftsections: [{sketch, profiles?} | {sketch, point: id} (an apex)] (2 or more, in order); operation?solid.boxBoxlength, width, height: expr; corner?: [x,y,z] | center?: [x,y,z]; operation?solid.cylinderCylinderradius | diameter, height: expr; base?: [x,y,z]; axis?: [x,y,z]; operation?solid.sphereSphereradius | diameter: expr; center?: [x,y,z]; operation?solid.torusTorusmajor, minor: expr (radii); center?; operation?solid.pattern.rectangularRectangular Patternfeatures: [names] | bodies: [names] | components: [names]; dir1: [x,y,z]; count1; spacing1; dir2?, count2?, spacing2?solid.pattern.circularCircular Patternfeatures: [names] | bodies: [names] | components: [names]; axis: X|Y|Z | {origin, dir}; count; angle? (default 360 deg)solid.pattern.pathPattern on Pathfeatures: [names] | bodies: [names]; path_sketch: sketch; path: [curve ids in order]; count; spacing (between instances) | distance (first to last); orientation?: identical|path; flip?: boolsolid.mirrorMirrorfeatures: [names] | bodies: [names] (combine?: join with the original); plane: XY|XZ|YZ | {origin, x_dir, y_dir}solid.holeHole (H)position: [x,y,z] on a face | sketch + points: [sketch point ids] (one hole each, perpendicular to the sketch) | sketch alone (every sketch point: standalone points and circle centres); direction?: [x,y,z] (default: into the face); diameter | thread (a tapped hole: the tap drill, major − pitch) | 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); type?: simple|drilled|counterbore|countersink; tip_angle?; cb_diameter?, cb_depth?; cs_diameter?, cs_angle?; thread?: "M6" (cosmetic)solid.pipePipepath_sketch: sketch; path: [curve ids in order]; diameter: expr; wall?: expr (hollow); operation?, targets?, name?, body_name?solid.bounding_solidBounding Solidbodies?: [names] (default: all); margin?: expr; name?, body_name?solid.threadThreadface: [x,y,z] on a cylindrical face; designation?: ISO metric ("M8", "M8x1"; default: the size that fits); length?: expr (default: the whole face). Cosmetic: the model is unchanged, model.threads lists itSurfaceSculptBoundary Filltools: [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?: boolsolid.coilCoildiameter; two of revolutions (or turns), height, pitch; section_size; section?: circular|square; section_position?: inside|center|outside; base?: [x,y,z]; axis?: X|Y|Z|[x,y,z] (default Z); start_angle?; clockwise?: bool; operation?, targets?, name?, body_name?solid.ribRibsketch, curves: [open curve ids, in order]; thickness (across the sketch plane); depth? (default: to the next face); flip?: bool (fill to the other side)solid.webWebsketch, curves: [open curve ids] (one wall each); thickness; depth? (default: to the next face); flip?: boolsolid.embossEmbosssketch (on a planar face), profiles?; depth; mode?: emboss|deboss (default emboss); targets?joint.originJoint Originname?, occurrence?: id|name (default: from the pick), face: [x,y,z] (centre of a planar face) | circle: [x,y,z] (centre of a round end) | point: [x,y,z], z?: [x,y,z]
SOLID › MODIFY
solid.filletFillet (F)edges: [[x,y,z] point on edge | {body, index}]; radius: expr; body?; type?: constant|chord (radius is the width across)|variable (radius at the end nearest start, radius2 at the other); radius2?; start?: [x,y,z]solid.chamferChamferedges: [[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?solid.combineCombinetarget: body; tools: [body]; operation?: join|cut|intersect; keep_tools?: bool; new_component?: bool (the result goes into a new component; component_name?)solid.scaleScalebodies: [names]; factor: expr | factors: [x, y, z] exprs; origin?: [x,y,z]face.deleteDelete Facefaces: [[x,y,z] points on the faces]; body?. The neighbouring planes close the gap, as when a rounded edge, hole or boss is taken offsolid.offset_faceOffset Facefaces: [[x,y,z] points on planar faces]; distance: expr (positive: outward); body?solid.shellShellfaces: [[x,y,z] points on the faces to remove]; thickness: expr; direction?: inside|outside|both; tangent_chain?: bool (also open faces joined smoothly to the picked ones); body?solid.draftDraftfaces: [[x,y,z]]; angle: expr; neutral: XY|XZ|YZ|plane name|{origin, normal}; pull?: [x,y,z] (default: neutral plane normal); body?solid.split_faceSplit Facefaces: [[x,y,z] points on the faces to split]; 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)solid.split_bodySplit Bodybody: name; plane: XY|XZ|YZ|plane name|{origin, normal} — or tool: {body, point} (a face of a body, its surface extended)solid.press_pullPress Pull (Q)face: [x,y,z] and distance: expr (positive adds material, negative cuts), or edges: [[x,y,z]…] and distance (a fillet)solid.moveMove/Copy (M)bodies: [names]; translate?: [x,y,z] (exprs or numbers); axis?: [x,y,z]; angle?: exprsolid.replace_faceReplace Facefaces: [[x,y,z] points on planar faces]; target: plane (XY|XZ|YZ|construction plane|{origin, normal}) | target_face: [x,y,z] on a parallel planar face; body?solid.alignAlignbodies: [names]; from: [x,y,z] | {snap: vertex|edge_mid|face_center|circle_center, at: [x,y,z]} | from_face: [x,y,z]; to: (the same) | to_face: [x,y,z] (faces also turn to meet); flip?: boolsolid.removeRemovebodies: [names] (removed from here on in the timeline)timeline.compute_allCompute Alltimeline.deleteDelete (Delete)features: [id or name] (a sketch takes its dependent features with it)parameters.changeChange Parametersname, expression (number or text), unit?: mm|cm|m|in|ft|deg|rad|"" (new parameters default to their value's unit), comment?, favorite?: bool, new_name?: rename (references follow); or delete: namematerial.assignPhysical Materialbodies: [names]; material: name (Steel, Aluminum, ABS Plastic, …; `material.list`)appearance.assignAppearance (A)bodies?: [names]; faces?: [[x,y,z] on faces] (body?: which body, default the nearest); components?: [names or ids, or occurrence names]; appearance?: library name (appearance.library) | color: "#rrggbb" or [r, g, b] (0–255), opacity? (0–1, default 1); neither (or color: null): clear, so the face follows its body, the body its component, the component its material
SOLID › CONSTRUCT
construct.plane.offsetOffset Planebase: XY|XZ|YZ|plane name; offset: expr; name?construct.plane.at_anglePlane at Anglebase: XY|XZ|YZ|plane name; axis: X|Y|Z|{origin, dir}; angle: expr; name?construct.axis.through_cylinderAxis Through Cylinder/Cone/Torusface: ref (a cylinder, cone or torus face, or a circular edge); name?construct.axis.normal_to_faceAxis Perpendicular To Faceface: ref; point: ref (the axis goes through it, square to the face); name?construct.axis.two_planesAxis Through Two Planesa, b: plane or planar face refs; name?construct.axis.two_pointsAxis Through Two Pointsa, b: point refs (vertices, sketch points, points); name?construct.axis.edgeAxis Through Edgeedge: ref (a straight edge or sketch line); name?construct.point.vertexPoint At Vertexpoint: ref (vertex, sketch point, point); name?construct.point.two_edgesPoint Through Two Edgesa, b: edge or line refs (where they meet, or come closest); name?construct.point.three_planesPoint Through Three Planesa, b, c: plane or planar face refs; name?construct.point.centerPoint At Center Of Circle/Sphere/Torusof: ref (circular edge, sketch circle or arc, sphere or torus face); name?construct.point.edge_planePoint At Edge And Planeedge: edge or line ref; plane: plane or planar face ref; name?construct.point.along_pathPoint Along Pathpath: edge or sketch curve ref; t: expr, 0…1 along it (default 0.5); name?construct.plane.midplaneMidplanea, b: plane or planar face refs (halfway between; bisecting when they meet at an angle); name?construct.plane.two_edgesPlane Through Two Edgesa, b: straight edge or line refs in one plane; name?construct.plane.three_pointsPlane Through Three Pointsa, b, c: point refs; name?construct.plane.tangentTangent Planeface: ref (cylinder, cone, sphere or torus); at?: [x,y,z] where it touches (default: the face pick); name?construct.plane.along_pathPlane Along Pathpath: edge or sketch curve ref; t: expr, 0…1 along it (default 0.5); name? The plane is square to the path there
SOLID › ASSEMBLE
component.createNew Componentname?, parent?: component id or name (default: the active one); activate?: bool (default true)joint.createJoint (J)type: rigid|revolute|slider|cylindrical|pin_slot|planar|ball; a, b: joint origins ({occurrence, face|circle|point: [x,y,z] in world} | origin name); values?: [angles in deg or expressions, distances]; limits?: [[min, max] | null…]; flip?: bool; offset?; angle?; name?joint.as_builtAs-Built Jointtype, a: occurrence, b: occurrence (stay where they are); at?: joint origin (default: b's origin) — the joint's framejoint.rigid_groupRigid Groupoccurrences: [ids or names] (move together, as they are)joint.driveDrive Jointsjoint: id|name; value | values: [angles in deg or expressions, distances] (clamped to the limits)joint.motion_linkMotion Linka, b: joints; ratio (b = ratio · a + offset); offset?; ia?, ib?: value indices (default 0)contact.enable_setsEnable Contact Setsenabled?: bool (default true) — driving joints and motion studies stop where bodies in one contact set would pass through each othercontact.enable_allEnable All Contactevery occurrence takes part in contact (one set of all)contact.disable_allDisable Contactcontact offcontact.createNew Contact Setoccurrences: [ids or names] (two or more); name?motion.studyMotion Studyname?; 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)explode.createExploded Viewname?; 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
component.capture_positionCapture Positionkeeps pending occurrence movescomponent.revert_positionRevert Positiondrops pending occurrence moves
SOLID › INSPECT
inspect.interferenceInterferencebodies?: [names] (default: all, in world placement) → pairs that overlap with the overlap volume, area, bounding box and centreinspect.measureMeasure (I)bodies?: [names] (default all); or items: [1-2 selections] → length/area/volume, minimum distance (from, to) and angleinspect.sectionSection Analysisplane: XY|XZ|YZ | {origin, normal}; offset?: expr along the normal; flip?: bool; or clear: true
SOLID › CONFIGURE
config.configureConfigureparams?: [parameter names]; features?: [feature names] (columns to add; with no rows yet, a "Default" row takes the current values)config.tableDisplay Configuration Table→ columns (param:<name> or suppress:<feature>), rows, the active row and whether the design still matches it
SOLID › INSERT
parts.fastenerInsert Fastenerthe Insert Part dialog's command (same as parts.insert): family, size, length?, at?|point?+direction?, name?file.insert_stepInsert STEPpath: .step/.stp file (its bodies join the design as an Import base feature); name?file.insert_meshInsert Meshpath: .3mf or .stl file (its meshes join the design as mesh bodies); name?
SKETCH › FINISH SKETCH
sketch.finishFinish Sketchsketch.finish_auto_constrainFinish with AutoConstrainlike AutoConstrain, then Finish Sketch
SKETCH › CREATE
sketch.lineLine (L)points: [[x,y] | "l1.end" | {at, ref}…], closed?: bool, construction?: bool, ids?: [..], infer?: bool (add horizontal/vertical)sketch.rectangle.two_point2-Point Rectangle (R)p0, p1: opposite corners [x,y]; construction?sketch.rectangle.three_point3-Point Rectanglep0, p1: first edge; p2: point on the opposite edgesketch.rectangle.centerCenter Rectanglecenter, cornersketch.circle.centerCenter Diameter Circle (C)center: [x,y] or point ref, radius | diameter: numbersketch.circle.two_point2-Point Circlep0, p1: diameter endssketch.circle.three_point3-Point Circlep0, p1, p2sketch.arc.three_point3-Point Arcstart, end, throughsketch.arc.center_pointCenter Point Arccenter, start, end (counter-clockwise from start) | center, start, sweep: signed degreessketch.polygon.inscribedInscribed Polygoncenter, radius (to vertices), sides, angle?: degsketch.polygon.circumscribedCircumscribed Polygoncenter, radius (to edge midpoints), sides, angle?: degsketch.polygon.edgeEdge Polygonp0, p1: one edge, sidessketch.slot.center_to_centerCenter to Center Slotp0, p1: arc centres, widthsketch.slot.overallOverall Slotp0, p1: overall ends, widthsketch.pointPointpoint: [x,y], id?sketch.dimensionSketch Dimension (D)entities: [refs], type?: auto|distance|horizontal|vertical|length|radius|diameter|angle, value?: number or expression (default: current)sketch.constructionNormal/Construction (X)curves: [ids], value?: bool (default: construction unless all of them already are)sketch.line.midpointMidpoint Linemid: [x,y] or point ref, end: [x,y]; construction?sketch.arc.tangentTangent Arcstart: 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)sketch.circle.two_tangent2-Tangent Circlecurves: [two lines/circles/arcs], radius | diameter: expr, near: [x,y] (which of the solutions)sketch.circle.three_tangent3-Tangent Circlecurves: [three lines/circles/arcs], near: [x,y] (which of the solutions)sketch.slot.center_pointCenter Point Slotcenter: slot centre, end: one arc centre, widthsketch.slot.arc_three_pointThree Point Arc Slotstart, through, end: points on the centre arc, widthsketch.slot.arc_centerCenter Point Arc Slotcenter, start: centre arc start, end: direction of the centre arc end (counter-clockwise), widthsketch.ellipseEllipsecenter, major: end of the major axis [x,y], minor: a point the ellipse passes through on the minor side [x,y] | minor_radiussketch.spline.fit_pointFit Point Splinepoints: [[x,y] or point refs…] (2…500): the spline passes through themsketch.spline.control_pointControl Point Splinepoints: control points (2…500), cubicsketch.spline.control_point_5Control Point Spline (Degree 5)points: control points (2…500), degree 5sketch.conicConic Curvestart, end, apex: [x,y] or point refs; rho?: 0…1 (default 0.5: parabola)sketch.textTexttext: string (\n breaks lines), at: baseline start [x,y], height?: capital height mm (default 5), angle?: degsketch.constrainerSketch Constrainerentities: [one or two sketch entity ids]: applies the constraint the geometry nearly has (horizontal/vertical, parallel, perpendicular, collinear, coincident, concentric, equal, tangent)sketch.mirrorMirrorentities: [curve/point ids], line: mirror line id (symmetry constraints are added)sketch.pattern.circularCircular Patternentities: [ids], center: [x,y] or point ref, count: 2…500, angle?: total deg (default 360)sketch.pattern.rectangularRectangular Patternentities: [ids], dir?: [x,y] (default x), count, spacing; count2?, spacing2? (perpendicular direction)sketch.projectProject (P)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}]sketch.intersectIntersectrefs: [{body}, {face: [x,y,z], body?}, {edge: [x,y,z], body?}, {sketch, curve}]: section curves (or points) with the sketch plane, linkedsketch.include_3dInclude 3D Geometryrefs: like Project (edges, vertices, sketch curves and points); curves out of the sketch plane are flattened onto itsketch.project_to_surfaceProject To Surfacecurves: [{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 curvessketch.intersection_curveIntersection Curvea, b: {body} | {face: [x,y,z], body?}: where they meet, as linked 3D curvessketch.spun_profileSpun Profilebody: name, axis: a line of the active sketch (curve id) | X|Y|Z: the body's outline revolved about the axis, in the sketch planesketch.isoparametric_curveIsoparametric Curveface: [x,y,z] point on the face (the curve passes through it), body?, direction?: u|v|[x,y,z] (default u): linked 3D curvesketch.fit_curves_to_sectionFit Curves to Mesh Sectionbody: name (a mesh or solid body): its section with the sketch plane, fitted with lines and arcs, as normal (unlinked) geometry
SKETCH › CONSTRAINTS
sketch.constraint.horizontal_verticalHorizontal/Verticalline: id | points: [a, b]; mode?: horizontal|verticalsketch.constraint.coincidentCoincidenta: point, b: point or curvesketch.constraint.tangentTangenta, b: curvessketch.constraint.equalEquala, b: lines or circles/arcssketch.constraint.parallelParallela, b: linessketch.constraint.perpendicularPerpendiculara, b: linessketch.constraint.fixFix/UnFixentity: point or curve | entities: [points and curves]; fixed?: bool (default: fix them all unless all are fixed, then unfix)sketch.constraint.midpointMidPointpoint, linesketch.constraint.concentricConcentrica, b: circles/arcssketch.constraint.collinearCollineara, b: linessketch.constraint.symmetrySymmetrya, b: points, line: symmetry linesketch.constraint.curvatureCurvaturea, b: two curves sharing an end point (curvature continuous, G2)sketch.constraint.polygonPolygonlines: [line ids] of a closed chain: equal sides with every corner on one circle (a regular polygon)sketch.auto_constrainAutoConstraintolerance?: mm (default 0.01), angle_tolerance?: deg (default 1), dimensions?: bool (default true): infer constraints, then dimension until fully constrainedsketch.auto_constrain_from_datumAutoConstrain from datumdatum: point ref (default origin); like AutoConstrain with positions measured from the datum
SKETCH › MODIFY
sketch.blend_curveBlend Curvea, b: end points of two curves ("l1.end", "a2.start"): a smooth (tangent) spline joining themsketch.filletFilletpoint: corner shared by two curves ("l1.end") | a, b: two lines or arcs meeting at a corner; radius: exprsketch.chamfer.equal_distanceEqual Distance Chamferpoint | a, b: a corner of two lines; distancesketch.chamfer.distance_angleDistance and Angle Chamferpoint | a, b: a corner of two lines; distance: along a, angle: deg from asketch.chamfer.two_distanceTwo Distance Chamferpoint | a, b: a corner of two lines; distance: along a, distance2: along bsketch.offsetOffset (O)curves: [ids], distance: number (positive = left of the chain, or toward `side`), side?: [x,y], chain?: bool (default true: the connected chain)sketch.trimTrim (T)curve: id, at: [x,y] (the piece to remove: between the nearest intersections around it)sketch.extendExtendcurve: id, at: [x,y] near the end to extend (to the nearest curve)sketch.breakBreakcurve: id, at: [x,y] (split at the nearest intersections around it) | params: split at points: [[x,y]…]sketch.scaleSketch Scaleentities: [curve/point ids], base: [x,y] or point ref, factor: number
SKETCH › INSERT
sketch.insert_dxfInsert DXFpath | text: ASCII DXF (blocks expanded); at?: [x,y] offset, scale?, tolerance?: mm within which ends join (default 0.0001); plane?: (when no sketch is active, a new sketch on it; default XY)sketch.insert_svgInsert SVGpath | text: SVG; at?: [x,y] offset, scale?, tolerance?: mm within which ends join (default 0.0001); plane?: (when no sketch is active, a new sketch on it; default XY)canvas.insertCanvaspath | 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?canvas.decalDecalpath | data; face: [x,y,z] on a planar face (the image is centred there); width?: mm; angle?: deg; opacity?: (default 1)canvas.editEdit Canvascanvas: id or name; center?, width?, angle?: deg, opacity?, flip?, visible?, name?
SKETCH › INSPECT
inspect.curvature_combCurvature Comb Analysiscurves?: [sketch curve ids] (sketch?: id|name, default active) | edges?: [[x,y,z] points on body edges]; density?: teeth per curve (default 40), scale?: comb length per unit curvature (default auto)inspect.minimum_radiusMinimum Radius Analysiscurves?: [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 isinspect.isocurveIsocurve Analysisfaces: [[x,y,z]…] points on faces; count?: curves each way (default 8): the faces' isoparametric curvesinspect.center_of_massCenter of Massbodies?: [names] (default all): the centre of mass (uniform density) of each and of all togetherinspect.zebraZebra Analysisstripes?: number across the view (default 12); clear?: true turns the analysis offinspect.draftDraft Analysispull?: [x,y,z] direction (default Z); angle?: degrees (default 1); clear?: true turns the analysis offinspect.environment_mapEnvironment Map Analysisclear?: true turns the analysis offinspect.accessibilityAccessibility Analysisdirection?: [x,y,z] the tool comes from (default Z, from above); clear?: true turns the analysis offinspect.curvature_mapCurvature Map Analysisradius?: mm shown red (default: a tenth of the model's size); clear?: true turns the analysis off
SURFACE › CREATE
surface.patchPatchsketch?: id|name and profiles? (planar patches of sketch regions) | edges: [[x,y,z]] (points on a closed loop of a body's edges) and body?; name?, body_name?surface.thickenThickenbodies: [surface names]; thickness: expr (negative: against the normal); symmetric?: bool; operation?: new|join|cut|intersect; targets?
SURFACE › MODIFY
surface.stitchStitchbodies: [names] (surfaces sewn into the first; a solid when they close); tolerance?: expr (default: tight)surface.extendExtendbody: planar surface name; edges: [[x,y,z]] (points on straight edges to push out); distance: exprsurface.trimTrimbody: surface name; plane: XY|XZ|YZ|plane name|{origin, normal} or tool: {body, point} (a face, its surface extended); keep: [x,y,z] (a point on the side to keep)
SHEET METAL › CREATE
sheet.flangeFlangebase: sketch + profiles? (rule?, flip?) | contour: sketch + curves (open chain of lines) + distance (rule?, flip?, reverse?) | edge: edges: [[x,y,z] on top/bottom edges] + height (to the outer face), angle? (default 90 deg), radius? (default: rule), position?: inside|outside|middle, flip?, body?; type?: base|edge|contour (default from the inputs)sheet.hemHemedges: [[x,y,z] on sheet edges]; length (to the outer face); gap? (inner radius, default: rule); flip?; body?sheet.foldFoldline: 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?sheet.convertConvert to Sheet Metalbody; face: [x,y,z] on its large flat face (a plate of even thickness); rule?sheet.flat_patternCreate Flat Patternbody? → flat size, outline loops and cut-outs (flat coordinates), bend lines and the bend table
SHEET METAL › MODIFY
sheet.unfoldUnfoldbody? (default: the last sheet body) — all bendssheet.refoldRefoldbody? (default: the last unfolded sheet)sheet.manage_rulesSheet Metal Rulesname (new or existing); thickness?, k_factor?, bend_radius?, relief_width?, relief_depth?, corner_relief?, hem_gap?, gap? (expressions; `Thickness` is the rule's); active?: bool; delete?: bool
PLASTIC › CREATE
plastic.bossBossposition: [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?plastic.lipLipface: [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?plastic.snap_fitSnap Fitposition: [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; body?plastic.restRestposition: [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
plastic.manage_rulesManage Plastic Rulesname (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)plastic.assign_ruleAssign Plastic Rulebodies: [names]; rule: name (empty: no rule) — plastic features on these bodies take its draft and clearance as defaults
Other: sketch.*
sketch.editEdit Sketchsketch: id or namesketch.deleteDelete Sketch Entitiesentities: [curve, point or constraint ids]sketch.move_pointDrag Sketch Pointpoint: ref, to: [x,y]sketch.dragDrag Sketch Geometryentity: point or curve, from: [x,y] where it was grabbed, to: [x,y]: a point goes to `to`, a line (spline, conic) slides by to - from, a circle or arc grabbed on its edge takes the radius to `to`; the rest moves as little as it cansketch.dimension_textMove Dimension Textdimension: constraint id or parameter name; at: [x,y] text centre (sketch coordinates) | reset: true (default place)sketch.optionsSketch Palette Optionssketch?: id|name (default active); any of show_profile, show_points, show_dimensions, show_constraints, show_projected, grid, snap, slice, sketch_3d: bool. Returns them allsketch.chainChaincurve: id: the curves joined to it end to end (what a double-click selects)sketch.solveSolve Sketchreports the solver status and degrees of freedomsketch.centerlineCenterlinecurves: [line ids], value?: bool (default toggles): centerline line type (not in profiles; an axis)sketch.edit_textEdit Textlink: the text's link id (or entity: one of its curves), text?, at?, height?, angle?: degsketch.toggle_drivenToggle Driven Dimensionconstraint: dimension id, driven?: bool (default toggles); a driving dimension gets a parameter with its current valuesketch.glyphsConstraint Glyphssketch?: id|name: every constraint with where to draw its glyph (sketch coordinates)sketch.snapSnap Inferenceat: [x,y], from?: [x,y] (previous click), radius?: mm (snap distance, default 1): what a click there would snap to and infersketch.export_dxfSave As DXFsketch?: id|name (default active); path?: file to write (else the DXF text is returned)sketch.line3d3D Linepoints: [[x,y,z]…] world points (2 or more): one 3D line per segment, joined end to end (needs 3D Sketch on)sketch.spline3d3D Splinepoints: [[x,y,z]…] world fit points (2 or more): a smooth 3D curve through them (needs 3D Sketch on)sketch.point3d3D Pointpoint: [x,y,z] world point (needs 3D Sketch on)sketch.move3dMove 3D Pointwire: drawn 3D curve or point id, index?: fit point (default 0), to: [x,y,z] | by: [dx,dy,dz]; points joined to it move toosketch.moveMove/Copy Sketch Entitiesentities: [ids], translate?: [dx,dy], angle?: deg, center?: [x,y], copy?: boolsketch.break_linkBreak Linklink?: link id (j1) | entities?: [curve or point ids]: linked geometry becomes normal sketch geometrysketch.auto_projectAuto-Project Edges on Referencevalue?: bool (default toggles): project a face's edges when a sketch is created on itsketch.redefineRedefine Sketch Planesketch: id or name; plane: XY|XZ|YZ | construction plane name | {face: [x,y,z]} | {origin, normal} (the sketch keeps its geometry)sketch.inspectInspect Sketchsketch?: id|name (default active)
Other: canvas.*
canvas.calibrateCalibrate Canvascanvas: id or name; a, b: two points on the canvas plane [x,y]; distance: their true distance (mm): scales the image about acanvas.deleteDelete Canvascanvas: id or name
Other: sheet.*
sheet.rulesList Sheet Metal Rules→ rules with their expressions and values, and the active onesheet.export_dxfExport Flat Pattern as DXFbody?, path? → DXF (mm): OUTLINE, BEND_UP and BEND_DOWN (centre lines) layers
Other: plastic.*
plastic.rulesList Plastic Rules→ rules with their expressions and values, the active rule and the rules assigned to bodies
Other: component.*
component.from_bodiesCreate Components from Bodiesbodies: [names] → one new component per body, named after itcomponent.activateActivate Componentcomponent: id or name (0 or "root" for the design)component.listList Componentscomponent.renameRename Componentcomponent, namecomponent.deleteDelete Componentcomponent: deletes it, its subcomponents, occurrences and featurescomponent.move_bodiesMove Bodies to Componentbodies: [names], componentcomponent.move_sketchesMove Sketches to Componentsketches: [ids or names], componentcomponent.paste_newPaste Newcomponent: copied with its features into a new, independent component; translate?: [x,y,z]
Other: occurrence.*
occurrence.copyPaste (Instance)component (or occurrence): what to place again; parent?: component (default: the same parent); translate?: [x,y,z]occurrence.moveMove Occurrenceoccurrence; translate?: [x,y,z]; axis?: [x,y,z], angle?: radians or "30 deg", origin?: [x,y,z]; capture?: bool (default true; false keeps it pending until Capture Position)occurrence.groundGroundoccurrence; grounded?: bool (default toggles)
Other: joint.*
joint.limitsJoint Limitsjoint; index?: value index (default 0); min?, max? (omit both to clear)joint.editEdit Jointjoint; type?, flip?, offset?, angle?, suppressed?, name?joint.deleteDelete Jointjoint: id|name (or origin: name, link: index)joint.listList Joints→ joints (type, occurrences, values, limits), origins, links, degrees of freedom, conflictsjoint.solveSolve Jointsre-place the occurrences from the joints → conflicts, errors, dof
Other: contact.*
contact.editEdit Contact Setset: id|name; occurrences?; name?; suppressed?: bool; delete?: boolcontact.listList Contact Sets→ sets, enabled, allcontact.checkCheck Contact→ pairs in contact sets that pass through each other now
Other: motion.*
motion.playPlay Motion Studystudy: id|name; step (0…steps) — sets the joints to the study's values there (contact sets stop them) → positionsmotion.exportExport Motion Studystudy; samples? (default steps + 1); path? (.json or .csv) → occurrence positions per step (the design is not changed)motion.listList Motion Studiesmotion.deleteDelete Motion Studystudy
Other: explode.*
explode.showShow Exploded Viewview: id|name (or none: back to the assembled placement) — shown only; the design keeps its placementsexplode.listList Exploded Viewsexplode.deleteDelete Exploded Viewview
Other: config.*
config.columnConfiguration Columnparam? | feature? (name); delete?: boolconfig.rowConfiguration Rowname; from? (row to copy; default the current values); values?: {param name or feature name: expression or true/false}; rename?; delete?: boolconfig.activateActivate Configurationrow: name — sets its parameter values and suppressions
Other: parts.*
parts.libraryStandard Partsfamily? → families (name, standard, sizes, lengths) and, with family, each size's dimensions (mm)parts.insertInsert Partfamily (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?parts.resizeChange Part Sizecomponent: id|name of an inserted part; size?; length? — rebuilds it (joints keep it seated)
Other: body.*
body.renameRename Bodybody: its name, name: the new name (features that use the body follow)
Other: browser.*
browser.groupNew Groupfolder: bodies|sketches|construction; component?: id (default 0); name?; items?: [keys] (they leave other groups)browser.ungroupUngroupgroup: id (its items go back to the folder)browser.rename_groupRename Groupgroup: id, namebrowser.moveMove Browser Itemsfolder, component?, items: [keys]; group?: id (into it; omit to take them out of groups); before?: key (position in the group)browser.orderOrder Browser Itemsfolder, component?, order: [keys] (the folder's items in display order)
Other: document.*
document.unitsChange Design Unitsunits: mm|cm|m|in|ft (display and unit-less parameters; values keep their meaning)document.inspectInspect Designmeasure?: bool (include body measurements)
Other: view.*
view.saveNew Named Viewname?; camera: {target: [x,y,z], yaw, pitch, distance, fov?} (replaces a view of the same name)view.renameRename Named Viewview: name, nameview.deleteDelete Named Viewview: nameview.listList Named Views
Other: selection.*
selection.deleteDeleteitems?: [selection items]; components?, occurrences?, canvases?: [ids or names]; dry_run?: bool → deleted, would_fail, skipped (one undo step)
Other: edit.*
edit.undoUndo (Ctrl+Z)edit.redoRedo (Ctrl+Y)
Other: timeline.*
timeline.renameRenamefeature: id|name, nametimeline.rollbackMove Timeline Markerposition: number of features to keep active (omit = end)timeline.roll_toRoll History Markerposition: number of features to keep active (omit = end) | feature: id|name (the marker goes right after it)timeline.reorderReorder Featurefeature: id|name, position: its new index; refused when a feature that worked would failtimeline.redefineEdit Featurefeature: id|name, command: the feature's command id, params: its parameters (the feature is rebuilt in place)timeline.dependentsFeature Dependentsfeature: id|name → features deleted with it, and features that would fail without ittimeline.suppressSuppress Featurefeature: id|name, suppressed?: bool (default toggles)timeline.editEdit Featurefeature: id|name, set: {fields to change, e.g. {"extent": {"distance": "30"}}}timeline.copyCopy Featuresfeatures: [ids or names] (in timeline order on paste)timeline.pastePaste Featurestranslate?: [x,y,z] (moves the copies' geometry); the copies go at the marker
Other: select.*
select.setSelectitems: [{type: body|edge|face|feature|sketch_curve|sketch_point|profile, …}], add?: boolselect.clearClear Selection
Other: parameters.*
parameters.listList Parametersfavorites?: 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 cyclesparameters.addAdd User Parametername, expression, unit?, comment?, favorite?: boolparameters.renameRename Parametername, new_name (expressions, feature inputs and dimensions that use it follow)parameters.deleteDelete Parametername (refused while something uses it; the error lists the users)parameters.favoriteFavourite Parametername, favorite?: bool (default true)parameters.commentComment Parametername, commentparameters.usersParameter Usersname → users (parameters, feature inputs, dimensions) and the features it drivesparameters.graphParameter Graph→ nodes, edges [user, used] (users include Feature:<name>), cycles, errorsparameters.exportExport Parametersformat?: csv|json (default csv, or from the path), path?: write to a file → textparameters.importImport Parameterstext: CSV (header with name, expression, unit?, comment?) or JSON [{name, expression, unit?, comment?}] | path; all or nothingparameters.completeComplete Expressionprefix?: the text before the cursor → word, suggestions [{text, kind: parameter|function|unit|constant, detail}]
Other: expr.*
expr.evaluateEvaluate Expressionexpression, kind?: length|angle|unitless (or unit?: mm|in|deg…) → ok, value (mm/rad), display_value, references, error
Other: file.*
file.newNew Design (Ctrl+N)name?file.saveSave (Ctrl+S)path? (default: current file)file.save_asSave Aspathfile.exportExportpath; format?: stl|stla|obj|step|3mf (default from extension); bodies?: [names]file.save_meshSave As Meshpath; bodies?: [names]; ascii?: bool
Other: doc.*
doc.openOpen (Ctrl+O)path: .solvecraft design, or a .step/.stp file (opens as a new design)doc.recovery_listRecoverable Designsdir? (default: the recovery folder) → designs autosaved by apps that crashed or closed with unsaved changesdoc.recoverRecover Designid (from doc.recovery_list); dir?; discard?: bool (delete the entry once open) — opens it with its file path, unsaveddoc.recovery_discardDiscard Recovered Designid, or all: true; dir?
Other: model.*
model.edgesList Edgesbody: namemodel.facesList Facesbody: namemodel.threadsList Threads
Other: appearance.*
appearance.libraryAppearance Library→ the built-in appearances (name, colour, opacity)appearance.listList Appearancesbody?: name → assigned appearances; with body, its resolved look and its faces' (face index, look)
Other: material.*
material.listList Materials
Other: engine.*
engine.commandsList Commands