Initial commit: Feldbett Design-Dokumentation und FreeCAD-Scripts
Modulares Schwergewicht-Feldbett aus Alu-Rohren (25×1.5) mit Stahl-Konnektoren (33.7×2.5). Design-Docs, Materialrecherche, Gewichtsberechnung, Korrosionsschutz-Analyse, und zwei getestete FreeCAD-Makros (Struktur + Konnektoren-Detail). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
__pycache__/
|
||||||
34
README.md
Normal file
34
README.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Feldbett
|
||||||
|
|
||||||
|
Konstruktion eines modularen Schwergewicht-Feldbetts aus Aluminium-Standardrohren mit Stahl-Konnektoren.
|
||||||
|
|
||||||
|
## Projektstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
feldbett/
|
||||||
|
├── README.md
|
||||||
|
├── docs/
|
||||||
|
│ ├── 01-designuebersicht.md Strukturprinzip, Geometrie, Konnektoren
|
||||||
|
│ ├── 02-materialauswahl.md Rohr-Triplets, Festigkeitsvergleich, Katalogdaten
|
||||||
|
│ ├── 03-kontaktkorrosion.md Alu↔Stahl Korrosionsschutz-Strategien
|
||||||
|
│ ├── 04-gewichtsberechnung.md Stückliste, Gewichte für alle Varianten
|
||||||
|
│ ├── 05-original-scripts.md Dokumentation der FreeCAD-Ausgangsskripte
|
||||||
|
│ └── 06-offene-fragen.md Offene Entscheidungen, nächste Schritte
|
||||||
|
└── scripts/
|
||||||
|
└── original/
|
||||||
|
├── feldbett.py FreeCAD: Gesamtstruktur
|
||||||
|
├── feldbett_connectors.py FreeCAD: Connector v1 (Kugelknoten)
|
||||||
|
├── feldbett_connectors_v2.py FreeCAD: Connector v2 (Schweißhülsen)
|
||||||
|
└── feldbett_fem.py FreeCAD: FEM-Simulation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Designprinzip
|
||||||
|
|
||||||
|
- Alle Strukturrohre identische Länge (~35 cm), modular steckbar
|
||||||
|
- Zwei Connector-Typen: C1 (2-fach, A↔D / Fuß) und C2 (4-fach, Q↔D↔B)
|
||||||
|
- Inline-Stift-Verbinder für Längsstangen (Federbolzen-gesichert)
|
||||||
|
- Drei Rohrtypen die ineinander passen: Standard, Hülse (außen), Stift (innen)
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Design-Phase. Recherche zu Materialauswahl, Verfügbarkeit und Korrosionsschutz abgeschlossen. Triplet-Auswahl und Maßoptimierung stehen aus.
|
||||||
105
docs/01-designuebersicht.md
Normal file
105
docs/01-designuebersicht.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Feldbett — Designübersicht
|
||||||
|
|
||||||
|
## Projektziel
|
||||||
|
|
||||||
|
Konstruktion eines **Schwergewicht-Feldbetts** aus modularen Stahlrohren (bzw. Aluminiumrohren) mit zwei Sorten von Konnektoren. Alle Strukturrohre haben die **gleiche Länge** (~35 cm) und werden durch Zusammenstecken verbunden.
|
||||||
|
|
||||||
|
- **Ziel-Liegelänge**: ~2 Meter
|
||||||
|
- **Ziel-Breite**: ~70 cm (Abstand A–A)
|
||||||
|
- **Tragfähigkeit**: 200 kg Personenlast, Sicherheitsfaktor 2
|
||||||
|
- **Designprinzip**: Vollständig modular, zerlegbar, alle Rohre austauschbar
|
||||||
|
|
||||||
|
## Strukturprinzip
|
||||||
|
|
||||||
|
Das Bett besteht aus sich wiederholenden **Modulen** in X-Form:
|
||||||
|
|
||||||
|
```
|
||||||
|
Draufsicht (X = Längs, Z = Quer):
|
||||||
|
|
||||||
|
A ════════╤══════════╤══════════╤═══════ A ← Längsstange (oben)
|
||||||
|
│╲ ╱│╲ ╱│
|
||||||
|
│ D D │ D D │ ← Diagonalen
|
||||||
|
│ ╲╱ │ ╲╱ │
|
||||||
|
Q────┼─────Q────┼─────Q ← Querstreben (mitte)
|
||||||
|
│ ╱╲ │ ╱╲ │
|
||||||
|
│ B B │ B B │ ← Beine
|
||||||
|
│╱ ╲│╱ ╲│
|
||||||
|
F──────────F──────────F ← Fußpunkte (unten)
|
||||||
|
|
||||||
|
A ════════╤══════════╤══════════╤═══════ A ← zweite Längsstange
|
||||||
|
```
|
||||||
|
|
||||||
|
### Koordinatensystem
|
||||||
|
|
||||||
|
| Achse | Richtung | Beschreibung |
|
||||||
|
|-------|----------|-------------|
|
||||||
|
| X | Längs | Liegeachse |
|
||||||
|
| Y | Vertikal | Höhe |
|
||||||
|
| Z | Quer | Breite |
|
||||||
|
|
||||||
|
### Bauteile
|
||||||
|
|
||||||
|
| Typ | Beschreibung | Richtung | Anzahl (3 Module) |
|
||||||
|
|-----|-------------|----------|-------------------|
|
||||||
|
| **A** | Längsstangen | X (horizontal, oben) | 10 Stück (5 pro Seite, segmentiert) |
|
||||||
|
| **Q** | Querstreben | Z (horizontal, mitte) | 3 Stück (1 pro Modul) |
|
||||||
|
| **D** | Diagonalen | 3D (oben→mitte) | 12 Stück (4 pro Modul) |
|
||||||
|
| **B** | Beine | 3D (mitte→unten) | 12 Stück (4 pro Modul) |
|
||||||
|
|
||||||
|
### Knotenpunkte
|
||||||
|
|
||||||
|
Jedes Modul erzeugt diese Knotenpunkte:
|
||||||
|
|
||||||
|
- **Oben** (Y=totalH): 4 A-Knoten (xAL/xAR × ±hwA) — hier treffen A und D
|
||||||
|
- **Mitte** (Y=legH): 2 Q-Enden (xQ × ±hwQ) — hier treffen Q, D und B
|
||||||
|
- **Unten** (Y=0): 4 Fußknoten (xAL/xAR × ±hwA) — hier enden B
|
||||||
|
|
||||||
|
## Konnektoren
|
||||||
|
|
||||||
|
### Connector 1 — A↔D / Fuß (24 Stück)
|
||||||
|
|
||||||
|
Verbindet Längsstange (A) mit Diagonale (D) an den oberen Knotenpunkten. **Identisches Teil, um 180° gedreht**, dient auch als Fußkonnektor (B↔Boden).
|
||||||
|
|
||||||
|
- 2 Hülsen pro Connector: eine für A-Richtung, eine für D-Richtung
|
||||||
|
- Hülsen werden auf Gehrung gesägt und zusammengeschweißt
|
||||||
|
- 12× oben (A↔D) + 12× unten (B↔Fuß) = **24 Stück**
|
||||||
|
|
||||||
|
### Connector 2 — Q↔D↔B Kreuzknoten (6 Stück)
|
||||||
|
|
||||||
|
Verbindet Querstrebe (Q, durchlaufend) mit Diagonale (D, von oben) und Bein (B, nach unten) an den Q-Enden.
|
||||||
|
|
||||||
|
- 4 Hülsen pro Connector: Q-links, Q-rechts, D, B
|
||||||
|
- Alle auf Gehrung gesägt und sternförmig zusammengeschweißt
|
||||||
|
- 3 Module × 2 Q-Enden = **6 Stück**
|
||||||
|
|
||||||
|
### Inline-Verbinder (Stift) für A-Stangen (8 Stück)
|
||||||
|
|
||||||
|
Die Längsstangen bestehen aus 5 Segmenten pro Seite. Zwischen je zwei Segmenten sitzt ein **dünnerer Stift** (Rohr mit kleinerem Durchmesser), der von innen in beide Standardrohre eingeführt wird und dort durch **Federbolzen** gehalten wird.
|
||||||
|
|
||||||
|
- 4 Verbindungen pro Seite × 2 Seiten = **8 Stück**
|
||||||
|
- Stiftlänge: ~100 mm (50 mm Einstecktiefe pro Seite)
|
||||||
|
|
||||||
|
## Geometrie (berechnete Werte bei L=350mm, Breite=700mm, 3 Module)
|
||||||
|
|
||||||
|
| Parameter | Wert | Beschreibung |
|
||||||
|
|-----------|------|-------------|
|
||||||
|
| L | 350 mm | Einheitliche Stangenlänge |
|
||||||
|
| BREITE_AA | 700 mm | Abstand zwischen Längsstangen |
|
||||||
|
| hwA | 350 mm | Halbe Breite (A–A) |
|
||||||
|
| hwQ | 175 mm | Halbe Q-Länge |
|
||||||
|
| dZ | 175 mm | Z-Einzug von A nach Q |
|
||||||
|
| legH | 247.5 mm | Höhe einer Ebene (A→Q oder Q→Boden) |
|
||||||
|
| totalH | 495 mm | Gesamthöhe (~49.5 cm) |
|
||||||
|
| aEnd | 1750 mm | Liegelänge (175 cm) |
|
||||||
|
| step | 700 mm | Modulabstand |
|
||||||
|
|
||||||
|
**Hinweis**: 175 cm Liegelänge ist kürzer als das 2m-Ziel. Optionen:
|
||||||
|
- L auf 400 mm erhöhen → 2.0 m
|
||||||
|
- 4. Modul hinzufügen → 2.45 m (zu lang)
|
||||||
|
- Halbes Modul am Ende
|
||||||
|
|
||||||
|
## Nächste Schritte
|
||||||
|
|
||||||
|
→ Siehe [02-materialauswahl.md](02-materialauswahl.md) für Rohrauswahl
|
||||||
|
→ Siehe [03-kontaktkorrosion.md](03-kontaktkorrosion.md) für Korrosionsschutz
|
||||||
|
→ Siehe [04-gewichtsberechnung.md](04-gewichtsberechnung.md) für Stückliste und Gewichte
|
||||||
147
docs/02-materialauswahl.md
Normal file
147
docs/02-materialauswahl.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# Feldbett — Materialauswahl Rohre
|
||||||
|
|
||||||
|
## Anforderungen an die Rohrauswahl
|
||||||
|
|
||||||
|
Das Design erfordert **drei Rohrtypen**, die ineinander passen (Triplet):
|
||||||
|
|
||||||
|
1. **Standardrohr** — Hauptstruktur (alle 37 Stangen)
|
||||||
|
2. **Hülsenrohr** — passt außen über das Standardrohr (für Konnektoren, Stahl)
|
||||||
|
3. **Stiftrohr** — passt innen in das Standardrohr (für Inline-Verbinder)
|
||||||
|
|
||||||
|
### Spielvorgaben
|
||||||
|
|
||||||
|
| Verbindung | Minimum | Maximum | Begründung |
|
||||||
|
|-----------|---------|---------|-----------|
|
||||||
|
| Hülse ID – Standard OD | 1.5 mm | 4.0 mm | Blog stahlshop.de: mind. 2-3mm empfohlen wg. Toleranzen/Schweißnähte |
|
||||||
|
| Standard ID – Stift OD | 1.0 mm | 4.0 mm | Muss steckbar sein, aber nicht zu viel Wackeln |
|
||||||
|
|
||||||
|
## Materialentscheidung: Aluminium Standard + Stahl Hülsen
|
||||||
|
|
||||||
|
### Begründung
|
||||||
|
|
||||||
|
- **Standardrohre aus Alu**: Gewichtsersparnis ~50% gegenüber Vollstahl
|
||||||
|
- **Hülsen (Konnektoren) aus Stahl**: werden zusammengeschweißt (MIG/MAG), einfacher als Alu-WIG
|
||||||
|
- **Stifte aus Alu**: verbinden Alu-Rohre inline, kein Schweißen nötig (Federbolzen)
|
||||||
|
- **Kontaktkorrosion Alu↔Stahl**: muss durch Isolierung/Beschichtung gelöst werden (→ siehe [03-kontaktkorrosion.md](03-kontaktkorrosion.md))
|
||||||
|
|
||||||
|
### Alternative: Alles Stahl
|
||||||
|
|
||||||
|
| Variante | Gewicht | Bemerkung |
|
||||||
|
|----------|---------|-----------|
|
||||||
|
| Alles Stahl (33.7×2.0) | ~27 kg | Robust, kein Korrosionsproblem, schwer |
|
||||||
|
| Alu Standard + Stahl Hülsen (33.7×2.0) | ~14 kg | Überdimensioniert |
|
||||||
|
| Alu Standard + Stahl Hülsen (optimiert) | ~8-10 kg | Richtig dimensioniert |
|
||||||
|
|
||||||
|
## Verfügbare Rohre — Katalog Metallparadies
|
||||||
|
|
||||||
|
### Aluminium Rundrohr (AlMgSi 0.5 / EN AW-6060 T6)
|
||||||
|
|
||||||
|
Quelle: [metallparadies.de/alurohr.html](https://www.metallparadies.de/alurohr.html)
|
||||||
|
|
||||||
|
| OD [mm] | Verfügbare Wandstärken [mm] |
|
||||||
|
|---------|---------------------------|
|
||||||
|
| 15 | 1, 1.5, 2 |
|
||||||
|
| 16 | 1, 1.5, 2, 2.5, 3 |
|
||||||
|
| 18 | 1, 1.5, 2 |
|
||||||
|
| 20 | 1, 1.5, 2, 2.5, 3, 5 |
|
||||||
|
| 22 | 1, 1.5, 2 |
|
||||||
|
| 25 | 1, 1.5, 2, 2.5, 3, 5 |
|
||||||
|
| 28 | 1.5, 2, 2.5, 4 |
|
||||||
|
| 30 | 1.5, 2, 2.5, 3, 4, 5 |
|
||||||
|
| 35 | 1.5, 2, 2.5, 3, 4, 5 |
|
||||||
|
| 40 | 1.5, 2, 2.5, 3, 4, 5, 8, 10 |
|
||||||
|
| 45 | 1.5, 2, 2.5, 3, 4, 5, 10 |
|
||||||
|
|
||||||
|
Material: AlMgSi 0.5 (EN AW-6060), Zustand T6:
|
||||||
|
- Streckgrenze Rp0.2 = 150 MPa
|
||||||
|
- Zugfestigkeit Rm = 195 MPa
|
||||||
|
- E-Modul = 69.000 MPa
|
||||||
|
- Dichte = 2.700 kg/m³
|
||||||
|
|
||||||
|
### Stahl Rundrohr (S235JR)
|
||||||
|
|
||||||
|
Quelle: [metallparadies.de/stahl-rundrohr-s235jr.html](https://www.metallparadies.de/stahl-rundrohr-s235jr.html)
|
||||||
|
|
||||||
|
| OD [mm] | Verfügbare Wandstärken [mm] |
|
||||||
|
|---------|---------------------------|
|
||||||
|
| 20 | 2.0 |
|
||||||
|
| 21.3 | 2.0, 2.65, 3.25 |
|
||||||
|
| 25 | 2.0 |
|
||||||
|
| 26.9 | 2.0, 2.6, 3.25 |
|
||||||
|
| 30 | 2.0 |
|
||||||
|
| 33.7 | 2.0, 2.5, 3.25, 4.05 |
|
||||||
|
| 38 | 2.6 |
|
||||||
|
| 40 | 2.0 |
|
||||||
|
| 42.4 | 2.0, 2.5, 3.25, 4.05 |
|
||||||
|
| 44.5 | 2.6 |
|
||||||
|
|
||||||
|
Material: S235JR (Baustahl):
|
||||||
|
- Streckgrenze Rp0.2 = 235 MPa
|
||||||
|
- E-Modul = 210.000 MPa
|
||||||
|
- Dichte = 7.850 kg/m³
|
||||||
|
|
||||||
|
## Triplet-Analyse — Gültige Kombinationen
|
||||||
|
|
||||||
|
Systematische Suche über alle Alu-Standard × Alu-Stift × Stahl-Hülse Kombinationen.
|
||||||
|
Filterkriterien: Biegeauslastung 10-60%, Knick-SF > 5, Spiel in Grenzen.
|
||||||
|
|
||||||
|
**Ergebnis: 230 gültige Triplets gefunden.**
|
||||||
|
|
||||||
|
### Top 20 nach Gewicht
|
||||||
|
|
||||||
|
| # | Standard (Alu) | Stift (Alu) | Hülse (Stahl) | Sp.H [mm] | Sp.S [mm] | σ [MPa] | Ausl. | Knick-SF | Gewicht |
|
||||||
|
|---|---------------|------------|--------------|----------|----------|---------|-------|---------|---------|
|
||||||
|
| 1 | 22×1.0 | 16×1.0 | 30×2.0 | 4.0 | 4.0 | 86.3 | 58% | 124× | 6.9 kg |
|
||||||
|
| 2 | 20×1.5 | 15×1.0 | 26.9×2.0 | 2.9 | 2.0 | 76.2 | 51% | 128× | 7.2 kg |
|
||||||
|
| 3 | **22×1.5** | **15×1.0** | **30×2.0** | **4.0** | **4.0** | **61.7** | **41%** | **173×** | **8.0 kg** |
|
||||||
|
| 4 | 20×2.0 | 15×1.0 | 26.9×2.0 | 2.9 | 1.0 | 61.7 | 41% | 158× | 8.1 kg |
|
||||||
|
| 5 | 20×1.5 | 15×1.0 | 26.9×2.6 | 1.7 | 2.0 | 76.2 | 51% | 128× | 8.1 kg |
|
||||||
|
| 6 | 25×1.0 | 20×1.0 | 33.7×2.5 | 3.7 | 3.0 | 65.8 | 44% | 185× | 8.8 kg |
|
||||||
|
| 7 | 22×2.0 | 15×1.0 | 30×2.0 | 4.0 | 3.0 | 49.6 | 33% | 216× | 9.0 kg |
|
||||||
|
| 8 | 20×2.0 | 15×1.0 | 26.9×2.6 | 1.7 | 1.0 | 61.7 | 41% | 158× | 9.0 kg |
|
||||||
|
| 9 | 28×1.5 | 22×1.0 | 33.7×2.0 | 1.7 | 3.0 | 36.4 | 24% | 374× | 9.5 kg |
|
||||||
|
| 10 | **25×1.5** | **18×1.0** | **33.7×2.5** | **3.7** | **4.0** | **46.6** | **31%** | **261×** | **10.0 kg** |
|
||||||
|
| 11 | 25×1.0 | 20×1.0 | 33.7×3.25 | 2.2 | 3.0 | 65.8 | 44% | 185× | 10.3 kg |
|
||||||
|
| 12 | 28×2.0 | 20×1.0 | 33.7×2.0 | 1.7 | 4.0 | 28.8 | 19% | 472× | 10.8 kg |
|
||||||
|
| 13 | 25×2.0 | 18×1.0 | 33.7×2.5 | 3.7 | 3.0 | 37.1 | 25% | 327× | 11.2 kg |
|
||||||
|
| 14 | 25×1.5 | 18×1.0 | 33.7×3.25 | 2.2 | 4.0 | 46.6 | 31% | 261× | 11.5 kg |
|
||||||
|
| 15 | 30×1.5 | 25×1.0 | 38×2.6 | 2.8 | 2.0 | 31.4 | 21% | 465× | 11.9 kg |
|
||||||
|
|
||||||
|
### Empfohlene Triplets
|
||||||
|
|
||||||
|
#### Sweet Spot: 22×1.5 / 15×1.0 / 30×2.0
|
||||||
|
|
||||||
|
- 41% Auslastung, 173× Knick-Sicherheit, 0.83 mm Durchbiegung
|
||||||
|
- **8.0 kg** Gesamtgewicht
|
||||||
|
- Bedenken: Dünne Wände (1.5mm Standard, 1.0mm Stift) → empfindlich bei Federbolzen-Bohrungen
|
||||||
|
|
||||||
|
#### Robuster Prototyp: 25×1.5 / 18×1.0 / 33.7×2.5
|
||||||
|
|
||||||
|
- 31% Auslastung, 261× Knick-Sicherheit, 0.50 mm Durchbiegung
|
||||||
|
- **10.0 kg** Gesamtgewicht
|
||||||
|
- Genug Wandstärke für Bohrungen, haptisch robuster
|
||||||
|
- 33.7mm Hülse ist DIN-Rohrgröße (häufig verfügbar)
|
||||||
|
|
||||||
|
#### Ursprüngliches Triplet (überdimensioniert): 33.7×2.0 / 26.9×2.0 / 40×2.0
|
||||||
|
|
||||||
|
- 13% Auslastung — stark überdimensioniert
|
||||||
|
- **13.8 kg** (Alu) / **27.1 kg** (Stahl)
|
||||||
|
|
||||||
|
## Festigkeitsvergleich Alu vs. Stahl
|
||||||
|
|
||||||
|
Lastannahmen: 200 kg Personenlast, 6 A-Stangen tragen gleichzeitig, Einzellast mittig auf 350 mm Spannweite.
|
||||||
|
|
||||||
|
| Material | E [GPa] | Rp0.2 [MPa] | σ_biege [MPa] | Auslastung | Durchbiegung | Euler-Knick-SF |
|
||||||
|
|----------|---------|-------------|---------------|-----------|-------------|---------------|
|
||||||
|
| Stahl S235 | 210 | 235 | 19.2 | 8% | 0.06 mm | 2599× |
|
||||||
|
| Alu 6060-T6 | 69 | 150 | 19.2 | 13% | 0.17 mm | 854× |
|
||||||
|
| Alu 6082-T6 | 70 | 260 | 19.2 | 7% | 0.17 mm | 866× |
|
||||||
|
|
||||||
|
**Fazit**: Bei gleichen Querschnitten (33.7×2.0) ist die Biegespannung identisch — nur das Verhältnis zur Streckgrenze ändert sich. Alu biegt sich 3× so viel durch wie Stahl (E-Modul-Verhältnis), aber bei 350 mm Spannweite ist die absolute Durchbiegung <1 mm und irrelevant.
|
||||||
|
|
||||||
|
## Quellen
|
||||||
|
|
||||||
|
- [Metallparadies — Alu Rundrohr Katalog](https://www.metallparadies.de/alurohr.html)
|
||||||
|
- [Metallparadies — Stahl Rundrohr S235JR Katalog](https://www.metallparadies.de/stahl-rundrohr-s235jr.html)
|
||||||
|
- [Metallparadies — Welche Rohre passen ineinander?](https://www.metallparadies.de/info/welche-rohre-passen.html)
|
||||||
|
- [Stahlshop.de Blog — Rohre ineinander stecken](https://blog.stahlshop.de/rohre-ineinander-stecken-worauf-ist-zu-achten-was-muss-ich-bestellen/)
|
||||||
139
docs/03-kontaktkorrosion.md
Normal file
139
docs/03-kontaktkorrosion.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# Feldbett — Kontaktkorrosion Alu↔Stahl
|
||||||
|
|
||||||
|
## Das Problem
|
||||||
|
|
||||||
|
Wenn Aluminium (unedles Metall) und Stahl (edleres Metall) in Kontakt kommen und Feuchtigkeit vorhanden ist, entsteht eine **galvanische Zelle**. Das Aluminium wird als Anode angegriffen (Lochfraß).
|
||||||
|
|
||||||
|
Verschärfende Faktoren beim Feldbett:
|
||||||
|
- **Reibung** beim Auf-/Abbau zerstört Schutzschichten (Fretting-Korrosion)
|
||||||
|
- **Outdoor-Nutzung** → Regen, Tau, Schweiß
|
||||||
|
- Alu-Rohre stecken in Stahl-Hülsen → große Kontaktfläche
|
||||||
|
- Spaltkorrosion im Inneren der Verbindung (schwer zu inspizieren)
|
||||||
|
|
||||||
|
## Lösungsansätze
|
||||||
|
|
||||||
|
### A. Physische Trennung (Isolierung)
|
||||||
|
|
||||||
|
#### A1. Kunststoff-Buchsen / Gleitlager (POM/Delrin)
|
||||||
|
|
||||||
|
Nylon- oder POM-Buchse zwischen Alu-Rohr und Stahl-Hülse.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Vorteile** | Bester Schutz: kein Metallkontakt; POM ist verschleißfest, günstig, leicht zu drehen; als Presssitz in Stahl-Hülse einsetzbar; austauschbar bei Verschleiß |
|
||||||
|
| **Nachteile** | Muss für jeden Durchmesser gefertigt werden (Drehbank nötig); nimmt Spiel weg (konstruktiv einplanen) |
|
||||||
|
|
||||||
|
#### A2. Schrumpfschlauch auf Alu-Rohrenden
|
||||||
|
|
||||||
|
Dickwandiger Schrumpfschlauch (3:1) über die Einsteckenden.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Vorteile** | Sehr günstig, schnell angebracht, nachträglich ersetzbar |
|
||||||
|
| **Nachteile** | Verschleißt bei häufigem Stecken (50-200 Zyklen); erhöht OD um ~0.5-1 mm |
|
||||||
|
|
||||||
|
#### A3. PTFE-Band (Teflonband)
|
||||||
|
|
||||||
|
Einige Lagen Teflonband auf die Einsteckenden wickeln.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Vorteile** | Extrem günstig, überall erhältlich, gute Gleiteigenschaften |
|
||||||
|
| **Nachteile** | Verschleißt schnell, muss regelmäßig erneuert werden |
|
||||||
|
|
||||||
|
#### A4. Gummitüllen / O-Ringe
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Vorteile** | Dichten gegen Wasser ab, verhindern Klappern |
|
||||||
|
| **Nachteile** | Werden bei Last gequetscht, Verfügbarkeit in Sondergrößen |
|
||||||
|
|
||||||
|
### B. Oberflächenbehandlung
|
||||||
|
|
||||||
|
#### B1. Alu eloxieren / hartanodisieren
|
||||||
|
|
||||||
|
Erzeugt harte Al₂O₃-Schicht (20-50 µm).
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Vorteile** | Extrem verschleißfest, übersteht Reibung; elektrisch isolierend → unterbricht galvanische Zelle; professionell, dauerhaft |
|
||||||
|
| **Nachteile** | Kostet ~5-15 EUR pro Teil (Lohneloxierung); muss vor Zusammenbau erfolgen |
|
||||||
|
|
||||||
|
#### B2. Stahl-Hülsen verzinken
|
||||||
|
|
||||||
|
Feuerverzinkung oder galvanische Verzinkung.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Vorteile** | Zink als Opferanode schützt Alu UND Stahl; günstig (~2-5 EUR/kg) |
|
||||||
|
| **Nachteile** | Schichtdicke kann Passung beeinflussen (+50-100 µm) |
|
||||||
|
|
||||||
|
#### B3. Pulverbeschichtung der Stahl-Hülsen
|
||||||
|
|
||||||
|
Epoxy/Polyester-Pulverlack.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Vorteile** | Robuste Isolierschicht, optisch ansprechend |
|
||||||
|
| **Nachteile** | Schichtdicke 60-120 µm → Passung einplanen; kann bei Reibung abplatzen |
|
||||||
|
|
||||||
|
### C. Materialwechsel
|
||||||
|
|
||||||
|
#### C1. Hülsen auch aus Alu (WIG-schweißen)
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Vorteile** | Kein Materialmix = keine Kontaktkorrosion; leichter |
|
||||||
|
| **Nachteile** | Braucht WIG-Schweißgerät + Erfahrung; Schweißnähte schwächen Material (HAZ ~50% Festigkeitsverlust); Hülse ist aber nicht das tragende Teil |
|
||||||
|
|
||||||
|
#### C2. Hülsen aus Edelstahl (V2A / 1.4301)
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Vorteile** | Alu↔Edelstahl weniger kritisch als Alu↔Baustahl (geringere Potentialdifferenz); kein Rost am Stahl; bei Metallparadies verfügbar |
|
||||||
|
| **Nachteile** | Teurer, schwerer zu schweißen; Kontaktkorrosion nicht eliminiert, nur reduziert |
|
||||||
|
|
||||||
|
#### C3. Hülsen aus GFK/CFK-Rohr (Faserverbund)
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Vorteile** | Kein Metall = null Kontaktkorrosion; extrem leicht |
|
||||||
|
| **Nachteile** | Nicht schweißbar → müsste geklebt werden (Epoxy); Verfügbarkeit fraglich; Bruchverhalten spröde |
|
||||||
|
|
||||||
|
### D. Schmierung / Fett
|
||||||
|
|
||||||
|
#### D1. Korrosionsschutzfett (Tectyl, Mike Sanders)
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Vorteile** | Sperrt Feuchtigkeit aus; gleichzeitig Schmierung; günstig |
|
||||||
|
| **Nachteile** | Muss regelmäßig erneuert werden; schmiert (unhandlich) |
|
||||||
|
|
||||||
|
#### D2. Lanolinspray (Fluid Film, Timemax)
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Vorteile** | Kriechfähig, dringt in Spalte ein; Langzeitschutz (Marine-bewährt) |
|
||||||
|
| **Nachteile** | Regelmäßig nachsprühen |
|
||||||
|
|
||||||
|
## Empfohlene Kombination (Dreifach-Absicherung)
|
||||||
|
|
||||||
|
1. **Alu-Rohre hartanodisieren** (B1) — dauerhafter Verschleiß- und Korrosionsschutz, elektrische Isolation
|
||||||
|
2. **POM-Buchsen in Stahl-Hülsen** (A1) — kein direkter Metall-Metall-Kontakt, austauschbar
|
||||||
|
3. **Stahl-Hülsen verzinken** (B2) — Backup-Schutz, Opferanoden-Effekt
|
||||||
|
|
||||||
|
Auch wenn eine Schicht versagt, schützen die anderen beiden weiterhin.
|
||||||
|
|
||||||
|
### Alternative: Materialwechsel
|
||||||
|
|
||||||
|
Hülsen komplett aus Alu (C1) → eliminiert das Problem. Machbar, da Hülsen nicht das tragende Teil sind. Erfordert WIG-Schweißen.
|
||||||
|
|
||||||
|
## Quellen
|
||||||
|
|
||||||
|
- [Kontaktkorrosion Aluminium und Edelstahl — edelstahlrohrshop.com](https://www.edelstahlrohrshop.com/blog/service/kontaktkorrosion-von-aluminium-und-edelstahl.html)
|
||||||
|
- [Aluminium-Edelstahl Kontaktkorrosion vermeiden — hausjournal.net](https://www.hausjournal.net/kontaktkorrosion-aluminium-edelstahl)
|
||||||
|
- [Kontaktkorrosion bei Aluminium und Stahl — heimwerk.org](https://heimwerk.org/kontaktkorrosion-aluminium-stahl)
|
||||||
|
- [Galvanische Korrosion vermeiden — iwofr.org](https://iwofr.org/de/galvanische-korrosion-vermeiden/)
|
||||||
|
- [Kontaktkorrosion Arbeitsblatt — feuerverzinken.com](https://www.feuerverzinken.com/praxiswissen/arbeitsblaetter-feuerverzinken/kontaktkorrosion)
|
||||||
|
- [Lochfraßvermeidung Alu/Stahl — womobox.de](https://womobox.de/forum/thread/8409-lochfra%C3%9Fvermeidung-bei-verbindung-alu-stahl/)
|
||||||
|
- [Korrosionsschutz von Aluminium — aluminium-guide.com](https://aluminium-guide.com/de/zashhita-ot-korrozii-po-evrokodu-9-alyuminij-v-kontakte-s-metallami/)
|
||||||
103
docs/04-gewichtsberechnung.md
Normal file
103
docs/04-gewichtsberechnung.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# Feldbett — Gewichtsberechnung & Stückliste
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
- Stangenlänge L = 350 mm
|
||||||
|
- Breite A–A = 700 mm
|
||||||
|
- Module = 3
|
||||||
|
- Liegelänge = 1750 mm
|
||||||
|
- Höhe = 495 mm
|
||||||
|
- Hülsenlänge (Konnektoren) = 40 mm
|
||||||
|
- Stiftlänge (Inline-Verbinder) = 100 mm
|
||||||
|
|
||||||
|
## Stückliste
|
||||||
|
|
||||||
|
### Standardrohre (alle identisch, je 350 mm lang)
|
||||||
|
|
||||||
|
| Typ | Funktion | Anzahl | Herleitung |
|
||||||
|
|-----|----------|--------|-----------|
|
||||||
|
| A | Längsstangen | 10 | 5 pro Seite × 2 |
|
||||||
|
| Q | Querstreben | 3 | 1 pro Modul |
|
||||||
|
| D | Diagonalen | 12 | 4 pro Modul × 3 |
|
||||||
|
| B | Beine | 12 | 4 pro Modul × 3 |
|
||||||
|
| **Summe** | | **37** | |
|
||||||
|
|
||||||
|
### Stift-Verbinder (Inline, je 100 mm)
|
||||||
|
|
||||||
|
| Funktion | Anzahl | Herleitung |
|
||||||
|
|----------|--------|-----------|
|
||||||
|
| A-Stangen verbinden | 8 | (5-1) pro Seite × 2 |
|
||||||
|
|
||||||
|
### Konnektoren
|
||||||
|
|
||||||
|
| Typ | Anzahl | Hülsen pro Stück | Hülsen gesamt |
|
||||||
|
|-----|--------|-----------------|--------------|
|
||||||
|
| C1 (A↔D / Fuß) | 24 | 2 | 48 |
|
||||||
|
| C2 (Q↔D↔B) | 6 | 4 | 24 |
|
||||||
|
| **Summe** | **30** | | **72** |
|
||||||
|
|
||||||
|
## Gewichtsberechnung — Variante: Alles Stahl (Triplet 33.7/26.9/40)
|
||||||
|
|
||||||
|
| Komponente | Rohr | Anz. | Einzelgewicht | Gesamt |
|
||||||
|
|-----------|------|------|--------------|--------|
|
||||||
|
| Standardrohre | 33.7×2.0 S235 | 37 | 547 g | 20.248 g |
|
||||||
|
| Stifte | 26.9×2.0 S235 | 8 | 123 g | 983 g |
|
||||||
|
| Hülsen C1+C2 | 40×2.0 S235 | 72 | 75 g | 5.398 g |
|
||||||
|
| Kleinteile | — | — | — | 500 g |
|
||||||
|
| **Gesamt** | | | | **27.128 g = 27.1 kg** |
|
||||||
|
|
||||||
|
### Bestellmenge Stahl
|
||||||
|
|
||||||
|
| Rohr | Meter | Bestellen |
|
||||||
|
|------|-------|----------|
|
||||||
|
| 33.7×2.0 | 12.95 m | 13 m (+Verschnitt) |
|
||||||
|
| 26.9×2.0 | 0.80 m | 1 m |
|
||||||
|
| 40×2.0 | 2.88 m | 3 m |
|
||||||
|
|
||||||
|
## Gewichtsberechnung — Variante: Alu Standard + Stahl Hülsen (Triplet 33.7/26.9/40)
|
||||||
|
|
||||||
|
| Komponente | Material | Anz. | Gesamt |
|
||||||
|
|-----------|----------|------|--------|
|
||||||
|
| Standardrohre 33.7×2.0 × 350mm | Alu | 37 | 6.964 g |
|
||||||
|
| Stifte 26.9×2.0 × 100mm | Stahl | 8 | 983 g |
|
||||||
|
| Hülsen 40×2.0 × 40mm | Stahl | 72 | 5.398 g |
|
||||||
|
| Kleinteile | — | — | 500 g |
|
||||||
|
| **Gesamt** | | | **13.845 g = 13.8 kg** |
|
||||||
|
|
||||||
|
Ersparnis gegenüber Vollstahl: **13.3 kg (49%)**
|
||||||
|
|
||||||
|
## Gewichtsberechnung — Optimierte Alu-Triplets
|
||||||
|
|
||||||
|
### Leichtestes: Alu 22×1.0 / 16×1.0 / Stahl 30×2.0
|
||||||
|
|
||||||
|
| Komponente | Anz. | Gesamt |
|
||||||
|
|-----------|------|--------|
|
||||||
|
| Standardrohre 22×1.0 × 350mm (Alu) | 37 | 2.307 g |
|
||||||
|
| Stifte 16×1.0 × 100mm (Alu) | 8 | 102 g |
|
||||||
|
| Hülsen 30×2.0 × 40mm (Stahl) | 72 | 3.977 g |
|
||||||
|
| Kleinteile | — | 500 g |
|
||||||
|
| **Gesamt** | | **6.886 g = 6.9 kg** |
|
||||||
|
|
||||||
|
### Sweet Spot: Alu 22×1.5 / 15×1.0 / Stahl 30×2.0
|
||||||
|
|
||||||
|
| Komponente | Anz. | Gesamt |
|
||||||
|
|-----------|------|--------|
|
||||||
|
| Standardrohre 22×1.5 × 350mm (Alu) | 37 | 3.378 g |
|
||||||
|
| Stifte 15×1.0 × 100mm (Alu) | 8 | 95 g |
|
||||||
|
| Hülsen 30×2.0 × 40mm (Stahl) | 72 | 3.977 g |
|
||||||
|
| Kleinteile | — | 500 g |
|
||||||
|
| **Gesamt** | | **7.950 g = 8.0 kg** |
|
||||||
|
|
||||||
|
### Robuster Prototyp: Alu 25×1.5 / 18×1.0 / Stahl 33.7×2.5
|
||||||
|
|
||||||
|
| Komponente | Anz. | Gesamt |
|
||||||
|
|-----------|------|--------|
|
||||||
|
| Standardrohre 25×1.5 × 350mm (Alu) | 37 | 4.605 g |
|
||||||
|
| Stifte 18×1.0 × 100mm (Alu) | 8 | 116 g |
|
||||||
|
| Hülsen 33.7×2.5 × 40mm (Stahl) | 72 | 4.745 g |
|
||||||
|
| Kleinteile | — | 500 g |
|
||||||
|
| **Gesamt** | | **9.966 g = 10.0 kg** |
|
||||||
|
|
||||||
|
## Hinweis zu Wandstärken und Federbolzen
|
||||||
|
|
||||||
|
Dünne Wände (1.0–1.5 mm) werden durch Federbolzen-Bohrungen geschwächt. Bei einem Standardrohr mit 1.0 mm Wandstärke und einem 4 mm Federbolzen-Loch verbleibt sehr wenig Material. Empfehlung für Prototyp: mindestens 1.5 mm Wandstärke am Standardrohr.
|
||||||
77
docs/05-original-scripts.md
Normal file
77
docs/05-original-scripts.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Feldbett — Originale FreeCAD Scripts
|
||||||
|
|
||||||
|
## Herkunft
|
||||||
|
|
||||||
|
Die Scripts wurden in Claude Web erstellt und als Ausgangspunkt für die Konstruktion verwendet. Sie liegen als Kopie im Projektordner unter `scripts/original/`.
|
||||||
|
|
||||||
|
**Achtung**: Die Originalscripts verwenden L=35 mm (Millimeter) statt der realen L=350 mm. Alle Maße sind um Faktor 10 zu klein. Dies war ein Modellmaßstab, der bei der Weiterentwicklung korrigiert werden muss.
|
||||||
|
|
||||||
|
## Dateiübersicht
|
||||||
|
|
||||||
|
### `scripts/original/feldbett.py`
|
||||||
|
|
||||||
|
**Zweck**: Gesamtstruktur des Feldbetts als FreeCAD-Makro.
|
||||||
|
|
||||||
|
- Erzeugt alle Stangen (A, Q, D, B, F) als Hohlrohre
|
||||||
|
- Farbcodierung: A=blau, D=grün, Q=amber, B=lila, F=grau
|
||||||
|
- A-Stangen sind **durchgehend** (nicht segmentiert) — muss geändert werden
|
||||||
|
- Berechnet und gibt Geometrie-Kontrollwerte aus
|
||||||
|
|
||||||
|
**Bekannte Probleme**:
|
||||||
|
- Maßstab falsch (mm statt cm)
|
||||||
|
- A-Stangen nicht segmentiert
|
||||||
|
- Keine Konnektoren dargestellt
|
||||||
|
|
||||||
|
### `scripts/original/feldbett_connectors.py`
|
||||||
|
|
||||||
|
**Zweck**: Connector v1 — Kugelknoten mit Rohrstutzen.
|
||||||
|
|
||||||
|
- Connector 1: Kugelförmiger Hub + Stutzen für A und D
|
||||||
|
- Connector 2: Kugelförmiger Hub + Stutzen für Q (durchlaufend), D, B
|
||||||
|
- Durchgangsbohrungen für Rohre
|
||||||
|
- Design für 3D-Druck oder Aluminiumguss
|
||||||
|
- Optionale Verrundung (Fillet)
|
||||||
|
|
||||||
|
**Status**: Konzeptstudie, durch v2 ersetzt.
|
||||||
|
|
||||||
|
### `scripts/original/feldbett_connectors_v2.py`
|
||||||
|
|
||||||
|
**Zweck**: Connector v2 — Schweißbare Hülsen auf Gehrung.
|
||||||
|
|
||||||
|
- Connector 1: 2 Hülsen (A + D), auf Gehrung, zusammengeschweißt
|
||||||
|
- Connector 2: 4 Hülsen (Q-links, Q-rechts, D, B), sternförmig geschweißt
|
||||||
|
- Spannstift-Bohrungen modelliert
|
||||||
|
- Enthält Belastungsrechnung (200 kg, SF=2)
|
||||||
|
- Berechnet Sägewinkel für Gehrungsschnitte
|
||||||
|
- Einzelteile-Ansicht der C2-Hülsen
|
||||||
|
|
||||||
|
**Berechnete Werte** (bei L=35 mm, Maßstab falsch):
|
||||||
|
- Einstecktiefe: 45 mm
|
||||||
|
- Hülsen-DA: 32 mm
|
||||||
|
- Spannstift: 6 mm Durchmesser
|
||||||
|
- Material: Stahl S235, Streckgrenze 235 N/mm²
|
||||||
|
|
||||||
|
### `scripts/original/feldbett_fem.py`
|
||||||
|
|
||||||
|
**Zweck**: FEM-Simulation für Connector 2.
|
||||||
|
|
||||||
|
- Setzt FreeCAD FEM-Analysis auf (CalculiX-Solver)
|
||||||
|
- Material: Stahl S235 (E=210.000 MPa)
|
||||||
|
- Vereinfachter Connector 2 als Solid
|
||||||
|
- Einspannung an Q-Ende, Kräfte an D- und B-Stutzen
|
||||||
|
- Mesh: Netgen, quadratische Tetraeder
|
||||||
|
- Face-Referenzen sind Platzhalter (müssen in GUI angepasst werden)
|
||||||
|
|
||||||
|
**Voraussetzungen**: FreeCAD ≥ 0.20, CalculiX installiert.
|
||||||
|
|
||||||
|
## Quellverzeichnis
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts/original/
|
||||||
|
├── feldbett.py ← Gesamtstruktur
|
||||||
|
├── feldbett_connectors.py ← Connector v1 (Kugelknoten)
|
||||||
|
├── feldbett_connectors_v2.py ← Connector v2 (Schweißhülsen)
|
||||||
|
└── feldbett_fem.py ← FEM-Simulation
|
||||||
|
```
|
||||||
|
|
||||||
|
Originale Quelldateien: `~/Downloads/feldbett*.py`
|
||||||
50
docs/06-offene-fragen.md
Normal file
50
docs/06-offene-fragen.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Feldbett — Offene Entscheidungen & nächste Schritte
|
||||||
|
|
||||||
|
## Offene Entscheidungen
|
||||||
|
|
||||||
|
### 1. Rohr-Triplet festlegen
|
||||||
|
|
||||||
|
Kandidaten (siehe [02-materialauswahl.md](02-materialauswahl.md)):
|
||||||
|
|
||||||
|
| Option | Standard (Alu) | Stift (Alu) | Hülse (Stahl) | Gewicht | Auslastung |
|
||||||
|
|--------|---------------|------------|--------------|---------|-----------|
|
||||||
|
| Sweet Spot | 22×1.5 | 15×1.0 | 30×2.0 | 8.0 kg | 41% |
|
||||||
|
| Robuster Prototyp | 25×1.5 | 18×1.0 | 33.7×2.5 | 10.0 kg | 31% |
|
||||||
|
| Überdimensioniert | 33.7×2.0 | 26.9×2.0 | 40×2.0 | 13.8 kg | 13% |
|
||||||
|
|
||||||
|
**Zu bedenken**: Wandstärke vs. Federbolzen-Bohrungen, Haptik, Verfügbarkeit.
|
||||||
|
|
||||||
|
### 2. Liegelänge optimieren
|
||||||
|
|
||||||
|
Aktuell 175 cm bei 3 Modulen × L=350 mm. Optionen:
|
||||||
|
- L = 400 mm → 200 cm (exakt 2 m) bei 3 Modulen, aber Stangen werden 40 cm
|
||||||
|
- L = 350 mm + 4 Module → 245 cm (zu lang?)
|
||||||
|
- Asymmetrisches Endmodul?
|
||||||
|
|
||||||
|
### 3. Korrosionsschutz-Strategie
|
||||||
|
|
||||||
|
Siehe [03-kontaktkorrosion.md](03-kontaktkorrosion.md). Favoriten:
|
||||||
|
- Hartanodisieren + POM-Buchsen + Verzinken (Dreifach-Absicherung)
|
||||||
|
- Oder: Hülsen auch aus Alu (WIG-schweißen) → Problem eliminieren
|
||||||
|
|
||||||
|
### 4. Liegefläche
|
||||||
|
|
||||||
|
Noch nicht besprochen. Optionen:
|
||||||
|
- Textilbespannung (wie Bundeswehr-Feldbett)
|
||||||
|
- Gurtband-Bespannung
|
||||||
|
- Lattenrost-artige Querleisten
|
||||||
|
|
||||||
|
### 5. FreeCAD-Scripts aktualisieren
|
||||||
|
|
||||||
|
- Maßstab auf reale Werte korrigieren
|
||||||
|
- A-Stangen segmentieren
|
||||||
|
- Inline-Stift-Verbinder modellieren
|
||||||
|
- Connector-Winkel und Gehrungsschnitte für neues Triplet berechnen
|
||||||
|
- Parameter zentralisieren (ein config-Modul statt Duplikation)
|
||||||
|
|
||||||
|
## Nächste Schritte
|
||||||
|
|
||||||
|
1. **Triplet und Liegelänge festlegen** → Grundlage für alle weiteren Berechnungen
|
||||||
|
2. **Scripts konsolidieren und korrigieren** → parametrisch, zentralisierte Config
|
||||||
|
3. **Korrosionsschutz entscheiden** → beeinflusst Passungsberechnung (Beschichtungsdicken)
|
||||||
|
4. **Prototyp-Planung** → Bestellliste, Werkzeugbedarf, Arbeitsschritte
|
||||||
80
docs/07-freecad-anleitung.md
Normal file
80
docs/07-freecad-anleitung.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Feldbett — FreeCAD Anleitung
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
- **FreeCAD 1.1.0** (installiert als Flatpak: `org.freecad.FreeCAD`)
|
||||||
|
|
||||||
|
## Scripts ausführen
|
||||||
|
|
||||||
|
### Methode 1: Makro-Dialog (einfachste)
|
||||||
|
|
||||||
|
1. FreeCAD öffnen
|
||||||
|
2. Menü → **Makro** → **Makro ausführen...**
|
||||||
|
3. Navigiere zu `~/projects/feldbett/scripts/`
|
||||||
|
4. Wähle Script → **Ausführen**
|
||||||
|
|
||||||
|
### Methode 2: Python-Konsole
|
||||||
|
|
||||||
|
1. FreeCAD öffnen
|
||||||
|
2. Menü → **Ansicht** → **Paneele** → **Python-Konsole** aktivieren
|
||||||
|
3. In der Konsole:
|
||||||
|
```python
|
||||||
|
exec(open("/home/ameyer/projects/feldbett/scripts/feldbett_struktur.py").read())
|
||||||
|
```
|
||||||
|
|
||||||
|
### Methode 3: Kommandozeile (ohne GUI, für Tests)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Headless — nur Geometrie-Berechnung und Konsolen-Output
|
||||||
|
flatpak run --command=FreeCADCmd org.freecad.FreeCAD ~/projects/feldbett/scripts/feldbett_struktur.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verfügbare Scripts
|
||||||
|
|
||||||
|
### `feldbett_struktur.py` — Gesamtansicht
|
||||||
|
|
||||||
|
Zeigt das komplette Feldbett mit allen Rohren:
|
||||||
|
- **Blaue** Rohre (A) = Längsstangen (segmentiert)
|
||||||
|
- **Orange** Rohre = Stift-Verbinder (in den A-Stangen)
|
||||||
|
- **Amber** Rohre (Q) = Querstreben
|
||||||
|
- **Grüne** Rohre (D) = Diagonalen
|
||||||
|
- **Lila** Rohre (B) = Beine
|
||||||
|
- **Rot-orange** Hülsen = Connector 1 (an Knotenpunkten)
|
||||||
|
- **Blau-grün** Hülsen = Connector 2 (an Q-Enden)
|
||||||
|
|
||||||
|
Tipp: Im **Modellbaum** (links) sind alle Teile in Gruppen sortiert. Mit **Leertaste** einzelne Gruppen ein-/ausblenden.
|
||||||
|
|
||||||
|
### `feldbett_konnektoren.py` — Detailansicht
|
||||||
|
|
||||||
|
Zeigt die drei Verbindungstypen nebeneinander:
|
||||||
|
- **Links**: Connector 1 mit Demo-Rohren (halbtransparent)
|
||||||
|
- **Mitte**: Connector 2 komplett + Einzelteile (explodiert)
|
||||||
|
- **Rechts**: Inline-Stift zwischen zwei A-Rohren
|
||||||
|
|
||||||
|
## Navigation in FreeCAD
|
||||||
|
|
||||||
|
| Aktion | Maus / Tastatur |
|
||||||
|
|--------|----------------|
|
||||||
|
| Drehen | Mittlere Maustaste gedrückt + bewegen |
|
||||||
|
| Verschieben | Mittlere Maustaste + Shift + bewegen |
|
||||||
|
| Zoomen | Scrollrad |
|
||||||
|
| Alles zeigen | V, F (oder Menü → Ansicht → Alles anzeigen) |
|
||||||
|
| Objekt auswählen | Linksklick |
|
||||||
|
| Objekt messen | Menü → Part → Measure (Abstand, Winkel) |
|
||||||
|
|
||||||
|
## Exportieren
|
||||||
|
|
||||||
|
- **STL** (3D-Druck): Objekt auswählen → Datei → Export → `.stl`
|
||||||
|
- **STEP** (CAD-Austausch): Datei → Export → `.step`
|
||||||
|
- **Bild**: Menü → Ansicht → Screenshot erstellen
|
||||||
|
|
||||||
|
## Rohrtriplet "Komfortabel"
|
||||||
|
|
||||||
|
| Typ | Material | OD×Wand | ID | Verwendung |
|
||||||
|
|-----|----------|---------|-----|-----------|
|
||||||
|
| Standard | Alu 6060-T6 | 25×1.5 | 22.0 | Alle 37 Strukturrohre |
|
||||||
|
| Stift | Alu 6060-T6 | 18×1.0 | 16.0 | 8 Inline-Verbinder |
|
||||||
|
| Hülse | Stahl S235 | 33.7×2.5 | 28.7 | 72 Hülsenstücke (Konnektoren) |
|
||||||
|
|
||||||
|
Spiel Hülse↔Standard: 3.7 mm (1.85 mm pro Seite)
|
||||||
|
Spiel Standard↔Stift: 4.0 mm (2.0 mm pro Seite)
|
||||||
403
scripts/feldbett_konnektoren.py
Normal file
403
scripts/feldbett_konnektoren.py
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
"""
|
||||||
|
Feldbett Konnektoren Detail — FreeCAD Python Script
|
||||||
|
====================================================
|
||||||
|
Zeigt alle drei Verbindungstypen als Einzelteile zur Inspektion:
|
||||||
|
1. Connector 1 (C1) — A↔D / Fuß: 2 Hülsen auf Gehrung
|
||||||
|
2. Connector 2 (C2) — Q↔D↔B: 4 Hülsen sternförmig
|
||||||
|
3. Inline-Stift — A-Stangen Verlängerung
|
||||||
|
|
||||||
|
Zusätzlich: Sägewinkel, Einstecktiefen, Passungsspiele.
|
||||||
|
|
||||||
|
Ausführen: FreeCAD → Makro → Makro ausführen → diese Datei wählen
|
||||||
|
|
||||||
|
Rohrtriplet "Komfortabel":
|
||||||
|
Standard : Alu 25×1.5 mm
|
||||||
|
Stift : Alu 18×1.0 mm
|
||||||
|
Hülse : Stahl 33.7×2.5 mm
|
||||||
|
"""
|
||||||
|
|
||||||
|
import FreeCAD as App
|
||||||
|
import FreeCADGui as Gui
|
||||||
|
import Part
|
||||||
|
import math
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PARAMETER
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
L = 350.0
|
||||||
|
BREITE_AA = 700.0
|
||||||
|
MODULE = 3
|
||||||
|
|
||||||
|
# Standard (Alu)
|
||||||
|
STD_DA = 25.0
|
||||||
|
STD_WAND = 1.5
|
||||||
|
STD_DI = STD_DA - 2.0 * STD_WAND
|
||||||
|
|
||||||
|
# Stift (Alu)
|
||||||
|
STIFT_DA = 18.0
|
||||||
|
STIFT_WAND = 1.0
|
||||||
|
STIFT_DI = STIFT_DA - 2.0 * STIFT_WAND
|
||||||
|
STIFT_LAENGE = 100.0
|
||||||
|
|
||||||
|
# Hülse (Stahl)
|
||||||
|
HUEL_DA = 33.7
|
||||||
|
HUEL_WAND = 2.5
|
||||||
|
HUEL_DI = HUEL_DA - 2.0 * HUEL_WAND
|
||||||
|
HUEL_LAENGE = 40.0
|
||||||
|
|
||||||
|
# Federbolzen
|
||||||
|
FEDER_BOLZEN_D = 4.0 # Durchmesser [mm]
|
||||||
|
|
||||||
|
# Farben
|
||||||
|
FARBE_A = (0.20, 0.45, 0.75)
|
||||||
|
FARBE_Q = (0.73, 0.46, 0.09)
|
||||||
|
FARBE_D = (0.06, 0.43, 0.34)
|
||||||
|
FARBE_B = (0.42, 0.25, 0.63)
|
||||||
|
|
||||||
|
# Passspiel
|
||||||
|
SPIEL_HUEL = HUEL_DI - STD_DA # 3.7 mm
|
||||||
|
SPIEL_STIFT = STD_DI - STIFT_DA # 4.0 mm
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# GEOMETRIE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
hwA = BREITE_AA / 2.0
|
||||||
|
hwQ = L / 2.0
|
||||||
|
dZ = hwA - hwQ
|
||||||
|
|
||||||
|
legH = math.sqrt(0.75 * L**2 - dZ**2)
|
||||||
|
totalH = 2.0 * legH
|
||||||
|
|
||||||
|
def norm(v):
|
||||||
|
l = math.sqrt(sum(x**2 for x in v))
|
||||||
|
return tuple(x/l for x in v)
|
||||||
|
|
||||||
|
# Richtungsvektoren (normiert)
|
||||||
|
vA = (1.0, 0.0, 0.0) # A-Stange: +X
|
||||||
|
vQ = (0.0, 0.0, 1.0) # Q-Strebe: +Z
|
||||||
|
vD = norm(( L/2, -legH, -dZ)) # Diagonale: A→Q
|
||||||
|
vDr = norm((-L/2, legH, dZ)) # umgekehrt: Q→A (aufwärts)
|
||||||
|
vB = norm((-L/2, -legH, dZ)) # Bein: Q→Boden
|
||||||
|
|
||||||
|
def angle_deg(v1, v2):
|
||||||
|
dot = sum(a*b for a,b in zip(v1,v2))
|
||||||
|
return math.degrees(math.acos(max(-1.0, min(1.0, abs(dot)))))
|
||||||
|
|
||||||
|
def angle_signed(v1, v2):
|
||||||
|
dot = sum(a*b for a,b in zip(v1,v2))
|
||||||
|
return math.degrees(math.acos(max(-1.0, min(1.0, dot))))
|
||||||
|
|
||||||
|
def saege_winkel(v1, v2):
|
||||||
|
"""Gehrungswinkel: 90° - (Winkel_zwischen / 2)"""
|
||||||
|
alpha = angle_signed(v1, v2)
|
||||||
|
return 90.0 - alpha / 2.0
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# KONSOLEN-AUSGABE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
SEP = "=" * 55
|
||||||
|
print(SEP)
|
||||||
|
print("FELDBETT KONNEKTOREN — DETAIL")
|
||||||
|
print(SEP)
|
||||||
|
|
||||||
|
print(f"\nRohre:")
|
||||||
|
print(f" Standard (Alu) : {STD_DA}×{STD_WAND} mm (ID={STD_DI:.1f})")
|
||||||
|
print(f" Stift (Alu) : {STIFT_DA}×{STIFT_WAND} mm (ID={STIFT_DI:.1f})")
|
||||||
|
print(f" Hülse (Stahl) : {HUEL_DA}×{HUEL_WAND} mm (ID={HUEL_DI:.1f})")
|
||||||
|
print(f" Spiel Hülse↔Std : {SPIEL_HUEL:.1f} mm ({SPIEL_HUEL/2:.1f} mm pro Seite)")
|
||||||
|
print(f" Spiel Std↔Stift : {SPIEL_STIFT:.1f} mm ({SPIEL_STIFT/2:.1f} mm pro Seite)")
|
||||||
|
|
||||||
|
print(f"\nWinkel:")
|
||||||
|
print(f" A ↔ D : {angle_deg(vA, vD):.1f}°")
|
||||||
|
print(f" Q ↔ D : {angle_deg(vQ, vD):.1f}°")
|
||||||
|
print(f" Q ↔ B : {angle_deg(vQ, vB):.1f}°")
|
||||||
|
print(f" D ↔ B : {angle_deg(vD, vB):.1f}°")
|
||||||
|
|
||||||
|
print(f"\nSägewinkel (Gehrung, Abweichung von 90°):")
|
||||||
|
print(f" C1: A–D Gehrung = {saege_winkel(vA, vD):.1f}°")
|
||||||
|
print(f" C2: Q–D Gehrung = {saege_winkel(vQ, vDr):.1f}°")
|
||||||
|
print(f" C2: Q–B Gehrung = {saege_winkel(vQ, vB):.1f}°")
|
||||||
|
print(f" C2: D–B Gehrung = {saege_winkel(vDr, vB):.1f}°")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# FreeCAD HILFSFUNKTIONEN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def fv(t):
|
||||||
|
return App.Vector(t[0], t[1], t[2])
|
||||||
|
|
||||||
|
def rotation_to_dir(direction):
|
||||||
|
z = App.Vector(0, 0, 1)
|
||||||
|
d = App.Vector(*direction).normalize()
|
||||||
|
cross = z.cross(d)
|
||||||
|
dot = z.dot(d)
|
||||||
|
if cross.Length > 1e-6:
|
||||||
|
angle = math.degrees(math.acos(max(-1.0, min(1.0, dot))))
|
||||||
|
return App.Rotation(cross, angle)
|
||||||
|
elif dot < 0:
|
||||||
|
return App.Rotation(App.Vector(1, 0, 0), 180)
|
||||||
|
else:
|
||||||
|
return App.Rotation()
|
||||||
|
|
||||||
|
def make_cyl(direction, length, radius, origin=(0,0,0)):
|
||||||
|
cyl = Part.makeCylinder(radius, length)
|
||||||
|
rot = rotation_to_dir(direction)
|
||||||
|
cyl.Placement = App.Placement(App.Vector(*origin), rot)
|
||||||
|
return cyl
|
||||||
|
|
||||||
|
def make_huelse(direction, origin=(0,0,0), length=None):
|
||||||
|
"""Hohle Hülse (Stahl) entlang direction."""
|
||||||
|
ll = length or HUEL_LAENGE
|
||||||
|
outer = make_cyl(direction, ll, HUEL_DA / 2.0, origin)
|
||||||
|
inner = make_cyl(direction, ll + 1.0, HUEL_DI / 2.0, origin)
|
||||||
|
return outer.cut(inner)
|
||||||
|
|
||||||
|
def make_rohr(direction, length, da, wand, origin=(0,0,0)):
|
||||||
|
"""Allgemeines Hohlrohr."""
|
||||||
|
di = da - 2.0 * wand
|
||||||
|
outer = make_cyl(direction, length, da / 2.0, origin)
|
||||||
|
inner = make_cyl(direction, length + 1.0, di / 2.0, origin)
|
||||||
|
return outer.cut(inner)
|
||||||
|
|
||||||
|
def make_federbolzen_bohrung(direction, origin, dist_from_start):
|
||||||
|
"""Querbohrung für Federbolzen."""
|
||||||
|
if FEDER_BOLZEN_D <= 0:
|
||||||
|
return None
|
||||||
|
d = App.Vector(*direction).normalize()
|
||||||
|
mid = App.Vector(
|
||||||
|
origin[0] + d.x * dist_from_start,
|
||||||
|
origin[1] + d.y * dist_from_start,
|
||||||
|
origin[2] + d.z * dist_from_start,
|
||||||
|
)
|
||||||
|
ref = App.Vector(0,1,0) if abs(d.dot(App.Vector(0,1,0))) < 0.9 else App.Vector(1,0,0)
|
||||||
|
bohr_dir = d.cross(ref).normalize()
|
||||||
|
bohr_len = HUEL_DA + 4.0
|
||||||
|
start = App.Vector(
|
||||||
|
mid.x - bohr_dir.x * bohr_len / 2.0,
|
||||||
|
mid.y - bohr_dir.y * bohr_len / 2.0,
|
||||||
|
mid.z - bohr_dir.z * bohr_len / 2.0,
|
||||||
|
)
|
||||||
|
cyl = Part.makeCylinder(FEDER_BOLZEN_D / 2.0, bohr_len)
|
||||||
|
rot = rotation_to_dir((bohr_dir.x, bohr_dir.y, bohr_dir.z))
|
||||||
|
cyl.Placement = App.Placement(start, rot)
|
||||||
|
return cyl
|
||||||
|
|
||||||
|
def add_part(doc, shape, name, color, group=None):
|
||||||
|
obj = doc.addObject("Part::Feature", name)
|
||||||
|
obj.Shape = shape
|
||||||
|
if obj.ViewObject:
|
||||||
|
obj.ViewObject.ShapeColor = color
|
||||||
|
if group:
|
||||||
|
group.addObject(obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DOKUMENT
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
doc_name = "Feldbett_Konnektoren_v2"
|
||||||
|
if doc_name in App.listDocuments():
|
||||||
|
App.closeDocument(doc_name)
|
||||||
|
doc = App.newDocument(doc_name)
|
||||||
|
|
||||||
|
grp_c1 = doc.addObject("App::DocumentObjectGroup", "Connector_1")
|
||||||
|
grp_c2 = doc.addObject("App::DocumentObjectGroup", "Connector_2")
|
||||||
|
grp_stift = doc.addObject("App::DocumentObjectGroup", "Inline_Stift")
|
||||||
|
grp_demo = doc.addObject("App::DocumentObjectGroup", "Demo_Rohre")
|
||||||
|
|
||||||
|
# Positionen der drei Baugruppen (nebeneinander in X)
|
||||||
|
O_C1 = (0, 0, 0)
|
||||||
|
O_C2 = (150, 0, 0)
|
||||||
|
O_STIFT = (350, 0, 0)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONNECTOR 1 — 2 Hülsen (A + D)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
print("Erzeuge Connector 1...")
|
||||||
|
|
||||||
|
# Hülse A-Richtung (+X und -X, durchlaufend)
|
||||||
|
h_A = make_huelse((1,0,0), O_C1)
|
||||||
|
# Hülse A Gegenrichtung
|
||||||
|
h_A2 = make_huelse((-1,0,0), O_C1)
|
||||||
|
# Hülse D-Richtung
|
||||||
|
h_D = make_huelse(vD, O_C1)
|
||||||
|
|
||||||
|
c1 = h_A.fuse(h_A2).fuse(h_D)
|
||||||
|
|
||||||
|
# Federbolzen-Bohrungen
|
||||||
|
for direction in [(1,0,0), (-1,0,0), vD]:
|
||||||
|
b = make_federbolzen_bohrung(direction, O_C1, HUEL_LAENGE * 0.6)
|
||||||
|
if b:
|
||||||
|
c1 = c1.cut(b)
|
||||||
|
|
||||||
|
try:
|
||||||
|
c1 = c1.makeFillet(1.0, c1.Edges)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
add_part(doc, c1, "C1_komplett", (0.85, 0.35, 0.15), grp_c1)
|
||||||
|
|
||||||
|
# Demo-Rohre in C1 einstecken (Alu-Stangen, halbtransparent)
|
||||||
|
demo_A = make_rohr((1,0,0), 120, STD_DA, STD_WAND,
|
||||||
|
(O_C1[0]-60, O_C1[1], O_C1[2]))
|
||||||
|
obj_dA = add_part(doc, demo_A, "Demo_A_in_C1", (0.2, 0.45, 0.75), grp_demo)
|
||||||
|
if obj_dA.ViewObject:
|
||||||
|
obj_dA.ViewObject.Transparency = 60
|
||||||
|
|
||||||
|
demo_D = make_rohr(vD, 100, STD_DA, STD_WAND, O_C1)
|
||||||
|
obj_dD = add_part(doc, demo_D, "Demo_D_in_C1", (0.06, 0.43, 0.34), grp_demo)
|
||||||
|
if obj_dD.ViewObject:
|
||||||
|
obj_dD.ViewObject.Transparency = 60
|
||||||
|
|
||||||
|
print(f" C1: 2+1 Hülsen à {HUEL_LAENGE:.0f}mm, Gehrung A-D = {saege_winkel(vA, vD):.1f}°")
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONNECTOR 2 — 4 Hülsen (Ql, Qr, D, B)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
print("Erzeuge Connector 2...")
|
||||||
|
|
||||||
|
h_Ql = make_huelse((0,0,-1), O_C2)
|
||||||
|
h_Qr = make_huelse((0,0,+1), O_C2)
|
||||||
|
h_Dr = make_huelse(vDr, O_C2)
|
||||||
|
h_Bv = make_huelse(vB, O_C2)
|
||||||
|
|
||||||
|
c2 = h_Ql.fuse(h_Qr).fuse(h_Dr).fuse(h_Bv)
|
||||||
|
|
||||||
|
# Federbolzen-Bohrungen
|
||||||
|
for direction in [(0,0,-1), (0,0,+1), vDr, vB]:
|
||||||
|
b = make_federbolzen_bohrung(direction, O_C2, HUEL_LAENGE * 0.6)
|
||||||
|
if b:
|
||||||
|
c2 = c2.cut(b)
|
||||||
|
|
||||||
|
try:
|
||||||
|
c2 = c2.makeFillet(1.0, c2.Edges)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
add_part(doc, c2, "C2_komplett", (0.15, 0.55, 0.75), grp_c2)
|
||||||
|
|
||||||
|
# Demo-Rohre in C2
|
||||||
|
for direction, length, label, color in [
|
||||||
|
((0,0,-1), 80, "Demo_Ql_in_C2", FARBE_Q),
|
||||||
|
((0,0,+1), 80, "Demo_Qr_in_C2", FARBE_Q),
|
||||||
|
(vDr, 80, "Demo_D_in_C2", (0.06, 0.43, 0.34)),
|
||||||
|
(vB, 80, "Demo_B_in_C2", (0.42, 0.25, 0.63)),
|
||||||
|
]:
|
||||||
|
r = make_rohr(direction, length, STD_DA, STD_WAND, O_C2)
|
||||||
|
o = add_part(doc, r, label, color, grp_demo)
|
||||||
|
if o.ViewObject:
|
||||||
|
o.ViewObject.Transparency = 60
|
||||||
|
|
||||||
|
print(f" C2: 4 Hülsen à {HUEL_LAENGE:.0f}mm")
|
||||||
|
print(f" Q-D Gehrung = {saege_winkel(vQ, vDr):.1f}°")
|
||||||
|
print(f" D-B Gehrung = {saege_winkel(vDr, vB):.1f}°")
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# INLINE-STIFT-VERBINDER (A-Stangen verlängern)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
print("Erzeuge Inline-Stift...")
|
||||||
|
|
||||||
|
# Zwei A-Rohrstücke + Stift dazwischen
|
||||||
|
gap = 2.0 # kleiner Spalt zwischen den Rohren [mm] für Sichtbarkeit
|
||||||
|
|
||||||
|
# linkes Rohr
|
||||||
|
r_left = make_rohr((-1,0,0), 120, STD_DA, STD_WAND,
|
||||||
|
(O_STIFT[0] - gap/2, O_STIFT[1], O_STIFT[2]))
|
||||||
|
o = add_part(doc, r_left, "Stift_Rohr_links", FARBE_A, grp_stift)
|
||||||
|
if o.ViewObject:
|
||||||
|
o.ViewObject.Transparency = 40
|
||||||
|
|
||||||
|
# rechtes Rohr
|
||||||
|
r_right = make_rohr((1,0,0), 120, STD_DA, STD_WAND,
|
||||||
|
(O_STIFT[0] + gap/2, O_STIFT[1], O_STIFT[2]))
|
||||||
|
o = add_part(doc, r_right, "Stift_Rohr_rechts", FARBE_A, grp_stift)
|
||||||
|
if o.ViewObject:
|
||||||
|
o.ViewObject.Transparency = 40
|
||||||
|
|
||||||
|
# Stift selbst (zentriert, ragt in beide Rohre)
|
||||||
|
stift = make_rohr((1,0,0), STIFT_LAENGE, STIFT_DA, STIFT_WAND,
|
||||||
|
(O_STIFT[0] - STIFT_LAENGE/2, O_STIFT[1], O_STIFT[2]))
|
||||||
|
add_part(doc, stift, "Stift_Verbinder", (0.85, 0.55, 0.15), grp_stift)
|
||||||
|
|
||||||
|
# Federbolzen-Löcher durch Rohr+Stift (beidseitig)
|
||||||
|
for xOff in [-STIFT_LAENGE/4, +STIFT_LAENGE/4]:
|
||||||
|
bohr = Part.makeCylinder(FEDER_BOLZEN_D / 2.0, STD_DA + 10)
|
||||||
|
bohr.Placement = App.Placement(
|
||||||
|
App.Vector(O_STIFT[0] + xOff, O_STIFT[1] - STD_DA/2 - 5, O_STIFT[2]),
|
||||||
|
App.Rotation(App.Vector(1, 0, 0), 90) # senkrecht durch Rohr
|
||||||
|
)
|
||||||
|
# Nur als Anschauung, nicht ausschneiden (Demo)
|
||||||
|
|
||||||
|
print(f" Stift: {STIFT_DA}×{STIFT_WAND} × {STIFT_LAENGE:.0f}mm")
|
||||||
|
print(f" Einstecktiefe: {STIFT_LAENGE/2:.0f}mm pro Seite")
|
||||||
|
print(f" Federbolzen: Ø{FEDER_BOLZEN_D:.0f}mm, {STIFT_LAENGE/4:.0f}mm vom Stoß")
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONNECTOR 2 EINZELTEILE (explodiert)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
print("\nErzeuge C2 Einzelteile (explodiert)...")
|
||||||
|
|
||||||
|
O_EXPL = (150, -120, 0)
|
||||||
|
einzelteile = [
|
||||||
|
((0, 0, -1), "C2_Teil_Ql", (0.7, 0.7, 0.2)),
|
||||||
|
((0, 0, +1), "C2_Teil_Qr", (0.7, 0.7, 0.2)),
|
||||||
|
(vDr, "C2_Teil_D", (0.2, 0.7, 0.4)),
|
||||||
|
(vB, "C2_Teil_B", (0.6, 0.3, 0.7)),
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, (direction, name, col) in enumerate(einzelteile):
|
||||||
|
ox = O_EXPL[0] + (i - 1.5) * (HUEL_DA + 20)
|
||||||
|
oy = O_EXPL[1]
|
||||||
|
oz = O_EXPL[2]
|
||||||
|
h = make_huelse(direction, (ox, oy, oz))
|
||||||
|
b = make_federbolzen_bohrung(direction, (ox, oy, oz), HUEL_LAENGE * 0.6)
|
||||||
|
if b:
|
||||||
|
h = h.cut(b)
|
||||||
|
add_part(doc, h, name, col, grp_c2)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# ABSCHLUSS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
doc.recompute()
|
||||||
|
|
||||||
|
try:
|
||||||
|
Gui.activeDocument().activeView().fitAll()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(SEP)
|
||||||
|
print("KONNEKTOREN ERFOLGREICH ERSTELLT")
|
||||||
|
print(SEP)
|
||||||
|
print(f"""
|
||||||
|
Baugruppen im Dokument:
|
||||||
|
Connector_1 — C1 komplett + Demo-Rohre
|
||||||
|
Connector_2 — C2 komplett + Einzelteile (explodiert) + Demo-Rohre
|
||||||
|
Inline_Stift — Stift in zwei A-Rohren
|
||||||
|
Demo_Rohre — halbtransparente Alu-Rohre zur Veranschaulichung
|
||||||
|
|
||||||
|
Connector 1 (×24):
|
||||||
|
2+1 Hülsen: A-links, A-rechts (durchlaufend), D
|
||||||
|
Gehrungswinkel A–D = {saege_winkel(vA, vD):.1f}°
|
||||||
|
Für Fuß: identisch, um 180° gedreht
|
||||||
|
|
||||||
|
Connector 2 (×6):
|
||||||
|
4 Hülsen: Q-links, Q-rechts, D (aufwärts), B (abwärts)
|
||||||
|
Gehrungswinkel Q–D = {saege_winkel(vQ, vDr):.1f}°
|
||||||
|
Gehrungswinkel D–B = {saege_winkel(vDr, vB):.1f}°
|
||||||
|
|
||||||
|
Inline-Stift (×8):
|
||||||
|
Alu {STIFT_DA}×{STIFT_WAND} × {STIFT_LAENGE:.0f}mm
|
||||||
|
Je 2 Federbolzen Ø{FEDER_BOLZEN_D:.0f}mm
|
||||||
|
|
||||||
|
Tipp: Einzelne Gruppen ein-/ausblenden via Modellbaum (Leertaste).
|
||||||
|
""")
|
||||||
359
scripts/feldbett_struktur.py
Normal file
359
scripts/feldbett_struktur.py
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
"""
|
||||||
|
Feldbett Struktur v2 — FreeCAD Python Script
|
||||||
|
=============================================
|
||||||
|
Zeigt das komplette Feldbett mit allen Rohren in Realgröße.
|
||||||
|
A-Stangen sind segmentiert (je 350mm), Stift-Verbinder angedeutet.
|
||||||
|
|
||||||
|
Ausführen: FreeCAD → Makro → Makro ausführen → diese Datei wählen
|
||||||
|
|
||||||
|
Koordinatensystem:
|
||||||
|
X = Längsrichtung (Liegeachse)
|
||||||
|
Y = Höhe (oben = Liegefläche)
|
||||||
|
Z = Querrichtung (Breite)
|
||||||
|
|
||||||
|
Rohrtriplet "Komfortabel":
|
||||||
|
Standard : Alu 25×1.5 mm (Strukturrohre)
|
||||||
|
Stift : Alu 18×1.0 mm (Inline-Verbinder A-Stangen)
|
||||||
|
Hülse : Stahl 33.7×2.5 mm (Konnektoren C1/C2)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import FreeCAD as App
|
||||||
|
import FreeCADGui as Gui
|
||||||
|
import Part
|
||||||
|
import math
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PARAMETER
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# Geometrie
|
||||||
|
L = 350.0 # Stangenlänge [mm] — ALLE Stangen gleich
|
||||||
|
BREITE_AA = 700.0 # Abstand zwischen Längsstangen (A–A) [mm]
|
||||||
|
MODULE = 3 # Anzahl Module
|
||||||
|
|
||||||
|
# Rohre — Triplet "Komfortabel"
|
||||||
|
# Standard (Alu 25×1.5)
|
||||||
|
STD_DA = 25.0 # Außendurchmesser [mm]
|
||||||
|
STD_WAND = 1.5 # Wandstärke [mm]
|
||||||
|
STD_DI = STD_DA - 2.0 * STD_WAND # 22.0 mm
|
||||||
|
|
||||||
|
# Stift-Verbinder (Alu 18×1.0)
|
||||||
|
STIFT_DA = 18.0
|
||||||
|
STIFT_WAND = 1.0
|
||||||
|
STIFT_DI = STIFT_DA - 2.0 * STIFT_WAND
|
||||||
|
STIFT_LAENGE = 100.0 # 50mm Einstecktiefe pro Seite
|
||||||
|
|
||||||
|
# Hülse / Konnektor (Stahl 33.7×2.5)
|
||||||
|
HUEL_DA = 33.7
|
||||||
|
HUEL_WAND = 2.5
|
||||||
|
HUEL_DI = HUEL_DA - 2.0 * HUEL_WAND # 28.7 mm
|
||||||
|
HUEL_LAENGE = 40.0 # Länge pro Hülsenstück
|
||||||
|
|
||||||
|
# Farben (R, G, B) je 0.0–1.0
|
||||||
|
FARBE_A = (0.20, 0.45, 0.75) # blau — Längsstangen
|
||||||
|
FARBE_D = (0.06, 0.43, 0.34) # grün — Diagonalen
|
||||||
|
FARBE_Q = (0.73, 0.46, 0.09) # amber — Querstreben
|
||||||
|
FARBE_B = (0.42, 0.25, 0.63) # lila — Beine
|
||||||
|
FARBE_STIFT = (0.85, 0.55, 0.15) # orange — Stift-Verbinder
|
||||||
|
FARBE_C1 = (0.85, 0.35, 0.15) # rot-orange — Connector 1
|
||||||
|
FARBE_C2 = (0.15, 0.55, 0.75) # blau-grün — Connector 2
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# BERECHNETE GEOMETRIE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
hwA = BREITE_AA / 2.0 # halbe Breite A–A
|
||||||
|
hwQ = L / 2.0 # halbe Q-Länge (Q = L)
|
||||||
|
dZ = hwA - hwQ # Z-Einzug von A nach Q
|
||||||
|
|
||||||
|
legH2 = 0.75 * L**2 - dZ**2
|
||||||
|
if legH2 <= 0:
|
||||||
|
raise ValueError(
|
||||||
|
f"Geometrie unmöglich: L={L} zu kurz für Breite={BREITE_AA}. "
|
||||||
|
f"Erhöhe L oder reduziere BREITE_AA."
|
||||||
|
)
|
||||||
|
|
||||||
|
legH = math.sqrt(legH2) # Höhe pro Ebene (A→Q oder Q→Boden)
|
||||||
|
totalH = 2.0 * legH # Gesamthöhe
|
||||||
|
step = 2.0 * L # Modul-Abstand in X
|
||||||
|
aEnd = (MODULE - 1) * step + L # X-Ende der Längsstangen
|
||||||
|
|
||||||
|
# Kontrollrechnung D/B-Länge
|
||||||
|
dLen = math.sqrt((L / 2)**2 + legH**2 + dZ**2)
|
||||||
|
|
||||||
|
# A-Stangen Segmente
|
||||||
|
a_per_side = int(aEnd / L)
|
||||||
|
|
||||||
|
print("=" * 55)
|
||||||
|
print("FELDBETT STRUKTUR v2")
|
||||||
|
print("=" * 55)
|
||||||
|
print(f" Stangenlänge L = {L:.0f} mm")
|
||||||
|
print(f" Breite A–A = {BREITE_AA:.0f} mm")
|
||||||
|
print(f" Liegelänge = {aEnd:.0f} mm = {aEnd/10:.0f} cm")
|
||||||
|
print(f" Höhe gesamt = {totalH:.0f} mm = {totalH/10:.1f} cm")
|
||||||
|
print(f" legH (pro Ebene) = {legH:.1f} mm")
|
||||||
|
print(f" D/B-Länge = {dLen:.1f} mm (soll = {L:.0f})")
|
||||||
|
print(f" Module = {MODULE}")
|
||||||
|
print(f" A-Segmente pro Seite = {a_per_side}")
|
||||||
|
print(f"\n Standard : {STD_DA}×{STD_WAND} mm (Alu)")
|
||||||
|
print(f" Stift : {STIFT_DA}×{STIFT_WAND} mm (Alu)")
|
||||||
|
print(f" Hülse : {HUEL_DA}×{HUEL_WAND} mm (Stahl)")
|
||||||
|
print(f" Spiel Hülse↔Std = {HUEL_DI - STD_DA:.1f} mm")
|
||||||
|
print(f" Spiel Std↔Stift = {STD_DI - STIFT_DA:.1f} mm")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# HILFSFUNKTIONEN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def make_tube(p1, p2, da, wand, label, color, group=None):
|
||||||
|
"""Hohlrohr von p1 nach p2."""
|
||||||
|
direction = p2 - p1
|
||||||
|
length = direction.Length
|
||||||
|
if length < 0.01:
|
||||||
|
return None
|
||||||
|
|
||||||
|
di = da - 2.0 * wand
|
||||||
|
outer = Part.makeCylinder(da / 2.0, length)
|
||||||
|
inner = Part.makeCylinder(di / 2.0, length)
|
||||||
|
tube = outer.cut(inner)
|
||||||
|
|
||||||
|
z_axis = App.Vector(0, 0, 1)
|
||||||
|
norm_dir = direction.normalize()
|
||||||
|
cross = z_axis.cross(norm_dir)
|
||||||
|
dot = z_axis.dot(norm_dir)
|
||||||
|
|
||||||
|
if cross.Length > 1e-6:
|
||||||
|
angle = math.degrees(math.acos(max(-1.0, min(1.0, dot))))
|
||||||
|
rot = App.Rotation(cross, angle)
|
||||||
|
elif dot < 0:
|
||||||
|
rot = App.Rotation(App.Vector(1, 0, 0), 180)
|
||||||
|
else:
|
||||||
|
rot = App.Rotation()
|
||||||
|
|
||||||
|
tube.Placement = App.Placement(p1, rot)
|
||||||
|
|
||||||
|
obj = doc.addObject("Part::Feature", label)
|
||||||
|
obj.Shape = tube
|
||||||
|
if hasattr(obj, "ViewObject") and obj.ViewObject:
|
||||||
|
obj.ViewObject.ShapeColor = color
|
||||||
|
|
||||||
|
if group:
|
||||||
|
group.addObject(obj)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def vec(x, y, z):
|
||||||
|
return App.Vector(x, y, z)
|
||||||
|
|
||||||
|
|
||||||
|
def make_group(name):
|
||||||
|
return doc.addObject("App::DocumentObjectGroup", name)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DOKUMENT ANLEGEN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
doc_name = "Feldbett_v2"
|
||||||
|
if doc_name in App.listDocuments():
|
||||||
|
App.closeDocument(doc_name)
|
||||||
|
|
||||||
|
doc = App.newDocument(doc_name)
|
||||||
|
|
||||||
|
grp_A = make_group("A_Laengsstangen")
|
||||||
|
grp_Q = make_group("Q_Querstreben")
|
||||||
|
grp_D = make_group("D_Diagonalen")
|
||||||
|
grp_B = make_group("B_Beine")
|
||||||
|
grp_stift = make_group("Stift_Verbinder")
|
||||||
|
grp_c1 = make_group("Connector_1_Huelsen")
|
||||||
|
grp_c2 = make_group("Connector_2_Huelsen")
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# A — LÄNGSSTANGEN (segmentiert, 2× je a_per_side Stücke)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
for side, zS in [("L", -1), ("R", +1)]:
|
||||||
|
zA = zS * hwA
|
||||||
|
for seg in range(a_per_side):
|
||||||
|
x0 = seg * L
|
||||||
|
x1 = x0 + L
|
||||||
|
make_tube(
|
||||||
|
vec(x0, totalH, zA),
|
||||||
|
vec(x1, totalH, zA),
|
||||||
|
STD_DA, STD_WAND,
|
||||||
|
f"A_{side}_seg{seg+1}", FARBE_A, grp_A
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# STIFT-VERBINDER (A-Stangen inline, zwischen Segmenten)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
for side, zS in [("L", -1), ("R", +1)]:
|
||||||
|
zA = zS * hwA
|
||||||
|
for joint in range(a_per_side - 1):
|
||||||
|
xJ = (joint + 1) * L # Stoßstelle
|
||||||
|
x0 = xJ - STIFT_LAENGE / 2.0
|
||||||
|
x1 = xJ + STIFT_LAENGE / 2.0
|
||||||
|
make_tube(
|
||||||
|
vec(x0, totalH, zA),
|
||||||
|
vec(x1, totalH, zA),
|
||||||
|
STIFT_DA, STIFT_WAND,
|
||||||
|
f"Stift_{side}_{joint+1}", FARBE_STIFT, grp_stift
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MODULE — Q, D, B + Konnektor-Hülsen
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
for m in range(MODULE):
|
||||||
|
xStart = m * step
|
||||||
|
xAL = xStart # X linker A-Knoten
|
||||||
|
xAR = xStart + L # X rechter A-Knoten
|
||||||
|
xQ = xStart + L / 2 # X Mitte → Q-Position
|
||||||
|
|
||||||
|
suf = f"_M{m+1}"
|
||||||
|
|
||||||
|
# --- Q Querstrebe ---
|
||||||
|
make_tube(
|
||||||
|
vec(xQ, legH, -hwQ),
|
||||||
|
vec(xQ, legH, +hwQ),
|
||||||
|
STD_DA, STD_WAND,
|
||||||
|
"Q" + suf, FARBE_Q, grp_Q
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- D Diagonalen (4 pro Modul) ---
|
||||||
|
for side, zS in [("L", -1), ("R", +1)]:
|
||||||
|
zA = zS * hwA
|
||||||
|
zQe = zS * hwQ
|
||||||
|
|
||||||
|
# linker A-Knoten → Q-Ende
|
||||||
|
make_tube(
|
||||||
|
vec(xAL, totalH, zA),
|
||||||
|
vec(xQ, legH, zQe),
|
||||||
|
STD_DA, STD_WAND,
|
||||||
|
f"D_AL_{side}{suf}", FARBE_D, grp_D
|
||||||
|
)
|
||||||
|
# rechter A-Knoten → Q-Ende
|
||||||
|
make_tube(
|
||||||
|
vec(xAR, totalH, zA),
|
||||||
|
vec(xQ, legH, zQe),
|
||||||
|
STD_DA, STD_WAND,
|
||||||
|
f"D_AR_{side}{suf}", FARBE_D, grp_D
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- B Beine (4 pro Modul, gespiegelt) ---
|
||||||
|
for side, zS in [("L", -1), ("R", +1)]:
|
||||||
|
zA = zS * hwA
|
||||||
|
zQe = zS * hwQ
|
||||||
|
|
||||||
|
make_tube(
|
||||||
|
vec(xQ, legH, zQe),
|
||||||
|
vec(xAL, 0, zA),
|
||||||
|
STD_DA, STD_WAND,
|
||||||
|
f"B_AL_{side}{suf}", FARBE_B, grp_B
|
||||||
|
)
|
||||||
|
make_tube(
|
||||||
|
vec(xQ, legH, zQe),
|
||||||
|
vec(xAR, 0, zA),
|
||||||
|
STD_DA, STD_WAND,
|
||||||
|
f"B_AR_{side}{suf}", FARBE_B, grp_B
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Connector 2 Hülsen (an Q-Enden) ---
|
||||||
|
# Jedes Q-Ende bekommt 4 Hülsen: Q-links, Q-rechts, D(aufwärts), B(abwärts)
|
||||||
|
for zS in [-1, +1]:
|
||||||
|
zA = zS * hwA
|
||||||
|
zQe = zS * hwQ
|
||||||
|
qEnd = vec(xQ, legH, zQe)
|
||||||
|
side_label = "L" if zS < 0 else "R"
|
||||||
|
|
||||||
|
# Richtungsvektoren vom Q-Ende aus
|
||||||
|
# Q verläuft in ±Z → Hülsen zeigen in +Z und -Z
|
||||||
|
for qdir, qlabel in [((0,0,-1), "Ql"), ((0,0,+1), "Qr")]:
|
||||||
|
d = App.Vector(*qdir).normalize()
|
||||||
|
p1 = qEnd
|
||||||
|
p2 = vec(qEnd.x + d.x*HUEL_LAENGE, qEnd.y + d.y*HUEL_LAENGE, qEnd.z + d.z*HUEL_LAENGE)
|
||||||
|
make_tube(p1, p2, HUEL_DA, HUEL_WAND,
|
||||||
|
f"C2_{qlabel}_{side_label}{suf}", FARBE_C2, grp_c2)
|
||||||
|
|
||||||
|
# D-Richtung (vom Q-Ende zum A-Knoten oben = umgekehrte D-Richtung)
|
||||||
|
# Wir nehmen die Richtung zum linken A-Knoten als Referenz
|
||||||
|
vDr = vec(xAL - xQ, totalH - legH, zA - zQe).normalize()
|
||||||
|
p2 = vec(qEnd.x + vDr.x*HUEL_LAENGE, qEnd.y + vDr.y*HUEL_LAENGE, qEnd.z + vDr.z*HUEL_LAENGE)
|
||||||
|
make_tube(qEnd, p2, HUEL_DA, HUEL_WAND,
|
||||||
|
f"C2_D_{side_label}{suf}", FARBE_C2, grp_c2)
|
||||||
|
|
||||||
|
# B-Richtung (vom Q-Ende zum Bodenknoten)
|
||||||
|
vB = vec(xAL - xQ, 0 - legH, zA - zQe).normalize()
|
||||||
|
p2 = vec(qEnd.x + vB.x*HUEL_LAENGE, qEnd.y + vB.y*HUEL_LAENGE, qEnd.z + vB.z*HUEL_LAENGE)
|
||||||
|
make_tube(qEnd, p2, HUEL_DA, HUEL_WAND,
|
||||||
|
f"C2_B_{side_label}{suf}", FARBE_C2, grp_c2)
|
||||||
|
|
||||||
|
# --- Connector 1 Hülsen (an A-Knoten oben und Fußpunkten unten) ---
|
||||||
|
for xF in [xAL, xAR]:
|
||||||
|
for side, zS in [("L", -1), ("R", +1)]:
|
||||||
|
zA = zS * hwA
|
||||||
|
zQe = zS * hwQ
|
||||||
|
xi = int(xF)
|
||||||
|
|
||||||
|
# === C1 oben: am A-Knoten, verbindet A-Stange mit D-Diagonale ===
|
||||||
|
aNode = vec(xF, totalH, zA)
|
||||||
|
|
||||||
|
# Hülse entlang A (±X)
|
||||||
|
make_tube(
|
||||||
|
vec(xF - HUEL_LAENGE/2, totalH, zA),
|
||||||
|
vec(xF + HUEL_LAENGE/2, totalH, zA),
|
||||||
|
HUEL_DA, HUEL_WAND,
|
||||||
|
f"C1o_A_x{xi}_{side}{suf}", FARBE_C1, grp_c1
|
||||||
|
)
|
||||||
|
# Hülse entlang D (nach unten zum Q-Ende)
|
||||||
|
vD = vec(xQ - xF, legH - totalH, zQe - zA).normalize()
|
||||||
|
p2 = vec(aNode.x + vD.x*HUEL_LAENGE, aNode.y + vD.y*HUEL_LAENGE, aNode.z + vD.z*HUEL_LAENGE)
|
||||||
|
make_tube(aNode, p2, HUEL_DA, HUEL_WAND,
|
||||||
|
f"C1o_D_x{xi}_{side}{suf}", FARBE_C1, grp_c1)
|
||||||
|
|
||||||
|
# === C1 unten (Fuß): am Bodenknoten, verbindet B-Bein mit Bodenkontakt ===
|
||||||
|
fNode = vec(xF, 0, zA)
|
||||||
|
|
||||||
|
# Hülse für B (nach oben zum Q-Ende)
|
||||||
|
vBup = vec(xQ - xF, legH, zQe - zA).normalize()
|
||||||
|
p2 = vec(fNode.x + vBup.x*HUEL_LAENGE, fNode.y + vBup.y*HUEL_LAENGE, fNode.z + vBup.z*HUEL_LAENGE)
|
||||||
|
make_tube(fNode, p2, HUEL_DA, HUEL_WAND,
|
||||||
|
f"C1u_B_x{xi}_{side}{suf}", FARBE_C1, grp_c1)
|
||||||
|
|
||||||
|
# Hülse als Fuß (nach unten, senkrecht)
|
||||||
|
make_tube(
|
||||||
|
vec(xF, 0, zA),
|
||||||
|
vec(xF, -HUEL_LAENGE, zA),
|
||||||
|
HUEL_DA, HUEL_WAND,
|
||||||
|
f"C1u_F_x{xi}_{side}{suf}", FARBE_C1, grp_c1
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# ABSCHLUSS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
doc.recompute()
|
||||||
|
|
||||||
|
try:
|
||||||
|
Gui.activeDocument().activeView().fitAll()
|
||||||
|
Gui.SendMsgToActiveView("ViewFit")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Stückliste
|
||||||
|
print("STÜCKLISTE:")
|
||||||
|
print(f" {a_per_side * 2:>3}× A Längsstangen (Alu {STD_DA}×{STD_WAND} × {L:.0f}mm)")
|
||||||
|
print(f" {MODULE:>3}× Q Querstreben (Alu {STD_DA}×{STD_WAND} × {L:.0f}mm)")
|
||||||
|
print(f" {MODULE*4:>3}× D Diagonalen (Alu {STD_DA}×{STD_WAND} × {L:.0f}mm)")
|
||||||
|
print(f" {MODULE*4:>3}× B Beine (Alu {STD_DA}×{STD_WAND} × {L:.0f}mm)")
|
||||||
|
print(f" {'-'*50}")
|
||||||
|
print(f" {a_per_side*2 + MODULE + MODULE*8:>3}× Standardrohre gesamt")
|
||||||
|
print()
|
||||||
|
print(f" {(a_per_side-1)*2:>3}× Stift-Verbinder (Alu {STIFT_DA}×{STIFT_WAND} × {STIFT_LAENGE:.0f}mm)")
|
||||||
|
print(f" {24:>3}× Connector 1 (je 2 Hülsen Stahl {HUEL_DA}×{HUEL_WAND} × {HUEL_LAENGE:.0f}mm)")
|
||||||
|
print(f" { 6:>3}× Connector 2 (je 4 Hülsen Stahl {HUEL_DA}×{HUEL_WAND} × {HUEL_LAENGE:.0f}mm)")
|
||||||
|
print()
|
||||||
|
print(f" Objekte im Dokument: {len(doc.Objects)}")
|
||||||
|
print("\nFeldbett v2 erfolgreich erstellt!")
|
||||||
260
scripts/original/feldbett.py
Normal file
260
scripts/original/feldbett.py
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
"""
|
||||||
|
Feldbett Konstruktion - FreeCAD Python Script
|
||||||
|
=============================================
|
||||||
|
Ausführen in FreeCAD: Menü → Makro → Makro ausführen → diese Datei wählen
|
||||||
|
|
||||||
|
Koordinatensystem:
|
||||||
|
X = Längsrichtung (Liegeachse)
|
||||||
|
Y = Höhe
|
||||||
|
Z = Querrichtung (Breite)
|
||||||
|
|
||||||
|
Struktur:
|
||||||
|
A = Längsstangen (2x, durchgehend, Z=±hwA, Y=totalH)
|
||||||
|
Q = Querstreben (3x, quer in Z, kürzer als Breite, Y=legH)
|
||||||
|
D = Diagonalen (12x, A-Knoten → Q-Ende, 3D-diagonal)
|
||||||
|
B = Beine (12x, Q-Ende → Bodenknoten, gespiegelt zu D)
|
||||||
|
F = Füße (kurze senkrechte Stücke unter Bodenknoten)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import FreeCAD as App
|
||||||
|
import FreeCADGui as Gui
|
||||||
|
import Part
|
||||||
|
import math
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PARAMETER — hier anpassen
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
L = 35.0 # Stangenlänge [mm] — ALLE Stangen gleich lang
|
||||||
|
BREITE_AA = 70.0 # Abstand zwischen den zwei Längsstangen (A–A) [mm]
|
||||||
|
MODULE = 3 # Anzahl Module
|
||||||
|
|
||||||
|
# Rohr-Parameter
|
||||||
|
ROHR_DA = 25.0 # Außendurchmesser Rohr [mm]
|
||||||
|
ROHR_WAND = 2.0 # Wandstärke [mm]
|
||||||
|
|
||||||
|
FUSS_LAENGE = 10.0 # Länge der Fußstücke nach unten [mm]
|
||||||
|
|
||||||
|
# Farben (R, G, B) je 0.0–1.0
|
||||||
|
FARBE_A = (0.20, 0.45, 0.75) # blau
|
||||||
|
FARBE_D = (0.06, 0.43, 0.34) # grün
|
||||||
|
FARBE_Q = (0.73, 0.46, 0.09) # amber
|
||||||
|
FARBE_B = (0.42, 0.25, 0.63) # lila
|
||||||
|
FARBE_F = (0.50, 0.50, 0.50) # grau
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# BERECHNETE GEOMETRIE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
hwA = BREITE_AA / 2.0 # halbe Breite A–A
|
||||||
|
hwQ = L / 2.0 # halbe Q-Länge (Q = L lang)
|
||||||
|
dZ = hwA - hwQ # Z-Einzug von A nach Q
|
||||||
|
|
||||||
|
legH2 = 0.75 * L**2 - dZ**2
|
||||||
|
if legH2 <= 0:
|
||||||
|
raise ValueError(
|
||||||
|
f"Geometrie unmöglich: L={L} zu kurz für Breite={BREITE_AA}. "
|
||||||
|
f"Mindest-L = {math.sqrt(dZ**2/0.75 + dZ**2/0.75*0.25):.1f} mm"
|
||||||
|
)
|
||||||
|
|
||||||
|
legH = math.sqrt(legH2) # Höhe einer Ebene (A→Q oder Q→Boden)
|
||||||
|
totalH = 2.0 * legH # Gesamthöhe
|
||||||
|
step = 2.0 * L # Abstand Modul-zu-Modul (inkl. Lücke)
|
||||||
|
aEnd = (MODULE - 1) * step + L # X-Ende der Längsstangen
|
||||||
|
|
||||||
|
# Kontrollrechnung D-Länge
|
||||||
|
dLen = math.sqrt((L/2)**2 + legH**2 + dZ**2)
|
||||||
|
|
||||||
|
print("=" * 50)
|
||||||
|
print("FELDBETT GEOMETRIE")
|
||||||
|
print("=" * 50)
|
||||||
|
print(f" Stangenlänge L = {L:.1f} mm")
|
||||||
|
print(f" Breite A–A = {BREITE_AA:.1f} mm")
|
||||||
|
print(f" Q-Länge = {L:.1f} mm (= L ✓)")
|
||||||
|
print(f" Liegelänge (A-Ende) = {aEnd:.1f} mm")
|
||||||
|
print(f" Höhe gesamt = {totalH:.1f} mm")
|
||||||
|
print(f" legH (pro Ebene) = {legH:.2f} mm")
|
||||||
|
print(f" D/B-Länge = {dLen:.2f} mm (sollte = {L:.1f})")
|
||||||
|
print(f" Module = {MODULE}")
|
||||||
|
print(f" Rohr DA/Wand = {ROHR_DA}/{ROHR_WAND} mm")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# HILFSFUNKTIONEN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
ROHR_DI = ROHR_DA - 2.0 * ROHR_WAND # Innendurchmesser
|
||||||
|
|
||||||
|
def make_tube(p1, p2, label, color):
|
||||||
|
"""
|
||||||
|
Erzeugt ein Hohlrohr von Punkt p1 nach p2.
|
||||||
|
p1, p2: FreeCAD.Vector
|
||||||
|
"""
|
||||||
|
direction = p2 - p1
|
||||||
|
length = direction.Length
|
||||||
|
|
||||||
|
if length < 0.01:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Außenzylinder
|
||||||
|
outer = Part.makeCylinder(ROHR_DA / 2.0, length)
|
||||||
|
# Innenzylinder (Hohlraum)
|
||||||
|
inner = Part.makeCylinder(ROHR_DI / 2.0, length)
|
||||||
|
tube = outer.cut(inner)
|
||||||
|
|
||||||
|
# Ausrichten: Zylinder liegt standardmäßig entlang Z-Achse
|
||||||
|
# → Rotation auf Zielrichtung
|
||||||
|
z_axis = App.Vector(0, 0, 1)
|
||||||
|
norm_dir = direction.normalize()
|
||||||
|
|
||||||
|
cross = z_axis.cross(norm_dir)
|
||||||
|
dot = z_axis.dot(norm_dir)
|
||||||
|
|
||||||
|
if cross.Length > 1e-6:
|
||||||
|
angle = math.degrees(math.acos(max(-1.0, min(1.0, dot))))
|
||||||
|
rot = App.Rotation(cross, angle)
|
||||||
|
elif dot < 0:
|
||||||
|
# 180° Rotation um beliebige Querachse
|
||||||
|
rot = App.Rotation(App.Vector(1, 0, 0), 180)
|
||||||
|
else:
|
||||||
|
rot = App.Rotation()
|
||||||
|
|
||||||
|
placement = App.Placement(p1, rot)
|
||||||
|
tube.Placement = placement
|
||||||
|
|
||||||
|
# FreeCAD Objekt anlegen
|
||||||
|
obj = doc.addObject("Part::Feature", label)
|
||||||
|
obj.Shape = tube
|
||||||
|
|
||||||
|
# Farbe setzen
|
||||||
|
if hasattr(obj, "ViewObject") and obj.ViewObject:
|
||||||
|
obj.ViewObject.ShapeColor = color
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def vec(x, y, z):
|
||||||
|
return App.Vector(x, y, z)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DOKUMENT ANLEGEN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
doc_name = "Feldbett"
|
||||||
|
if doc_name in App.listDocuments():
|
||||||
|
App.closeDocument(doc_name)
|
||||||
|
|
||||||
|
doc = App.newDocument(doc_name)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# LÄNGSSTANGEN A (2x durchgehend)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
make_tube(
|
||||||
|
vec(0, totalH, -hwA),
|
||||||
|
vec(aEnd, totalH, -hwA),
|
||||||
|
"A_links", FARBE_A
|
||||||
|
)
|
||||||
|
make_tube(
|
||||||
|
vec(0, totalH, +hwA),
|
||||||
|
vec(aEnd, totalH, +hwA),
|
||||||
|
"A_rechts", FARBE_A
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MODULE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
for m in range(MODULE):
|
||||||
|
xStart = m * step
|
||||||
|
xAL = xStart # X linker A-Knoten
|
||||||
|
xAR = xStart + L # X rechter A-Knoten
|
||||||
|
xQ = xStart + L / 2 # X Mitte → Q-Position
|
||||||
|
|
||||||
|
suffix = f"_M{m+1}"
|
||||||
|
|
||||||
|
# --- Querstrebe Q ---
|
||||||
|
make_tube(
|
||||||
|
vec(xQ, legH, -hwQ),
|
||||||
|
vec(xQ, legH, +hwQ),
|
||||||
|
"Q" + suffix, FARBE_Q
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Diagonalen D (4 pro Modul) ---
|
||||||
|
# Von A-Knoten (oben, außen) nach Q-Ende (mitte, innen)
|
||||||
|
for side, zS in [("L", -1), ("R", +1)]:
|
||||||
|
zA = zS * hwA
|
||||||
|
zQe = zS * hwQ
|
||||||
|
|
||||||
|
# linker A-Knoten → Q-Ende
|
||||||
|
make_tube(
|
||||||
|
vec(xAL, totalH, zA),
|
||||||
|
vec(xQ, legH, zQe),
|
||||||
|
f"D_xAL_{side}{suffix}", FARBE_D
|
||||||
|
)
|
||||||
|
# rechter A-Knoten → Q-Ende
|
||||||
|
make_tube(
|
||||||
|
vec(xAR, totalH, zA),
|
||||||
|
vec(xQ, legH, zQe),
|
||||||
|
f"D_xAR_{side}{suffix}", FARBE_D
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Beine B (4 pro Modul, gespiegelt zu D) ---
|
||||||
|
for side, zS in [("L", -1), ("R", +1)]:
|
||||||
|
zA = zS * hwA
|
||||||
|
zQe = zS * hwQ
|
||||||
|
|
||||||
|
# Q-Ende → linker Bodenknoten
|
||||||
|
make_tube(
|
||||||
|
vec(xQ, legH, zQe),
|
||||||
|
vec(xAL, 0, zA),
|
||||||
|
f"B_xAL_{side}{suffix}", FARBE_B
|
||||||
|
)
|
||||||
|
# Q-Ende → rechter Bodenknoten
|
||||||
|
make_tube(
|
||||||
|
vec(xQ, legH, zQe),
|
||||||
|
vec(xAR, 0, zA),
|
||||||
|
f"B_xAR_{side}{suffix}", FARBE_B
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Füße F (4 pro Modul, senkrecht nach unten) ---
|
||||||
|
for xF in [xAL, xAR]:
|
||||||
|
for zS in [-1, +1]:
|
||||||
|
zA = zS * hwA
|
||||||
|
make_tube(
|
||||||
|
vec(xF, 0, zA),
|
||||||
|
vec(xF, -FUSS_LAENGE, zA),
|
||||||
|
f"F_x{int(xF)}_z{int(zA)}{suffix}", FARBE_F
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# ABSCHLUSS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
doc.recompute()
|
||||||
|
|
||||||
|
# Kamera auf Objekt ausrichten
|
||||||
|
try:
|
||||||
|
Gui.activeDocument().activeView().fitAll()
|
||||||
|
Gui.SendMsgToActiveView("ViewFit")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print("Feldbett erfolgreich erstellt!")
|
||||||
|
print(f" Objekte im Dokument: {len(doc.Objects)}")
|
||||||
|
print()
|
||||||
|
print("STÜCKLISTE:")
|
||||||
|
print(f" A Längsstangen : 2 Stück à {aEnd:.0f} mm")
|
||||||
|
print(f" Q Querstreben : {MODULE} Stück à {L:.0f} mm")
|
||||||
|
print(f" D Diagonalen : {MODULE*4} Stück à {dLen:.1f} mm")
|
||||||
|
print(f" B Beine : {MODULE*4} Stück à {dLen:.1f} mm")
|
||||||
|
print(f" F Füße : {MODULE*4} Stück à {FUSS_LAENGE:.0f} mm")
|
||||||
|
print()
|
||||||
|
print("CONNECTOR-WINKEL:")
|
||||||
|
alpha_AD = math.degrees(math.acos(
|
||||||
|
abs(App.Vector(1,0,0).dot(App.Vector(L/2,-legH,-dZ).normalize()))
|
||||||
|
))
|
||||||
|
print(f" Connector 1 — Winkel A↔D : {alpha_AD:.1f}°")
|
||||||
|
print(f" Connector 2 — Winkel Q↔D : 60.0°")
|
||||||
|
print(f" Connector 2 — Winkel D↔B : 90.0°")
|
||||||
316
scripts/original/feldbett_connectors.py
Normal file
316
scripts/original/feldbett_connectors.py
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
"""
|
||||||
|
Feldbett Connectors - FreeCAD Python Script
|
||||||
|
============================================
|
||||||
|
Erzeugt Connector 1 und Connector 2 als separate 3D-Körper
|
||||||
|
(geeignet für 3D-Druck oder Aluminiumguss).
|
||||||
|
|
||||||
|
Ausführen in FreeCAD: Menü → Makro → Makro ausführen
|
||||||
|
|
||||||
|
Connector 1: A-Stange + eine Diagonale (D oder B)
|
||||||
|
Winkel A↔D = 60°, D ist 3D-diagonal
|
||||||
|
Kann gedreht als Standfuß verwendet werden.
|
||||||
|
|
||||||
|
Connector 2: Q-Stange (durchlaufend) + D (oben) + B (unten)
|
||||||
|
Q↔D = 60°, Q↔B = 60°, D↔B = 90°
|
||||||
|
"""
|
||||||
|
|
||||||
|
import FreeCAD as App
|
||||||
|
import FreeCADGui as Gui
|
||||||
|
import Part
|
||||||
|
import math
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PARAMETER
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
L = 35.0 # Stangenlänge [mm]
|
||||||
|
BREITE_AA = 70.0 # Abstand A–A [mm]
|
||||||
|
|
||||||
|
ROHR_DA = 25.0 # Rohr Außendurchmesser [mm]
|
||||||
|
ROHR_WAND = 2.0 # Rohr Wandstärke [mm]
|
||||||
|
ROHR_DI = ROHR_DA - 2.0 * ROHR_WAND
|
||||||
|
|
||||||
|
# Connector-Körper
|
||||||
|
HUB_RADIUS = ROHR_DA * 1.4 # Radius des zentralen Knotens [mm]
|
||||||
|
STUTZEN_LAENGE = ROHR_DA * 2.0 # Länge der Rohrstutzen am Connector [mm]
|
||||||
|
WAND_STARK = 4.0 # Wandstärke der Stutzen [mm]
|
||||||
|
STUTZEN_DA = ROHR_DA + WAND_STARK * 2 # Außendurchmesser Stutzen
|
||||||
|
|
||||||
|
# Passungsspiel (Rohr soll einsteckbar sein)
|
||||||
|
SPIEL = 0.3 # [mm] radiales Spiel
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# GEOMETRIE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
hwA = BREITE_AA / 2.0
|
||||||
|
hwQ = L / 2.0
|
||||||
|
dZ = hwA - hwQ
|
||||||
|
|
||||||
|
legH2 = 0.75 * L**2 - dZ**2
|
||||||
|
if legH2 <= 0:
|
||||||
|
raise ValueError(f"Geometrie unmöglich: L={L} zu kurz.")
|
||||||
|
|
||||||
|
legH = math.sqrt(legH2)
|
||||||
|
|
||||||
|
# Richtungsvektoren (normiert)
|
||||||
|
def norm(v):
|
||||||
|
l = math.sqrt(sum(x**2 for x in v))
|
||||||
|
return tuple(x/l for x in v)
|
||||||
|
|
||||||
|
def fv(t):
|
||||||
|
"""Tuple → FreeCAD.Vector"""
|
||||||
|
return App.Vector(t[0], t[1], t[2])
|
||||||
|
|
||||||
|
# D: von A-Knoten nach Q-Ende (deltaX=+L/2, deltaY=-legH, deltaZ=-dZ)
|
||||||
|
vD_raw = ( L/2, -legH, -dZ)
|
||||||
|
vD = norm(vD_raw)
|
||||||
|
|
||||||
|
# B: von Q-Ende nach Bodenknoten (gespiegelt X und Z)
|
||||||
|
vB_raw = (-L/2, -legH, +dZ)
|
||||||
|
vB = norm(vB_raw)
|
||||||
|
|
||||||
|
# A: X-Richtung
|
||||||
|
vA = (1.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
# Q: Z-Richtung
|
||||||
|
vQ = (0.0, 0.0, 1.0)
|
||||||
|
|
||||||
|
# Kontrollwinkel
|
||||||
|
def angle_between(v1, v2):
|
||||||
|
dot = sum(a*b for a,b in zip(v1,v2))
|
||||||
|
dot = max(-1.0, min(1.0, dot))
|
||||||
|
return math.degrees(math.acos(abs(dot)))
|
||||||
|
|
||||||
|
print("CONNECTOR WINKEL:")
|
||||||
|
print(f" A ↔ D : {angle_between(vA, vD):.1f}° (soll 60°)")
|
||||||
|
print(f" Q ↔ D : {angle_between(vQ, vD):.1f}° (soll 60°)")
|
||||||
|
print(f" Q ↔ B : {angle_between(vQ, vB):.1f}° (soll 60°)")
|
||||||
|
print(f" D ↔ B : {angle_between(vD, vB):.1f}° (soll 90°)")
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# HILFSFUNKTIONEN GEOMETRIE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def rotation_to_direction(direction):
|
||||||
|
"""
|
||||||
|
Gibt App.Rotation zurück die Z-Achse auf 'direction' dreht.
|
||||||
|
FreeCAD-Zylinder liegen standardmäßig entlang +Z.
|
||||||
|
"""
|
||||||
|
z = App.Vector(0, 0, 1)
|
||||||
|
d = App.Vector(*direction).normalize()
|
||||||
|
cross = z.cross(d)
|
||||||
|
dot = z.dot(d)
|
||||||
|
if cross.Length > 1e-6:
|
||||||
|
angle = math.degrees(math.acos(max(-1.0, min(1.0, dot))))
|
||||||
|
return App.Rotation(cross, angle)
|
||||||
|
elif dot < 0:
|
||||||
|
return App.Rotation(App.Vector(1, 0, 0), 180)
|
||||||
|
else:
|
||||||
|
return App.Rotation()
|
||||||
|
|
||||||
|
def make_cylinder_along(direction, length, radius, origin=(0,0,0)):
|
||||||
|
"""Zylinder entlang 'direction', startend bei 'origin'."""
|
||||||
|
cyl = Part.makeCylinder(radius, length)
|
||||||
|
rot = rotation_to_direction(direction)
|
||||||
|
cyl.Placement = App.Placement(App.Vector(*origin), rot)
|
||||||
|
return cyl
|
||||||
|
|
||||||
|
def make_stutzen(direction, outer_r, inner_r, length, origin=(0,0,0)):
|
||||||
|
"""Hohler Stutzen (Rohraufnahme) entlang direction."""
|
||||||
|
outer = make_cylinder_along(direction, length, outer_r, origin)
|
||||||
|
inner = make_cylinder_along(direction, length + 1.0, inner_r, origin)
|
||||||
|
return outer.cut(inner)
|
||||||
|
|
||||||
|
def make_hub(radius, origin=(0,0,0)):
|
||||||
|
"""Kugelförmiger Zentralknoten."""
|
||||||
|
sphere = Part.makeSphere(radius)
|
||||||
|
sphere.Placement = App.Placement(App.Vector(*origin), App.Rotation())
|
||||||
|
return sphere
|
||||||
|
|
||||||
|
def add_part(doc, shape, name, color):
|
||||||
|
obj = doc.addObject("Part::Feature", name)
|
||||||
|
obj.Shape = shape
|
||||||
|
if obj.ViewObject:
|
||||||
|
obj.ViewObject.ShapeColor = color
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DOKUMENT
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
doc_name = "Feldbett_Connectors"
|
||||||
|
if doc_name in App.listDocuments():
|
||||||
|
App.closeDocument(doc_name)
|
||||||
|
doc = App.newDocument(doc_name)
|
||||||
|
|
||||||
|
# Farben
|
||||||
|
C1_COL = (0.85, 0.35, 0.15) # orange-rot für Connector 1
|
||||||
|
C2_COL = (0.15, 0.55, 0.75) # blau-grün für Connector 2
|
||||||
|
|
||||||
|
# Bohrungsradius = Rohr-Außenradius + Spiel
|
||||||
|
BOHR_R = ROHR_DA / 2.0 + SPIEL
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONNECTOR 1
|
||||||
|
# Zentralknoten + Stutzen für A + Stutzen für D
|
||||||
|
# Platziert bei Ursprung (0,0,0)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
print("\nErzeuge Connector 1...")
|
||||||
|
|
||||||
|
# Zentraler Kugelknoten
|
||||||
|
c1_hub = make_hub(HUB_RADIUS)
|
||||||
|
|
||||||
|
# Stutzen A: beide Richtungen (+X und -X) — Rohr läuft durch
|
||||||
|
c1_stA_pos = make_stutzen(
|
||||||
|
(+1, 0, 0), STUTZEN_DA/2, BOHR_R, STUTZEN_LAENGE
|
||||||
|
)
|
||||||
|
c1_stA_neg = make_stutzen(
|
||||||
|
(-1, 0, 0), STUTZEN_DA/2, BOHR_R, STUTZEN_LAENGE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stutzen D: in D-Richtung
|
||||||
|
c1_stD = make_stutzen(
|
||||||
|
vD, STUTZEN_DA/2, BOHR_R, STUTZEN_LAENGE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Zusammenführen
|
||||||
|
c1_body = c1_hub.fuse(c1_stA_pos).fuse(c1_stA_neg).fuse(c1_stD)
|
||||||
|
|
||||||
|
# Durchgangsbohrung A (X-Achse, durchgehend)
|
||||||
|
bohr_A = make_cylinder_along(
|
||||||
|
(1, 0, 0), STUTZEN_LAENGE * 2 + HUB_RADIUS * 2,
|
||||||
|
BOHR_R,
|
||||||
|
(-STUTZEN_LAENGE - HUB_RADIUS, 0, 0)
|
||||||
|
)
|
||||||
|
# Bohrung D
|
||||||
|
bohr_D = make_cylinder_along(
|
||||||
|
vD, STUTZEN_LAENGE + HUB_RADIUS + 1.0,
|
||||||
|
BOHR_R
|
||||||
|
)
|
||||||
|
|
||||||
|
c1_final = c1_body.cut(bohr_A).cut(bohr_D)
|
||||||
|
|
||||||
|
# Abgerundete Kanten (optional, macht Druck/Guss besser)
|
||||||
|
try:
|
||||||
|
c1_final = c1_final.makeFillet(1.5, c1_final.Edges)
|
||||||
|
except Exception:
|
||||||
|
pass # Fillet kann bei komplexen Geometrien fehlschlagen
|
||||||
|
|
||||||
|
# Connector 1 einfügen — bei X=0 (normale Position oben)
|
||||||
|
c1_obj = add_part(doc, c1_final, "Connector_1_oben", C1_COL)
|
||||||
|
|
||||||
|
# Connector 1 als Fuß — um 180° um Z gedreht, versetzt
|
||||||
|
c1_fuss = c1_final.copy()
|
||||||
|
rot180 = App.Placement(
|
||||||
|
App.Vector(200, 0, 0),
|
||||||
|
App.Rotation(App.Vector(0, 0, 1), 180)
|
||||||
|
)
|
||||||
|
c1_fuss.Placement = rot180
|
||||||
|
c1_fuss_obj = add_part(doc, c1_fuss, "Connector_1_fuss_gedreht", C1_COL)
|
||||||
|
|
||||||
|
print(f" Connector 1 erstellt — {STUTZEN_LAENGE:.0f}mm Stutzen, {HUB_RADIUS:.1f}mm Hub-Radius")
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONNECTOR 2
|
||||||
|
# Zentralknoten + Stutzen für Q (beide Richtungen) + D + B
|
||||||
|
# Platziert bei Y=200 (Abstand zu C1)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
print("Erzeuge Connector 2...")
|
||||||
|
|
||||||
|
O2 = (0, 200, 0) # Versatz damit C1 und C2 sich nicht überlappen
|
||||||
|
|
||||||
|
# Zentraler Kugelknoten
|
||||||
|
c2_hub = make_hub(HUB_RADIUS, O2)
|
||||||
|
|
||||||
|
# Stutzen Q: beide Richtungen (+Z und -Z)
|
||||||
|
c2_stQ_pos = make_stutzen(
|
||||||
|
(0, 0, +1), STUTZEN_DA/2, BOHR_R, STUTZEN_LAENGE,
|
||||||
|
O2
|
||||||
|
)
|
||||||
|
c2_stQ_neg = make_stutzen(
|
||||||
|
(0, 0, -1), STUTZEN_DA/2, BOHR_R, STUTZEN_LAENGE,
|
||||||
|
O2
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stutzen D: D-Richtung umgekehrt (von Q-Ende Richtung A-Knoten = aufwärts)
|
||||||
|
vDr = (-vD[0], -vD[1], -vD[2]) # umgekehrt
|
||||||
|
c2_stD = make_stutzen(
|
||||||
|
vDr, STUTZEN_DA/2, BOHR_R, STUTZEN_LAENGE,
|
||||||
|
O2
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stutzen B: B-Richtung
|
||||||
|
c2_stB = make_stutzen(
|
||||||
|
vB, STUTZEN_DA/2, BOHR_R, STUTZEN_LAENGE,
|
||||||
|
O2
|
||||||
|
)
|
||||||
|
|
||||||
|
# Zusammenführen
|
||||||
|
c2_body = (c2_hub
|
||||||
|
.fuse(c2_stQ_pos)
|
||||||
|
.fuse(c2_stQ_neg)
|
||||||
|
.fuse(c2_stD)
|
||||||
|
.fuse(c2_stB))
|
||||||
|
|
||||||
|
# Bohrungen
|
||||||
|
# Q durchgehend (Z-Achse)
|
||||||
|
bohr_Q = make_cylinder_along(
|
||||||
|
(0, 0, 1),
|
||||||
|
STUTZEN_LAENGE * 2 + HUB_RADIUS * 2,
|
||||||
|
BOHR_R,
|
||||||
|
(O2[0], O2[1], O2[2] - STUTZEN_LAENGE - HUB_RADIUS)
|
||||||
|
)
|
||||||
|
# D-Bohrung
|
||||||
|
bohr_D2 = make_cylinder_along(
|
||||||
|
vDr, STUTZEN_LAENGE + HUB_RADIUS + 1.0,
|
||||||
|
BOHR_R, O2
|
||||||
|
)
|
||||||
|
# B-Bohrung
|
||||||
|
bohr_B2 = make_cylinder_along(
|
||||||
|
vB, STUTZEN_LAENGE + HUB_RADIUS + 1.0,
|
||||||
|
BOHR_R, O2
|
||||||
|
)
|
||||||
|
|
||||||
|
c2_final = c2_body.cut(bohr_Q).cut(bohr_D2).cut(bohr_B2)
|
||||||
|
|
||||||
|
try:
|
||||||
|
c2_final = c2_final.makeFillet(1.5, c2_final.Edges)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
c2_obj = add_part(doc, c2_final, "Connector_2", C2_COL)
|
||||||
|
|
||||||
|
print(f" Connector 2 erstellt — 3 Stutzen (Q durch, D+B je 60°)")
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# ABSCHLUSS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
doc.recompute()
|
||||||
|
|
||||||
|
try:
|
||||||
|
Gui.activeDocument().activeView().fitAll()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print("\n" + "="*50)
|
||||||
|
print("CONNECTORS ERFOLGREICH ERSTELLT")
|
||||||
|
print("="*50)
|
||||||
|
print(f"\nConnector 1 (×16 pro Bett):")
|
||||||
|
print(f" Hub-Radius : {HUB_RADIUS:.1f} mm")
|
||||||
|
print(f" Stutzen-L : {STUTZEN_LAENGE:.1f} mm")
|
||||||
|
print(f" Stutzen-DA : {STUTZEN_DA:.1f} mm")
|
||||||
|
print(f" Bohrung Ø : {BOHR_R*2:.1f} mm (Rohr DA={ROHR_DA} + Spiel={SPIEL*2:.1f})")
|
||||||
|
print(f" Winkel A↔D : {angle_between(vA, vD):.1f}°")
|
||||||
|
print(f"\nConnector 2 (×6 pro Bett):")
|
||||||
|
print(f" Hub-Radius : {HUB_RADIUS:.1f} mm")
|
||||||
|
print(f" Stutzen-L : {STUTZEN_LAENGE:.1f} mm")
|
||||||
|
print(f" Winkel Q↔D : {angle_between(vQ, vD):.1f}°")
|
||||||
|
print(f" Winkel Q↔B : {angle_between(vQ, vB):.1f}°")
|
||||||
|
print(f" Winkel D↔B : {angle_between(vD, vB):.1f}°")
|
||||||
|
print(f"\nExport-Tipp:")
|
||||||
|
print(f" Für 3D-Druck: Datei → Export → .stl wählen")
|
||||||
|
print(f" Für Guss: Datei → Export → .step wählen")
|
||||||
436
scripts/original/feldbett_connectors_v2.py
Normal file
436
scripts/original/feldbett_connectors_v2.py
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
"""
|
||||||
|
Feldbett Connectors v2 - FreeCAD Python Script
|
||||||
|
===============================================
|
||||||
|
Erzeugt Connector 1 und Connector 2 als schweißbare Einzelteile.
|
||||||
|
|
||||||
|
Connector 1: Hülse für A-Stange + Hülse für D oder B
|
||||||
|
→ 2 Teile, auf Gehrung gesägt und zusammengeschweißt
|
||||||
|
→ gedreht verwendbar als Standfuß
|
||||||
|
|
||||||
|
Connector 2: 5 Hülsen (Q links, Q rechts, D, B, optional Verstärkung)
|
||||||
|
→ auf Gehrung gesägt, sternförmig zusammengeschweißt
|
||||||
|
|
||||||
|
Ausführen in FreeCAD: Makro → Makro ausführen → diese Datei wählen
|
||||||
|
Konsolen-Output zeigt Sägewinkel, Mindestlängen und Warnungen.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import FreeCAD as App
|
||||||
|
import FreeCADGui as Gui
|
||||||
|
import Part
|
||||||
|
import math
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PARAMETER — hier anpassen
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# --- Feldbett-Geometrie ---
|
||||||
|
L = 35.0 # Stangenlänge [mm], alle Stangen gleich
|
||||||
|
BREITE_AA = 70.0 # Abstand Längsstange zu Längsstange [mm]
|
||||||
|
MODULE = 3 # Anzahl Module (nur für Info)
|
||||||
|
|
||||||
|
# --- Rohr Konstruktionsprofil ---
|
||||||
|
ROHR_TYP = "rund" # "rund" oder "vierkant"
|
||||||
|
ROHR_DA = 25.0 # Außendurchmesser (Rund) oder Außenmaß (Vierkant) [mm]
|
||||||
|
ROHR_WAND = 2.0 # Wandstärke [mm]
|
||||||
|
|
||||||
|
# --- Connector Hülsen ---
|
||||||
|
# Die Hülse ist ein kurzes Stück GRÖSSERES Rohr das über das Konstruktionsrohr passt
|
||||||
|
HUELSE_DA = 32.0 # Außendurchmesser der Hülse [mm]
|
||||||
|
# Empfehlung: ROHR_DA + 2×WAND_HUELSE + 2×SPIEL
|
||||||
|
HUELSE_WAND = 3.0 # Wandstärke der Hülse [mm]
|
||||||
|
EINSTECK_TIEFE = 45.0 # Wie weit das Rohr in die Hülse steckt [mm]
|
||||||
|
# Faustregel: ≥1.5×ROHR_DA geschweißt, ≥2.5×ROHR_DA nur Spannstift
|
||||||
|
SPIEL = 0.4 # Radiales Passspiel Rohr→Hülse [mm]
|
||||||
|
|
||||||
|
# --- Spannstift ---
|
||||||
|
SPANNSTIFT_D = 6.0 # Spannstift-Durchmesser [mm] (typisch ROHR_DA/4)
|
||||||
|
# 0 = kein Spannstift-Loch generieren
|
||||||
|
|
||||||
|
# --- Belastung (für Mindestlängen-Berechnung) ---
|
||||||
|
LAST_KG = 200.0 # Maximale Personenlast [kg]
|
||||||
|
SICHERHEIT = 2.0 # Sicherheitsfaktor (2 = doppelte Reserve)
|
||||||
|
|
||||||
|
# --- Material ---
|
||||||
|
# Streckgrenze [N/mm²]: Stahl S235=235, S355=355, Alu 6060=150, Alu 6082=260
|
||||||
|
STRECKGRENZE = 235.0 # [N/mm²]
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# BERECHNETE GEOMETRIE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
hwA = BREITE_AA / 2.0
|
||||||
|
hwQ = L / 2.0
|
||||||
|
dZ = hwA - hwQ
|
||||||
|
|
||||||
|
legH2 = 0.75 * L**2 - dZ**2
|
||||||
|
if legH2 <= 0:
|
||||||
|
raise ValueError(
|
||||||
|
f"Geometrie unmöglich: L={L}mm zu kurz für Breite={BREITE_AA}mm.\n"
|
||||||
|
f"Mindest-L = {math.sqrt(dZ**2/0.75 + 1):.1f}mm"
|
||||||
|
)
|
||||||
|
|
||||||
|
legH = math.sqrt(legH2)
|
||||||
|
totalH = 2.0 * legH
|
||||||
|
|
||||||
|
ROHR_DI = ROHR_DA - 2.0 * ROHR_WAND
|
||||||
|
HUELSE_DI = ROHR_DA + 2.0 * SPIEL # Innenbohrung der Hülse = Rohr-DA + Spiel
|
||||||
|
|
||||||
|
# Normierte Richtungsvektoren
|
||||||
|
def norm(v):
|
||||||
|
l = math.sqrt(sum(x**2 for x in v))
|
||||||
|
return tuple(x/l for x in v)
|
||||||
|
|
||||||
|
vA = (1.0, 0.0, 0.0)
|
||||||
|
vQ = (0.0, 0.0, 1.0)
|
||||||
|
vD = norm(( L/2, -legH, -dZ)) # A-Knoten → Q-Ende
|
||||||
|
vDr = norm((-L/2, legH, dZ)) # umgekehrt (von Q weg nach oben)
|
||||||
|
vB = norm((-L/2, -legH, dZ)) # Q-Ende → Bodenknoten
|
||||||
|
|
||||||
|
def angle_deg(v1, v2):
|
||||||
|
dot = sum(a*b for a,b in zip(v1,v2))
|
||||||
|
return math.degrees(math.acos(max(-1.0, min(1.0, abs(dot)))))
|
||||||
|
|
||||||
|
def angle_signed(v1, v2):
|
||||||
|
"""Winkel mit Vorzeichen, nicht abs."""
|
||||||
|
dot = sum(a*b for a,b in zip(v1,v2))
|
||||||
|
return math.degrees(math.acos(max(-1.0, min(1.0, dot))))
|
||||||
|
|
||||||
|
# Sägewinkel = Winkel zwischen Stutzen-Achse und der Schweißebene
|
||||||
|
# Schweißebene ist senkrecht zur Winkelhalbierenden zweier Stutzen
|
||||||
|
def saege_winkel(v1, v2):
|
||||||
|
"""
|
||||||
|
Gehrungswinkel für zwei Rohre die stumpf zusammengeschweißt werden.
|
||||||
|
Jedes Rohr wird um diesen Winkel von 90° abgesägt.
|
||||||
|
= 90° - (Winkel_zwischen / 2)
|
||||||
|
"""
|
||||||
|
alpha = angle_signed(v1, v2)
|
||||||
|
return 90.0 - alpha / 2.0
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# MINDESTLÄNGEN-BERECHNUNG (Hebelkraft)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
g = 9.81 # m/s²
|
||||||
|
F_gesamt = LAST_KG * g # Newton
|
||||||
|
|
||||||
|
# Kräfteverteilung: 3 Module, 2 Seiten, 2 Beine pro Modul = 12 Beine
|
||||||
|
# Vereinfacht: jeder Connector trägt gleichmäßig
|
||||||
|
F_pro_connector = F_gesamt / (MODULE * 4) # N pro unterem Connector
|
||||||
|
|
||||||
|
# Biegemoment am Stutzen-Eingang
|
||||||
|
# M = F × Hebel, Hebel ≈ halbe Rohrlänge (konservativ)
|
||||||
|
M_biege = F_pro_connector * (L / 2.0) # N·mm
|
||||||
|
|
||||||
|
# Widerstandsmoment Hohlrohr
|
||||||
|
W_rohr = (math.pi / 32.0) * (ROHR_DA**4 - ROHR_DI**4) / ROHR_DA # mm³
|
||||||
|
|
||||||
|
# Biegespannung
|
||||||
|
sigma_biege = M_biege / W_rohr # N/mm²
|
||||||
|
|
||||||
|
# Flächenpressung in der Hülse
|
||||||
|
# p = M / (L_ein × ROHR_DA) × Sicherheit ≤ Streckgrenze / 2 (Lochleibung)
|
||||||
|
sigma_zulaessig = STRECKGRENZE / SICHERHEIT
|
||||||
|
# Mindest-Einstecktiefe aus Lochleibung
|
||||||
|
L_ein_min_lb = (M_biege * SICHERHEIT) / (ROHR_DA * sigma_zulaessig)
|
||||||
|
# Mindest-Einstecktiefe aus Faustregel
|
||||||
|
L_ein_min_faust_schweiss = 1.5 * ROHR_DA
|
||||||
|
L_ein_min_faust_spannstift = 2.5 * ROHR_DA
|
||||||
|
L_ein_min = max(L_ein_min_lb, L_ein_min_faust_schweiss)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# AUSGABE KONSOLE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
SEP = "=" * 55
|
||||||
|
|
||||||
|
print(SEP)
|
||||||
|
print("FELDBETT CONNECTOR — BERECHNUNGSPROTOKOLL")
|
||||||
|
print(SEP)
|
||||||
|
print(f"\nGeometrie:")
|
||||||
|
print(f" L (Stangenlänge) = {L:.1f} mm")
|
||||||
|
print(f" Breite A–A = {BREITE_AA:.1f} mm")
|
||||||
|
print(f" Höhe gesamt = {totalH:.1f} mm")
|
||||||
|
print(f" legH (pro Ebene) = {legH:.2f} mm")
|
||||||
|
|
||||||
|
print(f"\nRohr:")
|
||||||
|
print(f" Typ = {ROHR_TYP}")
|
||||||
|
print(f" DA / Wand / DI = {ROHR_DA:.1f} / {ROHR_WAND:.1f} / {ROHR_DI:.1f} mm")
|
||||||
|
|
||||||
|
print(f"\nHülse:")
|
||||||
|
print(f" DA / Wand / DI = {HUELSE_DA:.1f} / {HUELSE_WAND:.1f} / {HUELSE_DI:.1f} mm")
|
||||||
|
print(f" Einstecktiefe = {EINSTECK_TIEFE:.1f} mm")
|
||||||
|
print(f" Spiel = {SPIEL:.2f} mm")
|
||||||
|
|
||||||
|
print(f"\nWinkel:")
|
||||||
|
print(f" A ↔ D = {angle_deg(vA, vD):.2f}°")
|
||||||
|
print(f" Q ↔ D = {angle_deg(vQ, vD):.2f}°")
|
||||||
|
print(f" Q ↔ B = {angle_deg(vQ, vB):.2f}°")
|
||||||
|
print(f" D ↔ B = {angle_deg(vD, vB):.2f}°")
|
||||||
|
|
||||||
|
print(f"\nSägewinkel (Gehrung, von 90° abweichend):")
|
||||||
|
print(f" Connector 1: A–D = {saege_winkel(vA, vD):.2f}° (jedes Teil um diesen Winkel sägen)")
|
||||||
|
print(f" Connector 2: Q–D = {saege_winkel(vQ, vDr):.2f}°")
|
||||||
|
print(f" Connector 2: Q–B = {saege_winkel(vQ, vB):.2f}°")
|
||||||
|
print(f" Connector 2: D–B = {saege_winkel(vDr, vB):.2f}°")
|
||||||
|
|
||||||
|
print(f"\nBelastungsrechnung (konservativ):")
|
||||||
|
print(f" Gesamtlast = {LAST_KG:.0f} kg × {SICHERHEIT:.0f} = {LAST_KG*SICHERHEIT:.0f} kg Auslegung")
|
||||||
|
print(f" Kraft pro Connector = {F_pro_connector:.1f} N")
|
||||||
|
print(f" Biegemoment Stutzen = {M_biege:.0f} N·mm")
|
||||||
|
print(f" Biegespannung Rohr = {sigma_biege:.1f} N/mm² (Streckgrenze: {STRECKGRENZE:.0f})")
|
||||||
|
print(f" Mindest-Einstecktiefe (Lochleibung) = {L_ein_min_lb:.1f} mm")
|
||||||
|
print(f" Mindest-Einstecktiefe (geschweißt) = {L_ein_min_faust_schweiss:.1f} mm")
|
||||||
|
print(f" Mindest-Einstecktiefe (nur Stift) = {L_ein_min_faust_spannstift:.1f} mm")
|
||||||
|
|
||||||
|
# Warnungen
|
||||||
|
print()
|
||||||
|
warn = False
|
||||||
|
if EINSTECK_TIEFE < L_ein_min:
|
||||||
|
print(f" ⚠ WARNUNG: Einstecktiefe {EINSTECK_TIEFE:.1f}mm < Minimum {L_ein_min:.1f}mm!")
|
||||||
|
print(f" → Erhöhe EINSTECK_TIEFE auf mindestens {math.ceil(L_ein_min/5)*5:.0f}mm")
|
||||||
|
warn = True
|
||||||
|
if HUELSE_DI < ROHR_DA + 2 * SPIEL - 0.1:
|
||||||
|
print(f" ⚠ WARNUNG: Hülsen-Innenmaß {HUELSE_DI:.1f}mm zu klein für Rohr {ROHR_DA:.1f}mm + Spiel!")
|
||||||
|
warn = True
|
||||||
|
if HUELSE_DA > L:
|
||||||
|
print(f" ⚠ WARNUNG: Hülsen-DA {HUELSE_DA:.1f}mm > L {L:.1f}mm — Connector größer als Stange!")
|
||||||
|
warn = True
|
||||||
|
if SPANNSTIFT_D > 0 and SPANNSTIFT_D > ROHR_DA / 3:
|
||||||
|
print(f" ⚠ WARNUNG: Spannstift Ø{SPANNSTIFT_D:.1f}mm > DA/3 — schwächt Rohr zu stark!")
|
||||||
|
warn = True
|
||||||
|
if not warn:
|
||||||
|
print(f" ✓ Alle Parameter plausibel.")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# HILFSFUNKTIONEN FreeCAD
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def fv(t):
|
||||||
|
return App.Vector(t[0], t[1], t[2])
|
||||||
|
|
||||||
|
def rotation_to_dir(direction):
|
||||||
|
z = App.Vector(0, 0, 1)
|
||||||
|
d = App.Vector(*direction).normalize()
|
||||||
|
cross = z.cross(d)
|
||||||
|
dot = z.dot(d)
|
||||||
|
if cross.Length > 1e-6:
|
||||||
|
angle = math.degrees(math.acos(max(-1.0, min(1.0, dot))))
|
||||||
|
return App.Rotation(cross, angle)
|
||||||
|
elif dot < 0:
|
||||||
|
return App.Rotation(App.Vector(1, 0, 0), 180)
|
||||||
|
else:
|
||||||
|
return App.Rotation()
|
||||||
|
|
||||||
|
def make_huelse(direction, origin=(0,0,0)):
|
||||||
|
"""
|
||||||
|
Hohle Hülse (kurzes Rohr das über Konstruktionsrohr passt).
|
||||||
|
Länge = EINSTECK_TIEFE.
|
||||||
|
Außen = HUELSE_DA, Innen = HUELSE_DI.
|
||||||
|
"""
|
||||||
|
outer = Part.makeCylinder(HUELSE_DA / 2.0, EINSTECK_TIEFE)
|
||||||
|
inner = Part.makeCylinder(HUELSE_DI / 2.0, EINSTECK_TIEFE + 1.0)
|
||||||
|
tube = outer.cut(inner)
|
||||||
|
|
||||||
|
rot = rotation_to_dir(direction)
|
||||||
|
tube.Placement = App.Placement(App.Vector(*origin), rot)
|
||||||
|
return tube
|
||||||
|
|
||||||
|
def make_spannstift_bohrung(direction, origin=(0,0,0)):
|
||||||
|
"""
|
||||||
|
Quer-Bohrung für Spannstift durch Hülse + Rohr.
|
||||||
|
Sitzt bei EINSTECK_TIEFE/2 vom Hülsen-Eingang.
|
||||||
|
"""
|
||||||
|
if SPANNSTIFT_D <= 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Mittelpunkt der Bohrung: entlang direction bei L/2
|
||||||
|
d = App.Vector(*direction).normalize()
|
||||||
|
mid = App.Vector(
|
||||||
|
origin[0] + d.x * EINSTECK_TIEFE / 2.0,
|
||||||
|
origin[1] + d.y * EINSTECK_TIEFE / 2.0,
|
||||||
|
origin[2] + d.z * EINSTECK_TIEFE / 2.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Bohrungsrichtung: senkrecht zu direction (nehme Kreuzprodukt mit Y oder X)
|
||||||
|
ref = App.Vector(0, 1, 0) if abs(d.dot(App.Vector(0,1,0))) < 0.9 else App.Vector(1,0,0)
|
||||||
|
bohr_dir = d.cross(ref).normalize()
|
||||||
|
|
||||||
|
# Langer Zylinder quer durch
|
||||||
|
bohr_len = HUELSE_DA + 4.0
|
||||||
|
start = App.Vector(
|
||||||
|
mid.x - bohr_dir.x * bohr_len / 2.0,
|
||||||
|
mid.y - bohr_dir.y * bohr_len / 2.0,
|
||||||
|
mid.z - bohr_dir.z * bohr_len / 2.0,
|
||||||
|
)
|
||||||
|
cyl = Part.makeCylinder(SPANNSTIFT_D / 2.0, bohr_len)
|
||||||
|
rot = rotation_to_dir((bohr_dir.x, bohr_dir.y, bohr_dir.z))
|
||||||
|
cyl.Placement = App.Placement(start, rot)
|
||||||
|
return cyl
|
||||||
|
|
||||||
|
def add_part(doc, shape, name, color):
|
||||||
|
obj = doc.addObject("Part::Feature", name)
|
||||||
|
obj.Shape = shape
|
||||||
|
if obj.ViewObject:
|
||||||
|
obj.ViewObject.ShapeColor = color
|
||||||
|
obj.ViewObject.Transparency = 0
|
||||||
|
return obj
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# DOKUMENT
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
doc_name = "Feldbett_Connectors_v2"
|
||||||
|
if doc_name in App.listDocuments():
|
||||||
|
App.closeDocument(doc_name)
|
||||||
|
doc = App.newDocument(doc_name)
|
||||||
|
|
||||||
|
C1_COL = (0.85, 0.35, 0.15)
|
||||||
|
C2_COL = (0.15, 0.55, 0.75)
|
||||||
|
BOHR_COL = (0.9, 0.1, 0.1)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONNECTOR 1 — 2 Hülsen
|
||||||
|
# Hülse A (in X-Richtung) + Hülse D
|
||||||
|
# Die zwei Hülsen werden einzeln dargestellt
|
||||||
|
# (im echten Bau: auf Gehrung sägen und zusammenschweißen)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
print("Erzeuge Connector 1...")
|
||||||
|
|
||||||
|
OFFSET_C1 = (0, 0, 0)
|
||||||
|
|
||||||
|
# Hülse A — in +X Richtung (Rohr kommt von -X rein)
|
||||||
|
h_A = make_huelse((1, 0, 0), OFFSET_C1)
|
||||||
|
|
||||||
|
# Hülse D — in D-Richtung
|
||||||
|
h_D = make_huelse(vD, OFFSET_C1)
|
||||||
|
|
||||||
|
# Spannstift-Bohrungen
|
||||||
|
b_A = make_spannstift_bohrung((1, 0, 0), OFFSET_C1)
|
||||||
|
b_D = make_spannstift_bohrung(vD, OFFSET_C1)
|
||||||
|
|
||||||
|
# Hülsen zusammenfügen (schweißt man in der Praxis)
|
||||||
|
c1 = h_A.fuse(h_D)
|
||||||
|
if b_A: c1 = c1.cut(b_A)
|
||||||
|
if b_D: c1 = c1.cut(b_D)
|
||||||
|
|
||||||
|
try:
|
||||||
|
c1 = c1.makeFillet(1.0, c1.Edges)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
add_part(doc, c1, "C1_oben_A_plus_D", C1_COL)
|
||||||
|
|
||||||
|
# Connector 1 als Fuß: gleiche Geometrie, 180° gedreht
|
||||||
|
# (D-Aufnahme zeigt nach oben, A-Aufnahme wird Standfuß)
|
||||||
|
c1_fuss = c1.copy()
|
||||||
|
c1_fuss.Placement = App.Placement(
|
||||||
|
App.Vector(EINSTECK_TIEFE * 2 + 20, 0, 0),
|
||||||
|
App.Rotation(App.Vector(0, 0, 1), 180)
|
||||||
|
)
|
||||||
|
add_part(doc, c1_fuss, "C1_unten_als_Fuss_gedreht", C1_COL)
|
||||||
|
|
||||||
|
print(f" ✓ Connector 1: 2 Hülsen à {EINSTECK_TIEFE:.0f}mm, DA={HUELSE_DA:.0f}mm")
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# CONNECTOR 2 — 5 Hülsen
|
||||||
|
# Q-links, Q-rechts, D (von oben), B (nach unten)
|
||||||
|
# + optionale 5. Verstärkungs-Hülse (hier weggelassen, Schweißnaht reicht)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
print("Erzeuge Connector 2...")
|
||||||
|
|
||||||
|
OFFSET_C2 = (0, EINSTECK_TIEFE * 2 + 60, 0)
|
||||||
|
|
||||||
|
# Hülse Q links (−Z)
|
||||||
|
h_Ql = make_huelse((0, 0, -1), OFFSET_C2)
|
||||||
|
# Hülse Q rechts (+Z)
|
||||||
|
h_Qr = make_huelse((0, 0, +1), OFFSET_C2)
|
||||||
|
# Hülse D (von Q-Ende Richtung A, also umgekehrter D-Vektor = aufwärts)
|
||||||
|
h_Dr = make_huelse(vDr, OFFSET_C2)
|
||||||
|
# Hülse B (nach unten-außen-längs)
|
||||||
|
h_Bv = make_huelse(vB, OFFSET_C2)
|
||||||
|
|
||||||
|
# Alle 4 Hülsen zusammenführen
|
||||||
|
c2 = h_Ql.fuse(h_Qr).fuse(h_Dr).fuse(h_Bv)
|
||||||
|
|
||||||
|
# Spannstift-Bohrungen
|
||||||
|
for direction, origin in [
|
||||||
|
((0,0,-1), OFFSET_C2),
|
||||||
|
((0,0,+1), OFFSET_C2),
|
||||||
|
(vDr, OFFSET_C2),
|
||||||
|
(vB, OFFSET_C2),
|
||||||
|
]:
|
||||||
|
b = make_spannstift_bohrung(direction, origin)
|
||||||
|
if b:
|
||||||
|
c2 = c2.cut(b)
|
||||||
|
|
||||||
|
try:
|
||||||
|
c2 = c2.makeFillet(1.0, c2.Edges)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
add_part(doc, c2, "C2_Q_plus_D_plus_B", C2_COL)
|
||||||
|
|
||||||
|
print(f" ✓ Connector 2: 4 Hülsen à {EINSTECK_TIEFE:.0f}mm, DA={HUELSE_DA:.0f}mm")
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# EINZELTEILE CONNECTOR 2 (zum Anschauen der Sägeschnitte)
|
||||||
|
# Jede Hülse separat, leicht versetzt
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
print("Erzeuge Connector 2 Einzelteile...")
|
||||||
|
|
||||||
|
einzel_offset = 0
|
||||||
|
einzelteile = [
|
||||||
|
((0, 0, -1), "C2_Teil_Q_links"),
|
||||||
|
((0, 0, +1), "C2_Teil_Q_rechts"),
|
||||||
|
(vDr, "C2_Teil_D"),
|
||||||
|
(vB, "C2_Teil_B"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, (direction, name) in enumerate(einzelteile):
|
||||||
|
ox = OFFSET_C2[0] + (i - 1.5) * (HUELSE_DA + 15)
|
||||||
|
oy = OFFSET_C2[1] - EINSTECK_TIEFE * 2 - 40
|
||||||
|
oz = OFFSET_C2[2]
|
||||||
|
h = make_huelse(direction, (ox, oy, oz))
|
||||||
|
b = make_spannstift_bohrung(direction, (ox, oy, oz))
|
||||||
|
if b:
|
||||||
|
h = h.cut(b)
|
||||||
|
col = (0.7, 0.7, 0.2) if "Q" in name else (
|
||||||
|
(0.2, 0.7, 0.4) if "D" in name else (0.6, 0.3, 0.7))
|
||||||
|
add_part(doc, h, name, col)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# ABSCHLUSS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
doc.recompute()
|
||||||
|
|
||||||
|
try:
|
||||||
|
Gui.activeDocument().activeView().fitAll()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(SEP)
|
||||||
|
print("STÜCKLISTE CONNECTORS pro Bett:")
|
||||||
|
print(SEP)
|
||||||
|
print(f" Connector 1 ×{MODULE*4*2} (A+D oben und B+Fuß unten, identisch gedreht)")
|
||||||
|
print(f" Connector 2 ×{MODULE*2} (Q+D+B Kreuzknoten)")
|
||||||
|
print()
|
||||||
|
print("SÄGEWINKEL ZUSAMMENFASSUNG:")
|
||||||
|
print(f" C1: A-Hülse sägen auf {90 - saege_winkel(vA, vD):.1f}° zur Rohrachse")
|
||||||
|
print(f" D-Hülse sägen auf {90 - saege_winkel(vA, vD):.1f}° zur Rohrachse (gespiegelt)")
|
||||||
|
print(f" C2: Q-Hülsen sägen auf {90 - saege_winkel(vQ, vDr):.1f}° (zu D/B hin)")
|
||||||
|
print(f" D-Hülse sägen auf {90 - saege_winkel(vDr, vB):.1f}° (zwischen D und B)")
|
||||||
|
print(f" B-Hülse sägen auf {90 - saege_winkel(vDr, vB):.1f}° (gespiegelt zu D)")
|
||||||
|
print()
|
||||||
|
print("FEM-TIPP:")
|
||||||
|
print(" Für Belastungssimulation in FreeCAD FEM:")
|
||||||
|
print(f" → Material: Stahl, E=210000 N/mm², σ_y={STRECKGRENZE:.0f} N/mm²")
|
||||||
|
print(f" → Last: {LAST_KG*SICHERHEIT*g:.0f} N verteilt auf Liegefläche")
|
||||||
|
print(f" → Einspannung: Fußpunkte fest (alle 6 DOF gesperrt)")
|
||||||
|
print(f" → Mesh: Tetraeder, max. Elementgröße = {ROHR_DA/2:.0f}mm")
|
||||||
314
scripts/original/feldbett_fem.py
Normal file
314
scripts/original/feldbett_fem.py
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
"""
|
||||||
|
Feldbett Connector FEM Simulation - FreeCAD Python Script
|
||||||
|
==========================================================
|
||||||
|
Simuliert die Belastung von Connector 2 (Q+D+B) unter Last.
|
||||||
|
|
||||||
|
VORAUSSETZUNGEN:
|
||||||
|
1. FreeCAD 0.20 oder neuer
|
||||||
|
2. CalculiX installiert:
|
||||||
|
Windows : meist mit FreeCAD mitgeliefert (prüfe Edit→Preferences→FEM→CalculiX)
|
||||||
|
Ubuntu : sudo apt install calculix-ccx
|
||||||
|
Mac : brew install calculix
|
||||||
|
3. Das Connector-Script (feldbett_connectors_v2.py) muss VORHER
|
||||||
|
ausgeführt worden sein — dieses Script baut auf dem Dokument
|
||||||
|
"Feldbett_Connectors_v2" auf.
|
||||||
|
ODER: Beide Scripts nacheinander in einer FreeCAD-Session ausführen.
|
||||||
|
|
||||||
|
ANWENDUNG:
|
||||||
|
Makro → Makro ausführen → diese Datei wählen
|
||||||
|
→ FreeCAD öffnet automatisch die FEM-Ansicht
|
||||||
|
→ Solver starten: Doppelklick auf "SolverCcxTools" im Modellbaum
|
||||||
|
→ "Write .inp file" → "Run CalculiX"
|
||||||
|
→ Ergebnisse: Doppelklick auf "CCX_Results" → Pipeline aktivieren
|
||||||
|
|
||||||
|
ERGEBNISSE INTERPRETIEREN:
|
||||||
|
Von-Mises-Spannung [N/mm²]:
|
||||||
|
< Streckgrenze/Sicherheit → grün, sicher
|
||||||
|
> Streckgrenze → rot, versagt
|
||||||
|
Verformung [mm]:
|
||||||
|
Zeigt wo der Connector nachgibt
|
||||||
|
"""
|
||||||
|
|
||||||
|
import FreeCAD as App
|
||||||
|
import FreeCADGui as Gui
|
||||||
|
import Part
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# PARAMETER — müssen identisch zu feldbett_connectors_v2.py sein!
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
L = 35.0
|
||||||
|
BREITE_AA = 70.0
|
||||||
|
MODULE = 3
|
||||||
|
|
||||||
|
ROHR_DA = 25.0
|
||||||
|
ROHR_WAND = 2.0
|
||||||
|
HUELSE_DA = 32.0
|
||||||
|
HUELSE_WAND= 3.0
|
||||||
|
EINSTECK_TIEFE = 45.0
|
||||||
|
SPIEL = 0.4
|
||||||
|
|
||||||
|
LAST_KG = 200.0
|
||||||
|
SICHERHEIT = 2.0
|
||||||
|
STRECKGRENZE = 235.0 # N/mm² (S235 Stahl)
|
||||||
|
E_MODUL = 210000.0 # N/mm²
|
||||||
|
POISSON = 0.3
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# GEOMETRIE (identisch zu Connector-Script)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
hwA = BREITE_AA / 2.0
|
||||||
|
hwQ = L / 2.0
|
||||||
|
dZ = hwA - hwQ
|
||||||
|
legH = math.sqrt(0.75 * L**2 - dZ**2)
|
||||||
|
|
||||||
|
def norm(v):
|
||||||
|
l = math.sqrt(sum(x**2 for x in v))
|
||||||
|
return tuple(x/l for x in v)
|
||||||
|
|
||||||
|
vQ = (0.0, 0.0, 1.0)
|
||||||
|
vD = norm(( L/2, -legH, -dZ))
|
||||||
|
vDr = norm((-L/2, legH, dZ))
|
||||||
|
vB = norm((-L/2, -legH, dZ))
|
||||||
|
|
||||||
|
ROHR_DI = ROHR_DA - 2.0 * ROHR_WAND
|
||||||
|
HUELSE_DI = ROHR_DA + 2.0 * SPIEL
|
||||||
|
|
||||||
|
g = 9.81
|
||||||
|
F_gesamt = LAST_KG * SICHERHEIT * g # N, mit Sicherheit
|
||||||
|
F_pro_connector = F_gesamt / (MODULE * 4) # N pro Connector 2
|
||||||
|
# An Connector 2 greifen D und B an — je 2 Stutzen
|
||||||
|
F_pro_stutzen = F_pro_connector / 2.0 # N pro Stutzen
|
||||||
|
|
||||||
|
print("=" * 55)
|
||||||
|
print("FEM SIMULATION — CONNECTOR 2")
|
||||||
|
print("=" * 55)
|
||||||
|
print(f" Auslegungslast = {LAST_KG*SICHERHEIT:.0f} kg ({SICHERHEIT:.0f}× Sicherheit)")
|
||||||
|
print(f" Kraft pro Stutzen = {F_pro_stutzen:.1f} N")
|
||||||
|
print(f" Streckgrenze = {STRECKGRENZE:.0f} N/mm²")
|
||||||
|
print(f" Zul. Spannung = {STRECKGRENZE/SICHERHEIT:.0f} N/mm²")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# HILFSFUNKTIONEN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def fv(t):
|
||||||
|
return App.Vector(t[0], t[1], t[2])
|
||||||
|
|
||||||
|
def rotation_to_dir(direction):
|
||||||
|
z = App.Vector(0, 0, 1)
|
||||||
|
d = App.Vector(*direction).normalize()
|
||||||
|
cross = z.cross(d)
|
||||||
|
dot = z.dot(d)
|
||||||
|
if cross.Length > 1e-6:
|
||||||
|
angle = math.degrees(math.acos(max(-1.0, min(1.0, dot))))
|
||||||
|
return App.Rotation(cross, angle)
|
||||||
|
elif dot < 0:
|
||||||
|
return App.Rotation(App.Vector(1, 0, 0), 180)
|
||||||
|
else:
|
||||||
|
return App.Rotation()
|
||||||
|
|
||||||
|
def make_huelse_solid(direction, origin=(0,0,0)):
|
||||||
|
"""Solide Hülse (für FEM — keine Bohrung, vereinfacht für Vernetzung)."""
|
||||||
|
outer = Part.makeCylinder(HUELSE_DA / 2.0, EINSTECK_TIEFE)
|
||||||
|
inner = Part.makeCylinder(HUELSE_DI / 2.0, EINSTECK_TIEFE + 1.0)
|
||||||
|
tube = outer.cut(inner)
|
||||||
|
rot = rotation_to_dir(direction)
|
||||||
|
tube.Placement = App.Placement(App.Vector(*origin), rot)
|
||||||
|
return tube
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# GEOMETRIE FÜR FEM AUFBAUEN
|
||||||
|
# Vereinfachter Connector 2: 4 Hülsen zusammengeschweißt
|
||||||
|
# (FEM arbeitet besser mit einem zusammenhängenden Solid)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
print("Erzeuge Connector-Geometrie für FEM...")
|
||||||
|
|
||||||
|
O = (0, 0, 0)
|
||||||
|
|
||||||
|
h_Ql = make_huelse_solid((0, 0, -1), O)
|
||||||
|
h_Qr = make_huelse_solid((0, 0, +1), O)
|
||||||
|
h_Dr = make_huelse_solid(vDr, O)
|
||||||
|
h_Bv = make_huelse_solid(vB, O)
|
||||||
|
|
||||||
|
# Alle zu einem Solid verschmelzen (wichtig für FEM — muss ein Body sein)
|
||||||
|
print(" Verschmelze Hülsen (kann einen Moment dauern)...")
|
||||||
|
try:
|
||||||
|
connector_solid = h_Ql.fuse(h_Qr).fuse(h_Dr).fuse(h_Bv)
|
||||||
|
# FEM braucht ein sauberes Solid ohne lose Faces
|
||||||
|
connector_solid = connector_solid.removeSplitter()
|
||||||
|
print(" ✓ Solid erstellt")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠ Fuse-Fehler: {e}")
|
||||||
|
print(" Verwende h_Ql als Fallback für Test")
|
||||||
|
connector_solid = h_Ql
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# FEM DOKUMENT AUFSETZEN
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
doc_name = "Feldbett_FEM"
|
||||||
|
if doc_name in App.listDocuments():
|
||||||
|
App.closeDocument(doc_name)
|
||||||
|
doc = App.newDocument(doc_name)
|
||||||
|
|
||||||
|
print("Erzeuge FEM-Struktur...")
|
||||||
|
|
||||||
|
# Geometrie-Objekt
|
||||||
|
geo_obj = doc.addObject("Part::Feature", "Connector2_Solid")
|
||||||
|
geo_obj.Shape = connector_solid
|
||||||
|
|
||||||
|
doc.recompute()
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# FEM ANALYSIS
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ObjectsFem
|
||||||
|
import FemGui
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"FEHLER: FEM-Modul nicht verfügbar: {e}")
|
||||||
|
print("Stelle sicher dass FreeCAD mit FEM-Workbench installiert ist.")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Analysis-Container
|
||||||
|
analysis = ObjectsFem.makeAnalysis(doc, "Analysis")
|
||||||
|
|
||||||
|
# --- Material ---
|
||||||
|
mat_obj = ObjectsFem.makeMaterialSolid(doc, "Material_Stahl")
|
||||||
|
mat = mat_obj.Material
|
||||||
|
mat["Name"] = "Stahl_S235"
|
||||||
|
mat["YoungsModulus"] = f"{E_MODUL:.0f} MPa"
|
||||||
|
mat["PoissonRatio"] = f"{POISSON}"
|
||||||
|
mat["Density"] = "7900 kg/m^3"
|
||||||
|
mat["UltimateTensileStrength"]= f"{STRECKGRENZE:.0f} MPa"
|
||||||
|
mat["YieldStrength"] = f"{STRECKGRENZE:.0f} MPa"
|
||||||
|
mat_obj.Material = mat
|
||||||
|
mat_obj.References = [(geo_obj, "Solid1")]
|
||||||
|
analysis.addObject(mat_obj)
|
||||||
|
|
||||||
|
# --- Mesh (Vernetzung) ---
|
||||||
|
# Netfgen oder GMSH — FreeCAD nutzt intern Netgen
|
||||||
|
mesh_obj = doc.addObject("Fem::FemMeshShapeNetgenObject", "FEMMesh")
|
||||||
|
mesh_obj.Shape = geo_obj
|
||||||
|
mesh_obj.MaxSize = ROHR_DA / 2.0 # Elementgröße = halber Rohrdurchmesser
|
||||||
|
mesh_obj.MinSize = ROHR_WAND # Mindestgröße = Wandstärke
|
||||||
|
mesh_obj.Fineness = 3 # 1=sehr grob, 5=sehr fein (3=mittel, gut für Start)
|
||||||
|
mesh_obj.Optimize = True
|
||||||
|
mesh_obj.SecondOrder = True # Quadratische Elemente = genauer
|
||||||
|
analysis.addObject(mesh_obj)
|
||||||
|
|
||||||
|
print(f" Mesh: MaxSize={ROHR_DA/2:.1f}mm, Fineness=3 (mittel)")
|
||||||
|
|
||||||
|
# --- Einspannung (Fixed Constraint) ---
|
||||||
|
# Q-Stange an einem Ende festhalten (simuliert Befestigung am Bett-Rahmen)
|
||||||
|
# Face-Auswahl: Stirnfläche der Q-links-Hülse
|
||||||
|
fix = ObjectsFem.makeConstraintFixed(doc, "Einspannung_Q_Ende")
|
||||||
|
# Face muss manuell ausgewählt werden — wir setzen eine plausible Referenz
|
||||||
|
# (In der Praxis: in FreeCAD GUI die richtige Face anklicken)
|
||||||
|
fix.References = [(geo_obj, "Face1")] # ← ggf. in GUI anpassen
|
||||||
|
analysis.addObject(fix)
|
||||||
|
|
||||||
|
# --- Kraft an D-Stutzen ---
|
||||||
|
force_D = ObjectsFem.makeConstraintForce(doc, "Last_D_Stutzen")
|
||||||
|
force_D.Force = F_pro_stutzen # N
|
||||||
|
force_D.Direction = (geo_obj, ["Edge1"]) # Richtung entlang D-Achse
|
||||||
|
force_D.Reversed = False
|
||||||
|
# Kraftvektor in D-Richtung
|
||||||
|
force_D.DirectionVector = App.Vector(*vD)
|
||||||
|
force_D.References = [(geo_obj, "Face2")] # ← Stirnfläche D-Stutzen, in GUI anpassen
|
||||||
|
analysis.addObject(force_D)
|
||||||
|
|
||||||
|
# --- Kraft an B-Stutzen ---
|
||||||
|
force_B = ObjectsFem.makeConstraintForce(doc, "Last_B_Stutzen")
|
||||||
|
force_B.Force = F_pro_stutzen
|
||||||
|
force_B.DirectionVector = App.Vector(*vB)
|
||||||
|
force_B.References = [(geo_obj, "Face3")] # ← Stirnfläche B-Stutzen, in GUI anpassen
|
||||||
|
analysis.addObject(force_B)
|
||||||
|
|
||||||
|
# --- Solver (CalculiX) ---
|
||||||
|
solver = ObjectsFem.makeSolverCalculixCcxTools(doc, "SolverCcxTools")
|
||||||
|
solver.AnalysisType = "static"
|
||||||
|
solver.GeometricalNonlinearity = "linear"
|
||||||
|
solver.ThermoMechSteadyState = False
|
||||||
|
solver.MatrixSolverType = "default"
|
||||||
|
solver.IterationsControlParameterTimeUse = False
|
||||||
|
solver.SplitInputWriter = False
|
||||||
|
|
||||||
|
# CalculiX-Pfad automatisch suchen
|
||||||
|
ccx_paths = [
|
||||||
|
"/usr/bin/ccx", # Linux apt
|
||||||
|
"/usr/local/bin/ccx", # Linux/Mac brew
|
||||||
|
"C:/Program Files/FreeCAD/bin/ccx.exe", # Windows
|
||||||
|
"C:/Program Files (x86)/FreeCAD/bin/ccx.exe",
|
||||||
|
]
|
||||||
|
for path in ccx_paths:
|
||||||
|
if os.path.exists(path):
|
||||||
|
solver.ccxBinaryPath = path
|
||||||
|
print(f" ✓ CalculiX gefunden: {path}")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(" ⚠ CalculiX nicht automatisch gefunden.")
|
||||||
|
print(" → Manuell setzen: Edit → Preferences → FEM → CalculiX")
|
||||||
|
|
||||||
|
analysis.addObject(solver)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# ABSCHLUSS & ANLEITUNG
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
doc.recompute()
|
||||||
|
|
||||||
|
# FEM-Workbench aktivieren
|
||||||
|
try:
|
||||||
|
Gui.activateWorkbench("FemWorkbench")
|
||||||
|
FemGui.setActiveAnalysis(analysis)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
Gui.activeDocument().activeView().fitAll()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 55)
|
||||||
|
print("FEM-SETUP ABGESCHLOSSEN")
|
||||||
|
print("=" * 55)
|
||||||
|
print("""
|
||||||
|
NÄCHSTE SCHRITTE IN FREECAD:
|
||||||
|
|
||||||
|
1. FACES ANPASSEN (wichtig!):
|
||||||
|
Die Einspannungen und Lasten sind auf "Face1/2/3"
|
||||||
|
gesetzt — das sind Platzhalter.
|
||||||
|
→ Im Modellbaum: Doppelklick auf "Einspannung_Q_Ende"
|
||||||
|
→ Stirnfläche der Q-Hülse anklicken → OK
|
||||||
|
→ Gleiches für "Last_D_Stutzen" und "Last_B_Stutzen"
|
||||||
|
|
||||||
|
2. MESH ERZEUGEN:
|
||||||
|
→ Doppelklick auf "FEMMesh" im Modellbaum
|
||||||
|
→ "Mesh parameters" prüfen → OK
|
||||||
|
→ Vernetzung startet automatisch (dauert 10-30 Sek.)
|
||||||
|
|
||||||
|
3. SIMULATION STARTEN:
|
||||||
|
→ Doppelklick auf "SolverCcxTools"
|
||||||
|
→ Button "Write .inp file"
|
||||||
|
→ Button "Run CalculiX"
|
||||||
|
→ Warten (je nach Mesh 30 Sek. bis 5 Min.)
|
||||||
|
|
||||||
|
4. ERGEBNISSE ANZEIGEN:
|
||||||
|
→ Im Modellbaum erscheint "CCX_Results"
|
||||||
|
→ Menü: FEM → Postprocessing → Apply pipeline to result
|
||||||
|
→ Wähle "Von Mises Stress" oder "Displacement"
|
||||||
|
→ Farbskala zeigt Spannungsverteilung
|
||||||
|
""")
|
||||||
|
print(f"GRENZWERTE:")
|
||||||
|
print(f" Zulässige Spannung : {STRECKGRENZE/SICHERHEIT:.0f} N/mm² (grün im Plot)")
|
||||||
|
print(f" Streckgrenze : {STRECKGRENZE:.0f} N/mm² (orange = kritisch)")
|
||||||
|
print(f" Simulierte Last : {LAST_KG*SICHERHEIT:.0f} kg ({SICHERHEIT:.0f}× Sicherheit)")
|
||||||
Reference in New Issue
Block a user