Files
solvecraft/skills/solidcraft-modeling/reference.md
T
froglemonade2 e063a7778e Axes and points from circular edges and round faces (#3128) (#4610)
* Axes and points from circular edges and round faces

In 3D, a round edge or face stands for its circle wherever a command
takes an axis or a point.

Revolve, Surface Revolve, both Circular Pattern dialogs, Plane at Angle
and Move -> Rotate take a circular edge (through its centre, along its
normal) or a cylinder, cone or torus face (its axis). The features keep
the pick as a model reference, so they follow the model when a parameter
moves it. Straight edges work as before; flat faces are refused.

Every 3D point input takes a circle's centre: construction points (which
follow the circle), To Object, patch points, hole placement, and Move's
pivot and points. Hovering shows the axis as a dashed construction line
or the centre as a star, and the dialog's chip names what the pick
stands for ("centre of edge 10", "axis of face 3").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Format the round-pick hover check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Selection chips: name a round pick as a circle centre or axis

The chip has room for little more than the body and a word or two:
"Shaft · circle centre", "Shaft · circle axis", "Shaft · face 2 axis",
and "Shaft · point" for a point of the model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Move: a circle's centre in a placed component is where the component shows it

Move's pivot and points read a picked circle from the model in its
component's own frame, while the pick is in the world. They now read it
from the world state (and, editing a Move, from the state before it,
placed the same way), as the viewport and the other round picks do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Plane at Angle on a planar face about a model edge: test that it follows both

Upstream #1956 made a planar face a base for Plane at Angle; this branch makes a model edge an
axis. The rebase keeps both in one PlaneRef::AtAngle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:27:32 +00:00

14 KiB

SolidCraft command reference for agents

These are the shapes of the commands a part build needs, so an agent rarely has to call list_commands. The worked example at the end runs in CI (skill_reference_example_builds_as_documented in crates/mcp/src/tests.rs).

  • Lengths are in mm.
  • Lengths take a unit suffix, in parameters and in feature fields: "16 in" is 406.4 mm, and length:"1 in" works in solid.box. measure still reports mm.
  • Angles inside expressions take units: "30 deg".
  • Any numeric field can be an expression that names parameters: "w/2 - wall - 6".

Sketch planes: where sketch (x, y) lands in the world

plane sketch x sketch y normal (+distance)
"XY" +X +Y +Z
"XZ" +X -Z +Y
"YZ" +Y +Z +X
{origin, x_dir, y_dir} x_dir y_dir x_dir x y_dir
  • offset: "expr" on sketch.create moves the plane along its normal and follows parameters.
  • Front wall (-Y) of a part centred on the origin:
    • Sketch on {"plane":"XZ","offset":"-d/2"}.
    • Draw a feature at height z at sketch y = -z.
    • A cut with a positive distance goes into the wall.
  • Top face: sketch on {"plane":"XY","offset":"h"}.
  • Hole direction: solid.hole {sketch} drills into the part from the face nearest the sketch. A hole sketch at z=0, or below the part, drills up into the bottom face. Put the sketch on the face the hole starts from.

Ids

  • Numbering: every creator returns created: {points, curves, constraints, dimensions}. Ids are stable and numbered per sketch.
  • Curves: l1, c1, a1.
  • Points: p1, l1.start, l1.end, c1.center, a1.center, and the sketch origin "origin".
  • Rectangles: sketch.rectangle.* also returns sides {bottom,right,top,left} and corners {bottom_left,...}.
    • rectangle.center adds 2 construction diagonals, so each corner joins 3 lines. Its centre point is p5.
    • sketch.fillet {point} therefore fails on its corners. Use {a:"l1", b:"l4", radius} or the 3D solid.fillet.
  • Slots: sketch.slot.overall and sketch.slot.center_to_center create 2 lines, 2 arcs and 4 points, plus a1.center and a2.center. The ids count on from what the sketch already has: in an empty sketch l1, a1, l2, a2 and p1-p4, after a rectangle.two_point l5, a1, l6, a2 and p5-p8.
    • The length role of slot.overall is the overall length. The arc centres are at ±(length/2 - width/2).
    • After length and width, 3 dof remain. To remove them, dimension origin to a1.center horizontally and vertically, and origin to a2.center vertically. For a slot along Y the solver refuses that last dimension as over-constraining; dimension a2.center horizontally instead.
    • sketch.slot.center_point creates no centre point. Dimension an arc centre instead.
  • Circles: sketch.circle.center creates c1 and c1.center, and no p point.
  • Dimensions share the k counter with constraints.
  • Constraints can add curves: a constraint about an axis, such as sketch.constraint.symmetry with line:"y", adds a construction line and so takes the next l id. Read created before you count further.
  • Over-constraining is refused with that conflicts with the sketch: neither entity can move to satisfy it. For example, symmetry already makes two circles equal.

Dimensions

{"command":"sketch.dimension","params":{"entities":["origin","c1.center"],"type":"horizontal","value":"w/2 - wall - 6"}}
{"command":"sketch.dimension","params":{"entities":["c1"],"type":"diameter","value":"6"}}
{"command":"sketch.dimension","params":{"entities":["l1"],"value":"w"}}
{"command":"sketch.dimension","params":{"entities":["l1","c1"],"value":"2"}}
{"command":"sketch.dimension","params":{"entities":["c1","c2"],"value":"3"}}

A line, a point or another circle with a circle or arc measures to its edge (0 is tangent, or the point on the circle); two circles measure between their edges (the overlap when they cross, the thinnest wall when one is inside the other). Give the centre point (c1.center) for the distance to the centre.

Distances are unsigned. Draw the geometry on the correct side first, and the dimension keeps it there.

sketch.draw.dimensioned creates a shape and its dimensions in one undo step. The dimension roles depend on the creator:

Creator Roles
line (exactly 2 points) length, angle
rectangle.* width, height
circle.* radius or diameter
arc.center_point radius, sweep
slot.center_to_center, slot.overall length, width
polygon.* radius
{"command":"sketch.draw.dimensioned","params":{"command":"sketch.rectangle.center","params":{"center":[0,0],"corner":[35,25]},"dimensions":{"width":"w","height":"d"}}}

Features

Command Minimal params Traps
solid.extrude distance profiles defaults to every region, with a region inside another as a hole in it: two concentric circles give a ring. Pick regions with [{"point":[x,y]}] or [index]. Other options: operation (new/join/cut/intersect/auto), direction (positive/negative/symmetric), through_all: true, start_offset. A cut that misses reports success.
solid.box length, width, height center:[x,y,z] or corner. The quickest way to block out a part. body_name names the body it makes. With operation:"cut", targets:["Top"] cuts only the bodies listed, so the cutter can pass through other bodies.
solid.cylinder radius or diameter, height base is the centre of the bottom (default origin), axis defaults to Z. Expressions work in base. Always pass operation. body_name and targets work as for solid.box.
sketch.slot.overall / sketch.slot.center_to_center p0, p1 (overall ends / arc centres), width Ids and dof are in the "Ids" section above.
solid.fillet / solid.chamfer edges:[[x,y,z]], radius / distance The pick snaps to the nearest edge. A pick farther than 5% of the body's size from every edge is refused, so check that the face count changed. One pick takes the whole tangent chain: a rounded rim needs 1 point, not 8.
solid.shell faces:[[x,y,z]], thickness A point on each face to remove, or its name from body_topology. Fillet first, except an edge of a removed face or a fillet between curved faces (see Order in SKILL.md). Bodies with lofted or swept faces are refused (offset: only bodies with planes, cylinders, cones, spheres and tori): hollow a loft with a smaller loft and operation: cut.
solid.hole position:[x,y,z] on a face (numbers or length expressions such as ["40", "width/2", "t"], which the hole then follows) + diameter, or sketch (+ points) clearance:"M3". depth defaults to through all. type is one of simple/drilled/counterbore/countersink. A countersink needs cs_diameter and cs_angle; a counterbore needs cb_diameter and cb_depth. position is a fixed point; to tie it to the part, add references: [{edge:[x,y,z], distance}] (up to two straight edges of the face): the centre then sits at those distances from the edges and follows them.
solid.pattern.rectangular features, dir1, count1, spacing1 (+ dir2, count2, spacing2) Spacing takes expressions and follows parameters.
solid.pattern.circular features, axis ("Z", {origin, dir}, or {face: [x,y,z]} / {edge: [x,y,z]} on a cylinder wall or circular edge, which the pattern follows), count angle defaults to 360 deg. A full pattern keeps the COM on the axis, so check its radius with a top-view screenshot.
solid.mirror features:[names] or bodies:[names], plane (XY/XZ/YZ, a construction plane, or {origin, normal} whose origin may use length expressions) Follows parameters. A mirror or pattern feature cannot be listed in features of another mirror or pattern, but bodies can mirror the body it made. The mirror is a new body with the first free default name: Body1, Body2 from features, and Side (2), Side (3) from bodies:["Side"]. Read the name from created_bodies. With combine:true, a mirror that overlaps the original joins it and makes no new body. features copies only the features listed, so a later cut is left out unless you list it too. bodies copies the finished body. To name the mirror, use body.rename {"body":"Side (2)","name":"Side_L"}. The new name stays when parameters change.
  • Names: name every feature ("name":"Standoff") and keep names unique across sketches and features. timeline.roll_to {"feature":"Plate"} picks the first match, which can be the sketch.

  • Counts as parameters: parameters.add {"name":"n","expression":"6","unit":""} creates a unitless parameter, and "count":"n" uses it in a pattern.

  • Holes on a parametric bolt circle:

    1. Cut one hole with solid.cylinder {"diameter":6.6,"height":"T + 2","base":["bcd/2",0,-1],"operation":"cut","name":"BoltHole"}.
    2. Repeat it with solid.pattern.circular {"features":["BoltHole"],"axis":"Z","count":"n"}.

    Both the radius and the count follow their parameters.

  • Parameter changes: for one change, call set_parameter {"name":"L","value":"120 mm"}. For several, use one batch of parameters.change steps instead of one set_parameter call each.

  • Primitive booleans: a solid.box or solid.cylinder cut that touches curved faces of the target can fail with the exact fallback ... takes planar faces only. Use a sketch profile and an extrude cut instead, for example a slot sketch rather than a box plus 2 cylinders.

Worked example: parametric plate with 4 counterbored holes (1 batch)

{"commands":[
 {"command":"parameters.add","params":{"name":"L","expression":"80 mm"}},
 {"command":"parameters.add","params":{"name":"W","expression":"50 mm"}},
 {"command":"parameters.add","params":{"name":"T","expression":"6 mm"}},
 {"command":"parameters.add","params":{"name":"inset","expression":"8 mm"}},
 {"command":"sketch.create","params":{"plane":"XY","name":"PlateSketch"}},
 {"command":"sketch.draw.dimensioned","params":{"command":"sketch.rectangle.center","params":{"center":[0,0],"corner":[40,25]},"dimensions":{"width":"L","height":"W"}}},
 {"command":"sketch.finish","params":{}},
 {"command":"solid.extrude","params":{"distance":"T","name":"Plate"}},
 {"command":"solid.fillet","params":{"edges":[[40,25,3],[-40,25,3],[40,-25,3],[-40,-25,3]],"radius":5,"name":"Corners"}},
 {"command":"sketch.create","params":{"plane":"XY","name":"HolePts","offset":"T"}},
 {"command":"sketch.point","params":{"point":[32,17]}},
 {"command":"sketch.dimension","params":{"entities":["origin","p1"],"type":"horizontal","value":"L/2 - inset"}},
 {"command":"sketch.dimension","params":{"entities":["origin","p1"],"type":"vertical","value":"W/2 - inset"}},
 {"command":"sketch.finish","params":{}},
 {"command":"solid.hole","params":{"sketch":"HolePts","diameter":3.4,"type":"counterbore","cb_diameter":6.5,"cb_depth":3.3,"name":"Hole"}},
 {"command":"solid.pattern.rectangular","params":{"features":["Hole"],"dir1":[-1,0,0],"count1":2,"spacing1":"L - 2*inset","dir2":[0,-1,0],"count2":2,"spacing2":"W - 2*inset","name":"Holes"}}
]}

Expected volume:

  • Plate: L*W*T - 4*r²(1-π/4)*T
  • Each hole: π*1.7²*(T-3.3) + π*3.25²*3.3
  • Total: 23335.2 mm³, with 22 faces.

After set_parameter L=120 the volume is 35335.2 mm³, and the holes stay in the corners.

Reading measure

  • Tolerance: analytic and measured volumes agree to about 0.001-0.01%, so treat a gap that small as a match. Long shallow arcs measure worse, about 0.1% (R 1000 mm over 20 degrees).
  • Face counts: compare the logical faces, not the larger kernel.faces.
  • Reply types: set_parameter returns recomputed as a number. A batch reply echoes every step.
  • Boolean warning: a feature warning such as Boolean retry: temporary coincident-face separation up to 2.050000e0 mm per face means the kernel nudged faces that coincide. A join flush on a face, such as a cylinder joined onto the floor of a shelled box, or a tapered cut that starts on a face, does not log it. When the volume still matches your number the warning is harmless. When it does not, the retry may have left the geometry wrong, with no error. A standoff built as a joined cylinder with a cut pilot hole, patterned together, reports tool displacement (moving a tool by about 2e-4 mm) but its volume agrees with the analytic growth; a boss with its hole as one ring profile (two concentric circles, extrude the ring) builds without a warning at every height tried (3 to 20 mm).

Reply cost (0.8, plate example)

Call Size
list_commands {} Over 100 KB. Never call it unfiltered; filter on a distinctive word such as "hole" or "slot".
body_topology 25-45 KB
inspect_design {} 12 KB, and it grows with the sketches
inspect_design {"measure":false,"sketches":false} 4 KB for one part: parameters, timeline errors, bodies. About 65 B more per body (14 KB at 210 bodies)
sketch.inspect 5 KB per small sketch
parameters.list {"filter":"feature"} 3 KB
measure {} about 0.9 KB per body: volume, bbox, COM, face/edge/vertex counts. On a large design, execute inspect.measure {"bodies":[...]} measures only the listed bodies
sketch.solve under 100 B: dof, status, profile count
screenshot {"source":"model","width":480,"height":320} one small image. Without source, a bridged server returns the whole app window and ignores the size.

Errors

Error text Fix
... is not available right now: no sketch is being edited Run sketch.create or sketch.edit {sketch} first.
unknown sketch entity 'p1' Use the ids from created. A circle's centre is c1.center.
the corner must join exactly two lines It is a rectangle.center corner. Use the {a, b} form.
`cs_diameter` must be a number or an expression Countersinks need cs_diameter and cs_angle, even with clearance.
unsupported dimension role for this creator Check the roles table above.
<Feature> cannot be patterned or mirrored yet Pattern the original feature, for example one 2x2 rectangular pattern.
an edge is a point [x,y,z], {point} or {body, index} An edge pick contained an expression. Use plain numbers.
boolean Cut: the operation failed ... planar faces only See "Primitive booleans" above.