CadQuery interference check
Purpose
Catch unintended solid overlap and unintended gaps before STEP export. Run after functional geometry (before detailing) and again after DFM and full assembly integration.
Audit script (write once per project)
Create <project>/tools/interference_audit.py:
"""Pairwise interference audit for assembly.py. Run: python tools/interference_audit.py"""
import runpy
import cadquery as cq
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
ns = runpy.run_path(str(ROOT / "assembly.py"))
assy = ns["result"]
def iter_parts(node, parent_loc=cq.Location()):
loc = parent_loc
if getattr(node, "loc", None) is not None:
loc = parent_loc * node.loc
name = getattr(node, "name", None) or "?"
if getattr(node, "obj", None) is not None:
obj = node.obj
shape = obj.val() if hasattr(obj, "val") else obj
yield name, shape.moved(loc)
for child in getattr(node, "children", []) or []:
yield from iter_parts(child, loc)
def collect(assy):
if isinstance(assy, cq.Assembly):
return list(iter_parts(assy))
return [("result", assy.val())]
parts = collect(assy)
VOL_EPS = 1e-1 # mm³ — ignore numerical noise below this
clashes = []
for i, (na, sa) in enumerate(parts):
for nb, sb in parts[i + 1 :]:
try:
inter = sa.intersect(sb)
vol = inter.Volume() if inter is not None else 0.0
except Exception as e:
clashes.append({"a": na, "b": nb, "error": str(e)})
continue
if vol > VOL_EPS:
bb = inter.BoundingBox()
clashes.append({
"a": na, "b": nb,
"volume_mm3": round(vol, 3),
"bbox": (bb.xmin, bb.ymin, bb.zmin, bb.xmax, bb.ymax, bb.zmax),
})
print(f"Pairs checked: {len(parts) * (len(parts) - 1) // 2}")
print(f"Clashes above {VOL_EPS} mm³: {len(clashes)}")
for c in clashes:
print(c)
For local checks during Phase B, temporarily runpy.run_path a scratch script that builds only the new part + neighbor envelopes — same pairwise logic.
Run:
cd <project> && python tools/interference_audit.py
Classify each clash
| Type | Expected? | Action |
|---|---|---|
| Press-fit spigot ↔ socket | Often yes | Verify volume matches comment; else resize spigot in common.py |
| Weld penetration | Small yes | Document |
| Flush housing faces | No | Fix loc= or use constrain().solve() |
| Rib ↔ neighbor wall | No | Trim rib in detailing or relieve wall |
| Fastener shank in clearance hole | No (hole should be open) | DFM clearance diameter |
Never fix clashes by shrinking a functional mate dimension without updating common.py and the partner part.
Gap check (flush mates)
From common.py # DECOMPOSITION, for each flush or butt mate:
- Identify the two faces that should touch (e.g.
lower_housingtop @BASE_TOP_Z,upper_housingbottom). - Compare bounding-box faces or use distance between shapes along the mate normal.
- Gap > 0.05 mm on a flush mate → fix placement in sub-assembly
loc=or constraints.
Document intentional gaps (snap clearance, thermal gap) in common.py as MATE_GAP_*.
Pair pruning (performance)
For assemblies with >30 solids, still check all pairs for export gate; during development, prioritize:
- New part vs each declared mating partner
- Siblings in the same sub-assembly
- Skip pairs > 50 mm apart (bbox center distance) unless mates declare otherwise
Report format
INTERFERENCE AUDIT — <project>
Pairs checked: N
Unintended clashes: <count>
- part_a ↔ part_b: <vol> mm³ bbox (...)
Intentional overlaps: <count>
- part_a ↔ part_b: <vol> mm³ [press-fit per common MATE_*]
Unintended gaps: <count>
- part_a ↔ part_b: <d> mm (expected flush @ Z=...)
Hand back to orchestrator only when unintended clashes = 0 and unintended gaps = 0 for declared flush mates.
Gotchas
- Compose parent × child
locwhen walking nestedcq.Assemblytrees. intersectmay throw on near-tangent contact — record aserrorand inspect in render view.- Duplicate instance names make reports ambiguous — enforce unique
name=inassy.add(). - Envelope-phase overlaps are OK only if documented as intentional interference fits; otherwise fix in
high_level_assembly.pybefore Phase B.
Fix loop
- Identify owning part (usually the later-added feature).
- Edit
parts/<name>.pyin the correct pass (functional vs detailing vs DFM). - Re-exec +
cadquery-render+ re-run audit.