cadforge / server /docs /skill.md
eventhorizon28's picture
Upload folder using huggingface_hub
7c72eb2 verified

CadQuery LLM Skill

This skill helps LLMs write correct, idiomatic CadQuery code. Load this file before any CadQuery task. For deeper reference, read the files listed at the bottom.


Two APIs β€” Know Which One You're Using

CadQuery provides two distinct APIs. Always identify which is appropriate before writing code.

Fluent (Workplane) API

import cadquery as cq
result = cq.Workplane("XY").box(10, 10, 10).faces(">Z").hole(3)
  • Chains operations on a Workplane object with hidden state (current plane, stack)
  • Best for: parts built up from sketches and feature operations (extrude, hole, fillet, shell)
  • Most tutorials and examples use this style

Free Function API

from cadquery.func import *
b = box(10, 10, 10)
result = b - cylinder(1.5, 10)
  • No hidden state β€” all operations are explicit free functions
  • Selectors still work as methods on Shape objects
  • Boolean ops available as operators: + (union), - (cut), * (intersect), / (split)
  • Best for: complex face-by-face construction, lofted/swept surfaces, avoiding booleans via addHole()/replace(), parametric surface mapping
  • More verbose but more explicit and composable

Do not mix the two styles in the same script unless you explicitly convert between them. When the task involves building shapes face-by-face, lofting between non-planar edges, or trimming surfaces in parametric space β€” prefer the Free Function API.


The Core Mindset: BRep, Not CSG

CadQuery uses Boundary Representation (BRep): a model is defined by its surfaces, edges, and vertices β€” not by Boolean operations on primitives.

The CSG reflex (union/cut everything) produces brittle, unidiomatic code. The BRep reflex asks:

  • What faces, edges, or vertices already exist on this shape?
  • Can I select them and build from there?
  • Does a workplane operation (extrude, shell, fillet, chamfer) get me there without a Boolean?

Wrong reflex (CSG):

result = base.union(cq.Workplane().box(10, 10, 5).translate((0, 0, 10)))

Right reflex (BRep):

result = base.faces(">Z").workplane().rect(10, 10).extrude(5)

Only reach for .union(), .cut(), or .intersect() when you genuinely need to combine separate solids.


Workplanes

A workplane is a local 2D coordinate system. Most geometry is created relative to it.

import cadquery as cq

# Start on XY plane at origin
wp = cq.Workplane("XY")

# Build a solid to work from
cube = cq.Workplane("XY").box(10, 10, 10)

# Move to a face
wp2 = cube.faces(">Z").workplane()

# Offset from a face
wp3 = cube.faces(">Z").workplane(offset=5)

# Arbitrary origin and normal β€” must use cq.Plane, not keyword args on Workplane
wp4 = cq.Workplane(cq.Plane(origin=(0, 0, 10), normal=(0, 1, 0)))

# Move with transformation (rotate is degrees around local X, Y, Z)
wp5 = cube.faces(">Z").workplane().transformed(offset=(1, 2, 3), rotate=(0, 0, 45))

Rules:

  • .workplane() resets the 2D origin to the center of the selected face/edge by default.
  • centerOption controls what "center" means β€” the default "ProjectedOrigin" projects the parent workplane origin onto the new face, which is often not the face center. Always set it explicitly when position matters.
  • After .workplane(), coordinates are local to that plane, not global.
  • .transformed() is cumulative within the current workplane context.

Selectors β€” Cheat Sheet

Selectors filter faces, edges, wires, or vertices from the current stack.

Selector Meaning
">Z" Face/edge with highest Z centroid
"<Z" Face/edge with lowest Z centroid
"|X" Faces whose normal is parallel to X axis
"#Z" Faces/edges whose normal or direction is orthogonal to Z
"+Z" Faces whose normal points in +Z direction
">>Z[1]" Second item when sorted ascending by Z (0-indexed)
"<<Z[0]" First item when sorted descending by Z
"%Plane" Faces of type Plane (vs Cylinder, Cone, etc.)
"not >Z" Inverts the selector
">Z and |X" Combines selectors (AND)
">Z or <Z" Combines selectors (OR)

Tag-based selection (preferred for stability):

result = (
    cq.Workplane("XY")
    .box(10, 10, 10)
    .faces(">Z").tag("top")
    .end()
    .faces(tag="top").workplane().hole(3)
)

The code above demonstrates how to use tags, and is not a suggestion of the proper way to use them. In practices you would not call end and then query the tagged face immediately again. Use tags when the geometry might need to be referred to later, and may become hard to access. Tagging can be very useful, but do not force it in to the script unless it seems needed.


Critical Anti-Patterns

1. Boolean when a feature operation suffices

Instead of... Use...
.cut(cylinder) .faces(...).hole(d)
.cut(shell_solid) .shell(thickness)
.union(chamfered_edge) .edges(...).chamfer(d)

2. workplane centerOption behaviors

By default, a workplane() call uses ProjectedOrigin as the default, which uses the current origin and projects it onto the plan defined by the selected faces. This usually works, but can cause unintended results sometimes. If the geometric center of the object is the indended center for the operation, it can sometimes be better to use CenterOfBoundBox, which uses the X, Y and Z centers of the object's bounding box. If the user mentions that the features added on the workplane are not in the expected position, one cause could be this centerOption parameter.

# Explicit is always better
cq.Workplane("XY").box(10, 10, 5).faces(">Z").workplane(centerOption="CenterOfBoundBox")

3. Selector ordering assumptions

Selectors like ">>Z[1]" depend on sort order across all matching entities. Adding fillets or other features changes the entity count and can shift indices. Prefer tags or geometric selectors (">Z") over index-based ones.

4. Unclosed wires

When building profiles with .lineTo(), .spline(), etc., always call .close() before .extrude() or .revolve() unless the wire is intentionally open.

5. Forgetting .end() after tagging or selector context

After .tag() or navigating into a sub-context, use .end() to return to the solid before chaining further operations.

6. BREP_API command not done

This OpenCASCADE kernel error means the requested geometry is invalid. Common causes:

  • Fillet/chamfer radius larger than the shortest adjacent edge, or adjacent fillets overlapping β€” reduce the radius or fillet edge groups separately
  • Self-intersecting profile (wire crosses itself, revolve profile crosses the axis)
  • Sweep profile too large for the path curvature β€” the solid folds back on itself
  • Shell thickness larger than the local radius of curvature

See patterns/anti-patterns.md #14 for detailed fixes.


Free Function API β€” Quick Reference

Import: from cadquery.func import *

Primitives:

box(w, h, d)
cylinder(r, h)
sphere(r)
cone(r1, r2)
plane(w, h)          # flat face
circle(r)            # edge
rect(w, h)           # edge
segment((x1,y1,z1), (x2,y2,z2))

Shape assembly:

wire(e1, e2, ...)         # edges β†’ wire
face(wire)                # closed wire β†’ face
solid(f1, f2, ...)        # faces β†’ solid
shell(f1, f2, ...)        # faces β†’ shell (open solid)
compound(s1, s2, ...)     # shapes β†’ compound

Operations:

extrude(profile, direction_vector)
loft(e1, e2, e3, cap=False)
sweep(profile, path)
revolve(face, axis_point, axis_dir, angle)

Placement (no workplane needed):

shape.moved(x=1, y=2, z=3)           # translate
shape.moved(rx=90, ry=0, rz=45)      # rotate (degrees)
shape.moved((1,0,0), (0,1,0))        # place at multiple locations β†’ compound
shape.move(z=5)                       # in-place variant

Boolean operators:

a + b      # fuse / union
a - b      # cut / difference
a * b      # intersect
a / plane  # split

Adding features without booleans (preferred for performance):

top = solid.faces(">Z")
inner = extrude(circle(r), (0, 0, h))
top_with_hole = top.addHole(inner.edges("<Z"))
result = solid(solid.remove(top).faces(), inner, top_with_hole)

Selectors work the same way β€” they are methods on Shape objects, not tied to either API.


.shell() and Hollow Bodies

Shell operates on the existing solid β€” select the face(s) to open, then shell:

box = cq.Workplane("XY").box(20, 20, 20)
hollow = box.faces(">Z").shell(-2)  # negative = inward

Common mistake: calling .shell() without first selecting which face to remove, resulting in a fully closed thin shell (usually not what you want). Shell can be a little buggy with certain profiles, as can offsetting.


For Deeper Reference

Topic File
BRep vs CSG concepts concepts/brep-mindset.md
Workplane (fluent) API concepts/workplanes.md
Free Function API concepts/free-function-api.md
Full selector reference concepts/selectors.md
Idiomatic patterns patterns/common-patterns.md
Anti-patterns (extended) patterns/anti-patterns.md
Annotated examples examples/
CadQuery API reference docs/

When working on a CadQuery task:

  1. Read this file first.
  2. If the task involves selectors, read concepts/selectors.md.
  3. Grep examples/ for methods you're unsure about: grep -rn "\.shell\(" examples/
  4. Grep docs/ for API signatures: grep -n "def shell" docs/