77eb5379d2
MCP server that lets Claude generate and render OpenSCAD models to STL files. Includes setup documentation and installation guide. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
3.3 KiB
Python
110 lines
3.3 KiB
Python
"""
|
|
OpenSCAD MCP Server
|
|
Ermöglicht Claude, OpenSCAD-Code zu rendern und STL-Dateien zu erzeugen.
|
|
"""
|
|
|
|
import subprocess
|
|
import tempfile
|
|
import os
|
|
import base64
|
|
from pathlib import Path
|
|
from mcp.server.fastmcp import FastMCP
|
|
|
|
# Pfad zur OpenSCAD-Executable (Windows-Standard)
|
|
OPENSCAD_PATH = os.environ.get(
|
|
"OPENSCAD_PATH",
|
|
r"C:\Program Files\OpenSCAD\openscad.exe"
|
|
)
|
|
|
|
# Output-Verzeichnis für STL-Dateien
|
|
OUTPUT_DIR = Path(os.environ.get("OPENSCAD_OUTPUT_DIR", Path.home() / "3d-models"))
|
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
mcp = FastMCP("openscad")
|
|
|
|
|
|
def _run_openscad(scad_code: str, output_path: Path, extra_args: list = None) -> tuple[bool, str]:
|
|
"""Führt OpenSCAD mit dem gegebenen Code aus. Gibt (success, output) zurück."""
|
|
with tempfile.NamedTemporaryFile(suffix=".scad", mode="w", delete=False, encoding="utf-8") as f:
|
|
f.write(scad_code)
|
|
scad_file = f.name
|
|
|
|
try:
|
|
cmd = [OPENSCAD_PATH, "-o", str(output_path)] + (extra_args or []) + [scad_file]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
output = (result.stdout + result.stderr).strip()
|
|
success = result.returncode == 0 and output_path.exists()
|
|
return success, output
|
|
finally:
|
|
os.unlink(scad_file)
|
|
|
|
|
|
@mcp.tool()
|
|
def render_stl(code: str, filename: str = "model.stl") -> str:
|
|
"""
|
|
Rendert OpenSCAD-Code als STL-Datei für den 3D-Drucker.
|
|
|
|
Args:
|
|
code: Gültiger OpenSCAD-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
|
|
success, output = _run_openscad(code, output_path)
|
|
|
|
if success:
|
|
size_kb = output_path.stat().st_size // 1024
|
|
return f"STL erfolgreich erstellt: {output_path}\nDateigröße: {size_kb} KB\nOpenSCAD-Output: {output or '(kein Output)'}"
|
|
else:
|
|
return f"Fehler beim Rendern:\n{output}\n\nBitte OpenSCAD-Code prüfen."
|
|
|
|
|
|
@mcp.tool()
|
|
def preview_png(code: str, filename: str = "preview.png") -> str:
|
|
"""
|
|
Rendert eine PNG-Vorschau des OpenSCAD-Modells.
|
|
|
|
Args:
|
|
code: Gültiger OpenSCAD-Code
|
|
filename: Dateiname für die PNG-Ausgabe
|
|
|
|
Returns:
|
|
Base64-kodiertes PNG-Bild oder Fehlermeldung
|
|
"""
|
|
if not filename.endswith(".png"):
|
|
filename += ".png"
|
|
|
|
output_path = OUTPUT_DIR / filename
|
|
# --camera=0,0,0,55,0,25,140 = isometrische Ansicht
|
|
extra = ["--camera=0,0,0,55,0,25,140", "--imgsize=800,600", "--colorscheme=DeepOcean"]
|
|
success, output = _run_openscad(code, output_path, extra_args=extra)
|
|
|
|
if success:
|
|
with open(output_path, "rb") as f:
|
|
img_b64 = base64.b64encode(f.read()).decode()
|
|
return f"data:image/png;base64,{img_b64}"
|
|
else:
|
|
return f"Fehler beim Rendern der Vorschau:\n{output}"
|
|
|
|
|
|
@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()
|