SolidCraft
SolidCraft is an open-source parametric 3D CAD application written in pure Rust. Sketch on a plane, constrain and dimension the sketch, turn profiles into solids with features on a timeline, and change a parameter to watch the whole timeline rebuild.
It runs as:
- a desktop app for Windows and Linux (egui + wgpu);
- a web app in the browser (wasm, WebGPU or WebGL2), try it here;
- a headless CLI,
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.
SolidCraft is early. Sketching, solid modelling, parameters, components with joints, sheet metal and a plastic-enclosure subset work; surfaces, drawings, CAM and simulation are deferred. The roadmap has the details, and command parity and the Fusion oracle measure how far along it is.
Existing .solvecraft designs and solvecraft-cli commands keep their names and behavior.
This book has two halves: the user guide for people modelling with SolidCraft, and the developer guide for people working on it. The command reference is generated from the code on every build.
SolidCraft is licensed MIT OR Apache-2.0. It is a clean-room implementation and is not affiliated with Autodesk; Fusion is a trademark of Autodesk, Inc.
Getting started
SolidCraft keeps its compatibility names: the desktop executable is solvecraft, the CLI
is solvecraft-cli, and saved designs remain .solvecraft. Published 0.2.0 and 0.2.1 packages
were released under the SolveCraft name.
Desktop
Download the latest build from the releases page, or build it yourself with stable Rust (1.95 or newer):
git clone https://github.com/bherbruck/solidcraft && cd solidcraft
cargo run --release -p 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.
Linux Flatpak
Install Flatpak using your distribution’s package manager. For an installation that receives updates, add Flathub for the required Freedesktop runtime and the SolidCraft application feed to the same user installation:
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak remote-add --user --if-not-exists solvecraft https://bherbruck.github.io/solidcraft/flatpak/solvecraft.flatpakrepo
flatpak install --user solvecraft ai.storyteller.solvecraft
flatpak run ai.storyteller.solvecraft
The application ID and remote name retain their compatibility names. To update an existing
remote that still uses the former website address, run
flatpak remote-modify --user --url=https://bherbruck.github.io/solidcraft/flatpak/repo/ solvecraft
and then flatpak update --user.
Alternatively, download the release’s .flatpak file for your architecture and run
flatpak install --user ./solvecraft-VERSION-linux-ARCH.flatpak. The bundle supplies the
Flathub runtime repository address; installation still needs internet access to fetch any
missing runtime. Launch with flatpak run ai.storyteller.solvecraft, rather than looking for
a host solvecraft binary. Files chosen through Open/Save use the desktop file-chooser portal;
command-line paths outside Documents may need a specific additional filesystem permission.
Web
The same app runs in the browser: open SolidCraft on the web (?sample opens the sample
part, ?webgl forces WebGL2 where WebGPU is missing). 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 right-drag orbits freely (the mouse preset in Preferences switches to SolidWorks or Inventor style). Shift+middle orbits freely in the default and Inventor presets. Shift+right-drag snaps to the nearest standard or diagonal view; Shift does not request snapping when the preset already requires it to orbit. The axis view shows the world’s X/Y/Z directions in SolidCraft’s colors. Click a signed axis tip to look along that axis; click the aligned tip again to look from the opposite side. Drag the axis view to orbit around the view centre, with Shift for snapping. Top and bottom keep the nearest quarter-turn heading. The house and navigation bar’s Home button go home. Right-click for the marking menu: eight commands around the cursor, plus a menu for whatever is under it. Shaded views omit certified smooth joins between spline spans on extruded walls. Real creases, cap outlines and authored face splits remain visible. Wireframe retains its edge presentation, and hiding a join does not remove its CAD identity or change the exported solid. Unsupported or uncertain wall geometry keeps its existing edge presentation.
- Dialogs dock on the right with a live preview. Most features also show drag handles and a value box in the viewport.
- 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.
Navigation › Smooth wheel zoom is on by default. Turn it off to apply each wheel or trackpad event immediately, with no smoothing tail. It is independent of Animate view changes, which controls axis views, Look At, Home and Fit. Re-enabling smoothing during a wheel gesture takes effect after that gesture ends, avoiding leftover zoom being replayed.
Shift-orbit snapping includes 45-degree edge views and isometric corner views, with a lightly damped spring transition and a small boundary margin to prevent flicker.
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.
Curves adapt to the current zoom and display scale. Picking and highlighting use the same visible boundary, including small circles at Fit and sketches in rotated components. Profile shading follows those curves while preserving holes. Zooming changes the display without changing sketch geometry, dimensions, or undo history. On short windows or enlarged displays, the Sketch Palette’s options scroll while its constraint status and Finish Sketch button remain visible.
Geometry
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.
Run from a script or the control channel, every command that adds sketch geometry returns the
ids of what it added (created: {points, curves, constraints, dimensions}). Ids are stable and
never reused within a sketch, even after a delete, so a script can dimension or constrain the new
geometry right away.
Extend finds intersections with exact ellipses, rational conics and spline spans. It keeps the original line or circular support and extends only beyond the selected endpoint; conflicting constraints or an ambiguous overlap leave the sketch unchanged.
Closed convex chains of tangent lines and circular arcs support inward offsets that consume a rounded corner. When the offset passes the corner radius, the neighbouring straight supports meet at a sharp corner. The surviving lines and endpoint IDs stay stable; the retired arcs return under their original IDs if a later edit restores their radius. Whole-boundary profile selections follow this transition through save and reopen, so an extruded lip can remain associative. Existing eligible closed analytic offsets upgrade without changing their saved geometry or IDs.
This transition requires a closed convex boundary with tangent junctions and a certified nonempty region. An extinguished arc referenced individually by a constraint or another offset cannot retire. Creating another offset from an already sharp retired boundary also requires breaking its association first. Open, nonconvex and mixed free-form chains keep their existing behavior. Break Offset Association freezes the current boundary, including private arc centres and construction flags, for independent editing.
Offsets of smooth ellipses, conics and splines follow their source after dimension changes,
dragging and reopening a design. Their stored cubic spline has a whole-span normal-distance
error bound of 0.002 mm. Singular tangents, curvature cusps, possible self-intersections and
excessive subdivisions are refused. Offset these curves individually: mixed chain junctions
currently require separate geometry. sketch.offset.break releases an offset for independent
editing, and undo restores its association. Older independently sampled offsets remain independent.
Smooth open and newly created smooth closed offsets can be offset again, including bounded
refinement after a source edit. New closed outputs use a periodic cubic fit with continuous
tangent and curvature at the seam. Break Offset Association preserves that interpolation,
and its closing point remains joined when dragged. Deleting an output also removes its
unused private fit points; deleting a source with a retained dependent is refused.
For explicit closed fit splines, sketch.spline.fit_point accepts periodic: true and at
least three distinct cyclic samples. It closes the loop automatically. Existing calls and
older saved closed splines retain natural interpolation; older associative closed offsets
also retain their saved natural fit. A legacy seam without a unique normal still refuses
another offset. Periodic intent is stored on each curve and survives save, reopen and Break.
While drawing a fit spline, click its starting point after three distinct samples to close
it smoothly. Right-click finishes an ordinary natural fit. Offset accepts both ellipse
handles and dense fitted curves; the second click sets the side and distance from the curve.
Enable 3D Sketch in the Sketch Palette to draw lines, fit splines and points off the plane.
New 3D geometry follows its sketch’s component placement, including the selected occurrence
and pending position changes. Click a drawn fit point to show its move triad; its arrows move
along world X, Y and Z. Scripts also supply world coordinates to sketch.line3d,
sketch.spline3d, sketch.point3d and sketch.move3d. Activate the desired occurrence before
editing an explicit sketch belonging to a component with multiple placements.
Older drawn or detached 3D wires keep their saved world positions. Included and surface-linked wires with an explicit component marker keep that meaning after the link is broken. Old files whose links became lost after an incorrectly scoped cross-component refresh may require a fresh link once the source is available; their stored point values are preserved on opening.
Constraints and dimensions
Horizontal/Vertical accepts a click on a line or clicks on two distinct sketch points, including the origin, endpoints and circle centres. For two points, it chooses horizontal or vertical alignment from their current positions. Esc cancels a pending first point.
The solver is SolidCraft’s own (damped Gauss–Newton with degree-of-freedom analysis). Constraints:
coincident, horizontal/vertical, parallel, perpendicular, tangent, equal, concentric, collinear,
midpoint, symmetry, fix, curvature (G2) and polygon. Sketch Dimension (D) adds linear,
aligned, horizontal, vertical, radius, diameter and angle dimensions. Each is a parameter
(d1, d2, …), so it can hold an expression.
For Coincident, pick two points, a point and a curve (in either order), or two distinct straight lines. Two lines join their closest pair of endpoints; the first picked line stays in place when the existing constraints allow it. Pick endpoints explicitly when you want to connect a different pair.
Horizontal/Vertical also applies immediately to one selected line or two distinct selected points. Shift-click to add the second point before choosing the constraint.
For Parallel, Perpendicular, Equal, Tangent, Concentric and Collinear, select exactly two sketch curves (Shift-click to add the second) and choose the constraint to apply it immediately. You can also start the constraint first and then pick its curves. An incomplete or mixed selection leaves the tool ready for ordinary picking.
Coincident accepts an exact selection of two points or a point and a curve. Midpoint accepts one point and one line, in either selection order. Symmetry accepts two distinct points and one symmetry line, regardless of where the line occurs in the selection. Successful constraints clear the selection; rejected constraints preserve it.
Under-constrained geometry is blue and fully constrained geometry turns dark, and the status shows the remaining degrees of freedom. A constraint or dimension that would over-constrain the sketch is refused rather than applied. Drag a point to move it; the solver keeps every constraint while you drag.
Profiles
Closed loops become profiles automatically, including regions split by crossing lines and islands inside outlines. Extrude, revolve, sweep and loft pick profiles.
A feature remembers a picked profile by the curves around it, not by where it was clicked:
"profiles": [{"curves": ["l1", "l2", "a1", "l3"]}] (or [{"curves": ["c1"]}] for a circle)
picks the region whose outer loop is made of exactly those curves, or the pieces of them that
other curves cut off. It is found again on every recompute, so the feature keeps the same region
when a parameter moves the geometry. Islands inside the region are its holes. When no region, or
more than one (both halves of a circle cut by a line), is bounded by exactly those curves, the
feature reports an error rather than taking another region. Profiles picked by index or by point
([{"point": [x, y]}]) are stored the same way when the feature is made.
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 |
Extrude automatically selects Join for additions when the active component already has a solid, including sideways elbow connections and screw bosses that meet only part of a face. Extruding into existing material selects Cut. In an empty component it selects New Body; choose New Body explicitly to keep an addition separate. Changing the default does not alter operations already saved in a design.
Editing an extrusion retains its signed distances, From/To Object inputs, taper expressions and explicit body targets. Revolve edits retain world, sketch and explicit-line axes, both angle expressions and To Object targets. An unchanged All profiles selection continues to include profiles added later. Cancel restores the complete timeline; a successful edit is one undo step, including downstream parameter changes. Editing a placed component previews the edited feature before its downstream history. Cylindrical Emboss profile guides follow the sketch’s chosen occurrence frame.
Newly selected adaptive profiles store complete outer and hole source identities when the entire boundary can be qualified. Changing an ellipse or periodic offset after extrusion can then change its spline span count while keeping the same selected material region. A partial, intersected or ambiguous boundary cannot satisfy that whole-source reference; changing the hole identities also makes it lost. Older curve-span selections retain their exact stored multisets. If an older feature has already lost its profile after subdivision changed, edit the feature and reselect the intended current region to capture the new identity.
New symmetric revolves split the entered angle evenly around the sketch profile, regardless of which way the selected axis points. Two Sides measures the second angle backward and the first angle forward along the sketch-normal sweep direction. Profiles on opposite sides of an axis each use their own sweep direction. Designs saved before format 11 keep their original angular placement when opened, edited or copied; creating another revolve uses the new behavior. To Object follows that same direction for each selected profile, including curved target faces. Pick a target away from the axis so its angular position is defined.
For cylindrical Emboss, select a supported exterior cylinder face and a tangent sketch profile. Deboss cuts radially into the wall. Orthogonal line profiles, including holes and concave outlines, wrap with exact radial walls; the profile must stay clear of seams and ends. Complete cylinders and coaxial sleeves are supported. Capture pending component positions before applying. The feature stores the selected target and author occurrence paths, so edits and reopening retain the chosen instances. Other curved surfaces and free-form profiles remain unsupported. A new Emboss without an explicit face infers a unique eligible supported cylinder; select the face when the target is ambiguous. Older saved features without a face retain their planar placement.
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.
Editing a feature from a script
timeline.inspect {feature} returns a feature’s definition: the fields timeline.edit changes,
with their current values. timeline.edit {feature, set} merges set into that definition
(objects are merged field by field, anything else replaces the value), rebuilds the feature and
reports recomputed and restored. The feature’s type cannot change, and sketch geometry is
edited with the sketch commands. Expressions are text ("30", "30 mm", "width / 2",
"45 deg"); a bare number is a length in the design’s units, or degrees for an angle.
{"command": "timeline.inspect", "params": {"feature": "Slab"}}
→ {"type": "ExtrudeFeature",
"definition": {"sketch": 1, "profiles": {"type": "all"}, "operation": "new_body",
"extent": {"distance": "10", "direction": "positive"}},
"inputs": [{"label": "Distance", "param": "d1", "expression": "10", "unit": "mm"}], …}
{"command": "timeline.edit", "params": {"feature": "Slab", "set": {"extent": {"distance": "12"}}}}
inputs lists the feature’s named inputs. parameters.change {name: "d1", expression} sets one
of those directly. Optional fields that are not set do not appear in the definition; they can
still be added through set. The fields of the common features:
| Feature | Fields |
|---|---|
| Extrude | extent: {distance, direction: positive|negative|symmetric, distance2, start_offset, through_all, taper, to, to_offset, from}; operation: new_body|join|cut|intersect; targets; profiles; sketch |
| Revolve | angle, angle2, symmetric, axis, to, operation, targets, profiles, sketch |
| Fillet | radius, edges (a point on each edge), body, style |
| Chamfer | distance, distance2 or angle, flip, edges, body |
| Box | length, width, height, corner, operation |
| Cylinder | radius, height, base, axis, operation |
| Hole | diameter, depth, position, direction, hole (simple, drilled, counterbore, countersink), thread, to |
| Shell | thickness, faces, direction, body, tangent_chain |
| Draft | angle, faces, neutral, pull, body |
| Offset face | distance, faces, body |
| Pattern | pattern (its kind and counts/spacing), features, bodies |
| Mirror | plane, features, bodies, combine |
For any other feature, timeline.inspect shows the fields.
Press Delete on anything selected (bodies, faces, sketches, features, planes, components, canvases). It runs as one undoable step and lists the features that would go with it or fail.
Face Extrude, face Revolve, and Press Pull retain linked generated profiles. When a modified planar face has no construction certificate, these internal profiles use the legacy named-face attachment and projection behavior so upstream edits still update the plane and outline. This compatibility path is private to those modeling tools: newly attached user sketches require a certified face or an explicitly fixed plane. Legacy internal attachments can follow the nearest matching face after topology changes; inspect their result after major upstream edits. Activate the intended occurrence before generating a face profile from a repeated component; capture a separately moved source position before creating a legacy profile across components.
Negative Press Pull across supported aligned prismatic faces retains round through-holes, including after a joined extrusion and in a rotated component occurrence. The cut partitions the current material into axial sections, preserving the hole through undo, redo, and reopen. Walls that cannot be qualified as a parallel extrusion still use the general Boolean path and may report an unsupported coincident-surface operation atomically.
Parameters and expressions
Every dimension and feature input is a model parameter (d1, d2, …). User parameters
are ones you add yourself, with a unit, an expression and an optional comment. Change them in
Change Parameters (parameters.change). The timeline rebuilds from the first feature that
uses them.
In the SOLID toolbar’s CONFIGURE panel, click Parameters to open the user and model parameter editor. The separate Configurations button opens the configuration table; Configure remains in the dropdown for configuring columns.
Press Enter in the new parameter’s name, expression or comment field to add it. Valid entries return focus to the name field for the next parameter; invalid entries remain editable. In expression fields, Up/Down selects an autocomplete suggestion, Enter/Tab accepts it, and Escape dismisses the suggestions. Accepting a suggestion does not also submit the parameter.
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, physical materials and body appearances, switched with config.activate.
The table’s Library picker selects material or appearance values. Choose Follow material
to clear an appearance override. Custom looks retain their colour and opacity across
activation, undo and save/reopen. See
configuration commands and examples.
Components and joints
A design is a tree of components. New Component (component.create) adds one, and
Create Components from Bodies (component.from_bodies) turns bodies into components. Each
component has its own origin, bodies, sketches and timeline entries. The active component
(the radio button in the browser) receives new sketches and features; the others are drawn
faded.
A component is placed by its occurrences. Paste creates another instance of the same component, and Paste New creates an independent copy. Move an occurrence with the move triad, then Capture Position to keep it or Revert to put it back. SolidCraft asks before the next command if a move wasn’t captured. Ground fixes an occurrence in place.
Paste New copies nested component definitions, sketches, feature histories and generated dimension parameters. Repeated child instances remain shared within the new copy. Materials, appearances and wholly internal joints/motion records receive copied references. Design-wide user parameters remain shared; retained external links and omitted cross-boundary motion records are reported. Unsupported destructive links refuse the entire copy. See individual-instance and copy details.
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.
Right-click a component in the Browser and choose Properties to inspect
material-aware mass, volume, surface area, centre of mass and world bounds. Descendants and
repeated instances contribute to assembly totals; pending moves are included. A component
uses its first placement; occurrence.properties selects a specific instance. Root Properties
measures the complete assembly. Volumes are summed, including overlapping bodies, rather than
measuring their union. The same workflow is available through component.properties and
occurrence.properties; see
placement and measurement details.
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 or a bend-table CSV through the save picker.
Bends follow the rule’s K-factor, so flat lengths match what a press brake will make. The oracle checks these against parts built in Fusion.
For a shop drawing or spreadsheet, sheet.export_bend_table returns CSV by default; choose
format: "json" for structured data. It includes the sheet rule, thickness, bend angles,
radii, allowances and flat centre-line coordinates. Lengths use millimetres and angles use
degrees. Supply path to save it, or omit path to receive the content through the command
response. body defaults to the last sheet body. Exporting does not add a timeline entry or
consume an undo step.
{"command":"sheet.export_bend_table","params":{"format":"csv","path":"bracket-bends.csv"}}
{"command":"sheet.export_dxf","params":{"path":"bracket-flat.dxf"}}
DXF cutout geometry preserves lines, circles and circular arcs; other curves are sampled
adaptively. The OUTLINE, BEND_UP and BEND_DOWN layers separate cuts from bend centres.
Base-flange profiles keep their interior holes. Through cuts that cross a sheet edge become notches in the flat outline; overlapping and repeated cuts remove their combined area once. The flat preview, DXF and unfolded solid use the same material boundaries. Lines and circular arcs remain exact; other profile curves use a 0.001 mm chord tolerance.
Cuts can leave separate material pieces, including the centre of a ring cut. These pieces remain one logical sheet group through Unfold / Refold and export as separate flat outlines. Large profiles that exceed the geometry processing budget report an error instead of exporting a partial outline.
Parallel, untapered extrude cuts also preserve shallow pockets through Unfold / Refold.
The pocket floor and side walls remain solid geometry; overlapping cuts from opposite sides
become a through hole wherever they remove the whole thickness. Flat-pattern JSON lists
pockets with their side, depth and bottom/top thickness coordinates in millimetres.
DXF pocket boundaries use POCKET_BOTTOM_Z…_TO_…_MM, POCKET_TOP_Z…_TO_…_MM, or
POCKET_INTERNAL_Z…_TO_…_MM layers. These are machining operations, so do not treat every
pocket layer as a through-cut contour when importing the drawing into a CAM tool.
Extrude Intersect on a straight base or an unfolded sheet retains the material inside both the current tool footprint and its depth interval, including interior holes, detached islands and earlier pockets. A shallow intersection records the removed layers above and below the retained material as top and bottom pockets. The sheet rule keeps the original stock gauge; the remaining solid can be thinner. A completely empty intersection removes the sheet and its metadata together. DXF records pocket depth in its layer names; importing that DXF as ordinary sketch geometry does not reconstruct the machining depths automatically. Each positive retained or removed depth layer must meet the kernel’s 0.000001 mm extrusion resolution; smaller layers report an error rather than being rounded away. To Object can retain a tool that trims to a straight prism, using its actual trimmed footprint. A curved tool with varying depth is refused before changing sheet geometry. Pocket profiles can move with planar panels when forming, but a pocket crossing a bend zone cannot yet be refolded. Machining folded bend material, tapered sheet cuts and other unsupported 3D sheet Cut/Intersect operations report an error before changing the design; they cannot silently restore removed material during a later sheet rebuild.
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.
Current designs use format 12, including persistent analytic offset boundary roles and association profile references, durable sketch face anchors, periodic fit splines and versioned revolve conventions. It also retains recorded body transfers, explicit 3D curve coordinate spaces and certified face bindings, alongside semantic configuration styles, associative free-form offsets and cylindrical emboss selections. Older ownership maps, geometry and expressions retain their saved meaning. Legacy 3D curves with retained associations receive their producer’s coordinate-space marker without changing any saved points; detached curves retain world coordinates. A lost cross-component association refreshed by an older build may need an explicit relink or refresh when its source returns. Loading never guesses a numerical correction. Association records, coordinate arrays and placements are bounded and checked.
Saving in the web app
In the web app, Save and Save As keep designs in this browser’s private storage; they do not open a dialog for choosing a folder on your computer. Clearing the site’s data can remove those designs. Download a copy to keep it outside the browser or move it to another device.
- Open the File menu and choose Open to show Files in this browser.
- Choose Download open design for the current design, including its unsaved changes, or Download beside a stored file for its last saved contents.
- Find the
.solvecraftfile in your browser’s downloads. Whether a folder chooser appears depends on your browser’s download settings.
Use Open from computer… in the same panel to reopen a downloaded design. The web app works on a browser-stored copy; later saves do not update the original downloaded file. Use Rename beside a stored file to change its name. Exports such as STEP and STL are also downloaded.
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.
SolidCraft and AI agents (MCP)
SolidCraft speaks the Model Context Protocol over stdio (JSON-RPC 2.0, one message per line; protocol revisions 2025-06-18, 2025-03-26 and 2024-11-05). An agent models exactly like a person does: every tool runs engine commands, the same ones the toolbar, palette, scripts and control channel use, and never opens a dialog.
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, visible), sketches (curves, points, constraints, profiles, DOF, solve status, visible), selection, undo depth. Hide helper sketches, bodies or construction planes with execute browser.visibility ({items, visible} or {folder, visible}). |
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 recomputed (features rebuilt), restored (features whose earlier result for the same inputs was reused, e.g. a value set back) and features that now fail. |
screenshot | view? (iso, top, front, back, bottom, left, right, home, fit), 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). |
To change a feature after it is made, read its definition with execute timeline.inspect
({feature}: id or name) and change fields with timeline.edit ({feature, set}; e.g.
{"extent": {"distance": "30"}}). The fields per feature type are listed in the book’s
Solid features chapter.
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 goes back to an engine checkpoint (edit.checkpoint) taken before the first
step: the design, the active sketch and the undo history are restored exactly as they were,
however long the history is. Batches that call file.new or doc.open get the design back,
not its file path. (An older app without checkpoints falls back to undoing as many steps as
the batch added to the undo history.)
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 SolidCraft
SolidCraft is an open-source parametric 3D CAD app written in pure Rust. You sketch on a plane, constrain the sketch, turn profiles into solids, and change a parameter to watch the timeline rebuild. It runs as a desktop app (Windows, 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.95 or newer (required by egui/eframe 0.36). For the web
build you also need rustup target add wasm32-unknown-unknown and trunk.
git clone https://github.com/bherbruck/solidcraft && cd solidcraft
cargo run --release -p solvecraft -- --sample # desktop app with a sample part
cargo xtask test # unit, engine and UI-scenario tests
cargo xtask ci # everything a PR must pass (see below)
SolidCraft is the product and repository name. Cargo packages and executables retain
their solvecraft names for compatibility with existing scripts and MCP clients.
Build the desktop app with cargo build --release -p solvecraft, or the CLI with
cargo build --release -p solvecraft-cli. Run the CLI with
cargo run --release -p solvecraft-cli -- commands (or target/release/solvecraft-cli commands
after building). There is no Cargo package named solidcraft.
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.
NixOS: nix develop
NixOS has no /usr/lib, so nothing is on LD_LIBRARY_PATH and a plain cargo run finds none of
the libraries the app loads at run time: the Wayland ones winit opens the window through, the EGL
loader wgpu renders through, and libdbus-1, which rfd’s file dialogs reach xdg-desktop-portal
through (a dialog then never appears and the pick returns no path):
ERROR solvecraft: winit EventLoopError: ... The wayland library could not be loaded
ERROR eframe::native::run: Exiting because of error: WGPU error: Failed to create surface for any enabled backend: {}
ERROR rfd::backend::xdg_desktop_portal::portal::libdbus: Can't connect to a portal: libdbus-1.so not found
nix develop (the flake.nix in the repository root) supplies Wayland,
X11, EGL, Vulkan-loader and D-Bus runtime libraries. It also exposes the NixOS
driver manifests in /run/opengl-driver/share, so Vulkan can discover the system
drivers. Enable hardware.graphics.enable on NixOS; other Linux distributions
may need a matching nixGL wrapper.
The default development shell retains the Rust/C toolchain already on PATH;
nix develop .#rust supplies the pinned toolchain and builds in target/nix.
To launch, use nix run .#solidcraft -- --sample. To install the app’s terminal
commands, desktop entry and generated icons, use nix profile install .#solidcraft.
nix build alone creates a build result; it does not install a launcher in your
desktop menu. The installed solidcraft command and compatible solvecraft
command run the same wrapped app; solvecraft-cli is included as well. See
Nix installation and troubleshooting for remote-flake commands,
desktop integration and how to distinguish an old executable on PATH.
How the code is laid out
The 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. SolidCraft 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 xtask test (or cargo test --workspace --profile ci) |
| 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.
Use the ci profile. CI runs the tests with --profile ci: light optimisation, line tables,
no LTO, and debug assertions off (truck has debug-only assertions that reject valid geometry). It
builds several times faster than --release. If a test fails only in a plain debug build, say so
in the PR. cargo xtask test builds the tests with it and then runs every test binary at once
(cargo test runs them one after another); arguments after -- go to each binary, e.g.
cargo xtask test -- shell. Tests run beside each other on a busy machine, so a test must not
depend on wall-clock timing.
The Fusion oracle is a set of 76 reference parts, each built in Fusion from a recipe.
SolidCraft 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, the asset, licence and layer checks, clippy (-D warnings), then the tests with the wasm build check alongside. 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).
Help reviewers see what the words mean. For observable CAD, UI or model behavior, please include actual descriptive screenshots or a short recording in the issue or PR. This evidence is required for changes or bugs that can be demonstrated in the app, including kernel, geometry, assembly and backend work. Coding agents should run a small reproduction and capture the evidence themselves; please don’t ask a human for evidence the agent can produce. Show the actual failure or result, preferably before and after, with brief steps and expected/actual behavior. An arrow or annotation can help identify the detail that matters.
Flowcharts and Mermaid diagrams cannot replace evidence of visible behavior. Diagrams are useful for architectural reasoning when actual screenshots cannot illustrate the point. Changes with no useful observable behavior do not need screenshot or video evidence. Use a small example without private designs or sensitive details; attach the media or link to an accessible recording rather than committing generated review artifacts.
Keep checking upstream commits and the relevant PR’s merge status throughout the work and
before handoff. Integrate upstream merges, resolve conflicts without losing others’ changes,
and validate the integrated result. Before pushing, run git pull --rebase origin main, re-run
CI if it brought changes, and push without force. Report the upstream/PR state you checked,
any conflicts resolved and validation results. While a task remains active, follow an open
PR through its status changes; at handoff, clearly identify anything still awaiting merge.
Much of SolidCraft so far was written by AI coding agents (Claude) working in parallel, each owning one area, with a human setting direction and reporting bugs. Their working notes are in AGENTS.md. Human contributors are just as welcome. Use whatever tools you like, as long as the change meets the rules above.
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. SolidCraft 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 SolidCraft. SolidCraft isn’t affiliated with Autodesk. Fusion is a trademark of Autodesk, Inc.
Architecture
SolidCraft is a Cargo workspace in layers. A crate only depends on crates in lower layers, and
cargo xtask layers fails the build otherwise.
| Layer | Crates | Role |
|---|---|---|
| L0 | geom | vectors, planes, profiles, meshes and their measures |
| L1 | sketch, kernel, render | constraint solver and profiles; the B-rep kernel boundary (truck); view math and 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 SolidCraft’s own types: bodies, faces, edges, meshes and measures. See The kernel boundary. - The document knows nothing about commands or the UI.
crates/docholds the parameters, the feature timeline and the component tree, and evaluates them into a model. - Everything the user can do is a command in
crates/engine. See The command engine. - The front end is swappable. Nothing below
ui-eguidepends on egui. The CLI and the MCP server run the same engine without a window, 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 SolidCraft’s B-rep kernel. It wraps
truck (Apache-2.0) for topology, NURBS geometry, booleans,
tessellation and STEP parsing, and it is the only crate allowed to use truck. Callers get
SolidCraft types (Body, face and edge indices, meshes, measures) and a
KernelError instead of truck types and panics.
guard
truck can panic on geometry it doesn’t handle. Every call into it goes through
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 SolidCraft code in the kernel: constant, chord and variable-radius fillets with corner blends, chamfers, shell and offset, Delete Face, Split Face, Replace Face, sewing, STEP and IGES reading and writing beyond truck’s parser, and face provenance (which input face each output face came from, for persistent naming).
Vendored truck crates
Patched copies of truck-shapeops, truck-stepio and truck-meshalgo live in vendor/,
wired in with [patch.crates-io]. The patches fix robustness bugs found by our tests and the
STEP corpus. vendor/ is excluded from our fmt and clippy rules, and each patch should go
upstream when it can.
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.
SolidCraft 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, xtask assets,
xtask licences --check, xtask layers, clippy with -D warnings, the tests with the ci
profile and the wasm build check.
| Harness | Where | What it catches |
|---|---|---|
| Unit and engine tests | next to the code, *_tests.rs | measured results: volume, area, topology counts against derived values |
| Hostile input | hostile_params_never_panic, crates/engine/src/tests.rs | every command × every documented parameter × junk values: errors, never panics |
| Sketch fuzz | crates/engine/src/cmd/sketch_fuzz_tests.rs | random sketches through the solver and profile finder |
| UI scenarios | crates/ui-egui/tests/scenarios/*.json | the real app, headless: clicks, keys, drags, assertions |
| Layout tests | crates/ui-egui/tests/ui_layout.rs, window_widths.rs | panels and dialogs at every window width |
| Design-file corpus | apps/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.
CI profile
CI runs the tests with --profile ci: light optimisation, line tables, no LTO and debug
assertions off. Some truck debug assertions fail on valid geometry; the ci profile disables
them while building faster than --release. If a test fails only in a plain debug build, say
so in the PR.
Run cargo xtask test to build with this profile and run the test binaries concurrently,
with doc tests alongside. Arguments after -- go to each test binary, for example
cargo xtask test -- shell. To run the test binaries sequentially instead, use
cargo test --workspace --profile ci.
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.
The channel is untrusted input. A request line may be at most 16 MiB; a longer one is answered
with {"ok": false, "error": "request too large"} and the connection is closed, as are
connections that send eight lines that are not JSON. At most 16 connections are served at once
(further ones get {"ok": false, "error": "too many connections"}), at most 64 queued requests
run per frame so a flood cannot starve drawing, and a request that the UI thread has not
answered within 60 seconds is replied to with {"ok": false, "error": "timeout"}. The same
16 MiB line limit applies to the MCP server.
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.
Every sketch.* command that changes a sketch adds created: {points, curves, constraints, dimensions} to its result (and wires for 3D curves): the ids of what it added, in creation
order, next to whatever else the command returns (curves, sides, corners…). Sketch ids are
stable and never reused, even after a delete (l1, l2, then l3 once l2 is gone; only undo
rolls the numbering back with the design), so they can go straight into later commands:
{"id":7,"method":"engine.execute","params":{"command":"sketch.line","params":{"points":[[0,0],[40,0]]}}}
→ {"curves":["l1"],"created":{"points":["l1.start","l1.end"],"curves":["l1"],"constraints":["k1"],"dimensions":[]},…}
{"id":8,"method":"engine.execute","params":{"command":"sketch.dimension","params":{"entities":["l1"],"value":50}}}
UI
| Method | Params | Notes |
|---|---|---|
ui.inspect | — | UI state, what is hidden (visibility: bodies, origin and construction items, sketches; set it with the browser.visibility command), 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, dark, …), and hiddenBodies, hiddenOrigin, hiddenSketches, shownSketches (whole lists; browser.visibility changes single items) | 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; orientation tips axis_view:x, axis_view:-x (and y/z) |
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); cut by the section the viewport shows (inspect.section, an open Section Analysis dialog’s preview, a sketch’s Slice), with the cut face capped |
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.file | path; insert?: bool | starts an interactive background Open or Insert; poll ui.inspect.file_busy for completion; the window stays responsive |
ui.home | open?: bool; sample?: index | the start page: show or hide it, open a built-in sample (once built); returns open and the recent designs |
ui.shortcut | command, key?, replace? | a command’s key, or rebinds it ("" clears; a key another command uses is refused unless replace) |
ui.help | item?: about, shortcuts (read only), report (copies diagnostics) | the version, commit, build date and diagnostic text |
ui.prefs | any of default_units, nav (fusion, solidworks, inventor, alias, tinkercad), zoom_reverse, zoom_to_cursor, smooth_wheel_zoom (default true), orbit_cursor, look_at_anim, look_at_speed, msaa, length_decimals, angle_decimals, theme (dark, light, system), language (en, ja; see localization), ui_scale, up_axis (z, y), offer_stand_up; open (a page: General, Navigation, Keyboard Shortcuts, Design, Graphics, Sketch, Selection) opens the Preferences dialog there; button: ok, cancel, apply, defaults; stand_up: true stands up a just-opened Y-up file | sets preferences (kept between runs), returns them all plus dialog (the open page or null), dark and stand_up_offer |
ui.capture | action?: capture, revert, cancel | answers the Capture Position question (asked when a command starts or the design is saved while components were moved without capturing); without action, what waits |
ui.window | action: minimize, maximize, restore, toggle, close | what the title bar’s caption buttons do (on Windows and Linux the application bar is the title bar); close asks about unsaved designs first |
ui.agent_cursor | show (bool), speed: a number from 1 to 5 (times the normal speed) or "instant" (no animation: the command runs at once and the pointer jumps to where it acted), follow_camera (bool); all optional | the settings. While the cursor is shown, engine.execute first glides to the command’s toolbar button and to each pick, then runs and replies; later requests wait their turn. "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"}}
Hold Shift during an orbit gesture to snap to the nearest standard or diagonal view. This applies to right-drag, the Orbit navigation tool, and dragging the axis view. Under the default preset, Shift+middle also orbits with snapping; middle alone pans. Axis-view tips use world coordinates, regardless of the up-axis preference. Clicking an aligned tip again flips to its opposite.
Shift-orbit snapping includes 45-degree edge views and isometric corner views, with a lightly damped spring transition and a small boundary margin to prevent flicker.
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). SolidCraft replays the recipe through its command engine and compares body count, volume and area (relative tolerance 1e-3) and face/edge/vertex counts (exact). SolidCraft 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 SolidCraft/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; SolidCraft 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 SolidCraft/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 SolidCraft/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; SolidCraft’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 SolidCraft 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): 335 / 541 commands live (62%). SOLID + SKETCH: 188 / 291 (65%). Registered commands: 311.
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: 630 / 1175 (54%).
| Tab | Panel | Live | Total | % |
|---|---|---|---|---|
| SOLID | CREATE | 23 | 35 | 66% |
| SOLID | MODIFY | 19 | 32 | 59% |
| SOLID | ASSEMBLE | 12 | 21 | 57% |
| SOLID | CONFIGURE | 3 | 3 | 100% |
| SOLID | CONSTRUCT | 19 | 20 | 95% |
| 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 | 3 | 3 | 100% |
| 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 | 3 | 3 | 100% |
| SHEET METAL | CONSTRUCT | 19 | 20 | 95% |
| 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 | 3 | 3 | 100% |
| ASSEMBLY | CONSTRUCT | 19 | 20 | 95% |
| 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 | 3 | 3 | 100% |
| SURFACE | CONSTRUCT | 19 | 20 | 95% |
| 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 | 3 | 3 | 100% |
| MESH | CONSTRUCT | 19 | 20 | 95% |
| 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 | 19 | 20 | 95% |
| 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 | 3 | 3 | 100% |
| PLASTIC | CONSTRUCT | 19 | 20 | 95% |
| 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 › CONFIGURE › Create Configuration Rules
- SOLID › CONSTRUCT › Offset Plane
- SOLID › CONSTRUCT › Plane at Angle
- SOLID › CONSTRUCT › Tangent Plane
- SOLID › CONSTRUCT › Midplane
- SOLID › CONSTRUCT › Perpendicular Plane
- 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 › CONFIGURE › Create Configuration Rules
- 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 › CONSTRUCT › User Coordinate System (UCS)
- 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 › 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
SolidCraft reads STEP (ISO 10303-21; AP203, AP214 and AP242 geometry) with its own reader in crates/kernel/src/step_in (written from the public standards; no third-party STEP code). A STEP file becomes an Import base feature on the timeline: it holds the file’s B-rep (the STEP text is kept in the design, so the design reopens without the file) and later features — fillets, holes, cuts sketched on its faces, shells — build on its bodies.
- Open: File → Open (
doc.open) accepts.step/.stpin any case and starts a new design;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). SolidCraft 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 (313 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 | construction plane name or id | {origin, x_dir, y_dir} | {face: [world x,y,z], body?: exact world body, index?: face index, path?: complete occurrence ids, fixed?: bool}; new face attachments require a unique certified source, fixed:true explicitly freezes the selected plane; offset?: expr, project_edges?: bool, name?solid.extrudeExtrude (E)distance: expr (or through_all: true); taper?: angle expr; sketch?: id|name (default: active or last sketch); profiles?: all | [index] | [{curves: [ids]}] (the profile whose outer loop is made of exactly these curves, or pieces of them; found again on every recompute, so it follows parameter changes; islands inside are its holes; an error when no profile or several match) | [{point:[x,y]}] (index and point picks are stored by their curves when that finds the same profile); face?: [x,y,z] (extrude a planar body face instead of a sketch profile); direction?: positive|negative|symmetric; distance2?; start_offset?: expr (along the sketch normal, also with direction: negative); to?: [x,y,z] (To Object: a face, vertex or point; to_offset?: expr past it); from?: [x,y,z] (From Object: start at that face or point); operation?: new|join|cut|intersect|auto (cut into a body, otherwise join existing solids; new in an empty component); targets?: [body]; 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, independent of axis direction); convention?: sketch_normal|legacy_axis_start (default sketch_normal; legacy retains older angular starts); to?: [x,y,z] (To Object: turn until that face, vertex or point); sketch?, profiles? | face: [x,y,z] (a planar body face as the profile), 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] (numbers in mm or length expressions; default: the origin); operation?solid.cylinderCylinderradius | diameter, height: expr; base?: [x,y,z] (centre of the bottom: numbers in mm or length expressions; default: the origin); axis?: X|Y|Z|[x,y,z] (default Z); operation?solid.sphereSphereradius | diameter: expr; center?: [x,y,z] (numbers in mm or length expressions; default: the origin); operation?solid.torusTorusmajor, minor: expr (radii); center?: [x,y,z] (numbers in mm or length expressions; default: the origin); 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|construction plane name or id|{origin, normal}|{origin, x_dir, y_dir} (a construction plane keeps the mirror following it)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] (numbers in mm or length expressions; default: the origin); 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, profiles?; depth; mode?: emboss|deboss; targets?; face?: world point [x,y,z] selecting a planar or complete exterior cylindrical wall (inferred when exactly one supported tangent cylinder matches the selected profiles)joint.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]; mid?: [[position 0…1 from start, radius expr], …] (variable radii between the ends); smooth?: bool (smooth instead of straight between the radii)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: name → recomputed: features rebuilt, restored: features whose earlier result for the same inputs was reused (e.g. a value set back)material.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 thereconstruct.plane.perpendicularPerpendicular Planeplane: plane or planar face ref; line: straight edge, sketch line or axis ref; name? The plane holds the line and is square to the plane
SOLID › ASSEMBLE
component.createNew Componentname?, parent?: component id or name (default: the active one); parent_path?: exact parent placement; 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 Positionoccurrences?: [ids] (default all); drops selected 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 | construction plane name or id | {origin, normal}; offset?: expr along the plane's normal (XY: +Z, XZ: +Y, YZ: +X); at?: expr, the cut's world coordinate on the named plane's axis (XY: Z, XZ: Y, YZ: X), instead of offset; flip?: bool; or clear: true. Returns the cut's origin and normal, and for a named plane its axis and at
SOLID › CONFIGURE
config.configureConfigureparams?: [parameter names]; features?: [feature names]; materials?: [body names]; appearances?: [body names] (columns to add; with no rows yet, a "Default" row takes the current values)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); connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.rectangle.two_point2-Point Rectangle (R)p0, p1: opposite corners [x,y]; construction?. Returns sides {bottom, right, top, left} (line ids) and corners {bottom_left, bottom_right, top_right, top_left} (point ids); connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.rectangle.three_point3-Point Rectanglep0, p1: first edge; p2: point on the opposite edge; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.rectangle.centerCenter Rectanglecenter, corner. Returns sides {bottom, right, top, left} (line ids) and corners {bottom_left, bottom_right, top_right, top_left} (point ids); connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.circle.centerCenter Diameter Circle (C)center: [x,y] or point ref, radius | diameter: number; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.circle.two_point2-Point Circlep0, p1: diameter ends; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.circle.three_point3-Point Circlep0, p1, p2; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.arc.three_point3-Point Arcstart, end, through; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.arc.center_pointCenter Point Arccenter, start, end (counter-clockwise from start) | center, start, sweep: signed degrees; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.polygon.inscribedInscribed Polygoncenter, radius (to vertices), sides, angle?: deg; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.polygon.circumscribedCircumscribed Polygoncenter, radius (to edge midpoints), sides, angle?: deg; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.polygon.edgeEdge Polygonp0, p1: one edge, sides; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.slot.center_to_centerCenter to Center Slotp0, p1: arc centres, width; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.slot.overallOverall Slotp0, p1: overall ends, width; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.pointPointpoint: [x,y], id?; connect?: bool (default true: a new point landing exactly on an existing point, the origin too, is made coincident with it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.dimensionSketch Dimension (D)entities: [refs], type?: auto|distance|horizontal|vertical|length|radius|diameter|angle, value?: number or expression (default: current); text_at?: [x, y]. Two lines: the angle between them (acute, or 180° − it when the value is nearer that) unless text_at picks a sector. Returns measured in unit (mm or deg)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?. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.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). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.circle.two_tangent2-Tangent Circlecurves: [two lines/circles/arcs], radius | diameter: expr, near: [x,y] (which of the solutions). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.circle.three_tangent3-Tangent Circlecurves: [three lines/circles/arcs], near: [x,y] (which of the solutions). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.slot.center_pointCenter Point Slotcenter: slot centre, end: one arc centre, width. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.slot.arc_three_pointThree Point Arc Slotstart, through, end: points on the centre arc, width. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.slot.arc_centerCenter Point Arc Slotcenter, start: centre arc start, end: direction of the centre arc end (counter-clockwise), width. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.ellipseEllipsecenter, major: end of the major axis [x,y], minor: a point the ellipse passes through on the minor side [x,y] | minor_radius. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.spline.fit_pointFit Point Splinepoints: [[x,y] or point refs…] (2…500): the spline passes through them, periodic?: boolean (default false); periodic cubic fit closes smoothly through at least 3 distinct samples. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.spline.control_pointControl Point Splinepoints: control points (2…500), cubic. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.spline.control_point_5Control Point Spline (Degree 5)points: control points (2…500), degree 5. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.conicConic Curvestart, end, apex: [x,y] or point refs; rho?: 0…1 (default 0.5: parabola). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.textTexttext: string (\n breaks lines), at: baseline start [x,y], height?: capital height mm (default 5), angle?: deg. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.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). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.pattern.circularCircular Patternentities: [ids], center: [x,y] or point ref, count: 2…500, angle?: total deg (default 360). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.pattern.rectangularRectangular Patternentities: [ids], dir?: [x,y] (default x), count, spacing; count2?, spacing2? (perpendicular direction). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.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}]. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.intersectIntersectrefs: [{body}, {face: [x,y,z], body?}, {edge: [x,y,z], body?}, {sketch, curve}]: section curves (or points) with the sketch plane, linked. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.include_3dInclude 3D Geometryrefs: like Project (edges, vertices, sketch curves and points); curves out of the sketch plane are flattened onto it. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.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 curves. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.intersection_curveIntersection Curvea, b: {body} | {face: [x,y,z], body?}: where they meet, as linked 3D curves. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.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 plane. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.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 curve. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.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. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)
SKETCH › CONSTRAINTS
sketch.constraint.horizontal_verticalHorizontal/Verticalline: id | points: [a, b]; mode?: horizontal|verticalsketch.constraint.coincidentCoincidenta, b: points, a point and a curve, or two distinct lines (joins their closest endpoints)sketch.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 them. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.filletFilletpoint: corner shared by two curves ("l1.end") | points: [corners] (all rounded alike: one radius, the rest equal) | a, b: two lines or arcs meeting at a corner; radius: expr. A corner that dimensions or constraints measure to stays as a virtual sharp corner on both curves' extensions (`virtual_corners`; the moved ones in `moved_to_virtual_corner`). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.chamfer.equal_distanceEqual Distance Chamferpoint | a, b: a corner of two lines; distance. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.chamfer.distance_angleDistance and Angle Chamferpoint | a, b: a corner of two lines; distance: along a, angle: deg from a. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.chamfer.two_distanceTwo Distance Chamferpoint | a, b: a corner of two lines; distance: along a, distance2: along b. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.offsetOffset (O)curves: [ids], distance: number (positive = left of the chain, or toward `side`), side?: [x,y], chain?: bool (default true: the connected chain). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.trimTrim (T)curve: id, at: [x,y] (the piece to remove: between the nearest intersections around it). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.extendExtendcurve: id, at: [x,y] near the end to extend (to the nearest curve)sketch.breakBreakcurve: id, at: [x,y] (split at intersections) | points: [[x,y]…] (1…1000 positions projected onto the curve; duplicates are merged). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.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). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.insert_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). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)canvas.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.draw.dimensionedDraw Dimensioned Sketch Geometrycommand: sketch creation command, params: object, dimensions: object of role to numeric or expression value; creates geometry and all driving dimensions atomically in one undo step. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.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?: deg. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.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). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.spline3d3D Splinepoints: [[x,y,z]…] world fit points (2 or more): a smooth 3D curve through them (needs 3D Sketch on). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.point3d3D Pointpoint: [x,y,z] world point (needs 3D Sketch on). Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)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.offset.breakBreak Offset Associationcurve: derived offset curve id (retains geometry and makes its points editable)sketch.moveMove/Copy Sketch Entitiesentities: [ids], translate?: [dx,dy], angle?: deg, center?: [x,y], copy?: bool. Returns created: {points, curves, constraints, dimensions}: the ids of what it added (stable and never reused: usable right away in sketch.dimension, sketch.constraint.*, sketch.delete…)sketch.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: as sketch.create; author_path?: exact owner occurrence ids; explicitly rebinds or freezes the plane while preserving geometry and dimension namessketch.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) layerssheet.export_bend_tableExport Bend Tablebody?, format?: csv|json (default csv), path? → bend table in mm and degrees, including rule and flat centre-line coordinates
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); occurrence?, path?: exact placement used to author shared geometrycomponent.listList Componentscomponent.propertiesComponent Physical Propertiescomponent?: id or name (default active); descendants?: bool (default true). Measures the first placement, or all placements for root, including pending moves; material-aware mass and center of mass in world coordinatescomponent.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: independently copy its nested definition graph and history, materials and styles; occurrence?, path?: selected source placement; translate?: [x,y,z]. Reassigned external bodies become base geometry; retained external links and omitted boundary joints are reported
Other: occurrence.*
occurrence.propertiesOccurrence Physical Propertiesoccurrence: id or name; path?: [occurrence ids] (required if nested under multiple instances); descendants?: bool (default true). Physical properties of this placement in world coordinates, including pending movesoccurrence.copyPaste (Instance)component (or occurrence): what to place again; path?: validates selected occurrence; parent?: component (default: the same parent); translate?: [x,y,z]. Copies the selected effective transform, including pending movesoccurrence.moveMove Occurrenceoccurrence; path?: exact placement; space?: local|world (default local); translate?: [x,y,z]; axis?: [x,y,z], angle?: radians or "30 deg", origin?: [x,y,z]; capture?: bool (default true; false keeps it pending until Capture Position). Nested occurrence definitions remain sharedoccurrence.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? | material? | appearance? (target name); delete?: boolconfig.rowConfiguration Rowname; from? (row to copy; default current values); values?: {column key: expression, suppression, material name, appearance name or Look object}; rename?; delete?: boolconfig.activateActivate Configurationrow: name — sets its parameter values and suppressionsconfig.previewPreview Configurationrow: name — validates a rebuild without changing the design or historyconfig.ruleConfiguration Rulerule?: {id,name,enabled,condition,effect}; id? + delete?; id? + index? (ordered move)config.rulesConfiguration Rulesordered typed rules, rows and active state
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)browser.visibilityShow/Hidevisible: bool; items?: [sketch id or name | body name | construction name | O|X|Y|Z|XY|XZ|YZ]; or folder?: bodies|sketches|construction|origin (all of it); or paths?: [[occurrence ids]] (exact placements and descendants), isolate?: bool (requires paths and visible:true). View state: not saved, not an undo step
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)edit.checkpointCheckpoint→ {checkpoint: id}: remember the design, active sketch and undo history (restore with edit.restore_checkpoint)edit.restore_checkpointRestore Checkpointcheckpoint: id; forget?: bool (true = drop it without going back). Goes back to it, undo history included
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, merged into the feature's definition as timeline.inspect returns it, e.g. {"extent": {"distance": "30"}} (extrude), {"angle": "90 deg"} (revolve); expressions are text} → recomputed: features rebuilt, restored: features whose earlier result for the same inputs was reusedtimeline.inspectInspect Featurefeature: id|name → type, definition (the fields timeline.edit's set changes, with their current values), inputs: [{label, param, expression, unit}] (named inputs parameters.change can also set), error, warningtimeline.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