Metadata-Version: 2.4
Name: cadloop
Version: 0.1.0
Summary: Parametric CAD, meshing verification and slicing, behind MCP
Project-URL: Homepage, https://github.com/richardofortune/cadloop
Project-URL: Repository, https://github.com/richardofortune/cadloop
Project-URL: Issues, https://github.com/richardofortune/cadloop/issues
Author-email: Richard Fortune <richard.fortune@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: 3d-printing,cad,gcode,mcp,model-context-protocol,openscad,orcaslicer,parametric,slicer
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Manufacturing
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.10
Requires-Dist: mcp>=1.0
Provides-Extra: verify
Requires-Dist: shapely>=2.0; extra == 'verify'
Description-Content-Type: text/markdown

# cadloop

<!-- mcp-name: io.github.richardofortune/cadloop-openscad -->
<!-- mcp-name: io.github.richardofortune/cadloop-slicer -->

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.

![A walkthrough of the loop, ending on the verifier catching a wheel that jams](https://raw.githubusercontent.com/richardofortune/cadloop/main/docs/walkthrough/out/cadloop-hero.gif)

*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](https://github.com/richardofortune/cadloop/tree/main/docs/walkthrough).*

```text
  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

```console
pip install "cadloop[verify]"
```

Or from a checkout, which is what you want if you intend to run the worked
example or the tests:

```console
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.

```console
$ 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.

```console
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](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.
