Metadata-Version: 2.5
Name: dcc-mcp-freecad
Version: 0.6.0
Summary: Production FreeCAD adapter for typed parametric modeling, document validation, and CAD exchange
Project-URL: Homepage, https://github.com/dcc-mcp/dcc-mcp-freecad
Project-URL: Repository, https://github.com/dcc-mcp/dcc-mcp-freecad
Project-URL: Issues, https://github.com/dcc-mcp/dcc-mcp-freecad/issues
Author-email: loonghao <hal.long@outlook.com>
License: MIT
License-File: LICENSE
Keywords: automation,cad,dcc,freecad,mcp,parametric-modeling
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.7
Requires-Dist: dcc-mcp-core<1.0.0,>=0.20.36
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest<8,>=7.4; (python_version < '3.8') and extra == 'dev'
Requires-Dist: pytest>=8; (python_version >= '3.8') and extra == 'dev'
Requires-Dist: pyyaml>=6; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Requires-Dist: twine>=6; extra == 'dev'
Provides-Extra: test
Requires-Dist: pytest<8,>=7.4; (python_version < '3.8') and extra == 'test'
Requires-Dist: pytest>=8; (python_version >= '3.8') and extra == 'test'
Requires-Dist: pyyaml>=6; extra == 'test'
Requires-Dist: ruff>=0.8; extra == 'test'
Description-Content-Type: text/markdown

# dcc-mcp-freecad

<p align="center">
  <img src="docs/assets/dcc-mcp-freecad.svg" alt="DCC-MCP · FREECAD" width="600">
</p>

Production FreeCAD adapter for typed, durable parametric modeling and CAD
exchange through DCC-MCP.

![Parametric bracket moving through FreeCAD topology validation to a game-ready mesh](docs/images/dcc-mcp-freecad-showcase.webp)

_Illustrative workflow based on the live OpenSCAD → FreeCAD → Blender/Godot acceptance run; generated source is retained in `docs/images/dcc-mcp-freecad-showcase-source.png`._

## Capabilities

- Detect FreeCADCmd and report the actual FreeCAD/Python runtime.
- Create, inspect, recompute, validate, copy, and dependency-safely edit FCStd
  documents.
- Add and update boxes, cylinders, spheres, cones, and tori with typed
  dimensions and placements.
- Create parametric union, cut, and intersection features.
- Import or export STEP, IGES, BREP, STL, and OBJ geometry.
- Return object links, placements, shape/mesh topology counts, volume, area,
  bounding boxes, file size, and SHA-256 provenance.

No arbitrary Python, macros, module paths, or FreeCAD command-line flags are
accepted. The adapter invokes only its packaged method-dispatch driver.

## Requirements

This adapter spans **two separate Python interpreters**. They have different
versions, different `sys.path`, and different site-packages:

| Side | What runs there | Python version | Who owns that version |
| --- | --- | --- | --- |
| Wrapper (service side) | the `dcc-mcp-freecad` CLI, server, and `doctor`, plus the process that launches `FreeCADCmd` | **3.7+** (`requires-python = ">=3.7"`) | the environment you `pip install dcc-mcp-freecad` into |
| Host (FreeCAD side) | `src/dcc_mcp_freecad/freecad_driver.py` and every skill script | **3.10+** — the interpreter FreeCAD is built against and ships (3.11 in both release lines CI exercises) | your FreeCAD installation |

- `dcc-mcp-core` 0.20.36+ in the **wrapper** environment.
- A FreeCAD version covered by the compatibility matrix below, with either a working
  `FreeCADCmd`/`freecadcmd` or an explicitly configured compatible executable Python
  interpreter and installed native FreeCAD library for the opt-in `python-module`
  backend. See [native-library configuration](docs/python-module-backend.md).

Because the two interpreters are separate:

- **Do not create a Python 3.7 virtualenv and expect `import FreeCAD` to work in
  it.** The wrapper runs fine on 3.7, but FreeCAD's modules exist only inside
  FreeCAD's own Python, which is 3.10 or newer in every supported release.
- Installing the wheel into FreeCAD's interpreter is neither required nor
  supported; the wrapper drives the host by launching `FreeCADCmd` with the
  packaged driver.
- `dcc-mcp-freecad doctor --json` reports the host interpreter as
  `checks.runtime.python_version`, next to `checks.runtime.freecad_version`. See
  [install.md](install.md#host-python-interpreter) for how to read and
  cross-check both.

### FreeCAD compatibility matrix

Supported host versions are declared in
[`compat_matrix.json`](src/dcc_mcp_freecad/compat_matrix.json) — that file is the
contract, this section only points at it:

| FreeCAD | Status |
| --- | --- |
| 1.0.x | supported |
| 1.1.x | supported |
| anything else | rejected with an explicit error code |

Both supported lines are exercised end-to-end on real FreeCAD in CI —
**1.0.2** and **1.1.4**, the two pinned AppImages in
[`.github/workflows/ci.yml`](.github/workflows/ci.yml) — and both ship Python
3.11 as the host interpreter. A version outside the matrix is **not** silently
downgraded to "probably fine":
`dcc-mcp-freecad doctor --json` fails with an `error_code`
(`freecad_host_version_unsupported`, `freecad_host_version_unverified`,
`freecad_host_version_unlisted`, or `freecad_host_version_unparsable`), the
supported ranges, and the command that pins a supported one. The same matrix is
re-checked inside FreeCAD before any geometry work, so a skipped preflight does
not turn into a silent modelling bug.

## Install

Install the published wheel, prove the standalone FreeCAD runtime, then start
the service:

```text
python -m pip install --upgrade dcc-mcp-freecad
dcc-mcp-freecad doctor --json
dcc-mcp-freecad verify --json
dcc-mcp-freecad
```

See [install.md](install.md) for Windows, macOS, and Linux discovery, stable
doctor exits, configuration, wheel upgrades, uninstall, and troubleshooting.
FreeCAD remains OS/package-manager owned; the adapter does not download or
cache external binaries.

## Configuration

| Variable | Purpose | Default |
| --- | --- | --- |
| `DCC_MCP_FREECAD_EXECUTABLE` | Exact FreeCADCmd executable or install directory | `PATH` and standard locations |
| `DCC_MCP_FREECAD_ALLOWED_ROOTS` | `os.pathsep`-separated document/import/export roots | server working directory |
| `DCC_MCP_FREECAD_MAX_DOCUMENT_BYTES` | Maximum FCStd input size | 2 GiB |
| `DCC_MCP_FREECAD_MAX_TIMEOUT_SECS` | Maximum per-call deadline | 1800 seconds |
| `DCC_MCP_FREECAD_PORT` | Fixed adapter port when direct addressing is required | OS-assigned |

The service is a standalone DCC-MCP instance with no GUI PID. Every operation
launches a clean FreeCADCmd process in safe mode with a temporary user config.

## Agent workflow

```bash
dcc-mcp-cli list
dcc-mcp-cli search --query "FreeCAD create boolean export STEP"
dcc-mcp-cli load-skill freecad-session --dcc-type freecad --instance-id <instance-short>
dcc-mcp-cli load-skill freecad-modeling --dcc-type freecad --instance-id <instance-short>
```

Typical sequence: `create_document` → `add_primitive` → `transform_object` →
`boolean_operation` → `validate_document` → `export_geometry`.

## Safety contract

- Requested documents and geometry stay under configured allowed roots.
- Existing outputs require explicit `overwrite=true`.
- Every `save_copy` call with `overwrite=false`, plain or presentation,
  publishes exclusively: a sibling hard link where the output filesystem
  supports one, otherwise an exclusive create (`O_CREAT|O_EXCL`) plus copy on
  volumes without hard-link support (FAT/exFAT, some SMB/FUSE mounts). Both
  refuse a destination created during the native call; only the hard link is a
  single atomic inode commit. Publication fails if neither is available —
  retry with `overwrite=true` or publish to a filesystem with hard-link
  support. Explicit overwrite uses atomic replacement.
- FCStd mutations happen on sibling staging copies and replace the original
  only after a successful, non-empty save.
- Failed mutations leave the original document byte-for-byte unchanged.
- Object names, dimensional fields, placements, formats, tessellation, result
  size, process output, and deadlines are bounded.
- Cancellation and timeouts terminate the owned FreeCADCmd process.
- Cascade removal is explicit and reports every removed dependent.

FreeCAD Python API reference: <https://www.freecad.org/api/>

## Optional native-library backend

The default is still FreeCADCmd. A separately configured `python-module` backend runs the same typed driver in a fresh compatible interpreter and installed FreeCAD library, with isolated temporary user directories. It is opt-in and has no automatic fallback. See [configuration and exact qualification](docs/python-module-backend.md).

### Native presentation copies

`save_copy` accepts an optional bounded `visible_objects` list and a standard
`view` (`isometric`, `front`, `top`, `right`). With this explicit opt-in it uses
FreeCAD's installed GUI library to save view-provider visibility and an
orthographic camera. Selections must be top-level non-container objects;
Groups, LinkGroups, Parts, Bodies and their members are rejected before changing visibility because a
hidden parent can hide or override a child's local state. Other objects remain
editable but hidden. The original
file stays unchanged; presentation mode refuses replacing the source even when
overwrite is set. GUI-library availability is required; this is not a claim of
offscreen OpenGL rendering.

The isolated native process disables view animation before selecting the camera,
verifies visibility, orthographic type and native quaternion orientation against
the request, then saves and reopens its staging file. It verifies camera values
(single-precision serialization tolerance of 1e-6), object names, links, topology
counts, placement, volumes and analytic bounds (double precision tolerance 1e-10 relative,
1e-9 absolute). Analytic bounds explicitly exclude GUI triangulation caches.
A cancellation check precedes final publication. No FCStd XML or binary data is
rewritten outside native FreeCAD APIs. Native raster thumbnails are disabled in
this isolated process because an offscreen host may lack an OpenGL context.

No-overwrite copy publication uses a sibling hard link to publish the complete
native file without replacing a destination created during the native call;
the output filesystem must support this operation. Where it does not, the same
exclusive publication falls back to an exclusive create plus copy, which keeps
the refusal but not the single atomic inode commit; a failure names hard links
and the `overwrite=true` escape hatch. Explicit overwrite continues to use
atomic replacement. These readbacks cover recorded geometry metrics,
not full BRep or mesh connectivity equivalence. Source qualification and the
supported-host GUI persistence gate are recorded in
[presentation-copy validation](docs/validation/presentation-copy.md).
