# Anti-Patterns Common mistakes LLMs make when generating CadQuery code, and how to fix them. --- ## 1. The CSG Reflex — Booleans Instead of Feature Operations The most pervasive anti-pattern: reaching for `.union()` / `.cut()` when a direct feature operation would be cleaner, faster, and produce better topology. | Instead of... | Use... | |--------------|--------| | `.union(cylinder(...))` for a boss | `.faces(...).workplane().circle(r).extrude(h)` | | `.cut(cylinder(...))` for a hole | `.faces(...).hole(diameter)` | | `.cut(box(...))` for a pocket | `.faces(...).workplane().rect(w, h).cutBlind(depth)` | | `.union(...)` for a chamfered edge | `.edges(...).chamfer(d)` | | `.union(...)` for a filleted edge | `.edges(...).fillet(r)` | | `.cut(shell_solid)` to hollow | `.faces(...).shell(thickness)` | Booleans rebuild the full boundary from scratch. Feature operations extend or modify the existing BRep directly - they are faster and leave cleaner topology for subsequent selections. **Use booleans only when combining genuinely separate solids** or when the geometry cannot be expressed as a profile operation. --- ## 2. Using `.translate()` on a Workplane Chain `.translate()` is a method on `Shape` objects, not on `Workplane` objects. Calling it in a fluent chain after building geometry moves the shape, but does so in global coordinates — bypassing the workplane system entirely. ```python # Wrong: translate is called on the Workplane, not the Shape result = cq.Workplane("XY").box(10, 10, 10).translate((5, 0, 0)) # If you need to position geometry, use workplane placement instead result = cq.Workplane("XY").center(5, 0).box(10, 10, 10) # Or move the workplane origin explicitly result = cq.Workplane(cq.Plane(origin=(5, 0, 0))).box(10, 10, 10) # .translate() is valid when called on a Shape extracted from the chain shape = cq.Workplane("XY").box(10, 10, 10).val() moved = shape.translate(cq.Vector(5, 0, 0)) ``` --- ## 3. Building in Global Coordinates Instead of Using Workplanes Hardcoding global coordinates creates scripts that are brittle and hard to modify. Use workplanes and relative positioning instead. ```python # Wrong: all positions hardcoded in global space result = ( cq.Workplane("XY") .box(30, 30, 10) .union(cq.Workplane("XY").cylinder(5, 8).translate((0, 0, 9))) .union(cq.Workplane("XY").cylinder(5, 8).translate((10, 10, 9))) ) # Right: build relative to existing faces result = ( cq.Workplane("XY") .box(30, 30, 10) .faces(">Z").workplane() .circle(5).extrude(8) # center boss .faces(">Z").workplane() .center(10, 10).circle(5).extrude(8) # offset boss ) ``` --- ## 4. Forgetting `combine=False` on `.extrude()` By default, `.extrude()` unions the new solid with whatever is on the context stack. If you want a separate solid (e.g., to combine later or export independently), pass `combine=False`. ```python # Default: extrusion is unioned into the existing solid result = solid.faces(">Z").workplane().circle(3).extrude(5) # Separate solid new_part = cq.Workplane("XY").circle(3).extrude(5, combine=False) ``` The inverse mistake also occurs: forgetting that `combine=True` is the default, and then trying to union the result again — producing a double union. --- ## 5. Misreading `.cutBlind()` Direction `.cutBlind(depth)` cuts in the direction of the workplane normal. The normal points **outward** from the selected face by default, so cutting from the top face without inverting will cut away from the solid. ```python # Wrong: cuts upward, away from the solid solid.faces(">Z").workplane().rect(5, 5).cutBlind(3) # Correct: invert=True flips the normal into the solid solid.faces(">Z").workplane(invert=True).rect(5, 5).cutBlind(3) ``` Also applies to `.extrude(..., combine="cut")`. --- ## 6. `.val()` on a Multi-Item Stack `.val()` returns the **first** item on the stack. It does not error if there are multiple items - it silently discards the rest. Use `.vals()` when you need all items. ```python solid = cq.Workplane("XY").box(10, 10, 10) # Returns one Face - the first face in iteration order, not necessarily ">Z" face = solid.faces().val() # Returns all 6 faces as a list faces = solid.faces().vals() # To get a specific face as a Shape, select first top_face = solid.faces(">Z").val() ``` --- ## 7. Misusing `.each()` `.each(callback)` calls `callback` on every item in the stack and **replaces the stack** with the results. The callback must return a `Shape`. It is not for iteration with side effects. ```python # Wrong: callback doesn't return a Shape; result is undefined solid.edges().each(lambda e: print(e.Length())) # Wrong: trying to accumulate results results = [] solid.faces().each(lambda f: results.append(f)) # each() ignores the return value None # Right: use .each() to transform stack items # e.g., get the center point of each face as a Vertex centers = solid.faces().each(lambda f: f.Center()) # For iteration/inspection, use .vals() instead for face in solid.faces().vals(): print(face.Area()) ``` --- ## 8. Losing the Solid Context After a Selector After `.faces(...)` or `.edges(...)`, the stack contains only the selected entities — not the full solid. Calling `.workplane()` on a face selection is correct; calling feature operations directly on the selection without a workplane can silently use the wrong context. ```python # Wrong: after .faces(), the solid is not the active context solid.faces(">Z").box(5, 5, 3) # adds a new box to the face selection context, not the solid # Correct: use .workplane() to re-establish context for sketching solid.faces(">Z").workplane().rect(5, 5).extrude(3) # Correct: use .end() to return to the solid context solid.faces(">Z").tag("top").end().faces("Z").workplane().circle(3).extrude(5) # Explicit and predictable solid.faces(">Z").workplane(centerOption="CenterOfBoundBox").circle(3).extrude(5) ``` --- ## 10. Index Selector Fragility Selectors like `">>Z[1]"` depend on a sorted count of all matching entities. Adding fillets, chamfers, holes, or shells changes the entity count and can silently shift which entity is selected. ```python # Fragile: index may shift after any feature is added solid.faces(">>Z[1]").workplane().hole(3) # Robust: tag the face before adding features solid = ( cq.Workplane("XY") .box(20, 20, 20) .faces(">>Z[1]").tag("target") .end() .edges(">Z").fillet(1) # this would shift the index above .faces(tag="target").workplane().hole(3) ) ``` --- ## 11. Mixing Fluent and Free Function APIs Unintentionally The two APIs are not interchangeable mid-script. A `Workplane` object is not a `Shape`, and Free Function operations do not accept `Workplane` objects. ```python from cadquery.func import * import cadquery as cq # Wrong: passing a Workplane to a free function b = box(10, 10, 10) wp = cq.Workplane("XY").box(5, 5, 5) result = b + wp # TypeError — wp is not a Shape # Correct: extract the Shape from the Workplane first wp_shape = cq.Workplane("XY").box(5, 5, 5).val() result = b + wp_shape ``` When mixing is intentional, always extract with `.val()` or `.vals()` before passing to Free Function API operations. --- ## 12. Unclosed Wires When building a profile with `.lineTo()`, `.spline()`, `.threePointArc()`, etc., the wire must be closed before `.extrude()` or `.revolve()`. An unclosed wire produces an error or unexpected open shell. ```python # Wrong: missing .close() result = ( cq.Workplane("XY") .moveTo(0, 0) .lineTo(10, 0) .lineTo(10, 5) .lineTo(0, 5) .extrude(3) # error - wire is not closed ) # Correct result = ( cq.Workplane("XY") .moveTo(0, 0) .lineTo(10, 0) .lineTo(10, 5) .lineTo(0, 5) .close() .extrude(3) ) ``` --- ## 13. Free Function API: Boolean in a Loop Boolean operations in the Free Function API (and the fluent API) are expensive because they recompute the full boundary. Never perform them in a loop. ```python from cadquery.func import * holes = [cylinder(0.5, 5).moved(x=i*2) for i in range(10)] # Wrong: 10 sequential boolean operations result = box(20, 5, 5) for h in holes: result = result - h # Correct: combine into a compound first, then subtract once result = box(20, 5, 5) - compound(holes) ``` --- ## 14. `BREP_API command not done` Errors This error comes from the OpenCASCADE kernel and means a BRep operation could not be completed geometrically. It is not a CadQuery bug — it means the requested geometry is invalid or degenerate. The error message does not identify which operation failed. **Common causes:** ### Fillet or chamfer radius too large A fillet fails when its radius is larger than the shortest adjacent edge, or when adjacent fillets overlap each other. ```python # Fails if the box edge is shorter than radius=5, or if adjacent fillets intersect solid.edges("|Z").fillet(5) # BREP_API command not done ``` Fixes: - Reduce the radius - Fillet edges in separate calls, smallest features first - Fillet different edge groups separately rather than all at once ```python # Apply smaller fillets to shorter edges first result = ( cq.Workplane("XY") .box(20, 20, 5) .edges("|Z").fillet(1) # vertical edges first .edges(">Z").fillet(0.5) # then top perimeter with smaller radius ) ``` ### Self-intersecting profiles A profile (wire or sketch) that crosses itself cannot be extruded, revolved, or swept. This includes profiles where an arc or spline curves back through the outline. Check that: - All `lineTo()` / `spline()` sequences form a simple (non-self-intersecting) closed loop - Revolve profiles do not cross the axis of revolution - Sweep profiles are small enough to navigate tight path curvature without self-intersection ### Self-intersecting sweep A sweep fails when the profile is too large relative to the path curvature — the extruded solid folds back on itself at tight bends. Reduce the profile size or increase the path radius. ### Shell thickness too large `.shell()` fails when the thickness is larger than the smallest local radius of curvature — the offset surface self-intersects. Reduce the shell thickness or simplify the geometry before shelling.