* 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>
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, andlength:"1 in"works insolid.box.measurestill 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"onsketch.createmoves 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.
- Sketch on
- 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 returnssides {bottom,right,top,left}andcorners {bottom_left,...}.rectangle.centeradds 2 construction diagonals, so each corner joins 3 lines. Its centre point isp5.sketch.fillet {point}therefore fails on its corners. Use{a:"l1", b:"l4", radius}or the 3Dsolid.fillet.
- Slots:
sketch.slot.overallandsketch.slot.center_to_centercreate 2 lines, 2 arcs and 4 points, plusa1.centeranda2.center. The ids count on from what the sketch already has: in an empty sketchl1, a1, l2, a2andp1-p4, after arectangle.two_pointl5, a1, l6, a2andp5-p8.- The
lengthrole ofslot.overallis the overall length. The arc centres are at ±(length/2 - width/2). - After
lengthandwidth, 3 dof remain. To remove them, dimensionorigintoa1.centerhorizontally and vertically, andorigintoa2.centervertically. For a slot along Y the solver refuses that last dimension as over-constraining; dimensiona2.centerhorizontally instead. sketch.slot.center_pointcreates no centre point. Dimension an arc centre instead.
- The
- Circles:
sketch.circle.centercreatesc1andc1.center, and noppoint. - Dimensions share the
kcounter with constraints. - Constraints can add curves: a constraint about an axis, such as
sketch.constraint.symmetrywithline:"y", adds a construction line and so takes the nextlid. Readcreatedbefore 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:
- Cut one hole with
solid.cylinder {"diameter":6.6,"height":"T + 2","base":["bcd/2",0,-1],"operation":"cut","name":"BoltHole"}. - Repeat it with
solid.pattern.circular {"features":["BoltHole"],"axis":"Z","count":"n"}.
Both the radius and the count follow their parameters.
- Cut one hole with
-
Parameter changes: for one change, call
set_parameter {"name":"L","value":"120 mm"}. For several, use onebatchofparameters.changesteps instead of oneset_parametercall each. -
Primitive booleans: a
solid.boxorsolid.cylindercut that touches curved faces of the target can fail withthe 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 largerkernel.faces. - Reply types:
set_parameterreturnsrecomputedas a number. A batch reply echoes every step. - Boolean warning: a feature
warningsuch asBoolean retry: temporary coincident-face separation up to 2.050000e0 mm per facemeans 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, reportstool 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. |