commit d849c4e86de8b5d8d6074db62c33a23936b0d27f Author: Axel Meyer Date: Thu Apr 9 14:30:35 2026 +0200 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) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..d28e662 --- /dev/null +++ b/README.md @@ -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. diff --git a/docs/01-designuebersicht.md b/docs/01-designuebersicht.md new file mode 100644 index 0000000..bb04eca --- /dev/null +++ b/docs/01-designuebersicht.md @@ -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 diff --git a/docs/02-materialauswahl.md b/docs/02-materialauswahl.md new file mode 100644 index 0000000..029e226 --- /dev/null +++ b/docs/02-materialauswahl.md @@ -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/) diff --git a/docs/03-kontaktkorrosion.md b/docs/03-kontaktkorrosion.md new file mode 100644 index 0000000..ef04ce5 --- /dev/null +++ b/docs/03-kontaktkorrosion.md @@ -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/) diff --git a/docs/04-gewichtsberechnung.md b/docs/04-gewichtsberechnung.md new file mode 100644 index 0000000..ca9ed11 --- /dev/null +++ b/docs/04-gewichtsberechnung.md @@ -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. diff --git a/docs/05-original-scripts.md b/docs/05-original-scripts.md new file mode 100644 index 0000000..28118df --- /dev/null +++ b/docs/05-original-scripts.md @@ -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` diff --git a/docs/06-offene-fragen.md b/docs/06-offene-fragen.md new file mode 100644 index 0000000..6830473 --- /dev/null +++ b/docs/06-offene-fragen.md @@ -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 diff --git a/docs/07-freecad-anleitung.md b/docs/07-freecad-anleitung.md new file mode 100644 index 0000000..8ff01c9 --- /dev/null +++ b/docs/07-freecad-anleitung.md @@ -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) diff --git a/scripts/feldbett_konnektoren.py b/scripts/feldbett_konnektoren.py new file mode 100644 index 0000000..34fd5b7 --- /dev/null +++ b/scripts/feldbett_konnektoren.py @@ -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). +""") diff --git a/scripts/feldbett_struktur.py b/scripts/feldbett_struktur.py new file mode 100644 index 0000000..4f711e4 --- /dev/null +++ b/scripts/feldbett_struktur.py @@ -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!") diff --git a/scripts/original/feldbett.py b/scripts/original/feldbett.py new file mode 100644 index 0000000..f3ae69a --- /dev/null +++ b/scripts/original/feldbett.py @@ -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°") diff --git a/scripts/original/feldbett_connectors.py b/scripts/original/feldbett_connectors.py new file mode 100644 index 0000000..fbecf12 --- /dev/null +++ b/scripts/original/feldbett_connectors.py @@ -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") diff --git a/scripts/original/feldbett_connectors_v2.py b/scripts/original/feldbett_connectors_v2.py new file mode 100644 index 0000000..3f10599 --- /dev/null +++ b/scripts/original/feldbett_connectors_v2.py @@ -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") diff --git a/scripts/original/feldbett_fem.py b/scripts/original/feldbett_fem.py new file mode 100644 index 0000000..36f28ce --- /dev/null +++ b/scripts/original/feldbett_fem.py @@ -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)")