CAD
Parametric CAD in Eustress builds a solid part from an ordered feature tree of sketches and features, evaluated by a pure-Rust B-rep kernel. Change a variable and the part regenerates; export it to GLB with its parameters inside.
01Overview
A CAD Part
A CAD part is an ordinary Part whose shape comes from a feature tree instead of
a primitive mesh. The tree lives in a features.toml file beside
the part's _instance.toml, in the part's own folder under Workspace. The Explorer and Properties show it like any other
Part; only its mesh, collider and Size come from the tree.
Workspace/
CadPlateHole/
_instance.toml # class_name = "Part": position, color, material
features.toml # the feature tree: variables, sketches, featuresWhen a part loads with a features.toml beside it, the engine
attaches the CAD behavior. From then on it compares the file on disk with the tree
in memory every 500 ms, so an edit made in a text editor, restored from git or
written by an agent regenerates the part within half a second.
Feature Tree
Sketches and features in TOML, evaluated top to bottom.
B-rep Kernel
Exact solids; triangles only for display, physics and export.
Mates
Hinges, slides and ball joints that move in Play.
18 Agent Tools
Author, inspect, validate and export over MCP.
The Kernel
The kernel is the eustress-cad crate, built on the truck family
of pure-Rust boundary representation (B-rep) crates. B-rep means the part is held as
exact faces, edges and vertices rather than triangles: a drilled hole is a true
cylinder until the moment it is drawn.
Tessellation keeps the triangles within 1 mm of the true surface by default. A tree
can set its own tolerance in meters with mesh_tolerance under [metadata]; smaller means smoother curves and more triangles.
The collider is chosen from the mesh, in descending order of accuracy:
- Convex hull when the body is convex. A convex body's hull is the body, so this is exact.
- Convex decomposition for everything else, so an L-bracket's notch and a plate's hole stay open.
- Convex hull, approximate when the mesh has more than 20,000 triangles, so a slider drag never stalls on a decomposition.
- Bounding box when the mesh is not closed or no hull can be built.
The part's status line names the collider it received, next to how many features evaluated. It is shown at the top of the Sketch panel.
02Feature Tree
features.toml
A feature tree is a TOML document with a [variables] table, an
ordered list of [[entry]] tables and optional [metadata]. Each entry is either a sketch (kind = sketch) or a feature (kind = feature with an op). The
kernel walks the entries top to bottom, so a feature can only use sketches and
features that come before it.
This is the Hole template that Studio inserts, with the plate's profile shortened:
[variables]
length = "0.1 m"
width = "0.06 m"
height = "0.01 m"
hole_dia = "0.012 m"
hole_depth = "0.015 m"
[[entry]]
name = "BaseSketch"
kind = "sketch"
plane = "xy"
[[entry.entities]]
type = "line"
p1 = [-0.05, -0.03]
p2 = [0.05, -0.03]
# ...three more lines close the rectangle. Horizontal and vertical
# constraints keep it square; linear dimensions tie it to length and width.
[[entry]]
name = "Base"
kind = "feature"
op = "extrude"
sketch = "BaseSketch"
depth = "height"
both_sides = true
[[entry]]
name = "HoleSketch"
kind = "sketch"
plane = "xy"
[[entry.entities]]
type = "point"
p = [0.0, 0.0]
[[entry]]
name = "Hole1"
kind = "feature"
op = "hole"
sketch_point = "HoleSketch/point-0"
diameter = "hole_dia"
depth = "hole_depth"An entry can also be suppressed: the kernel skips it but keeps its body in the file, so it can be switched back on without losing anything.
Sketches
A sketch is a named set of 2D entities, with coordinates in meters: line, rectangle, circle, arc, point and construction lines.
It also holds driving dimensions (linear, radial, angular) and geometric constraints (coincident, concentric, collinear, parallel,
perpendicular, tangent, horizontal, vertical, equal length, equal radius, symmetric
and fix). Dimensions and constraints refer to entities by their position in the list,
starting at 0.
- Profiles: extrude, revolve and sweep take exactly one rectangle, exactly one circle, or a closed loop of lines. Arcs do not form profiles yet.
- Points: a hole drills at
Sketch/point-N, where N counts point entities only, starting at 0. - Dimension-driven outlines: draw them as a loop of lines. A linear dimension on a rectangle drives only its width, which is why every template uses line loops.
- Solving: a damped Gauss-Newton solver runs on every sketch with constraints or dimensions before features use it, for up to 50 iterations. Line ends that start out touching stay welded while the solver moves them.
The kernel builds each profile in the part's XY plane and extrudes along +Z.
The plane a sketch names (xy, xz or yz) is stored in the file but does not
move the profile yet. Eustress is Y-up and CAD parts are inserted unrotated, so
extrusions point along the world Z axis: the Cylinder template lies on its side
until you rotate the part.
Features
Each feature has an op. This is what each one does today:
| op | Status | What it does |
|---|---|---|
extrude | Working | Sweeps a profile along +Z by depth. both_sides or the mid_plane end condition centers it on the sketch; through_all passes through the whole body. |
revolve | Working | Turns a profile about the world x, y or z axis by angle. |
hole | Working | Drills at a sketch point, blind or through, with an optional counterbore. A countersink is cut as a straight 5 mm step. |
pattern | Working | Linear or circular copies of earlier features. count includes the original; with combine = subtract it repeats a cut. |
mirror | Working | Reflects the body, or named features, across the xy, xz or yz plane. |
boolean | Working | Union, difference or intersection of the running body with an earlier feature's result. |
split | Working | Cuts the body with the xy, xz or yz plane and keeps one side. |
sweep | Working | Carries a profile along a path sketch of lines, one straight run per segment, joined into one solid. |
fillet | Approximate | Rounds mesh creases after tessellation; the solid is unchanged. |
chamfer | Approximate | The same crease softening, driven by distance. |
shell | Approximate | Hollows the body with wall_thickness, always open at the top (+Z). |
loft | Not implemented | Refused with an error. |
A feature that asks for something the kernel cannot build fails with an error that names the field, instead of quietly building something else. The full list is under Not Supported Yet.
Combining Bodies
Extrude, revolve, sweep, mirror and pattern take a combine mode that
says how their result meets the running body: new_body replaces it, add (the default) unions with it, subtract cuts it and intersect keeps the overlap. A hole always subtracts.
Every entry reports ok, a message, and a degraded
flag. Degraded means the feature produced a body, but not the one you asked for: when
an add cannot union because the two bodies do not touch, the kernel
keeps the new body and drops the previous one. The status line says DEGRADED and names
the feature. Treat it as an error, because every later feature builds on the wrong body.
The kernel carries a single solid per part. A result made of separate pieces cannot be represented: mirroring a feature to a spot where the copy does not touch the original degrades, and a linear pattern of bosses that do not touch the body fails. Patterned cuts work, because each cut meets the body. Build separate pieces as separate parts.
03Variables
Values Carry Units
Every length and angle in a tree is a string with a unit, such as 50 mm, 0.1 m or 90 deg. The kernel
converts to meters and radians internally and keeps the unit you wrote in the file.
| Kind | Units accepted |
|---|---|
| Length | m, mm, cm, km, in, ft, yd |
| Angle | deg, rad |
| Mass | kg, g, lb |
| Force | N, lbf |
A length written as 10 with no unit is refused, and the error
names the variable and the value it resolved to. There is no safe default
between meters and millimeters, and a guess is a silent factor of 1,000 that
still produces a perfectly valid solid. Angles are the exception: a bare number
is read as degrees.
Expressions
Anywhere a value is expected, you can write a variable name or an arithmetic expression over variables and quantities. The kernel tries a literal first, then a variable, then an expression, so a value that worked before never changes meaning. Variables may refer to other variables, up to 32 levels deep.
[variables]
length = "0.1 m"
wall = "3 mm"
pitch = "length/4" # a length divided by a number: 25 mm
inner = "length - 2 * wall" # 94 mm
rows = "3"
cols = "4"
# elsewhere, on a pattern feature:
# count = "rows * cols" # 12 instances, recomputed on every changeArithmetic follows dimensional analysis, and a mismatch is an error that names both sides:
| Expression | Result |
|---|---|
| length + length, length - length | length |
| length * number, number * length, length / number | length |
| length / length | number |
| length + number | refused |
| length * length | refused (the kernel has no area unit) |
Regeneration
Any change to the tree regenerates the part: the kernel re-evaluates every entry, the mesh and collider are rebuilt, and Size is updated from the new bounds. Three things change a tree:
- Properties: editing Size writes the variables behind each axis (see Editing Dimensions).
- Agents:
cad_set_variableand the other write tools rewritefeatures.toml. - The file itself: any edit on disk is picked up by the 500 ms comparison.
Each Studio edit to a CAD part (inserting it, a Size change, a new constraint, a
solve) is one labeled undo step. Undoing a tree edit writes the previous tree back to features.toml; undoing an insert removes the part and moves its folder
to the Space's trash.
04In Studio
Inserting a Part
The Model tab's Parts group holds seven parametric templates next to the ordinary
inserts. Each one inserts a CAD part 8 m in front of the camera, saves its folder under Workspace immediately, and selects it. The Model tab is part of the
default Engineering mode; Studio covers modes and the
tabs each one shows.
| Button | Template | Default shape | Variables |
|---|---|---|---|
| Plate | plate | 100 x 60 x 10 mm | length, width, height |
| Box | box | 50 mm cube | size |
| Cylinder | cylinder | 40 mm across, 60 mm long | radius, height |
| Hole | plate_hole | The plate with a 12 mm through-hole | plate variables, hole_dia, hole_depth |
| L-Bracket | l_bracket | A 60 x 50 mm L profile, 8 mm thick | thickness |
| Frame | constrained_frame | Four skewed lines the solver squares, 10 mm deep | depth |
| Shell | shelled_box | A 50 mm cube hollowed to a 4 mm wall | size, wall |
The folder is named after the template (CadPlate, CadBox and so on) with a suffix when the name is taken. The same seven
templates are what agents create with cad_create_part.
Editing Dimensions
A CAD part's Size in Properties is its computed extent. Typing a new Size writes the variables that drive each axis, converted from the display unit to meters, and the part regenerates. Which variable an axis drives depends on the names the tree uses:
| Variables in the tree | X | Y | Z |
|---|---|---|---|
length, width, height | length | width | height |
radius, height | diameter | diameter | height |
size | size | size | size |
height, thickness or depth alone | none | none | that variable |
When several axes drive the same variable, the last axis wins: on a Box the Z value
sets size, and on a Cylinder the Y value sets the diameter. A part
with none of these names prints a warning in the Output instead. Variables that do
not map to an axis, such as hole_dia or wall, are
changed in features.toml or with cad_set_variable.
The Sketch Panel
Selecting a CAD part that has a sketch opens the Sketch panel on its first sketch.
Close it with x or Esc; double-click the part to
open it again. The panel shows the part's status line, every entity with its
coordinates in the display unit, and the sketch's constraints.
- Click an entity row to pick it as A; click a second row to pick B.
- Press H or V to make A horizontal or vertical, or ⊥ (perpendicular) or ⊙ (coincident) to relate A and B.
- The constraint is added and the sketch solved at once; the result is written to
features.tomland the part regenerates. - Press Solve Sketch (or Solve on the Model tab) to solve every sketch in the part and write the solved coordinates back to the file.
The solver reports one of four states: under-constrained, fully constrained, over-constrained or failed, with the residual and the remaining degrees of freedom. Insert the Frame template to see it work: its four lines start skewed, and the horizontal and vertical constraints square them.
Exporting GLB
Select a CAD part and press GLB in the Model tab's Parts group.
Studio evaluates the tree and writes <Name>.glb beside features.toml, then copies it into Workspace/Assets/Cad/
with a small TOML file listing its variables. The file holds positions, normals, UVs
and triangle indices in meters, and records how it was made in the node's extras:
{
"eustress": {
"kind": "CadPart",
"generator": "eustress-cad",
"variables": { "height": "0.01 m", "length": "0.1 m", "width": "0.06 m" },
"features": [{ "name": "Extrude1", "ok": true, "message": "ok" }]
}
}The features list has one row per tree entry (the sketch rows are
left out above). A program or model reading the GLB can see the parameters behind
the shape without the tree.
05Assemblies
Mates
A mate joins two parts with an Avian physics joint, so an assembly moves the way the mechanism would. Mates work on any two parts, CAD or not.
| Mate | Avian joint | Behavior | Start it from |
|---|---|---|---|
| Hinge | RevoluteJoint | Rotates about the Y axis | Model tab, Constraints: Hinge |
| Slide | PrismaticJoint | Slides along the X axis | Model tab, Constraints: Slide |
| Ball | SphericalJoint | Rotates freely about the anchors | Model tab, Constraints: Ball |
| Weld | FixedJoint | Locks the two parts together | The Mate choice in the tool options |
| Distance | DistanceJoint | Holds the anchors at their current distance, within 1% | The Mate choice in the tool options |
- Press Hinge, Slide or Ball. The tool options bar shows a Mate choice with all five kinds.
- Click the first part. The point you click becomes its anchor.
- Click the second part. Its anchor is recorded and the mate commits.
Press Esc to cancel before the second click. Creating a mate is an
undo step. The axes are fixed today: Y for Hinge and X for Slide.
Making Them Move
Studio runs physics only in Play, and a joint only moves parts that are not anchored. When Play starts, every unanchored part with a collider becomes a dynamic body. CAD parts are inserted anchored, so clear Anchored on the part that should move, keep its partner anchored, and press Play to see the hinge swing or the slide travel. Stopping Play restores every part to where it was. See Physics for bodies and joints in general.
A mate and its joint are not saved with the Space. After the Space is reopened the parts are still there, but the mates are gone and have to be added again.
06Agents
The cad_ Tools
The MCP server exposes 18 CAD tools. They read and
write features.toml files inside the open Space, and a running engine
picks each change up within half a second. Paths are relative to the Space; a path
containing .. or an absolute path is refused.
| Job | Tools |
|---|---|
| Create and change | cad_create_part, cad_set_variable, cad_add_feature, cad_edit_feature, cad_delete_feature |
| Sketch | cad_create_sketch, cad_add_sketch_entity, cad_add_constraint, cad_dimension, cad_offset_sketch, cad_solve_sketch |
| Inspect | cad_describe_part, cad_validate_part, cad_measure, cad_list_templates |
| Export and share | cad_export_glb, cad_publish_part, cad_list_sources |
The sketch tools return the solver status and remaining degrees of freedom with every
edit, because a constraint's effect is global. cad_offset_sketch makes
a new sketch whose profile is an existing one moved in or out by a distance, which is
how a parametric wall is built: offset the outline inward by -wall,
extrude it, and subtract it. cad_delete_feature refuses to delete an
entry that later entries reference unless asked to force it.
Author, Then Verify
CAD failures are often silent and partial: a boolean can succeed and still leave a hole in the surface. The tools are built for a loop of one edit, then one read. The feature and sketch tools return the re-evaluated state with every edit, including the volume before and after, and flag an edit that changed the file but not the solid.
{ "tool": "cad_create_part", "arguments": { "name": "Bracket", "template": "plate_hole" } }
{ "tool": "cad_add_feature", "arguments": {
"path": "Workspace/Bracket", "op": "pattern", "name": "HoleRow",
"pattern_kind": "linear", "features": ["Hole1"], "count": 3,
"spacing": "20 mm", "direction": [1, 0, 0], "combine": "subtract" } }
{ "tool": "cad_validate_part", "arguments": { "path": "Workspace/Bracket" } }
{ "tool": "cad_measure", "arguments": { "path": "Workspace/Bracket", "density": "7850 kg/m^3" } }
{ "tool": "cad_export_glb", "arguments": { "path": "Workspace/Bracket" } }The pattern repeats the hole's cutter, not the drilled plate: three holes at 20 mm
pitch, the original included. cad_validate_part returns pass or fail
for parses, evaluates, all_features_ok,
no_degraded_features, non_empty_body, watertight, manifold, no_degenerate_triangles and positive_volume. cad_measure returns volume,
surface area, center of mass and bounds, plus mass when given a density, and the exact
minimum distance to a second part with against. cad_export_glb writes export.glb beside the tree unless out names another path.
Shared Parts
A part can be published once and placed many times. cad_publish_part copies a part's tree into the Universe's library at .eustress/assets/cad/<id>/features.toml, refusing a tree that does not
evaluate. cad_create_part with source set to that id
makes a placement: a part whose cad_source attribute points at the
library entry.
- One owner: editing the library file restates every placement. Placements in the open Space follow within half a second; placements in other Spaces read it when their Space opens.
- Edits to a placement are refused: Studio shows a warning instead of writing a tree the next sync would overwrite.
- Ids: one path segment of letters, digits,
_,-and., up to 128 characters.cad_list_sourceslists them with their variables.
07Kernel Limits
Booleans Need Overlap
Every subtract, union, intersect, hole, split and shell goes through the boolean operations of truck-shapeops. They fail when two bodies share a face exactly or do not touch at all, so the kernel pushes hole cuts and through-all cuts past the faces they enter. When you author a cut yourself, make it protrude through the surface rather than stop flush with it.
The boolean library has a scale floor: the same shapes that combine cleanly at unit size fail at centimeter size, where real parts live. The kernel works around it by scaling both bodies toward unit size before each operation, trying six tolerances in turn, and scaling the result back. It also guards each attempt, so a failure inside the library becomes an error on the feature instead of a crash. A test in the crate fails on the day the library fixes the floor, so the workaround can be removed.
Approximate Features
- Fillet and chamfer soften the mesh after tessellation. They apply to every crease in the part at the largest radius any fillet or chamfer in the tree asks for; the edge list is not used to pick edges. The solid is untouched, so
cad_measurereports the volume without them. - Shell is always open at the top: the inner cut exits through +Z, and
open_facesis not read. - Countersink is cut as a straight 5 mm step at
countersink_diameter, not a cone. - Split keeps one side of the plane; the other side is discarded.
Not Supported Yet
Each of these fails with an error that says so, rather than building something else:
| Request | Today |
|---|---|
loft | Refused: needs a multi-profile solver the truck crates do not provide. |
Extrude end conditions to_plane, to_surface, up_to_next | Refused: the feature has no field naming the target. |
A non-zero draft_angle | Refused. |
Patterns of kind path or sketch | Refused. |
Hole tap_class or countersink_angle | Refused. Set diameter to the tap-drill size instead. |
Chamfer distance2 or angle, fillet with propagate_tangent = false | Refused. |
| Sketches on a feature's face | Refused by the sketch tools; evaluation has no face references. |
| Tangent, symmetric constraints | Tangent solves only between a line and a circle; symmetric only keeps a segment's midpoint on its axis. |
| STEP files | Not read or written. The truck STEP crate is a dependency with no caller yet. |
08What's Next
Sketching in Studio
Today the Sketch panel adds constraints to an existing sketch, and new sketches come from templates or from agents. The CAD platform plan's sketch phase will add a Studio canvas where you draw entities, apply constraints and drag them with a live solve, a feature-tree panel for reordering and suppressing entries, and face references so a sketch can sit on the face of an earlier feature. Variables that do not map to Size will be editable directly in Properties.
Deeper Modeling
Next come procedural models whose attributes regenerate their children, with built-in generators for gears, stairs, railings, trusses and pipe runs, and Luau or Rune generator scripts. After that: loft and path sweeps once face and path references land, shells with real offset surfaces, true B-rep fillets and chamfers as the truck crates gain them, STEP export, a bill of materials from the assembly graph, and stress analysis that extends the realism crate's deformation model onto the CAD mesh.
Sketch it, dimension it, change one number.