Initial commit: OpenSCAD MCP server for Claude Code
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>
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
@@ -0,0 +1,66 @@
|
||||
# OpenSCAD MCP Server
|
||||
|
||||
LLM-gestützte 3D-Modell-Erstellung für den 3D-Drucker via Claude Code.
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. OpenSCAD installieren
|
||||
https://openscad.org/downloads.html → Windows-Installer
|
||||
|
||||
### 2. Python-Abhängigkeiten
|
||||
```bash
|
||||
pip install mcp
|
||||
```
|
||||
|
||||
### 3. MCP-Server in Claude Code registrieren
|
||||
|
||||
In Claude Code: **Settings → MCP Servers → Add** (oder direkt in `~/.claude/claude_desktop_config.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"openscad": {
|
||||
"command": "python",
|
||||
"args": ["C:/Users/thors/Documents/Claude/openscad-mcp/server.py"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Umgebungsvariablen (optional)
|
||||
| Variable | Standard | Beschreibung |
|
||||
|----------|----------|--------------|
|
||||
| `OPENSCAD_PATH` | `C:\Program Files\OpenSCAD\openscad.exe` | Pfad zur OpenSCAD-Executable |
|
||||
| `OPENSCAD_OUTPUT_DIR` | `~/3d-models` | Ausgabeverzeichnis für STL-Dateien |
|
||||
|
||||
## Verfügbare Tools
|
||||
|
||||
| Tool | Beschreibung |
|
||||
|------|--------------|
|
||||
| `render_stl(code, filename)` | OpenSCAD-Code → STL-Datei |
|
||||
| `preview_png(code, filename)` | OpenSCAD-Code → PNG-Vorschau |
|
||||
| `list_models()` | Alle STL-Dateien auflisten |
|
||||
|
||||
## Beispiel-Prompt für Claude
|
||||
|
||||
```
|
||||
Erstelle einen Wandhalter für einen 32mm Rohrdurchmesser,
|
||||
Wandstärke 3mm, mit 2 Schraubenlöchern (M4), Gesamtbreite 60mm.
|
||||
Rendere es als "rohrhalter.stl".
|
||||
```
|
||||
|
||||
## OpenSCAD Tipps für LLMs
|
||||
|
||||
Claude generiert typisch solchen Code:
|
||||
```openscad
|
||||
difference() {
|
||||
union() {
|
||||
cylinder(h=20, d=40);
|
||||
translate([0,0,10]) cube([60,10,20], center=true);
|
||||
}
|
||||
cylinder(h=22, d=32, center=true); // Loch für Rohr
|
||||
// Schraubenlöcher
|
||||
translate([20,0,0]) cylinder(h=22, d=4.5, center=true);
|
||||
translate([-20,0,0]) cylinder(h=22, d=4.5, center=true);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,277 @@
|
||||
# OpenSCAD MCP Server — Installationsanleitung
|
||||
|
||||
LLM-gestützte 3D-Modell-Erstellung: Claude generiert OpenSCAD-Code und rendert ihn
|
||||
direkt zu STL-Dateien für den 3D-Drucker.
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Windows 10/11
|
||||
- Claude Code Desktop App installiert
|
||||
- Python (Microsoft Store) — bereits installiert auf diesem System
|
||||
|
||||
---
|
||||
|
||||
## Schritt 1: OpenSCAD installieren
|
||||
|
||||
1. https://openscad.org/downloads.html aufrufen
|
||||
2. **Windows Installer** herunterladen und ausführen
|
||||
3. Standardpfad bestätigen: `C:\Program Files\OpenSCAD\`
|
||||
|
||||
Verifizieren:
|
||||
```
|
||||
"C:\Program Files\OpenSCAD\openscad.exe" --version
|
||||
```
|
||||
Erwartete Ausgabe: `OpenSCAD version 2021.01` (oder neuer)
|
||||
|
||||
---
|
||||
|
||||
## Schritt 2: Projektverzeichnis anlegen
|
||||
|
||||
```
|
||||
mkdir C:\Users\<USERNAME>\Documents\Claude\openscad-mcp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 3: Python Virtual Environment erstellen
|
||||
|
||||
> **Wichtig:** Den Microsoft Store Python verwenden (`python3.13.exe` in WindowsApps).
|
||||
> Das `python.exe` aus `C:\Users\...\AppData\Local\Programs\Python\Python313\` ist ein
|
||||
> eingebettetes Python **ohne pip** und funktioniert nicht.
|
||||
> Die `WindowsApps\python3.13.exe` ist ein App Execution Alias — funktioniert nur in
|
||||
> interaktiven Shells, **nicht** als Subprocess von Claude Code.
|
||||
> Lösung: Virtual Environment erstellen — das `.venv\Scripts\python.exe` ist eine
|
||||
> echte Executable ohne diese Einschränkung.
|
||||
|
||||
```
|
||||
cd C:\Users\<USERNAME>\Documents\Claude\openscad-mcp
|
||||
|
||||
"C:\Users\<USERNAME>\AppData\Local\Microsoft\WindowsApps\python3.13.exe" -m venv .venv
|
||||
|
||||
.venv\Scripts\pip install mcp
|
||||
```
|
||||
|
||||
Verifizieren:
|
||||
```
|
||||
.venv\Scripts\python.exe -c "from mcp.server.fastmcp import FastMCP; print('OK')"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 4: MCP-Server erstellen
|
||||
|
||||
Datei `C:\Users\<USERNAME>\Documents\Claude\openscad-mcp\server.py`:
|
||||
|
||||
```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
|
||||
|
||||
OPENSCAD_PATH = os.environ.get(
|
||||
"OPENSCAD_PATH",
|
||||
r"C:\Program Files\OpenSCAD\openscad.exe"
|
||||
)
|
||||
|
||||
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]:
|
||||
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."""
|
||||
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}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def preview_png(code: str, filename: str = "preview.png") -> str:
|
||||
"""Rendert eine PNG-Vorschau des OpenSCAD-Modells."""
|
||||
if not filename.endswith(".png"):
|
||||
filename += ".png"
|
||||
output_path = OUTPUT_DIR / filename
|
||||
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()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 5: MCP-Server testen
|
||||
|
||||
```
|
||||
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | .venv\Scripts\python.exe server.py
|
||||
```
|
||||
|
||||
Erwartete Ausgabe: JSON-Antwort mit `"name":"openscad"` — Server funktioniert.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 6: Claude Code Desktop konfigurieren
|
||||
|
||||
> **Wichtig:** Es gibt zwei Settings-Dateien. Nur eine davon wird von der Desktop App gelesen:
|
||||
>
|
||||
> | Datei | Gelesen von |
|
||||
> |-------|------------|
|
||||
> | `C:\Users\<USERNAME>\.claude\settings.json` | Claude Code **CLI** (Terminal) |
|
||||
> | `C:\Users\<USERNAME>\AppData\Roaming\Claude\claude_desktop_config.json` | Claude Code **Desktop App** |
|
||||
>
|
||||
> Da die Desktop App verwendet wird, muss der Eintrag in `claude_desktop_config.json`.
|
||||
|
||||
Datei öffnen: `C:\Users\<USERNAME>\AppData\Roaming\Claude\claude_desktop_config.json`
|
||||
|
||||
Den `mcpServers`-Block hinzufügen (**Backslashes** im Pfad beachten):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"openscad": {
|
||||
"command": "C:\\Users\\<USERNAME>\\Documents\\Claude\\openscad-mcp\\.venv\\Scripts\\python.exe",
|
||||
"args": ["C:\\Users\\<USERNAME>\\Documents\\Claude\\openscad-mcp\\server.py"]
|
||||
}
|
||||
},
|
||||
"preferences": {
|
||||
...vorhandene Einstellungen beibehalten...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 7: Claude Code neu starten
|
||||
|
||||
**Vollständig beenden** — nicht nur neues Chat-Fenster öffnen:
|
||||
- Rechtsklick auf Claude-Icon im **Systemtray** → **Quit/Beenden**
|
||||
- Alternativ: Task-Manager → `claude.exe` beenden
|
||||
|
||||
Dann Claude Code neu starten und einen neuen Chat öffnen.
|
||||
|
||||
---
|
||||
|
||||
## Verifikation
|
||||
|
||||
In einem neuen Chat eingeben:
|
||||
> "Erstelle einen einfachen Würfel 20x20x20mm als test.stl"
|
||||
|
||||
Claude sollte automatisch das Tool `render_stl` aufrufen und eine STL-Datei in
|
||||
`C:\Users\<USERNAME>\3d-models\test.stl` erzeugen.
|
||||
|
||||
---
|
||||
|
||||
## Beispiel-Prompts
|
||||
|
||||
```
|
||||
Erstelle einen Filamentrollenhalter für 200mm Außendurchmesser, 30mm Achse, als filamenthalter.stl
|
||||
```
|
||||
```
|
||||
Erstelle einen Wandhalter für ein 32mm Rohr, Wandstärke 3mm, 2 Schraubenlöcher M4, als rohrhalter.stl
|
||||
```
|
||||
```
|
||||
Erstelle eine Box 80x50x30mm mit Deckel und Scharnier, Wandstärke 2mm, als box.stl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Server wird nicht geladen (Tools nicht sichtbar)
|
||||
- Prüfen ob Eintrag in `claude_desktop_config.json` ist (nicht in `settings.json`)
|
||||
- Claude Code **vollständig** neu starten (Systemtray → Quit)
|
||||
- Debug-Log prüfen: `C:\Users\<USERNAME>\.claude\debug\` — neueste Datei nach `openscad` durchsuchen
|
||||
|
||||
### STL-Render schlägt fehl
|
||||
- OpenSCAD-Pfad prüfen: `"C:\Program Files\OpenSCAD\openscad.exe" --version`
|
||||
- Output-Verzeichnis prüfen: `C:\Users\<USERNAME>\3d-models\` muss existieren (wird automatisch erstellt)
|
||||
|
||||
### Python/mcp Import-Fehler
|
||||
- Virtual Environment neu erstellen (Schritt 3)
|
||||
- Prüfen: `.venv\Scripts\python.exe -c "from mcp.server.fastmcp import FastMCP; print('OK')"`
|
||||
|
||||
---
|
||||
|
||||
## Dateistruktur
|
||||
|
||||
```
|
||||
C:\Users\<USERNAME>\
|
||||
├── AppData\Roaming\Claude\
|
||||
│ └── claude_desktop_config.json ← MCP-Server Registrierung
|
||||
├── .claude\
|
||||
│ └── settings.json ← Claude Code CLI-Settings (nicht relevant)
|
||||
├── Documents\Claude\openscad-mcp\
|
||||
│ ├── .venv\ ← Python Virtual Environment
|
||||
│ │ └── Scripts\python.exe ← Echter Python-Interpreter
|
||||
│ └── server.py ← MCP-Server Code
|
||||
└── 3d-models\ ← Ausgabeverzeichnis für STL-Dateien
|
||||
├── filamenthalter.stl
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hintergrund: Warum so kompliziert?
|
||||
|
||||
1. **Zwei Config-Dateien**: Claude Code Desktop liest `claude_desktop_config.json`,
|
||||
nicht `~/.claude/settings.json`. Letzteres ist nur für den CLI-Modus.
|
||||
|
||||
2. **Windows App Execution Aliases**: `WindowsApps\python3.13.exe` ist ein Stub
|
||||
der nur aus interaktiven Shells funktioniert. Als Subprocess von Claude Code
|
||||
schlägt er ohne Fehlermeldung fehl. Ein Virtual Environment erstellt eine
|
||||
echte `python.exe` ohne diese Einschränkung.
|
||||
|
||||
3. **Eingebettetes Python**: `C:\...\Programs\Python\Python313\python.exe` (das
|
||||
vom `py`-Launcher verwendete) hat kein `pip` und kann nicht für MCP verwendet
|
||||
werden.
|
||||
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
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()
|
||||
@@ -0,0 +1,92 @@
|
||||
// Trichter
|
||||
$fn = 64;
|
||||
|
||||
// Parameter
|
||||
top_r = 40; // Radius oben
|
||||
cone_h = 50; // Höhe des Kegelabschnitts
|
||||
spout_r = 6; // Radius des Rohrs
|
||||
spout_h = 25; // Länge des geraden Rohrs
|
||||
wall = 2; // Wandstärke
|
||||
ext_h = 20; // Länge der abgewinkelten Verlängerung
|
||||
angle = 135; // Winkel der Verlängerung in Grad
|
||||
spout_h2 = 25; // Länge des zweiten Ausgussrohrs
|
||||
angle2 = 45; // Winkel von Knie 2
|
||||
|
||||
difference() {
|
||||
// --- Außenhülle ---
|
||||
union() {
|
||||
// Kegelabschnitt
|
||||
cylinder(h=cone_h, r1=spout_r, r2=top_r);
|
||||
|
||||
// Gerades Ausgussrohr
|
||||
translate([0, 0, -spout_h])
|
||||
cylinder(h=spout_h, r=spout_r);
|
||||
|
||||
// Knie 1
|
||||
hull() {
|
||||
translate([0, 0, -spout_h])
|
||||
cylinder(h=0.01, r=spout_r);
|
||||
translate([0, 0, -spout_h])
|
||||
rotate([angle, 0, 0])
|
||||
cylinder(h=0.01, r=spout_r);
|
||||
}
|
||||
|
||||
// Abgewinkelte Verlängerung
|
||||
translate([0, 0, -spout_h])
|
||||
rotate([angle, 0, 0])
|
||||
cylinder(h=ext_h, r=spout_r);
|
||||
|
||||
// Knie 2 + zweites Ausgussrohr
|
||||
translate([0, 0, -spout_h])
|
||||
rotate([angle, 0, 0])
|
||||
translate([0, 0, ext_h]) {
|
||||
hull() {
|
||||
cylinder(h=0.01, r=spout_r);
|
||||
rotate([angle2, 0, 0])
|
||||
cylinder(h=0.01, r=spout_r);
|
||||
}
|
||||
rotate([angle2, 0, 0])
|
||||
cylinder(h=spout_h2, r=spout_r);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Innenkanal ---
|
||||
union() {
|
||||
// Kegel innen
|
||||
translate([0, 0, wall])
|
||||
cylinder(h=cone_h, r1=spout_r - wall, r2=top_r - wall);
|
||||
|
||||
// Gerades Rohr innen
|
||||
translate([0, 0, -(spout_h + 1)])
|
||||
cylinder(h=spout_h + 1 + wall, r=spout_r - wall);
|
||||
|
||||
// Knie 1 innen
|
||||
hull() {
|
||||
translate([0, 0, -spout_h])
|
||||
cylinder(h=0.01, r=spout_r - wall);
|
||||
translate([0, 0, -spout_h])
|
||||
rotate([angle, 0, 0])
|
||||
cylinder(h=0.01, r=spout_r - wall);
|
||||
}
|
||||
|
||||
// Verlängerung innen
|
||||
translate([0, 0, -spout_h])
|
||||
rotate([angle, 0, 0])
|
||||
translate([0, 0, -1])
|
||||
cylinder(h=ext_h + 2, r=spout_r - wall);
|
||||
|
||||
// Knie 2 innen + zweites Rohr innen
|
||||
translate([0, 0, -spout_h])
|
||||
rotate([angle, 0, 0])
|
||||
translate([0, 0, ext_h]) {
|
||||
hull() {
|
||||
cylinder(h=0.01, r=spout_r - wall);
|
||||
rotate([angle2, 0, 0])
|
||||
cylinder(h=0.01, r=spout_r - wall);
|
||||
}
|
||||
rotate([angle2, 0, 0])
|
||||
translate([0, 0, -1])
|
||||
cylinder(h=spout_h2 + 2, r=spout_r - wall);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user