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

Common Patterns

Idiomatic CadQuery recipes for tasks that come up frequently. Each pattern shows the preferred approach and explains why.


Fluent API Patterns

Base solid with features on multiple faces

Build the base first, then add features face by face. Tag faces before adding features that might change topology.

import cadquery as cq

result = (
    cq.Workplane("XY")
    .box(40, 30, 15)
    .faces(">Z").tag("top")
    .faces("<Z").tag("bottom")
    .end()
    .faces(tag="top").workplane()
    .rect(20, 15).cutBlind(-5)          # pocket on top
    .faces(tag="bottom").workplane()
    .hole(6)                             # through hole from bottom
    .edges("|Z").fillet(2)               # fillet vertical edges last
)

Polar pattern of features

Use .polarArray() to place features at equal angular intervals.

result = (
    cq.Workplane("XY")
    .cylinder(5, 20)
    .faces(">Z").workplane()
    .polarArray(radius=8, startAngle=0, angle=360, count=6)
    .hole(2, depth=8)
)

Rectangular pattern of features

Use .rarray() to place features in a grid.

result = (
    cq.Workplane("XY")
    .box(40, 40, 10)
    .faces(">Z").workplane()
    .rarray(xSpacing=10, ySpacing=10, xCount=3, yCount=3, center=True)
    .hole(3, depth=8)
)

Revolve a profile

Define the profile on a plane that includes the rotation axis, then revolve. The axis is always on the X axis of the workplane by default.

result = (
    cq.Workplane("XZ")
    .moveTo(5, 0)
    .lineTo(5, 10)
    .lineTo(8, 10)
    .lineTo(8, 6)
    .lineTo(5, 6)
    .close()
    .revolve(angleDegrees=360, axisStart=(0, 0, 0), axisEnd=(0, 1, 0))
)

Sweep a profile along a path

Define the path, then the profile, then sweep. The profile is placed perpendicular to the path at its start point.

path = (
    cq.Workplane("XZ")
    .moveTo(0, 0)
    .spline([(10, 5), (20, 0)], includeCurrent=True)
)

result = (
    cq.Workplane("YZ")
    .circle(2)
    .sweep(path)
)

Loft between profiles

Chain profiles on the same Workplane — each sketch pushed onto the stack before .loft() becomes a section. Use .workplane(offset=...) to move between levels.

result = (
    cq.Workplane("XY")
    .rect(20, 10)
    .workplane(offset=15)
    .circle(6)
    .loft()
)

Shell a solid

Select the face(s) to open before calling .shell(). Negative thickness = inward.

# Open-top box
result = cq.Workplane("XY").box(30, 20, 15).faces(">Z").shell(-2)

# Open on two opposite faces
result = cq.Workplane("XY").box(30, 20, 15).faces(">Z").shell(-2)

Reusable sketch profile with .placeSketch()

Define the profile once and place it at multiple locations.

slot = (
    cq.Sketch()
    .slot(8, 3)   # length, width
)

result = (
    cq.Workplane("XY")
    .box(40, 20, 8)
    .faces(">Z").workplane()
    .placeSketch(
        slot.moved(cq.Location((-10, 0, 0))),
        slot.moved(cq.Location((10, 0, 0))),
    )
    .cutBlind(-4)
)

Selecting edges for fillet/chamfer

Fillet or chamfer specific edges by combining type and position selectors.

result = (
    cq.Workplane("XY")
    .box(20, 20, 20)
    .edges(">Z").chamfer(1)          # top face perimeter edges
    .edges("<Z").fillet(2)           # bottom face perimeter edges
    .edges("|Z").fillet(1)           # vertical edges
)

Free Function API Patterns

Loft with curvature-continuous cap

Use loft() for the side and cap() (not fill()) for the top when you need the top to maintain curvature continuity with the side surface.

from cadquery.func import *

r = 5
h = 10

bottom = circle(r)
mid = circle(r * 1.3).moved(z=h * 0.5)
top_edge_guide = circle(r).moved(z=h)

side = loft(bottom, mid, top_edge_guide)
base = fill(side.edges("<Z"))
top = cap(side.edges(">Z"), side)   # curvature-continuous with side

result = solid(side, base, top)

Adding a hole without a boolean

For performance on complex shapes, use addHole() instead of subtracting a cylinder.

from cadquery.func import *

w = 1
r = 0.9*w/2

# box
b = box(w, w, w)
# bottom face
b_bot = b.faces('<Z')
# top faces
b_top = b.faces('>Z')

# inner face
inner = extrude(circle(r), (0,0,w))

# add holes to the bottom and top face
b_bot_hole = b_bot.addHole(inner.edges('<Z'))
b_top_hole = b_top.addHole(inner.edges('>Z'))

# construct the final solid
result = solid(
    b.remove(b_top, b_bot).faces(), #side faces
    b_bot_hole, # bottom with a hole
    inner, # inner cylinder face
    b_top_hole, # top with a hole
)

Pattern with .moved()

Pass multiple location tuples to .moved() to create a compound of copies.

from cadquery.func import *

peg = cylinder(2, 8)

# 4 pegs in a 2x2 grid
result = peg.moved(
    (-5, -5, 0),
    ( 5, -5, 0),
    (-5,  5, 0),
    ( 5,  5, 0),
)

Boolean on 2D shapes

Boolean operators work on faces and wires, not just solids. Useful for constructing profiles before extruding.

from cadquery.func import *

outer = plane(20, 20)
cutout = plane(10, 10)

profile = outer - cutout   # frame-shaped face with hole
result = extrude(profile, (0, 0, 5))   # hollow frame solid

Text along a spine

text() takes positional arguments — keyword args will fail multimethod dispatch. For planar text, pass a line segment as the spine with planar=True. For text projected onto a curved surface, see the full example in the CadQuery docs.

from cadquery.func import *

from math import pi

# parameters
D = 5
H = 2*D
S = H/10
TH = S/10
TXT = "CadQuery"

c = cylinder(D, H).moved(rz=-135)
spine = (c*plane().moved(z=D)).edges().trim(pi/2, pi)

# planar
r1 = text(TXT, 1, spine, planar=True).moved(z=-S)