Parametric CAD, meshing verification and slicing, behind MCP
Project description
cadloop
A closed loop from parametric source to G-code, with the verification step in the middle that neither the CAD tool nor the slicer can do.
Two MCP servers and a checker. A model writes OpenSCAD, compiles it, reads computed values back out, looks at a render, proves the parts actually work, checks they fit the bed, slices them and pulls out the G-code. No GUI at any point.
Every number in that walkthrough is real output. The failing wheel is a real measurement, not an illustration. How it's built and where each number comes from is in docs/walkthrough.
write .scad
|
check syntax and references, about a second
|
echo read computed values without rendering
|
preview look at it
|
verify does it actually work <- the part tooling usually skips
|
measure bounding box and volume
|
check_bed_fit against the machine profile's printable area
|
slice_model -> .gcode.3mf
|
extract_gcode -> .gcode
The ordering is the point. check and echo skip geometry evaluation
entirely, so they cost a second against models where a full render takes
minutes. Pay for the expensive steps only once the cheap ones pass.
Install
pip install "cadloop[verify]"
Or from a checkout, which is what you want if you intend to run the worked example or the tests:
git clone https://github.com/richardofortune/cadloop
cd cadloop
pip install -e ".[verify]"
Needs OpenSCAD on PATH or at OPENSCAD_BIN, and for slicing, OrcaSlicer,
Bambu Studio, ElegooSlicer or Creality Print at SLICER_BIN. They share a CLI,
so any of them works, with one exception: Creality Print's CLI is broken
headless on macOS, covered under testing status below.
Configure
Copy mcp.json into your client's config and fix the paths. Both servers
should point at the same workspace directory, which is the only place either
one reads or writes.
| Variable | Default | Meaning |
|---|---|---|
OPENSCAD_BIN, SLICER_BIN |
auto-detected | binary paths |
OPENSCAD_WORKSPACE, SLICER_WORKSPACE |
~/cad |
the sandbox |
SLICER_PROFILE_DIRS |
auto-detected | extra profile roots |
OPENSCAD_TIMEOUT, SLICER_TIMEOUT |
300, 600 | seconds before a kill |
The servers
openscad exposes check, echo, render, measure, preview and
workspace file access. preview returns the PNG as an MCP image, so the model
can look at what it built rather than inferring from numbers. render returns
OpenSCAD's manifold report alongside a bounding box and volume, where
simple: yes with a sensible volume count is the signal the mesh is printable.
slicer exposes slicer_info, list_profiles, check_bed_fit,
slice_model, slice_summary and extract_gcode. Call slicer_info first:
the Orca-family CLI is undocumented, changes between releases, and Creality's
fork diverges, so the flag list it reads off your install is more trustworthy
than anything assumed. slice_model has an extra_args escape hatch and a
dry_run mode.
check_bed_fit is where the two meet. The slicer will emit out-of-bounds
G-code without complaining, so this measures the STL, reads printable_area
out of the machine profile, and compares. It checks the 45 degree diagonal too,
since a part that misses square-on often fits rotated.
Reading printable_area back out is fiddlier than it looks. Stock profiles
write it both as a list of "XxY" strings and as one comma-separated string,
and most Bambu machines carry no bed of their own at all, inheriting it through
inherits from a common base. So the lookup parses both forms and walks the
inheritance chain. Of the 473 concrete machine profiles shipped with Creality
Print 7.1.1, 469 resolve; the remaining four define no bed anywhere in their
chain, and those report ok: null with a reason rather than guessing.
The verification step
cadloop-verify is the reference example of the third thing you need, and the
one no general tool provides. For the spirograph it lays each wheel's pitch
curve onto the ring's pitch circle, walks a full circuit, and measures overlap
between the two solids at every position. Zero overlap across the whole circuit
at some meshing phase is the pass condition.
$ cadloop-verify
part teeth overlap mm2 result
24T 24 0.000000 pass
...
trefoil 23 0.000000 pass
15/15 parts mesh cleanly
This is model-specific by nature, which is the honest lesson. A manifold mesh that fits the bed and slices cleanly can still be a part that does not work. Whatever your equivalent of "does it actually roll" is, it belongs in the loop between preview and measure, and you have to write it yourself.
The worked example
models/spirograph.scad is the model the loop was built around. A 96 tooth
internal ring in a flanged body, an outer ring, twelve circular wheels and
three non-circular ones (ellipse, egg, trefoil), all involute geared at module
1.5 with pen holes on a golden-angle spiral.
The non-circular wheels are why the verifier exists. A first attempt used capsule and teardrop outlines built from tangent lines; those looked right, rendered as clean manifolds, and would have sliced without complaint, but the rolling check showed them ploughing 30 to 80 mm² into the ring teeth. A flat section of pitch curve touches the ring at one point and stands proud of it everywhere else. Nothing downstream of CAD would have caught that. The shapes that shipped are smooth convex curves whose radius of curvature stays inside the ring's everywhere.
make verify # rolling interference across all 15 parts
make render PART=ring
make smoke # both servers, end to end
Testing status
make smoke drives both servers over real MCP stdio sessions and asserts on
twenty-one behaviours: tool surface, defines reaching the script, a deliberate
syntax error being caught, measured geometry matching known dimensions, an
image coming back from preview, profile classification, argument ordering,
archive parsing, G-code extraction, bed fit passing and failing, both
printable_area spellings and an inherited bed, and the workspace guard
rejecting traversal.
The OpenSCAD half runs against a real OpenSCAD and skips if none is installed.
The slicer half runs against a mock binary that emits an Orca-shaped help text
and a representative .gcode.3mf, so everything except the real slicer's own
behaviour is covered.
Against a real install, the OpenSCAD half and check_bed_fit are confirmed end
to end: a 24 tooth wheel rendered out of spirograph.scad measures 38.99 mm and
passes the K1's 220 mm bed. Every flag slice_model builds is present in
Creality Print 7.1.1's --help.
Creality Print's CLI does not work headless on macOS. On 7.1.1.4472 under
macOS 26, every operation except --help segfaults, including --info with no
profiles loaded at all. It dies in Slic3r::GUI::PartPlate::set_shape called
from Slic3r::CLI::run, a null dereference in GUI bed setup that the headless
path never initialises, and no combination of --datadir, --outputdir or
profile arguments avoids it. Point SLICER_BIN at OrcaSlicer, Bambu Studio or
ElegooSlicer instead, or run Creality Print's CLI on Linux. If you do pass
--datadir, it must be writable: an unwritable one aborts in set_data_dir
rather than segfaulting, which is a different failure with the same outcome.
The workspace guard covers tool arguments, not the scripts themselves.
OpenSCAD's include, use and import can reach any file the process can
read, so running an untrusted .scad is running untrusted code.
License
MIT. See LICENSE.
Not affiliated with Creality, Bambu Lab, Elegoo, the OrcaSlicer project or the OpenSCAD project. Those names appear here only to describe what this drives.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cadloop-0.1.0.tar.gz.
File metadata
- Download URL: cadloop-0.1.0.tar.gz
- Upload date:
- Size: 29.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a054ed74a2b348721346ad76ee6e5687126801fb7e8c4075ebd89d1e811db3a6
|
|
| MD5 |
9ef36dfa46c5ed647a7439ae3995e50c
|
|
| BLAKE2b-256 |
5837780d95a8fcfe9db02d82f1aec7c256e8f74219c893f9f9b3cb5c4b43e412
|
Provenance
The following attestation bundles were made for cadloop-0.1.0.tar.gz:
Publisher:
release.yml on richardofortune/cadloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cadloop-0.1.0.tar.gz -
Subject digest:
a054ed74a2b348721346ad76ee6e5687126801fb7e8c4075ebd89d1e811db3a6 - Sigstore transparency entry: 2262319815
- Sigstore integration time:
-
Permalink:
richardofortune/cadloop@8f545fc0c944efd0cd6b844355e2b940c782b7b3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/richardofortune
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8f545fc0c944efd0cd6b844355e2b940c782b7b3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file cadloop-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cadloop-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ad846aca74b31a47c56448a27c084b3b5410498544c43ccd7b16270395131f5c
|
|
| MD5 |
6b01e2c60e1a6a6c7e62dfa9253207da
|
|
| BLAKE2b-256 |
80cd3816f16ed08db04367737e1d0276ebb2ea4519f9d72e756765916e965cbe
|
Provenance
The following attestation bundles were made for cadloop-0.1.0-py3-none-any.whl:
Publisher:
release.yml on richardofortune/cadloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cadloop-0.1.0-py3-none-any.whl -
Subject digest:
ad846aca74b31a47c56448a27c084b3b5410498544c43ccd7b16270395131f5c - Sigstore transparency entry: 2262319953
- Sigstore integration time:
-
Permalink:
richardofortune/cadloop@8f545fc0c944efd0cd6b844355e2b940c782b7b3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/richardofortune
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8f545fc0c944efd0cd6b844355e2b940c782b7b3 -
Trigger Event:
release
-
Statement type: