cadpilot 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cadpilot-0.4.0/.gitattributes +7 -0
- cadpilot-0.4.0/.github/workflows/ci.yml +38 -0
- cadpilot-0.4.0/.github/workflows/publish.yml +32 -0
- cadpilot-0.4.0/.gitignore +34 -0
- cadpilot-0.4.0/.python-version +1 -0
- cadpilot-0.4.0/AGENTS.md +190 -0
- cadpilot-0.4.0/CHANGELOG.md +204 -0
- cadpilot-0.4.0/LICENSE +22 -0
- cadpilot-0.4.0/PKG-INFO +173 -0
- cadpilot-0.4.0/README.md +147 -0
- cadpilot-0.4.0/README.zh-CN.md +147 -0
- cadpilot-0.4.0/addon/CADPilot/Init.py +1 -0
- cadpilot-0.4.0/addon/CADPilot/InitGui.py +62 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/__init__.py +2 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/assembly_ops.py +512 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/commands.py +192 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/feature_ops.py +660 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/geometry_query.py +598 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/gui_dispatch.py +208 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/ip_filter.py +73 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/joint_ops.py +421 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/object_factory.py +36 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/property_mapper.py +140 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/rpc_server.py +1051 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/serialize.py +108 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/settings.py +42 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/sketcher_ops.py +471 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/trim_ops.py +47 -0
- cadpilot-0.4.0/addon/CADPilot/rpc_server/view_manager.py +148 -0
- cadpilot-0.4.0/docs/DESIGN.md +154 -0
- cadpilot-0.4.0/docs/DESIGN.zh-CN.md +154 -0
- cadpilot-0.4.0/examples/ModernBicycle.FCStd +0 -0
- cadpilot-0.4.0/pyproject.toml +72 -0
- cadpilot-0.4.0/src/cadpilot/__init__.py +0 -0
- cadpilot-0.4.0/src/cadpilot/assembly_state.py +192 -0
- cadpilot-0.4.0/src/cadpilot/freecad_client.py +319 -0
- cadpilot-0.4.0/src/cadpilot/guidance.py +192 -0
- cadpilot-0.4.0/src/cadpilot/operations/__init__.py +73 -0
- cadpilot-0.4.0/src/cadpilot/operations/assembly.py +219 -0
- cadpilot-0.4.0/src/cadpilot/operations/core.py +1102 -0
- cadpilot-0.4.0/src/cadpilot/pattern_store.py +127 -0
- cadpilot-0.4.0/src/cadpilot/prompt_text.py +131 -0
- cadpilot-0.4.0/src/cadpilot/py.typed +0 -0
- cadpilot-0.4.0/src/cadpilot/responses.py +25 -0
- cadpilot-0.4.0/src/cadpilot/server.py +948 -0
- cadpilot-0.4.0/src/cadpilot/server_state.py +26 -0
- cadpilot-0.4.0/src/cadpilot/session_state.py +253 -0
- cadpilot-0.4.0/src/cadpilot/tool_docs.py +266 -0
- cadpilot-0.4.0/tests/conftest.py +404 -0
- cadpilot-0.4.0/tests/live_sketch_verify.py +192 -0
- cadpilot-0.4.0/tests/test_assembly.py +181 -0
- cadpilot-0.4.0/tests/test_assembly_session.py +242 -0
- cadpilot-0.4.0/tests/test_assembly_state.py +146 -0
- cadpilot-0.4.0/tests/test_cad_features.py +99 -0
- cadpilot-0.4.0/tests/test_cad_sessions.py +435 -0
- cadpilot-0.4.0/tests/test_client_reconnect.py +155 -0
- cadpilot-0.4.0/tests/test_connectivity_audit.py +117 -0
- cadpilot-0.4.0/tests/test_geometry_sensing.py +67 -0
- cadpilot-0.4.0/tests/test_guidance.py +126 -0
- cadpilot-0.4.0/tests/test_operation_help.py +66 -0
- cadpilot-0.4.0/tests/test_operations.py +381 -0
- cadpilot-0.4.0/tests/test_pattern_store.py +52 -0
- cadpilot-0.4.0/tests/test_prompt_text.py +21 -0
- cadpilot-0.4.0/tests/test_responses.py +54 -0
- cadpilot-0.4.0/tests/test_server_state.py +29 -0
- cadpilot-0.4.0/tests/test_session_state.py +106 -0
- cadpilot-0.4.0/tests/test_sketch_features.py +143 -0
- cadpilot-0.4.0/uv.lock +613 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, windows-latest]
|
|
14
|
+
python-version: ["3.12", "3.13"]
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v5
|
|
22
|
+
with:
|
|
23
|
+
enable-cache: true
|
|
24
|
+
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
run: uv python install ${{ matrix.python-version }}
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: uv sync --python ${{ matrix.python-version }}
|
|
30
|
+
|
|
31
|
+
- name: Lint
|
|
32
|
+
run: uv run --no-sync ruff check .
|
|
33
|
+
|
|
34
|
+
- name: Format check
|
|
35
|
+
run: uv run --no-sync ruff format --check .
|
|
36
|
+
|
|
37
|
+
- name: Test
|
|
38
|
+
run: uv run --no-sync pytest -q
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- name: Install uv
|
|
13
|
+
uses: astral-sh/setup-uv@v5
|
|
14
|
+
- name: Run tests
|
|
15
|
+
run: |
|
|
16
|
+
uv sync
|
|
17
|
+
uv run --no-sync pytest -q
|
|
18
|
+
|
|
19
|
+
publish:
|
|
20
|
+
needs: test
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
environment: pypi
|
|
23
|
+
permissions:
|
|
24
|
+
id-token: write # required for PyPI Trusted Publishing (OIDC)
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- name: Install uv
|
|
28
|
+
uses: astral-sh/setup-uv@v5
|
|
29
|
+
- name: Build
|
|
30
|
+
run: uv build
|
|
31
|
+
- name: Publish to PyPI
|
|
32
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
*.pyo
|
|
5
|
+
.venv/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg-info/
|
|
9
|
+
|
|
10
|
+
# FreeCAD working files (demos / backups)
|
|
11
|
+
*.FCStd
|
|
12
|
+
*.FCBak
|
|
13
|
+
*.FCStd1
|
|
14
|
+
|
|
15
|
+
# Local plans / skills
|
|
16
|
+
docs/superpowers/
|
|
17
|
+
|
|
18
|
+
# OS / editor
|
|
19
|
+
.DS_Store
|
|
20
|
+
Thumbs.db
|
|
21
|
+
*.swp
|
|
22
|
+
*.swo
|
|
23
|
+
*~
|
|
24
|
+
|
|
25
|
+
# IDE
|
|
26
|
+
.idea/
|
|
27
|
+
.vscode/
|
|
28
|
+
.zcode/
|
|
29
|
+
|
|
30
|
+
# Local secrets
|
|
31
|
+
.env
|
|
32
|
+
|
|
33
|
+
# Allow the demo model in examples/
|
|
34
|
+
!examples/ModernBicycle.FCStd
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
cadpilot-0.4.0/AGENTS.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# CADPilot — Workspace Guide
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
MCP (Model Context Protocol) server that lets AI clients (Claude Desktop, LangChain, etc.) control FreeCAD remotely. Two main components communicate over XML-RPC:
|
|
6
|
+
|
|
7
|
+
1. **MCP server** (`src/cadpilot/`) — Python package run via `uvx cadpilot` or `uv run cadpilot`. Speaks MCP to the AI client and XML-RPC to FreeCAD.
|
|
8
|
+
2. **FreeCAD addon** (`addon/CADPilot/`) — Installed into FreeCAD's `Mod/` directory. Hosts the XML-RPC server inside the FreeCAD process and dispatches all document/GUI work onto the main thread.
|
|
9
|
+
|
|
10
|
+
## Directory Layout
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
src/cadpilot/ # MCP server package (published to PyPI)
|
|
14
|
+
server.py # FastMCP tool definitions & CLI entry point (main())
|
|
15
|
+
freecad_client.py # XML-RPC client proxy to FreeCAD addon
|
|
16
|
+
operations/core.py # Tool operation implementations (one function per tool)
|
|
17
|
+
operations/__init__.py # Re-exports all operations
|
|
18
|
+
responses.py # ToolResponse type alias, text/json/screenshot helpers
|
|
19
|
+
server_state.py # ServerState dataclass (connection, host, screenshot flags)
|
|
20
|
+
session_state.py # ModelingSession/Step dataclasses, JSON persistence, current-session registry
|
|
21
|
+
assembly_state.py # AssemblySession/AssemblyStep dataclasses, JSON persistence (~/.cadpilot/assembly/), precomputed-undo rollback
|
|
22
|
+
operations/assembly.py # assembly_session tool: spec validation → RPC assembly_op → session recording
|
|
23
|
+
pattern_store.py # Pattern memory (reusable workflows), keyword retrieval
|
|
24
|
+
guidance.py # Lightweight next-step suggestions & risk heuristics (incl. primitive_without_sketch / absolute_placement / assembly-mode steer)
|
|
25
|
+
prompt_text.py # ASSET_CREATION_STRATEGY prompt template
|
|
26
|
+
tool_docs.py # Long per-operation reference docs served by the operation_help tool
|
|
27
|
+
|
|
28
|
+
addon/CADPilot/ # FreeCAD workbench addon (copied to FreeCAD's Mod/ dir)
|
|
29
|
+
InitGui.py # Workbench registration, toolbar/menu, auto-start
|
|
30
|
+
Init.py # Path setup
|
|
31
|
+
rpc_server/
|
|
32
|
+
rpc_server.py # FreeCADRPC class — XML-RPC handler, server start/stop
|
|
33
|
+
gui_dispatch.py # dispatch_to_gui() — queues work onto the GUI thread
|
|
34
|
+
commands.py # FreeCAD Command classes for toolbar buttons
|
|
35
|
+
object_factory.py # create_object_gui() — object creation logic
|
|
36
|
+
property_mapper.py # set_object_property() — recursive property assignment
|
|
37
|
+
serialize.py # serialize_object() — object → dict for RPC responses
|
|
38
|
+
view_manager.py # save_active_screenshot() — camera/view screenshot logic
|
|
39
|
+
geometry_query.py # Read-only Shape queries — measure/topology/interference
|
|
40
|
+
assembly_ops.py # Anchor-based assembly — anchors (auto/explicit), assemble (mates), verify_assembly
|
|
41
|
+
joint_ops.py # Persistent-joint assembly — FreeCAD 1.1 Assembly WB lifecycle (Link wrap, joints, preSolve+solve, rollback, verify+gap profile)
|
|
42
|
+
trim_ops.py # Declarative priority trimming — non-destructive baked cut in loser-link frame
|
|
43
|
+
feature_ops.py # Parametric feature creation (boolean/fillet/sketch/pad/...) + selectors
|
|
44
|
+
sketcher_ops.py # Constrained-sketch builder (geometry/constraints/solver diagnostics)
|
|
45
|
+
ip_filter.py # FilteredXMLRPCServer — IP/CIDR allowlist
|
|
46
|
+
settings.py # JSON settings persistence (auto-start, remote, allowed IPs)
|
|
47
|
+
|
|
48
|
+
examples/ # Usage examples (adk/agent.py, langchain/react.py)
|
|
49
|
+
tests/ # pytest suite for the MCP server side (fake XML-RPC connection)
|
|
50
|
+
assets/ # Demo GIFs and images for README
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Build & Run
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Install dependencies (requires Python ≥3.12, uv)
|
|
57
|
+
uv sync
|
|
58
|
+
|
|
59
|
+
# Run MCP server locally (developer mode)
|
|
60
|
+
uv run cadpilot # connects to FreeCAD on localhost:9875
|
|
61
|
+
uv run cadpilot --with-screenshots # attach screenshots by default (multimodal models)
|
|
62
|
+
uv run cadpilot --only-text-feedback # never return screenshots (hard guarantee)
|
|
63
|
+
uv run cadpilot --host 192.168.1.100 # connect to remote FreeCAD
|
|
64
|
+
|
|
65
|
+
# Publish to PyPI (via hatchling)
|
|
66
|
+
uv build
|
|
67
|
+
|
|
68
|
+
# Run tests (MCP server side only; the addon needs a live FreeCAD)
|
|
69
|
+
uv run pytest
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The FreeCAD addon must be installed separately — copy `addon/CADPilot/` into FreeCAD's `Mod/` directory and restart FreeCAD.
|
|
73
|
+
|
|
74
|
+
## Addon Hot-Reload (No Restart)
|
|
75
|
+
|
|
76
|
+
During development you can reload the addon **without restarting FreeCAD**:
|
|
77
|
+
|
|
78
|
+
1. Copy the updated addon files to the live `Mod/` directory:
|
|
79
|
+
```bash
|
|
80
|
+
cp -rf "H:/My_Software/FreeCAD-MCP/addon/CADPilot/." \
|
|
81
|
+
"C:/Users/intel/AppData/Roaming/FreeCAD/v1-1/Mod/CADPilot/"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
2. In FreeCAD's Python console (or via `execute_code`) run:
|
|
85
|
+
```python
|
|
86
|
+
import sys, importlib
|
|
87
|
+
import rpc_server.rpc_server as rs_old
|
|
88
|
+
|
|
89
|
+
print("stop:", rs_old.stop_rpc_server())
|
|
90
|
+
|
|
91
|
+
from PySide import QtCore # or PySide6 / PySide2
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _start(rs):
|
|
95
|
+
result = rs.start_rpc_server(9875)
|
|
96
|
+
print("start:", result)
|
|
97
|
+
if "still stopping" in str(result):
|
|
98
|
+
# Previous stop is still draining; retry instead of giving up
|
|
99
|
+
# (giving up here leaves a half-restarted server: socket dead,
|
|
100
|
+
# no heartbeat, every GUI-dispatched call hangs).
|
|
101
|
+
QtCore.QTimer.singleShot(4000, lambda: _start(rs))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def restart():
|
|
105
|
+
for sub in [
|
|
106
|
+
"ip_filter",
|
|
107
|
+
"settings",
|
|
108
|
+
"gui_dispatch",
|
|
109
|
+
"object_factory",
|
|
110
|
+
"property_mapper",
|
|
111
|
+
"serialize",
|
|
112
|
+
"view_manager",
|
|
113
|
+
"commands",
|
|
114
|
+
"geometry_query",
|
|
115
|
+
"assembly_ops",
|
|
116
|
+
"trim_ops",
|
|
117
|
+
"joint_ops",
|
|
118
|
+
"sketcher_ops",
|
|
119
|
+
"feature_ops",
|
|
120
|
+
]:
|
|
121
|
+
name = f"rpc_server.{sub}"
|
|
122
|
+
if name in sys.modules:
|
|
123
|
+
importlib.reload(sys.modules[name])
|
|
124
|
+
rs = importlib.reload(rs_old)
|
|
125
|
+
_start(rs)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Deferred restart: the in-flight XML-RPC request blocks shutdown drain,
|
|
129
|
+
# so we wait 4s for server_close() to finish before re-binding the port.
|
|
130
|
+
QtCore.QTimer.singleShot(4000, restart)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
3. Wait ~8 seconds before issuing the next MCP call so the new server is ready.
|
|
134
|
+
|
|
135
|
+
4. If GUI-dispatched calls (e.g. `execute_code`) hang after the reload but
|
|
136
|
+
`ping` still answers, the heartbeat/waker chain died during the race.
|
|
137
|
+
Repair via `execute_code_async` (its worker runs without GUI dispatch):
|
|
138
|
+
```python
|
|
139
|
+
import rpc_server.gui_dispatch as gd
|
|
140
|
+
from PySide import QtCore
|
|
141
|
+
import FreeCADGui
|
|
142
|
+
|
|
143
|
+
def repair():
|
|
144
|
+
while not gd._rpc_request_queue.empty():
|
|
145
|
+
t = gd._rpc_request_queue.get()
|
|
146
|
+
if t is not gd._SHUTDOWN:
|
|
147
|
+
t()
|
|
148
|
+
QtCore.QTimer.singleShot(500, gd.process_gui_tasks)
|
|
149
|
+
|
|
150
|
+
QtCore.QTimer.singleShot(0, FreeCADGui.getMainWindow(), repair)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
> **Why deferred?** `stop_rpc_server()` calls `shutdown()` which blocks until the current request drains, and `server_close()` must release the socket before `start_rpc_server()` can bind again. Running the restart synchronously inside the same `execute_code` call deadlocks because the request itself is blocking shutdown. The `QTimer` deferral runs on FreeCAD's main GUI thread after the RPC call returns.
|
|
154
|
+
|
|
155
|
+
## Architecture & Key Constraints
|
|
156
|
+
|
|
157
|
+
- **GUI thread rule**: All FreeCAD document/GUI operations MUST run on FreeCAD's main (GUI) thread. The addon uses `dispatch_to_gui()` to queue lambdas and a `QTimer` waker to process them. Never call FreeCAD APIs directly from the RPC server thread.
|
|
158
|
+
- **Two-process model**: The MCP server and FreeCAD run in separate processes. They communicate exclusively via XML-RPC on port 9875. The MCP server never imports FreeCAD.
|
|
159
|
+
- **MCP version compatibility**: `server.py` imports `FastMCP` from `mcp.server.fastmcp` (1.x) with a fallback to `MCPServer` from `mcp.server.mcpserver` (2.x). Keep both paths working.
|
|
160
|
+
- **Timeouts**: Default XML-RPC transport timeout is 150s. `execute_code` has a 90s GUI-thread timeout; use `execute_code_async` for longer operations.
|
|
161
|
+
- **Reconnect**: `FreeCADConnection._invoke` rebuilds the XML-RPC proxy and retries once on dead-connection errors (FreeCAD/addon restart). Socket timeouts are NOT retried — the op may still be executing server-side.
|
|
162
|
+
- **Screenshot handling**: Screenshots are OPTIONAL and off by default. Per-call `with_screenshot` params opt in; `--with-screenshots` makes them default-on; `--only-text-feedback` is a hard off that overrides everything (see `ServerState.resolve_screenshot`). Screenshots are base64 PNG via temp files. Mutation tools (create/edit/delete/execute_operations) capture the screenshot inline in the same RPC dispatch; the client falls back to a second `get_active_screenshot` call against old addons. When no explicit size is given, the long edge is capped at 768px (`DEFAULT_MAX_DIM` in `view_manager.py`). Some view types (TechDraw, Spreadsheet) don't support screenshots — `get_active_screenshot` returns `None` in those cases.
|
|
163
|
+
- **Async tasks**: `execute_code_async` returns a `task_id`; status, `task_print()` output, and tracebacks are kept in a bounded in-memory registry (`_async_tasks`, FIFO max 50) and polled via `get_task_result`. `sys.stdout` is never redirected for background tasks (process-wide race).
|
|
164
|
+
- **Unified cad() tool**: All mutations go through `cad(operation=...)` (nsforge math()-style dispatcher) to keep the tool list small. Steps: the operation functions in `operations/core.py` remain the implementation; `cad_operation` dispatches and records session steps. Scope is modeling-only — FEM analysis, the parts library, and `reload_document` were removed in v0.2. Feature ops (boolean/fillet/chamfer/loft/sweep/mirror/pattern, plus Sketcher/PartDesign: variables/sketch/pad/pocket/revolution/groove/thickness/draft since v0.3, plus datum_plane/hull since v0.4) share the single RPC `create_feature` and pass params as a spec dict (obj_name = base, obj_properties = params). For `sketch`/`variables`/`datum_plane`/`hull`, obj_name names the NEW object and `spec["base"]` carries it (see `CAD_NO_BASE_OPERATIONS`). Sketches are built atomically in `sketcher_ops.py` (geometry + constraints in one transaction, solver runs immediately); results carry `dof`/`fully_constrained`/`warnings` via `describe_feature`, failures roll back with solver diagnostics. Thickness/draft support both the FreeCAD ≥1.1 LinkSub `Base` layout and the ≤1.0 `Faces` property (probe `PropertiesList`).
|
|
165
|
+
- **Sketch mode details** (live-verified on FreeCAD 1.1.3): sketch `external: [[obj, "EdgeN"|"VertexN"], ...]` adds external geometry (GeoIds from -3 in list order, start/end points ONLY — `mid`/`center` on external ids fails at solve with MalformedConstraints, so `_check_external_point_refs` rejects it up front); out-of-body targets are auto-bridged via `PartDesign::SubShapeBinder` (`_external_binder`, idempotent `Ext_<obj>_<elem>`) because PartDesign rejects external geometry outside the sketch's body, and `addExternal` takes `(str, str)`. `datum_plane` attaches a PartDesign::Plane to an origin plane (`body.Origin.OriginFeatures` Role lookup) or an existing face (FlatFace + optional offset); sketches attach via `plane={"datum": name}`. `hull` = visual hull: intersect 2-3 view-profile sketches extruded along their sketch normals (extent = union bbox projected per-normal ±`margin`, default max(1mm, 5% of diagonal)); result is a STATIC `Part::Feature` (no proxy — survives document reload), same-name re-run replaces the Shape in place (iterate), multiple solids → largest wins, empty intersection → RuntimeError. v1 limits: view sketches at the global origin, one closed outer profile per view. **Attachment fusion**: pad/pocket on a sketch attached to a solid's face (directly or through a datum plane) operate on THAT solid via attachment — pocket cuts it, pad fuses into it; do NOT pad-then-boolean-cut (the pad already contains the base solid). **execute_code + openTransaction**: always pair with try/except → `doc.abortTransaction()` — a leaked transaction leaves broken objects that poison later recomputes (null shapes).
|
|
166
|
+
- **Geometry sensing**: `measure_geometry`/`get_topology`/`check_interference` are read-only Shape queries (addon `geometry_query.py`), dispatched to the GUI thread without a transaction. Values are rounded to 4 significant digits; topology lists are size-sorted and paginated (`limit`/`offset`).
|
|
167
|
+
- **Assembly toolchain**: `get_anchors`/`set_anchors`/`assemble`/`verify_assembly` (addon `assembly_ops.py`) give the model data-driven spatial awareness — no screenshots required. Anchors are named points+directions: auto-derived per object (`bbox_center/min/max`, `com`, `axis_mid/start/end` from the dominant cylindrical face, `face0-2_center` from the largest planar faces) plus explicit named ones stored as JSON in an `App::PropertyString` named `MCP_Anchors` in LOCAL coords (they follow Placement). `set_anchors(coord_frame="global")` converts via `obj.Placement.inverse()` at write time — use it whenever the source coordinates are global, because many objects carry non-identity Placements. `assemble` takes a mate list `{obj, anchor, target, target_anchor, mode: center|touch|axis, offset}`, applies each mate in one FreeCAD transaction, then RE-RESOLVES the anchor post-move to report per-mate residuals (mm + deg) and aborts on `tolerance` violation; partial commit via `commit_if` when at least one mate passed. `verify_assembly` audits the whole document: floating objects (nearest-neighbour distance via bbox prefilter + `distToShape`), interferences (common volume for bbox-overlapping pairs), and explicit anchor-pair checks with per-check tolerance. PRECISION RULE: `_r()`/`_vec()` rounding (4 sig digits) applies ONLY at the report boundary — `_resolve_anchor`/`_auto_anchor_map` must return RAW `FreeCAD.Vector`s for math (rounding at ~1000mm coords = 0.1mm granularity → false residuals). `verify_assembly` also builds a union-find **contact graph** from pairs already scanned (exact `distToShape` ≤ `_CONTACT_TOLERANCE` = 0.5mm, or common volume > threshold) and reports connected components: the largest is the main assembly, the rest are `islands` (with `gap_mm`/`nearest_main`).
|
|
168
|
+
- **Connectivity auto-audit**: after every committed `cad()` mutation, `cad_operation` re-runs the read-only `verify_assembly` audit and appends a "⚠ Connectivity" warning (formatted by `_format_connectivity_warning`) to both the tool response and the recorded step's `result_summary`; `detect_risks` surfaces it as a `disconnected_islands` risk in `session_status`. Guards: skipped when `object_count > _AUTO_AUDIT_MAX_OBJECTS` (300), when the addon is old (no `islands` key), or globally via `--no-auto-audit` (`ServerState.auto_audit`). Audit failures never block the mutation.
|
|
169
|
+
- **Assembly mode (`assembly_session` tool)**: independent state machine for mate-based assembly with PERSISTENT joints (FreeCAD 1.1 native Assembly workbench — `Assembly::AssemblyObject` with `Type="Assembly"`, `App::Link` components that own Placements, `JointObject.Joint` joints in the JointGroup). Ops: start(ground) / add_component / mate / solve / unmate / rollback(to_step) / verify / status / complete. Mate refs: `{"part", face|anchor|point}` — resolved to `(link, ["FaceN", "VertexM"])` where the vertex sets the mate landing point (GUI click semantics). `preSolve` (matchJCS) runs before the final single `solve(True)` — skipping it lands mates with faces perpendicular; repeated solve passes corrupt storePrev state. Frames: `link.Shape` is GLOBAL, joint references/`findPlacement` are part-LOCAL; residuals are geometric truth (`fa.distToShape(fb)` + normal angle), never JCS math. `trim={"winner":...}` bakes a non-destructive cut in the loser-link local frame and re-points the link; rollback deletes joints/cuts, re-points links, restores pre-mate placements (precomputed per-step undo, merged by `assembly_state.plan_rollback`). Gap profiles sample the a-face UV grid and measure perpendicular lift from the mate plane (overhang ≠ gap).
|
|
170
|
+
- **Modeling sessions**: `session_state.py` binds a session to one document. Every committed mutation runs inside a FreeCAD transaction (addon `_run_op_with_screenshot` wraps `doc.openTransaction`), so `session_rollback` = `doc.undo()` × N + log truncation; removed steps sit in a redo buffer until a new step (mirrors FreeCAD redo semantics). `execute_code` steps are non-atomic and block rollback unless forced. Sessions/patterns persist under `$CADPILOT_HOME` (default `~/.cadpilot/`). Mutation results carry an `objects` fingerprint (sorted object names) used to detect state drift after rollback.
|
|
171
|
+
- **Knowledge hierarchy**: prompts instruct ① model's own knowledge → ② `recall_patterns` → ③ `inspect_freecad`; successful approaches are stored via `save_pattern`/`session_complete`.
|
|
172
|
+
- **Name sanitization**: FreeCAD sanitizes document/object names (spaces → underscores, deduplication). RPC handlers return the *actual* name from FreeCAD, not the requested name.
|
|
173
|
+
|
|
174
|
+
## Coding Conventions
|
|
175
|
+
|
|
176
|
+
- Python 3.12+ (uses `X | None` union syntax, `type` alias).
|
|
177
|
+
- Logging: use `logging.getLogger("CADPilotserver")` in both MCP server and addon code.
|
|
178
|
+
- Tool operations: each tool has a dedicated `_operation` function in `operations/core.py` that takes a `FreeCADConnection` as its first arg and returns a `ToolResponse` (`list[TextContent | ImageContent]`).
|
|
179
|
+
- Addon code uses `FreeCAD.Console.PrintMessage/PrintError/PrintWarning` for FreeCAD's Report View.
|
|
180
|
+
- Settings persisted as JSON via `cadpilot_settings.json` (in FreeCAD's user data dir).
|
|
181
|
+
- **Docstring budget (prompt economy)**: every `@mcp.tool()` docstring is injected into the AI client's context on `tools/list`. Keep docstrings to a 1-3 line summary + brief Args — long parameter/semantics references go in `src/cadpilot/tool_docs.py` (`CAD_OP_DOCS`) and are served on demand via the `operation_help` tool. `tests/test_operation_help.py::test_tool_docstring_budget` enforces a total budget (< 14,000 chars across all tools); it fails if docstrings creep back up.
|
|
182
|
+
|
|
183
|
+
## Adding a New MCP Tool
|
|
184
|
+
|
|
185
|
+
1. Add the operation function in `src/cadpilot/operations/core.py`.
|
|
186
|
+
2. Export it from `src/cadpilot/operations/__init__.py`.
|
|
187
|
+
3. Add the `@mcp.tool()` handler in `src/cadpilot/server.py` that calls the operation.
|
|
188
|
+
4. Add the corresponding RPC method in `addon/CADPilot/rpc_server/rpc_server.py` (on the `FreeCADRPC` class).
|
|
189
|
+
5. Add the client proxy method in `src/cadpilot/freecad_client.py`.
|
|
190
|
+
6. If the tool touches the document/GUI, dispatch via `dispatch_to_gui()` in the addon.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v0.4.0 (2026-07-30)
|
|
4
|
+
|
|
5
|
+
### Renamed to CADPilot
|
|
6
|
+
|
|
7
|
+
The project has been renamed from `freecad-mcp` to **CADPilot** (AI pilots FreeCAD). All identifiers updated:
|
|
8
|
+
|
|
9
|
+
- PyPI package: `freecad-mcp` → `cadpilot`
|
|
10
|
+
- Python module: `freecad_mcp` → `cadpilot`
|
|
11
|
+
- CLI command: `uvx freecad-mcp` → `uvx cadpilot`
|
|
12
|
+
- FreeCAD addon directory: `FreeCADMCP` → `CADPilot`
|
|
13
|
+
- Workbench name: "FreeCAD MCP" → "CADPilot"
|
|
14
|
+
- Environment variable: `FREECAD_MCP_HOME` → `CADPILOT_HOME`
|
|
15
|
+
- Data directory: `~/.freecad-mcp` → `~/.cadpilot`
|
|
16
|
+
- Settings file: `freecad_mcp_settings.json` → `cadpilot_settings.json`
|
|
17
|
+
- Logger name: `FreeCADMCPserver` → `CADPilot`
|
|
18
|
+
|
|
19
|
+
### New features (since v0.3.0)
|
|
20
|
+
|
|
21
|
+
- `datum_plane` and `hull` feature operations in `cad()`
|
|
22
|
+
- Assembly session with persistent joints (FreeCAD 1.1 Assembly workbench)
|
|
23
|
+
- Connectivity auto-audit after every `cad()` mutation
|
|
24
|
+
- Declarative priority trimming (`trim={"winner": ...}`)
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **`execute_code` namespace pollution**: user code now runs in a copy of the
|
|
29
|
+
addon module's globals (same as `execute_code_async`) — assignments can no
|
|
30
|
+
longer corrupt the RPC server's own namespace across calls.
|
|
31
|
+
- **Assembly state robustness** (`assembly_state.py`): `load()` returns `None`
|
|
32
|
+
on missing/corrupt/structurally-invalid session files instead of raising
|
|
33
|
+
(consistent with `session_state.load_session`); `save()` is now atomic
|
|
34
|
+
(tmp + replace) so a failed write can't truncate a saved session; the
|
|
35
|
+
`_current` registry is guarded by a lock.
|
|
36
|
+
- **`assembly_session` RPC error handling** (`operations/assembly.py`):
|
|
37
|
+
`start` / `add_component` / `mate` / `unmate` / `rollback` now check the
|
|
38
|
+
addon result for `{"success": false}` before consuming fields — a failed
|
|
39
|
+
RPC returns the addon's error message and records nothing (previously
|
|
40
|
+
crashed with `KeyError` after a half-mutated state). `mate` no longer
|
|
41
|
+
crashes when the result carries trim data but the call passed no `trim`.
|
|
42
|
+
- **Dev tooling**: the ruff config parsed invalidly (`[tool.ruff.format]
|
|
43
|
+
line-length`), silently disabling all lint/format runs; fixed and the whole
|
|
44
|
+
tree re-linted/reformatted (136 findings resolved).
|
|
45
|
+
|
|
46
|
+
## v0.3.0
|
|
47
|
+
|
|
48
|
+
### New features
|
|
49
|
+
|
|
50
|
+
- **Constrained sketches + PartDesign** — eight new `cad()` feature ops enabling
|
|
51
|
+
the full "variables → sketch → solid → dress-up" parametric chain:
|
|
52
|
+
- `variables` — create/update a Spreadsheet parameter table (`cells: {"A1": [alias, value]}`,
|
|
53
|
+
idempotent); everything downstream binds to it via `=Spreadsheet.alias`.
|
|
54
|
+
- `sketch` — atomic constrained sketch (`Sketcher::SketchObject` inside a
|
|
55
|
+
PartDesign Body, auto-created when absent). `geometry` (line/arc/circle/bspline/point;
|
|
56
|
+
list order = GeoId) + `constraints` (coincident, horizontal, vertical, tangent,
|
|
57
|
+
perpendicular, parallel, equal, symmetric, distance, distance_x, distance_y,
|
|
58
|
+
radius, angle) are applied in one transaction and solved immediately.
|
|
59
|
+
Point references are `[geo_id, "start"|"end"|"center"|"mid"]`; `[-1, *]` is the
|
|
60
|
+
origin. `plane`: "XY"/"XZ"/"YZ" (+`offset`) or `{"face": [obj, "FaceN"]}`.
|
|
61
|
+
Dimensional values accept `=expressions`. Results report `dof` /
|
|
62
|
+
`fully_constrained`; under-constrained sketches succeed with a warning,
|
|
63
|
+
conflicting/failed ones roll back with solver diagnostics
|
|
64
|
+
(`ConflictingConstraints`, `RedundantConstraints`).
|
|
65
|
+
- `pad` / `pocket` — PartDesign extrusion from a sketch (closed profile
|
|
66
|
+
enforced), `length`/`reversed`/`midplane`.
|
|
67
|
+
- `revolution` / `groove` — PartDesign revolve, `axis` ("X"/"Y"/"Z" sketch axes
|
|
68
|
+
or `{"edge": [obj, "EdgeN"]}`) and `angle`.
|
|
69
|
+
- `thickness` / `draft` — dress-up ops; FreeCAD ≥1.1 LinkSub `Base` and
|
|
70
|
+
≤1.0 `Faces` property layouts both supported. `pull_direction` takes
|
|
71
|
+
`{"edge": [obj, "EdgeN"]}`.
|
|
72
|
+
- Live verification script: `scripts/live_sketch_verify.py` (runs against a live
|
|
73
|
+
FreeCAD; builds a parametric bracket and checks volumes, expression
|
|
74
|
+
propagation, failure diagnostics, and undo).
|
|
75
|
+
|
|
76
|
+
## v0.2.0 (2026-07-30)
|
|
77
|
+
|
|
78
|
+
### Breaking changes
|
|
79
|
+
|
|
80
|
+
- Four standalone tools are merged into a single unified **`cad()`** tool
|
|
81
|
+
(nsforge `math()`-style dispatcher, reduces resident tool context):
|
|
82
|
+
- `create_object` → `cad(operation="create_object", doc_name, obj_type, obj_name, ...)`
|
|
83
|
+
- `edit_object` → `cad(operation="edit_object", ...)`
|
|
84
|
+
- `delete_object` → `cad(operation="delete_object", ...)`
|
|
85
|
+
- `execute_operations` → `cad(operation="batch", ops=[...])`
|
|
86
|
+
- **Removed** (modeling-only scope):
|
|
87
|
+
- `run_fem_analysis` and all `Fem::` object creation support
|
|
88
|
+
(addon `fem_executor.py` gone; no CalculiX/Gmsh dependency)
|
|
89
|
+
- `get_parts_list` and `insert_part_from_library` (addon `parts_library.py` gone)
|
|
90
|
+
- `reload_document`
|
|
91
|
+
- **RPC response format**: `get_objects`, `get_object`, and `list_documents` now
|
|
92
|
+
return `{"success": true, "objects"/"object"/"documents": ...}` instead of bare
|
|
93
|
+
lists/dicts. The MCP client (`freecad_client.py`) handles the conversion
|
|
94
|
+
transparently, so MCP tool callers see the same data — but direct XML-RPC
|
|
95
|
+
consumers must adapt.
|
|
96
|
+
- Clients calling the removed/merged tools must switch to `cad()`.
|
|
97
|
+
|
|
98
|
+
### New features
|
|
99
|
+
|
|
100
|
+
- **Modeling sessions**: `session_start` / `session_status` / `session_get_steps` /
|
|
101
|
+
`session_rollback` / `session_redo` / `session_add_note` / `session_pause` /
|
|
102
|
+
`session_resume` / `session_list` / `session_complete`.
|
|
103
|
+
- Every committed mutation runs inside a FreeCAD transaction; `session_rollback`
|
|
104
|
+
maps to native `doc.undo()` with log truncation, `session_redo` mirrors FreeCAD
|
|
105
|
+
redo semantics (a new step clears the redo buffer).
|
|
106
|
+
- `execute_code` steps are non-atomic and block rollback unless `force=True`.
|
|
107
|
+
- Sessions persist as JSON under `$CADPILOT_HOME/sessions/` (default
|
|
108
|
+
`~/.cadpilot/sessions/`).
|
|
109
|
+
- **Pattern memory**: `save_pattern` / `recall_patterns` — successful workflows can be
|
|
110
|
+
stored and retrieved by keyword search (CJK-safe substring matching).
|
|
111
|
+
`session_complete` can archive a session as a pattern.
|
|
112
|
+
- **Runtime introspection**: `inspect_freecad` — inspect an object's properties/methods
|
|
113
|
+
or a dotted-name API docstring without leaving the session.
|
|
114
|
+
|
|
115
|
+
### Fixed
|
|
116
|
+
|
|
117
|
+
- **Double coordinate transform in geometry queries**: FreeCAD Shapes carry the
|
|
118
|
+
object's Placement as their internal location, so `BoundBox`, `CenterOfMass`,
|
|
119
|
+
`Vertex.Point`, `Face.Surface` (Axis/Center) and `Face.normalAt` already return
|
|
120
|
+
GLOBAL coordinates. `measure_geometry` / `get_topology` / `get_positioning_info`
|
|
121
|
+
applied `obj.Placement` a second time, returning wrong positions/normals/axes for
|
|
122
|
+
any moved or rotated object (e.g. a rotated fuselage reported its bbox along -Z).
|
|
123
|
+
All manual placement transforms removed; `placement.rotation.angle_deg` now
|
|
124
|
+
actually reports degrees (was radians).
|
|
125
|
+
- **`align_shapes` radian/degree bug**: `Face.getAngle()` returns radians but
|
|
126
|
+
`FreeCAD.Rotation(axis, angle)` expects degrees — touch/axis modes rotated by a
|
|
127
|
+
far-too-small angle. Fixed with `math.degrees()`.
|
|
128
|
+
- **Expression binding**: in `cad()` create/edit `obj_properties`, string values starting with `=` are bound via the ExpressionEngine (`obj.setExpression`) instead of assigned literally — Spreadsheet-driven parametric design without new tools.
|
|
129
|
+
- **Feature operations**: `cad()` gains `boolean`/`fillet`/`chamfer`/`loft`/`sweep`/`mirror`/`pattern` — parametric FreeCAD objects (transactional, rollback-able), with edge/face selectors ("all" / indices / names) fed by `get_topology`.
|
|
130
|
+
- **Geometry sensing**: `measure_geometry` (volume/area/bbox/center of mass/validity), `get_topology` (paginated faces/edges/vertices with semantic info for selection), `check_interference` (distance + common volume) — quantitative feedback after each modeling step.
|
|
131
|
+
- **Spatial positioning** (the hardest problem in AI-driven CAD assembly):
|
|
132
|
+
- `cad(operation="move")` — relative translate/rotate on top of current Placement
|
|
133
|
+
(solves ~80% of positioning needs without manual coordinate math).
|
|
134
|
+
- `get_positioning_info` — global-coordinate spatial data for a specific face/edge/vertex
|
|
135
|
+
(center, normal, axis, radius, start/end points — all transformed by the object's Placement).
|
|
136
|
+
- `align_shapes` — move an object so one of its elements aligns with a target element on
|
|
137
|
+
another object. Modes: `"touch"` (face-to-face contact, normals opposed), `"center"`
|
|
138
|
+
(center-to-center), `"axis"` (cylindrical axis alignment). Optional `offset` for gap/overlap.
|
|
139
|
+
- **Global coordinates everywhere**: `measure_geometry` now returns bounding box and center of
|
|
140
|
+
mass in global coordinates (applies Placement transform). `get_topology` face/edge/vertex
|
|
141
|
+
entries now include global-coordinate data: face `radius`/`axis` (cylindrical/conical/spherical),
|
|
142
|
+
edge `start`/`end` vertices and `radius`/`axis` (circular), vertex global position.
|
|
143
|
+
- **Guidance**: mutation responses include lightweight `display_text` suggestions and
|
|
144
|
+
risk warnings (state drift after rollback, non-atomic steps, document closed).
|
|
145
|
+
|
|
146
|
+
### Bug fixes
|
|
147
|
+
|
|
148
|
+
- **Consistent edge schema**: closed (full-circle) edges in `get_topology` /
|
|
149
|
+
`get_positioning_info` now always carry an `end` point (equal to `start`) —
|
|
150
|
+
previously the key was absent for single-vertex edges, breaking callers that
|
|
151
|
+
iterate `start`/`end` uniformly.
|
|
152
|
+
- **`align_shapes` silent offset**: `offset` is only meaningful in `touch` mode;
|
|
153
|
+
passing a non-zero offset in `center`/`axis` mode now returns a `warning`
|
|
154
|
+
field instead of silently ignoring it.
|
|
155
|
+
- **Dead code**: removed an always-overwritten placement computation in
|
|
156
|
+
`_build_move` (`feature_ops.py`).
|
|
157
|
+
- **Null shape serialization**: `serialize_shape` now checks `shape.isNull()` in
|
|
158
|
+
addition to `shape is None`, preventing `AttributeError` on objects whose Shape
|
|
159
|
+
property exists but is a null OCCT handle.
|
|
160
|
+
- **Mirror feature type**: `_build_mirror` now tries `Part::Mirroring` first and
|
|
161
|
+
falls back to `Part::Mirror` only on type-not-found errors, with a clear
|
|
162
|
+
`ValueError` if neither exists — no more silent `TypeError` on FreeCAD builds
|
|
163
|
+
that only ship one of the two.
|
|
164
|
+
- **Thread safety**: `_now()` in `session_state.py` wrapped with a `threading.Lock`
|
|
165
|
+
to prevent rare timestamp collisions in concurrent session operations.
|
|
166
|
+
- **Object name normalization**: `_normalize_object_names()` handles both string
|
|
167
|
+
and dict elements from different RPC code paths, fixing `objects_after`
|
|
168
|
+
fingerprint mismatches in session steps.
|
|
169
|
+
- **Interference threshold**: `check_interference` common-volume threshold raised
|
|
170
|
+
from `1e-7` to `1e-4` mm³ to avoid false positives from floating-point noise.
|
|
171
|
+
- **Face normals**: `get_topology` now computes normals for ALL face types (not
|
|
172
|
+
just `Plane`), using `face.normalAt()` at the face center.
|
|
173
|
+
|
|
174
|
+
### Improvements
|
|
175
|
+
|
|
176
|
+
- **Screenshot policy**: screenshots are opt-in per call (`with_screenshot`).
|
|
177
|
+
Precedence: `--only-text-feedback` (hard off) > per-call `with_screenshot` >
|
|
178
|
+
`--with-screenshots` (default-on). Screenshots are capped at 768px on the long edge
|
|
179
|
+
by default to save tokens.
|
|
180
|
+
- **Inline screenshots**: `execute_code` and `create_document` now capture
|
|
181
|
+
screenshots in the same GUI dispatch (single RPC round trip) instead of a
|
|
182
|
+
separate `get_active_screenshot` call, halving latency for screenshot-enabled
|
|
183
|
+
workflows.
|
|
184
|
+
- **Stability**: read-only RPCs (`get_objects`, `get_object`, `list_documents`) now
|
|
185
|
+
dispatch onto the FreeCAD GUI thread; the XML-RPC client retries once on
|
|
186
|
+
recoverable connection errors.
|
|
187
|
+
- **Merged RPC**: mutations and their optional screenshot are fetched in a single
|
|
188
|
+
XML-RPC round trip; mutation results include an `objects` fingerprint (sorted
|
|
189
|
+
object names) used for drift detection.
|
|
190
|
+
- **Batch single-recompute**: `cad(operation="batch", ...)` now skips per-object
|
|
191
|
+
`doc.recompute()` and performs a single recompute after all ops, significantly
|
|
192
|
+
faster for large batches.
|
|
193
|
+
- **Boolean multi-tool**: `cad(operation="boolean", tool=["Obj1","Obj2",...])`
|
|
194
|
+
now accepts a list of tool objects — they are fused into a temporary compound
|
|
195
|
+
before the boolean operation, enabling multi-body cuts/fuses in one step.
|
|
196
|
+
- **ViewObject serialization**: extended with `DisplayMode`, `LineColor`,
|
|
197
|
+
`PointSize`, `LineWidth`, and `DrawStyle` properties for richer visual feedback.
|
|
198
|
+
- **Compatibility**: the new MCP server falls back gracefully against older addons
|
|
199
|
+
(single-shot screenshot RPC, optional params); older clients keep working against
|
|
200
|
+
the new addon.
|
|
201
|
+
- Tests: pytest suite (119 tests) covering responses, operations, reconnect, session
|
|
202
|
+
state, pattern store, guidance, cad() dispatch, name normalization, spatial
|
|
203
|
+
positioning (move, get_positioning_info, align_shapes), and global-coordinate
|
|
204
|
+
topology queries.
|
cadpilot-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Shirokuma (k tanaka)
|
|
4
|
+
Copyright (c) 2026 LBurny
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|