57466b4ba2
- server.py: FastMCP-Server mit render_stl, preview_png, list_models - README.md: Ausführliche Installationsanleitung (DE) - .gitignore: Schließt .venv, STL/PNG-Ausgaben und Cache aus Stack: cadquery 2.7.0, mcp 1.26.0, cq-editor 0.6.2, Python 3.13 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
124 lines
3.5 KiB
Python
124 lines
3.5 KiB
Python
"""
|
|
CadQuery MCP Server
|
|
Ermöglicht Claude, CadQuery-Python-Code auszuführen und STL-Dateien zu erzeugen.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import base64
|
|
import traceback
|
|
from pathlib import Path
|
|
from mcp.server.fastmcp import FastMCP
|
|
import cadquery as cq
|
|
from cadquery import exporters
|
|
|
|
OUTPUT_DIR = Path(os.environ.get("CADQUERY_OUTPUT_DIR", Path.home() / "3d-models"))
|
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
mcp = FastMCP("cadquery")
|
|
|
|
|
|
def _run_cadquery(code: str) -> cq.Workplane:
|
|
"""Führt CadQuery-Code aus und gibt das Ergebnis zurück."""
|
|
captured = {}
|
|
|
|
def show_object(obj, name="result", options=None):
|
|
captured["result"] = obj
|
|
|
|
def show(obj, name="result", options=None):
|
|
captured["result"] = obj
|
|
|
|
namespace = {
|
|
"cq": cq,
|
|
"cadquery": cq,
|
|
"show_object": show_object,
|
|
"show": show,
|
|
}
|
|
|
|
exec(code, namespace) # noqa: S102
|
|
|
|
if "result" in captured:
|
|
return captured["result"]
|
|
|
|
# Letzte Variable suchen, die ein Workplane oder Shape ist
|
|
for val in reversed(list(namespace.values())):
|
|
if isinstance(val, (cq.Workplane, cq.Shape, cq.Assembly)):
|
|
return val
|
|
|
|
raise ValueError(
|
|
"Kein CadQuery-Objekt gefunden. "
|
|
"Bitte 'show_object(result)' am Ende des Codes aufrufen "
|
|
"oder das Ergebnis in eine Variable namens 'result' speichern."
|
|
)
|
|
|
|
|
|
@mcp.tool()
|
|
def render_stl(code: str, filename: str = "model.stl") -> str:
|
|
"""
|
|
Rendert CadQuery-Python-Code als STL-Datei für den 3D-Drucker.
|
|
|
|
Args:
|
|
code: Gültiger CadQuery-Python-Code
|
|
filename: Dateiname für die STL-Ausgabe (z.B. "halter.stl")
|
|
|
|
Returns:
|
|
Pfad zur erzeugten STL-Datei oder Fehlermeldung
|
|
"""
|
|
if not filename.endswith(".stl"):
|
|
filename += ".stl"
|
|
|
|
output_path = OUTPUT_DIR / filename
|
|
|
|
try:
|
|
result = _run_cadquery(code)
|
|
exporters.export(result, str(output_path))
|
|
size_kb = output_path.stat().st_size // 1024
|
|
return f"STL erfolgreich erstellt: {output_path}\nDateigröße: {size_kb} KB"
|
|
except Exception as e:
|
|
return f"Fehler beim Rendern:\n{traceback.format_exc()}\nBitte CadQuery-Code prüfen."
|
|
|
|
|
|
@mcp.tool()
|
|
def preview_png(code: str, filename: str = "preview.png") -> str:
|
|
"""
|
|
Rendert eine PNG-Vorschau des CadQuery-Modells.
|
|
|
|
Args:
|
|
code: Gültiger CadQuery-Python-Code
|
|
filename: Dateiname für die PNG-Ausgabe
|
|
|
|
Returns:
|
|
Base64-kodiertes PNG-Bild oder Fehlermeldung
|
|
"""
|
|
if not filename.endswith(".png"):
|
|
filename += ".png"
|
|
|
|
svg_path = OUTPUT_DIR / filename.replace(".png", ".svg")
|
|
output_path = OUTPUT_DIR / filename
|
|
|
|
try:
|
|
result = _run_cadquery(code)
|
|
exporters.export(result, str(svg_path))
|
|
size_bytes = svg_path.stat().st_size
|
|
return f"SVG-Vorschau erstellt: {svg_path} ({size_bytes} Bytes)\n(PNG-Konvertierung erfordert zusätzliche Abhängigkeiten)"
|
|
except Exception as e:
|
|
return f"Fehler beim Rendern der Vorschau:\n{traceback.format_exc()}"
|
|
|
|
|
|
@mcp.tool()
|
|
def list_models() -> str:
|
|
"""Listet alle bisher erstellten 3D-Modelle im Output-Verzeichnis."""
|
|
files = list(OUTPUT_DIR.glob("*.stl"))
|
|
if not files:
|
|
return f"Keine STL-Dateien in {OUTPUT_DIR}"
|
|
lines = [f"Modelle in {OUTPUT_DIR}:"]
|
|
for f in sorted(files):
|
|
size_kb = f.stat().st_size // 1024
|
|
lines.append(f" {f.name} ({size_kb} KB)")
|
|
return "\n".join(lines)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
mcp.run()
|