cura-mcp 0.1.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.
- cura_mcp-0.1.0/.gitignore +47 -0
- cura_mcp-0.1.0/PKG-INFO +58 -0
- cura_mcp-0.1.0/README.md +40 -0
- cura_mcp-0.1.0/pyproject.toml +48 -0
- cura_mcp-0.1.0/src/cura_mcp/__init__.py +3 -0
- cura_mcp-0.1.0/src/cura_mcp/client.py +59 -0
- cura_mcp-0.1.0/src/cura_mcp/config.py +42 -0
- cura_mcp-0.1.0/src/cura_mcp/errors.py +117 -0
- cura_mcp-0.1.0/src/cura_mcp/models.py +357 -0
- cura_mcp-0.1.0/src/cura_mcp/server.py +142 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/__init__.py +1 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/arrange_all.py +19 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/center_model.py +18 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/clear_plate.py +20 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/duplicate_model.py +21 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/estimates.py +20 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/export_gcode.py +19 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/export_model.py +24 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/get_all_user_settings.py +19 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/get_machine_info.py +18 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/get_model_settings.py +18 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/get_setting.py +19 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/get_snapshot.py +23 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/group_models.py +26 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/list_machines.py +16 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/list_materials.py +18 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/list_models.py +20 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/list_variants.py +18 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/load_model.py +21 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/merge_models.py +25 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/mirror_model.py +18 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/move_model.py +31 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/open_project.py +38 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/orientation.py +22 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/remove_model.py +20 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/reset_all_settings.py +20 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/reset_model_setting.py +19 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/reset_setting.py +18 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/rotate.py +20 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/save_project.py +21 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/scale_model.py +31 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/scale_to_fit.py +18 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/select_model.py +19 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/set_adhesion.py +16 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/set_infill_density.py +16 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/set_layer_height.py +16 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/set_mesh_type.py +23 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/set_model_setting.py +25 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/set_quality.py +18 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/set_setting.py +23 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/set_supports.py +21 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/slice.py +25 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/status.py +23 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/switch_machine.py +18 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/switch_material.py +18 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/switch_variant.py +20 -0
- cura_mcp-0.1.0/src/cura_mcp/tools/ungroup_model.py +19 -0
- cura_mcp-0.1.0/tests/__init__.py +0 -0
- cura_mcp-0.1.0/tests/test_bridge.py +197 -0
- cura_mcp-0.1.0/uv.lock +1283 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# --- Project-specific ---
|
|
2
|
+
# Internal build specs: local reference for the implementation agent, never shipped.
|
|
3
|
+
specs/
|
|
4
|
+
docs/
|
|
5
|
+
# Local agent/dev context (implementation guide for the coding agent), never shipped.
|
|
6
|
+
CLAUDE.md
|
|
7
|
+
# Local dogfooding instrumentation (Layer-1 logger + local bridge entrypoint): kept
|
|
8
|
+
# on disk for local use, never published. The public cura_mcp package has no
|
|
9
|
+
# reference to it (see cura_dogfood/bridge.py).
|
|
10
|
+
cura_dogfood/
|
|
11
|
+
# Local auth token written by the Cura plugin at runtime (see SECURITY).
|
|
12
|
+
*.cura-mcp-token
|
|
13
|
+
.cura-mcp/
|
|
14
|
+
|
|
15
|
+
# --- Python ---
|
|
16
|
+
__pycache__/
|
|
17
|
+
*.py[cod]
|
|
18
|
+
*$py.class
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.eggs/
|
|
21
|
+
build/
|
|
22
|
+
dist/
|
|
23
|
+
.venv/
|
|
24
|
+
venv/
|
|
25
|
+
env/
|
|
26
|
+
.pytest_cache/
|
|
27
|
+
.mypy_cache/
|
|
28
|
+
.ruff_cache/
|
|
29
|
+
.coverage
|
|
30
|
+
htmlcov/
|
|
31
|
+
.tox/
|
|
32
|
+
|
|
33
|
+
# --- Editors / OS ---
|
|
34
|
+
.idea/
|
|
35
|
+
.vscode/
|
|
36
|
+
*.swp
|
|
37
|
+
.DS_Store
|
|
38
|
+
Thumbs.db
|
|
39
|
+
|
|
40
|
+
# --- Logs / local artifacts ---
|
|
41
|
+
logs/
|
|
42
|
+
*.log
|
|
43
|
+
*.gcode
|
|
44
|
+
*.stl
|
|
45
|
+
*.3mf
|
|
46
|
+
!examples/**/*.stl
|
|
47
|
+
!examples/**/*.3mf
|
cura_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cura-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local MCP server that drives the UltiMaker Cura slicer. No hub, no telemetry.
|
|
5
|
+
Author-email: padymies <padymies@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: 3d-printing,cura,mcp,model-context-protocol,slicer
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: httpx>=0.27
|
|
10
|
+
Requires-Dist: mcp>=1.2.0
|
|
11
|
+
Requires-Dist: pydantic>=2.6
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# cura-mcp (bridge)
|
|
20
|
+
|
|
21
|
+
The MCP server. Speaks MCP to your AI client and forwards each tool call to the
|
|
22
|
+
Cura plugin over loopback HTTP. Knows nothing about Cura internals — it is pure
|
|
23
|
+
transport, schemas, and error mapping.
|
|
24
|
+
|
|
25
|
+
## Run (users)
|
|
26
|
+
|
|
27
|
+
Users don't install this directly — their MCP client launches it via
|
|
28
|
+
[`uv`](https://docs.astral.sh/uv/): `uvx cura-mcp` (see the top-level
|
|
29
|
+
[`README`](../README.md#install)). `uv` brings its own Python, so no separate
|
|
30
|
+
Python install is required.
|
|
31
|
+
|
|
32
|
+
## Install (dev)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cd mcp-server
|
|
36
|
+
pip install -e ".[dev]"
|
|
37
|
+
python -m cura_mcp.server
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Requires Cura running with the `cura-plugin` installed (the bridge fails fast
|
|
41
|
+
with a clear error otherwise). Configuration via env vars — see `config.py`:
|
|
42
|
+
|
|
43
|
+
- `CURA_MCP_HOST` (default `127.0.0.1`)
|
|
44
|
+
- `CURA_MCP_PORT` (default `8765`)
|
|
45
|
+
- `CURA_MCP_TOKEN_FILE` (default: platform user dir `~/.cura-mcp/token`)
|
|
46
|
+
- `CURA_MCP_TIMEOUT` (default `30` seconds; slice waits use a longer timeout)
|
|
47
|
+
|
|
48
|
+
## Layout
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
src/cura_mcp/
|
|
52
|
+
server.py MCP entrypoint; registers tools
|
|
53
|
+
client.py HTTP client to the plugin (token-authenticated)
|
|
54
|
+
models.py pydantic schemas (tool I/O + plugin contract)
|
|
55
|
+
config.py settings
|
|
56
|
+
errors.py typed error hierarchy
|
|
57
|
+
tools/ one module per tool
|
|
58
|
+
```
|
cura_mcp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# cura-mcp (bridge)
|
|
2
|
+
|
|
3
|
+
The MCP server. Speaks MCP to your AI client and forwards each tool call to the
|
|
4
|
+
Cura plugin over loopback HTTP. Knows nothing about Cura internals — it is pure
|
|
5
|
+
transport, schemas, and error mapping.
|
|
6
|
+
|
|
7
|
+
## Run (users)
|
|
8
|
+
|
|
9
|
+
Users don't install this directly — their MCP client launches it via
|
|
10
|
+
[`uv`](https://docs.astral.sh/uv/): `uvx cura-mcp` (see the top-level
|
|
11
|
+
[`README`](../README.md#install)). `uv` brings its own Python, so no separate
|
|
12
|
+
Python install is required.
|
|
13
|
+
|
|
14
|
+
## Install (dev)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd mcp-server
|
|
18
|
+
pip install -e ".[dev]"
|
|
19
|
+
python -m cura_mcp.server
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Requires Cura running with the `cura-plugin` installed (the bridge fails fast
|
|
23
|
+
with a clear error otherwise). Configuration via env vars — see `config.py`:
|
|
24
|
+
|
|
25
|
+
- `CURA_MCP_HOST` (default `127.0.0.1`)
|
|
26
|
+
- `CURA_MCP_PORT` (default `8765`)
|
|
27
|
+
- `CURA_MCP_TOKEN_FILE` (default: platform user dir `~/.cura-mcp/token`)
|
|
28
|
+
- `CURA_MCP_TIMEOUT` (default `30` seconds; slice waits use a longer timeout)
|
|
29
|
+
|
|
30
|
+
## Layout
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
src/cura_mcp/
|
|
34
|
+
server.py MCP entrypoint; registers tools
|
|
35
|
+
client.py HTTP client to the plugin (token-authenticated)
|
|
36
|
+
models.py pydantic schemas (tool I/O + plugin contract)
|
|
37
|
+
config.py settings
|
|
38
|
+
errors.py typed error hierarchy
|
|
39
|
+
tools/ one module per tool
|
|
40
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cura-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Local MCP server that drives the UltiMaker Cura slicer. No hub, no telemetry."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "padymies", email = "padymies@gmail.com" }]
|
|
13
|
+
keywords = ["mcp", "cura", "3d-printing", "slicer", "model-context-protocol"]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"mcp>=1.2.0",
|
|
16
|
+
"httpx>=0.27",
|
|
17
|
+
"pydantic>=2.6",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
dev = [
|
|
22
|
+
"ruff>=0.6",
|
|
23
|
+
"mypy>=1.10",
|
|
24
|
+
"pytest>=8.0",
|
|
25
|
+
"pytest-asyncio>=0.23",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
cura-mcp = "cura_mcp.server:main"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["src/cura_mcp"]
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
line-length = 100
|
|
36
|
+
src = ["src", "tests"]
|
|
37
|
+
|
|
38
|
+
[tool.ruff.lint]
|
|
39
|
+
select = ["E", "F", "I", "B", "UP", "ASYNC"]
|
|
40
|
+
|
|
41
|
+
[tool.mypy]
|
|
42
|
+
python_version = "3.10"
|
|
43
|
+
strict = true
|
|
44
|
+
files = ["src"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
testpaths = ["tests"]
|
|
48
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""HTTP client to the Cura plugin's local server.
|
|
2
|
+
|
|
3
|
+
Reads the per-session token written by the plugin, injects it on every request,
|
|
4
|
+
and maps the plugin's structured error envelope back to typed exceptions. This is
|
|
5
|
+
the only place the bridge talks to the plugin.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from .config import Settings
|
|
14
|
+
from .errors import CuraNotRunning, from_plugin_code
|
|
15
|
+
from .models import PluginResponse
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PluginClient:
|
|
19
|
+
def __init__(self, settings: Settings) -> None:
|
|
20
|
+
self._settings = settings
|
|
21
|
+
self._client = httpx.AsyncClient(base_url=settings.base_url, timeout=settings.timeout)
|
|
22
|
+
|
|
23
|
+
def _read_token(self) -> str:
|
|
24
|
+
try:
|
|
25
|
+
return self._settings.token_file.read_text(encoding="utf-8").strip()
|
|
26
|
+
except OSError as exc:
|
|
27
|
+
raise CuraNotRunning(
|
|
28
|
+
"No Cura plugin token found. Is Cura running with the cura-mcp plugin?"
|
|
29
|
+
) from exc
|
|
30
|
+
|
|
31
|
+
async def call(
|
|
32
|
+
self,
|
|
33
|
+
method: str,
|
|
34
|
+
payload: dict[str, Any] | None = None,
|
|
35
|
+
*,
|
|
36
|
+
timeout: float | None = None,
|
|
37
|
+
) -> dict[str, Any]:
|
|
38
|
+
"""Call a plugin method; return ``data`` on success, raise a typed error otherwise."""
|
|
39
|
+
token = self._read_token()
|
|
40
|
+
try:
|
|
41
|
+
resp = await self._client.post(
|
|
42
|
+
"/rpc",
|
|
43
|
+
json={"method": method, "params": payload or {}},
|
|
44
|
+
headers={"X-Cura-Mcp-Token": token, "Host": self._settings.host},
|
|
45
|
+
timeout=timeout or self._settings.timeout,
|
|
46
|
+
)
|
|
47
|
+
except httpx.ConnectError as exc:
|
|
48
|
+
raise CuraNotRunning(
|
|
49
|
+
"Could not reach the Cura plugin server. Is Cura open?"
|
|
50
|
+
) from exc
|
|
51
|
+
|
|
52
|
+
body = PluginResponse.model_validate(resp.json())
|
|
53
|
+
if body.ok:
|
|
54
|
+
return body.data or {}
|
|
55
|
+
assert body.error is not None
|
|
56
|
+
raise from_plugin_code(body.error.code, body.error.message)
|
|
57
|
+
|
|
58
|
+
async def aclose(self) -> None:
|
|
59
|
+
await self._client.aclose()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Bridge configuration. All values overridable via environment variables.
|
|
2
|
+
|
|
3
|
+
The token-file path is the shared contract with the plugin: the plugin writes
|
|
4
|
+
the per-session token there, the bridge reads it. Keep both sides in sync.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _default_token_file() -> Path:
|
|
14
|
+
# Mirrors the plugin's token location (see cura-plugin/server/auth.py).
|
|
15
|
+
return Path.home() / ".cura-mcp" / "token"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class Settings:
|
|
20
|
+
host: str = os.environ.get("CURA_MCP_HOST", "127.0.0.1")
|
|
21
|
+
port: int = int(os.environ.get("CURA_MCP_PORT", "8765"))
|
|
22
|
+
token_file: Path = Path(os.environ.get("CURA_MCP_TOKEN_FILE", "")) or _default_token_file()
|
|
23
|
+
# General request timeout (seconds). Slicing uses slice_timeout instead.
|
|
24
|
+
timeout: float = float(os.environ.get("CURA_MCP_TIMEOUT", "30"))
|
|
25
|
+
slice_timeout: float = float(os.environ.get("CURA_MCP_SLICE_TIMEOUT", "300"))
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def base_url(self) -> str:
|
|
29
|
+
return f"http://{self.host}:{self.port}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_settings() -> Settings:
|
|
33
|
+
# Resolve token_file explicitly (dataclass default expr runs at import time).
|
|
34
|
+
env_token = os.environ.get("CURA_MCP_TOKEN_FILE")
|
|
35
|
+
token_file = Path(env_token) if env_token else _default_token_file()
|
|
36
|
+
return Settings(
|
|
37
|
+
host=os.environ.get("CURA_MCP_HOST", "127.0.0.1"),
|
|
38
|
+
port=int(os.environ.get("CURA_MCP_PORT", "8765")),
|
|
39
|
+
token_file=token_file,
|
|
40
|
+
timeout=float(os.environ.get("CURA_MCP_TIMEOUT", "30")),
|
|
41
|
+
slice_timeout=float(os.environ.get("CURA_MCP_SLICE_TIMEOUT", "300")),
|
|
42
|
+
)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Typed error hierarchy for the bridge.
|
|
2
|
+
|
|
3
|
+
Tool functions catch these and return structured errors to the LLM rather than
|
|
4
|
+
leaking stack traces. The plugin returns an error ``code`` in its JSON response;
|
|
5
|
+
``from_plugin_code`` maps it back to the right class.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CuraMcpError(Exception):
|
|
11
|
+
"""Base class for all bridge-surfaced errors."""
|
|
12
|
+
|
|
13
|
+
code = "cura_mcp_error"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CuraNotRunning(CuraMcpError):
|
|
17
|
+
"""The plugin's local server could not be reached (Cura not open?)."""
|
|
18
|
+
|
|
19
|
+
code = "cura_not_running"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AuthError(CuraMcpError):
|
|
23
|
+
"""Token missing/invalid, or request rejected by the plugin's auth layer."""
|
|
24
|
+
|
|
25
|
+
code = "auth_error"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class InvalidPath(CuraMcpError):
|
|
29
|
+
"""load_model path failed the plugin's sandbox (allow-list / traversal / ext)."""
|
|
30
|
+
|
|
31
|
+
code = "invalid_path"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class NodeNotFound(CuraMcpError):
|
|
35
|
+
"""No model on the plate matches the given node_id."""
|
|
36
|
+
|
|
37
|
+
code = "node_not_found"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ExportFailed(CuraMcpError):
|
|
41
|
+
"""Writing the mesh to disk failed (no writer for the format, or I/O error)."""
|
|
42
|
+
|
|
43
|
+
code = "export_failed"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class UnknownSetting(CuraMcpError):
|
|
47
|
+
"""The setting key does not exist in the active machine definition."""
|
|
48
|
+
|
|
49
|
+
code = "unknown_setting"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class InvalidSettingValue(CuraMcpError):
|
|
53
|
+
"""The value is the wrong type or outside the setting's allowed range/options."""
|
|
54
|
+
|
|
55
|
+
code = "invalid_setting_value"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class PerExtruderUnsupported(CuraMcpError):
|
|
59
|
+
"""The setting is per-extruder; v1 of the settings API handles global only."""
|
|
60
|
+
|
|
61
|
+
code = "per_extruder_unsupported"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class UnknownProfile(CuraMcpError):
|
|
65
|
+
"""No machine/material/quality profile matches the given name."""
|
|
66
|
+
|
|
67
|
+
code = "unknown_profile"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class LoadFailed(CuraMcpError):
|
|
71
|
+
"""The model load did not complete (e.g. timed out or the reader failed)."""
|
|
72
|
+
|
|
73
|
+
code = "load_failed"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SliceFailed(CuraMcpError):
|
|
77
|
+
"""The slice ended in an error state or the model was unsliceable."""
|
|
78
|
+
|
|
79
|
+
code = "slice_failed"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class SliceTimeout(CuraMcpError):
|
|
83
|
+
"""The slice did not settle within the timeout."""
|
|
84
|
+
|
|
85
|
+
code = "slice_timeout"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class NoActiveProfile(CuraMcpError):
|
|
89
|
+
"""No machine/material profile is active; estimates would be meaningless."""
|
|
90
|
+
|
|
91
|
+
code = "no_active_profile"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
_BY_CODE = {
|
|
95
|
+
cls.code: cls
|
|
96
|
+
for cls in (
|
|
97
|
+
CuraMcpError,
|
|
98
|
+
CuraNotRunning,
|
|
99
|
+
AuthError,
|
|
100
|
+
InvalidPath,
|
|
101
|
+
NodeNotFound,
|
|
102
|
+
ExportFailed,
|
|
103
|
+
UnknownSetting,
|
|
104
|
+
InvalidSettingValue,
|
|
105
|
+
PerExtruderUnsupported,
|
|
106
|
+
UnknownProfile,
|
|
107
|
+
LoadFailed,
|
|
108
|
+
SliceFailed,
|
|
109
|
+
SliceTimeout,
|
|
110
|
+
NoActiveProfile,
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def from_plugin_code(code: str, message: str) -> CuraMcpError:
|
|
116
|
+
"""Build the right error subtype from a plugin error code."""
|
|
117
|
+
return _BY_CODE.get(code, CuraMcpError)(message)
|