Learn/CAD

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.

Time18 min readLevelIntermediateUpdatedUpdated Sep 2026

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.

Space folder
Workspace/
  CadPlateHole/
    _instance.toml   # class_name = "Part": position, color, material
    features.toml    # the feature tree: variables, sketches, features

When 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

Feature Tree

Sketches and features in TOML, evaluated top to bottom.

B-rep

B-rep Kernel

Exact solids; triangles only for display, physics and export.

Mates

Mates

Hinges, slides and ball joints that move in Play.

Agents

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.

features.tomlSolve sketchesEvaluate featuresB-rep solidvariables, entriesexact faces and edgesTessellatewithin 1 mmViewport mesh + colliderGLB with parameters
The tree is the source of truth. Everything visible, from the viewport mesh to the collider and the exported file, is derived from it again on every change.

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:

features.toml
[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.
Pitfall
Every sketch sits in the XY plane

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:

opStatusWhat it does
extrudeWorkingSweeps 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.
revolveWorkingTurns a profile about the world x, y or z axis by angle.
holeWorkingDrills at a sketch point, blind or through, with an optional counterbore. A countersink is cut as a straight 5 mm step.
patternWorkingLinear or circular copies of earlier features. count includes the original; with combine = subtract it repeats a cut.
mirrorWorkingReflects the body, or named features, across the xy, xz or yz plane.
booleanWorkingUnion, difference or intersection of the running body with an earlier feature's result.
splitWorkingCuts the body with the xy, xz or yz plane and keeps one side.
sweepWorkingCarries a profile along a path sketch of lines, one straight run per segment, joined into one solid.
filletApproximateRounds mesh creases after tessellation; the solid is unchanged.
chamferApproximateThe same crease softening, driven by distance.
shellApproximateHollows the body with wall_thickness, always open at the top (+Z).
loftNot implementedRefused 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.

Pitfall
A part is one solid

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.

KindUnits accepted
Lengthm, mm, cm, km, in, ft, yd
Angledeg, rad
Masskg, g, lb
ForceN, lbf
Pitfall
A bare number is not a length

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.

features.toml
[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 change

Arithmetic follows dimensional analysis, and a mismatch is an error that names both sides:

ExpressionResult
length + length, length - lengthlength
length * number, number * length, length / numberlength
length / lengthnumber
length + numberrefused
length * lengthrefused (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_variable and the other write tools rewrite features.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.

ButtonTemplateDefault shapeVariables
Plateplate100 x 60 x 10 mmlength, width, height
Boxbox50 mm cubesize
Cylindercylinder40 mm across, 60 mm longradius, height
Holeplate_holeThe plate with a 12 mm through-holeplate variables, hole_dia, hole_depth
L-Bracketl_bracketA 60 x 50 mm L profile, 8 mm thickthickness
Frameconstrained_frameFour skewed lines the solver squares, 10 mm deepdepth
Shellshelled_boxA 50 mm cube hollowed to a 4 mm wallsize, 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 treeXYZ
length, width, heightlengthwidthheight
radius, heightdiameterdiameterheight
sizesizesizesize
height, thickness or depth alonenonenonethat 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.

  1. Click an entity row to pick it as A; click a second row to pick B.
  2. Press H or V to make A horizontal or vertical, or ⊥ (perpendicular) or ⊙ (coincident) to relate A and B.
  3. The constraint is added and the sketch solved at once; the result is written to features.toml and the part regenerates.
  4. 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:

GLB node extras (JSON)
{
  "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.

MateAvian jointBehaviorStart it from
HingeRevoluteJointRotates about the Y axisModel tab, Constraints: Hinge
SlidePrismaticJointSlides along the X axisModel tab, Constraints: Slide
BallSphericalJointRotates freely about the anchorsModel tab, Constraints: Ball
WeldFixedJointLocks the two parts togetherThe Mate choice in the tool options
DistanceDistanceJointHolds the anchors at their current distance, within 1%The Mate choice in the tool options
  1. Press Hinge, Slide or Ball. The tool options bar shows a Mate choice with all five kinds.
  2. Click the first part. The point you click becomes its anchor.
  3. 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.

Pitfall
Mates last for the session

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.

JobTools
Create and changecad_create_part, cad_set_variable, cad_add_feature, cad_edit_feature, cad_delete_feature
Sketchcad_create_sketch, cad_add_sketch_entity, cad_add_constraint, cad_dimension, cad_offset_sketch, cad_solve_sketch
Inspectcad_describe_part, cad_validate_part, cad_measure, cad_list_templates
Export and sharecad_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.

MCP
{ "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_sources lists 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.

Pitfall
Why booleans are rescaled

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_measure reports the volume without them.
  • Shell is always open at the top: the inner cut exits through +Z, and open_faces is 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:

RequestToday
loftRefused: needs a multi-profile solver the truck crates do not provide.
Extrude end conditions to_plane, to_surface, up_to_nextRefused: the feature has no field naming the target.
A non-zero draft_angleRefused.
Patterns of kind path or sketchRefused.
Hole tap_class or countersink_angleRefused. Set diameter to the tap-drill size instead.
Chamfer distance2 or angle, fillet with propagate_tangent = falseRefused.
Sketches on a feature's faceRefused by the sketch tools; evaluation has no face references.
Tangent, symmetric constraintsTangent solves only between a line and a circle; symmetric only keeps a segment's midpoint on its axis.
STEP filesNot 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.

Loading Eustress Engine...