Continuous Integration for Mechanical Design: A Pytest-Style Workflow for STEP Assemblies
Software has had continuous integration for a quarter of a century. Every pull request runs a unit-test suite, a linter, a type checker, and a coverage gate before a human is allowed to look at it. Mechanical design has the engineer's eyeballs and an expensive prototype.
CADCLAW is an open-source framework that qualified teams can evaluate as a repeatable check layer for authored STEP assemblies. When configured in CI, it emits structured findings and a pass/warn/fail exit code. It is not a CAD authoring tool, a security sandbox, or an engineering certification system.
This piece explains the check families, provides a pytest-style example, shows an illustrative GitHub Actions configuration, and summarizes bounded M3-CRETE development observations. A detected geometric condition is a review input: it does not by itself prove that a fabrication failure, cost, delay, or safety event would have occurred.
1. The CAD-test gap
A modern Python codebase ships with pytest, mypy, ruff, branch protection, and a dashboard that turns red the moment any of them complains. None of that exists for CAD. The closest most teams come to "CAD CI" is a senior engineer rotating the assembly model in Fusion before sign-off, looking for the obvious clips. This works for a small bracket. It does not scale to a large gantry where a sub-millimetre clip on a back-side rail is invisible from any single camera angle.
The bugs this gap ships to fabrication fall into a small set of categories:
- Interference. Two solids overlap in space. CNC the parts, try to bolt them together, discover a small clip that nobody's eye caught because it was on the back side of the rail.
- Adjacency drift. A motor is positioned far from the bracket that's supposed to hold it. The script that placed it had a stale offset constant.
- Dimensional bugs. A motor-mount plate exports thicker than designed because someone swapped the
box(...)arguments. The drawing says one thing. The STEP says another. The drawing wins on the bench; the STEP wins in fabrication. - Tolerance stack-up. A chain of dimensions are each within their per-feature spec. The accumulated stack at the end of the chain blows the assembly's design budget. Nothing failed individually. The assembly fails as a whole.
- Kinematic locks. A linear axis has a degree of freedom on paper, but the bracket geometry pre-loads the carriage at full extension. The motor stalls at one end of travel.
- Disassembly impossibility. The serviceable part you carefully designed is sealed inside the frame because you put the access panel on the wrong face. Discovered on the first warranty call.
These conditions can occur in non-trivial mechanical-design processes. Software CI demonstrates the value of encoding repeatable assertions; STEP-oriented checks can apply the same operating pattern to a limited, declared geometric and rules-based surface.
CADCLAW is what that runtime looks like.
A small note on the analogy. pytest assertions are discrete, while CAD geometry and tolerances are continuous and model-dependent. CADCLAW findings carry severity (PASS, WARN, FAIL) and structured evidence such as bounding boxes, overlap volumes, and distances against project-defined rules. A team may turn those findings into a merge gate, but the result covers only the configured checks and the supplied export.
2. What CADCLAW is
CADCLAW is an open-source Python framework that assembles STEP CAD from authored parts and runs validation gates against the result. It compiles a declarative assembly spec into a STEP file, placing parts by connector frames and datum chains rather than hand-typed coordinates, then checks what it built. It is MIT-licensed, distributed on PyPI as cadclaw, archived on Zenodo with DOI 10.5281/zenodo.19647390, and authored by Sunnyday Technologies. It is software for makers: a tool to enable mechanical engineers to make machines so they can make stuff. The audience is anyone driving hardware from a CAD repository, from aerospace bracketry to robotics chassis to custom industrial fixtures, optical mounts, surgical instruments, and prosumer 3D printers. M3-CRETE is the project the framework was first deployed against, and serves as a published case study; it is not the boundary of CADCLAW's scope.
Architecturally, CADCLAW has three layers:
cadclawis a Python package containing the assembly compiler (cadclaw.assembly_spec,cadclaw.assembly_compiler,cadclaw.connector_metadata,cadclaw.component_manifest) and gate implementations includingcadclaw.inventory,cadclaw.interference,cadclaw.adjacency,cadclaw.dimensional,cadclaw.pmi,cadclaw.kinematics,cadclaw.tolerance,cadclaw.disassembly,cadclaw.parity, andcadclaw.bom_audit. Modules are independently importable and emit structured findings with severity and evidence.cadclawCLI is a console-script entry point that drives the harness from a declarativecadclaw.yamlrule file. Subcommands includecadclaw doctor,cadclaw assemble(validate-spec,build,check-round,inspect-component,render-views,render-sequence),cadclaw harness,cadclaw bom-audit,cadclaw inspect,cadclaw publish-audit, andcadclaw claim-audit. Exit codes are pytest-style:0pass,1fail,2warn-only,3internal error.cadclaw_mcpis an MCP (Model Context Protocol) server that exposes CADCLAW's assembly tools, core checks, and audit helpers as tools an AI coding assistant can call directly, returning rendered review views as inline images so the assistant can see each assembly round. We come back to this in section 6.
The framework is honest about its scope. CADCLAW reads geometry; it does not certify designs. It does not substitute for FEA, fatigue analysis, or physical testing. Every report ships with a confidence budget that lists what was checked, what was not, and what assumptions were baked in (mm units, rigid bodies, STEP exports faithful to the native CAD model). The README explicitly disclaims structural certification, hidden-suppressed-part detection in the native CAD package, vendor-stock validation, and physical-build conformance. CADCLAW does the geometric checks. The engineer is still the engineer.
When a repository owner configures the workflow and required check, CADCLAW can run the declared checks without an interactive operator and emit a structured report. Human engineering review remains required for rule selection, exceptions, design decisions, and physical validation.
3. The five gate families
CADCLAW ships a set of gates that cluster into five families that map cleanly onto the CAD-bug taxonomy from section 1. Each gate is implemented as a module under cadclaw/, evaluates its declared file- or part-level inputs, and emits structured findings that the harness can aggregate into a report.
Dimensional
The dimensional gate catches the "the drawing says one thing, the STEP says another" class of bug. Implementation lives in cadclaw/dimensional.py. The user defines DimRule instances against bbox-signature labels:
from cadclaw.dimensional import DimRule
DimRule(label='ymount', thin_axis=4.0, thin_tol=0.5)
DimensionalCheck.run() walks every part with that label, sorts the bounding-box dimensions, and asserts the smallest axis is within tolerance of the declared thin_axis. A violation reports the actual dimensions and a human-readable message. This is the gate that catches a swapped-argument box(80, 90, 4) versus box(80, 4, 90) regression because the sorted bbox tuple changes when the thin axis is wrong.
Structural
The structural gate sizes the static load budget: beam sag, motor torque margin, and belt tension. It does not sweep range of motion or check clearance through travel. Implementation lives in cadclaw/kinematics.py. Three computations are exposed: beam_deflection() (Euler-Bernoulli simply-supported beam with point load and distributed self-weight), motor_torque_budget() (acceleration plus friction plus gravity force budget against motor holding torque, with belt efficiency and torque derating), and belt_tension() (per-belt tension against breaking and working limits).
Worked example: an X-axis carrying a moderate payload on a long extrusion, driven by a NEMA23 through a small pulley. motor_torque_budget(...) returns a MotorResult with a safety_factor field. If safety drops below the project's threshold, the gate fails. This is the bug that ships when an engineer swaps to a heavier toolhead and forgets to re-check the motor budget; in fabrication it manifests as missed steps under acceleration.
Tolerance
CADCLAW's cadclaw/tolerance.py implements a focused tolerance-chain analysis in Python. A ToleranceChain accumulates Dimension objects with nominal, plus, minus, distribution, and direction fields, then analyze() reports worst-case, RSS, and Monte Carlo results. It also reports a calculated Cpk value and per-dimension variance contribution. These outputs depend on the user's model and distribution assumptions and do not replace a qualified tolerance analysis or process study.
A real failure mode this catches: motor-shaft alignment. A chain of beam length, shim, plate, and motor offset, each with its own tolerance, can pass RSS while failing worst-case. The report tells you which dimension contributes the most variance. That is a tolerance-budget conversation grounded in numbers.
Adjacency
The adjacency gate (cadclaw/adjacency.py) catches the "motor far from its mount" class of bug: parts that should be near each other but aren't. The user declares AdjacencyRule(source='motor', target='bracket', max_distance=...). The AdjacencyCheck.run() method groups parts by label, finds the nearest target for every source, and emits an AdjacencyViolation if the distance exceeds the threshold. Floating parts (no target of the right type anywhere) report nearest_distance=inf.
If fasteners, interfaces, and labels are explicitly represented, an adjacency rule can flag the absence of an expected nearby labeled part. It can also flag unintended scattering, such as a pulley beyond the declared distance from any motor. It does not infer an unmodeled fastener, hole function, or design intent.
Interference and disassembly
Two gates share this slot because they are the geometric-truth checks. cadclaw/interference.py does pairwise BRep boolean intersection (BRepAlgoAPI_Common) with a bbox pre-filter; reported overlaps are real volumes, not bbox approximations. Every reported Clip carries a suggest_axis, suggest_shift_mm, and clearance_mm field, so the report reads plate at (...) clips cbeam by (volume), shift +Y by (distance) to clear with running clearance instead of leaving the engineer to derive the fix vector by hand.
cadclaw/disassembly.py provides a removal-order heuristic. DisassemblySequence.auto_sequence() orders parts by type priority and distance from the assembly centroid, then computes a candidate per-part removal axis. export_frames() writes individual STEP files for review. Failure to find a path is a serviceability-review finding, not proof that disassembly is impossible; a found path is likewise not a safety or serviceability certification.
4. A worked example
Let's write a CADCLAW test for an imagined gantry_corner sub-assembly: one C-beam at a fixed length, two NEMA23 motors, one motor-mount plate, one belt. We want to assert: parts are present, nothing clips, motors have a bracket nearby, and the mount plate is the right thickness.
from cadclaw.harness import Harness
from cadclaw.adjacency import AdjacencyRule
from cadclaw.dimensional import DimRule
def test_gantry_corner():
h = Harness("CAD/gantry_corner.step")
h.add_inventory(
labels={
(40.0, 80.0, 1000.0): 'cbeam',
(56.4, 56.4, 76.6): 'motor',
(4.0, 80.0, 90.0): 'mount',
},
expected={'cbeam': 1, 'motor': 2, 'mount': 1, 'belt': 1},
)
h.add_interference(skip_labels={'belt'}, min_clearance_mm=1.0)
h.add_adjacency(rules=[
AdjacencyRule(source='motor', target='mount', max_distance=80),
])
h.add_dimensional(rules=[
DimRule(label='mount', thin_axis=4.0, thin_tol=0.3),
])
report = h.run()
assert report.passed, str(report)
Drop that in tests/test_gantry.py, run pytest tests/test_gantry.py, and you have continuous integration for the gantry-corner assembly.
When it fails, the report is structured. Suppose the engineer authored mount with the wrong thickness and re-exported the STEP. The output is:
CAD HARNESS REPORT - FAILED
[PASS] inventory
[PASS] interference
[PASS] adjacency
[FAIL] dimensional
thin axis 5.0mm, expected 4 +/- 0.3mm
If the engineer instead shifted the mount plate so it clips a beam, the interference gate fires:
[FAIL] interference
mount at (1495, 540, 366) clips cbeam
shift +Y to clear with running clearance
The fix vector is in the report. The engineer applies the shift in Fusion, re-exports, re-runs. The cycle is the same one a software engineer runs against a unit-test failure: read the assertion, fix the cause, rerun until green. The difference is that the assertion is over geometry, and the cost of skipping it is a fabricated part rather than a runtime exception.
This is the workflow described under "place authored parts; do not generate them" in CADCLAW's AGENTS.md. The engineer authors geometry in their CAD package of choice (Fusion, Rhino, FreeCAD, SolidWorks); a CadQuery script places copies and emits a unified STEP; CADCLAW asserts against the unified STEP. CADCLAW does not draw parts. It checks them.
5. CADCLAW in CI (GitHub Actions)
CADCLAW is pip install cadclaw and a single console script. That makes the GitHub Actions configuration boring, which is the point.
# .github/workflows/cad-check.yml
name: CAD assembly validation
on:
pull_request:
paths:
- 'CAD/**.step'
- 'CAD/**.py'
- 'cadclaw.yaml'
- 'bom/data.json'
push:
branches: [main]
jobs:
cadclaw:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
lfs: true # STEP files are usually in LFS
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install CADCLAW
run: |
python -m pip install --upgrade pip
python -m pip install cadclaw
- name: Verify environment
run: cadclaw doctor
- name: Run validation harness
run: cadclaw harness --rules cadclaw.yaml --report-format md -o cadclaw-report.md
- name: BOM-vs-CAD audit
run: cadclaw bom-audit --rules cadclaw.yaml
- name: Upload report artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: cadclaw-report
path: cadclaw-report.md
A few notes on this workflow. The paths: filter ensures the harness only runs on PRs that actually touch geometry, BOM, or rules, not on a README typo. cadclaw doctor runs first because in our experience the most common CI failure is a CadQuery / OCC version skew, and doctor reports it in plain English instead of an opaque BRep traceback. The --report-format md flag emits a Markdown report you can post as a PR comment via a follow-up step (omitted here for brevity). The if: always() on the artifact upload preserves the report on failure, which is exactly when you want it.
Exit codes follow pytest: 0 is green, 1 is a fail, 2 is warn-only (the harness ran; nothing blocked but WARN findings exist), 3 is an internal error. Branch protection on main should require the cadclaw job to pass; that gives mechanical engineering the same merge gate that software engineering has had for decades.
This workflow is an illustrative configuration. A repository gains an actual merge gate only after its owner adds the workflow, configures applicable rules, enables branch protection, and requires the job. This article does not claim that the current M3-CRETE repository blocks every CAD pull request with CADCLAW.
6. The MCP server
The fastest-growing class of CAD-editing collaborator in 2026 is not a human. AI coding assistants (Claude, OpenAI's Operator, Google's coding agents, and the broader cohort that has emerged since 2024) are now routinely editing CadQuery scripts, build123d files, and assembly drivers. Without a check layer, that workflow is dangerous. Field testing on real hardware projects has documented sessions where parametric plate generation produced motor-mount hole patterns that were uniformly misaligned with the assembly; rounds of generate-critique-strip later, the shipping code was less code than the start. That experience is the reason AGENTS.md exists in the CADCLAW repository.
The MCP server is the integration layer that makes the AI-assisted workflow safer. CADCLAW ships cadclaw_mcp, a Model Context Protocol server that exposes core validation, analysis, and audit checks as callable tools. An assistant connected over MCP can issue:
load_assembly(path="CAD/your_assembly.step")check_inventory(expected={...})check_interference(skip_labels=["belt", "wheel"], min_clearance_mm=1.0)check_adjacency(rules=[...])check_dimensions(rules=[...])compute_deflection(span_m=..., point_load_kg=..., I_m4=..., beam_kg_per_m=...)compute_motor_budget(...)
Configuration is a few lines in the host's MCP config. For Claude Code:
{
"mcpServers": {
"cadclaw": {
"command": "python",
"args": ["-m", "cadclaw_mcp"],
"cwd": "/path/to/CADCLAW"
}
}
}
The protocol is open. Any MCP-compatible host (Claude Desktop, Claude Code, Cursor, and a growing list of others) can drive the harness without code generation.
The local MCP server exposes 24 declared CADCLAW assembly, check, analysis, audit, and render tools. The stateless run_harness tool uses the same versioned configured-harness entry point as the CLI and Python library. It does not provide native-CAD application control, but it is not a security sandbox: path-taking tools can read specified files, six assemble_* tools can write configured outputs, and the server inherits the local process account's filesystem permissions. Run it in a least-privilege working copy, restrict host approvals, and review tool inputs and outputs.
One approval-gated workflow is: a human supplies the task and authored parts; an assistant proposes edits to an assembly-placement spec; CADCLAW regenerates configured outputs and reports declared findings; a qualified human reviews the evidence and decides whether to accept another iteration. Passing checks means only that the supplied artifact passed the configured gates. It does not establish native-model parity, manufacturability, safety, or physical performance.
7. Case study: validating M3-CRETE
CADCLAW was developed alongside M3-CRETE, Sunnyday Technologies' open-source concrete 3D printer, and that project is the published deployment we have permission to cite. M3-CRETE is exactly the assembly density at which manual visual review breaks down: a pallet-scale gantry with NEMA23 motors on every motion axis, V-wheels on both faces of the X-rail, anti-racking belt drives on Y, self-tramming belt drives on Z, and a forest of mounts, brackets, and shims. The pattern generalises to any mechanical-hardware project with a non-trivial part count and a CAD-driven manufacturing pipeline.
A version-specific CADCLAW development exercise used M3-CRETE artifacts rather than only fixtures. The currently published package version is 0.10.0. The observations below are historical and method-specific; they do not demonstrate current M3-CRETE production deployment, physical validation, or universal behavior across assemblies.
Verified working (the regression-test surface):
- Tests exercised configured BOM publication-boundary rules and redaction behavior. This supports the tested paths and fixtures only; it is not a guarantee that every current or future output path removes all sensitive data.
cadclaw doctorcorrectly resolved a venv with apyvenv.cfgpointing at a deleted Python on a different machine, then ran clean against the freshly built venv.- The
init_rules.pyscaffolder produced a small number of confident matches and a larger number of commented-out unknowns from a real-world BOM, defaulting on the side of asking the user to inspect rather than over-promising. - The MCP server started and registered its available CADCLAW tools.
Historical interference observation. A retained development report describes the configured interference gate flagging an overlap between an X-carriage gantry plate and rear rail, with overlap geometry and a candidate shift direction. After the assembly artifact changed, the same configured condition no longer failed. This supports the gate's behavior in that versioned exercise; it does not establish what would have happened in fabrication or quantify avoided cost, labor, delay, or risk.
Other development notes motivated checks for rail, mount, connector, carriage, export-parity, placement, and BOM-drift conditions. Treat those notes as inputs to test design—not as claims that CADCLAW caught every regression, ran on every commit, or prevented a shipped defect. Current repository tests, versioned reports, and each project's configured CI are the controlling evidence.
False-positives the field test surfaced (and the next-version backlog). The v0.6 field test caught its own bugs, too, and that is the right kind of honesty for a CI tool. forbidden_terms substring matching flagged the BOM's intentional anti-substitution warnings ("do not substitute the belt used on Y/Z here") because the dumb match treated negation and assertion as equivalent. claim_audit.stale_terms flagged the README's CC BY-SA 4.0 attribution to OpenBuilds, deletion of which would itself be a license violation. The default forbidden_absolute "validated" flagged a sentence that described a third-party service's training data, not an M3-CRETE claim. cad.count_mismatch reported per-rule against the same label, producing redundant findings rather than one aggregated diff. Each of these shipped in v0.7 (negation-aware matching, attribution-block exemption, configurable absolute terms, and rule aggregation).
A new finding surfaced during cleanup that the field test had not anticipated: the BOM legitimately specifies a small spare-part quantity on top of the design count for some line items (because some suppliers only sell in pairs, or to leave a margin for kit packaging), and the v0.6 rule schema conflates design count with order count. Workarounds against the v0.6 surface failed, and the right fix is a rule-schema change adding expected_design_qty and spare_qty fields. That shipped in v0.7.
Teams working on bracketry, robotics chassis, industrial fixtures, optical mounts, instruments, printers, machine-tool fixtures, or research apparatus can evaluate whether their authored STEP exports and declared rules fit this approach. Suitability, economic value, and required review depend on the assembly, failure modes, evidence quality, and deployment controls; no return, defect-prevention, or fabrication outcome is guaranteed.
8. Where CADCLAW is going
CADCLAW's development branch has two unreleased, narrow AP242 gates. PMI_PRESENT_SEMANTIC reports declared class presence for dimensions, geometric tolerances, and datums. Opt-in ROUNDTRIP_STEP performs an actual OCCT XCAF import, AP242 export, and reimport, then compares CADCLAW's deduplicated imported renderable-shape count, bounded geometry measures, declared interface gaps, and source-present supported semantic-PMI class counts. The method count is not an authoritative STEP product count. Minimum-cost one-to-one matching is limited to 256 shapes; larger equal-count comparisons error before quadratic allocation rather than being sampled. These methods do not inspect graphical PMI, materials, process/general notes, validation properties, PMI values or associations, native-CAD correctness, verified translator identity, or standards conformance. Neither gate is part of the published 0.10.0 package. Active backlog items include using supported semantic values in dimensional/tolerance calculations, parametric assembly diff'ing, expanded structural gates for rotational axes and ball-screw drives, full-travel kinematic checks, and tighter BOM-audit integration.
Further out, the most interesting unknowns are not technical but social. CAD CI works well for an open-source hardware project with a coherent CadQuery placement layer and a Git-tracked STEP. It does not yet have a clean answer for shops that operate entirely inside SolidWorks PDM, where the canonical model is a binary native file and the STEP is an export artefact rather than a source of truth. The honest answer there is the same as for any CI tool: validation runs against the artefact you ship, and if your artefact diverges from your authoring, you have a process problem upstream of the test runner. CADCLAW's parity gate, which compares two STEP exports for signature drift, is the small first step toward catching that divergence in CI rather than at the fabricator. Track the roadmap in the GitHub milestones.
Install
pip install cadclaw
cadclaw doctor # verify environment
python examples/init_rules.py --step my.step --bom bom.json # scaffold cadclaw.yaml
cadclaw harness --rules cadclaw.yaml # run every declared gate
cadclaw bom-audit --rules cadclaw.yaml # run the BOM-vs-CAD audit standalone
These commands install and invoke CADCLAW in a compatible Python environment; project rules, representative fixtures, CI wiring, review thresholds, and branch protection still require configuration. Distributed via PyPI, source on GitHub, MIT-licensed, with no commercial CAD application required for CADCLAW's own STEP-oriented checks.
Cite this work
If you use CADCLAW in published research or derivative work, please cite via the project's CITATION.cff:
Sonnentag, N. (2026). CADCLAW: Automated assembly and validation framework for
STEP-based CAD [Software]. Sunnyday Technologies.
https://github.com/sunnyday-technologies/CADCLAW
DOI: 10.5281/zenodo.19647390
A CITATION.cff file is shipped in the repository for automated citation tooling (Zenodo, GitHub's citation widget, JOSS).