Initial commit: Feldbett Design-Dokumentation und FreeCAD-Scripts

Modulares Schwergewicht-Feldbett aus Alu-Rohren (25×1.5) mit Stahl-Konnektoren (33.7×2.5).
Design-Docs, Materialrecherche, Gewichtsberechnung, Korrosionsschutz-Analyse,
und zwei getestete FreeCAD-Makros (Struktur + Konnektoren-Detail).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Axel Meyer
2026-04-09 14:30:35 +02:00
commit d849c4e86d
15 changed files with 2824 additions and 0 deletions

View File

@@ -0,0 +1,314 @@
"""
Feldbett Connector FEM Simulation - FreeCAD Python Script
==========================================================
Simuliert die Belastung von Connector 2 (Q+D+B) unter Last.
VORAUSSETZUNGEN:
1. FreeCAD 0.20 oder neuer
2. CalculiX installiert:
Windows : meist mit FreeCAD mitgeliefert (prüfe Edit→Preferences→FEM→CalculiX)
Ubuntu : sudo apt install calculix-ccx
Mac : brew install calculix
3. Das Connector-Script (feldbett_connectors_v2.py) muss VORHER
ausgeführt worden sein — dieses Script baut auf dem Dokument
"Feldbett_Connectors_v2" auf.
ODER: Beide Scripts nacheinander in einer FreeCAD-Session ausführen.
ANWENDUNG:
Makro → Makro ausführen → diese Datei wählen
→ FreeCAD öffnet automatisch die FEM-Ansicht
→ Solver starten: Doppelklick auf "SolverCcxTools" im Modellbaum
"Write .inp file""Run CalculiX"
→ Ergebnisse: Doppelklick auf "CCX_Results" → Pipeline aktivieren
ERGEBNISSE INTERPRETIEREN:
Von-Mises-Spannung [N/mm²]:
< Streckgrenze/Sicherheit → grün, sicher
> Streckgrenze → rot, versagt
Verformung [mm]:
Zeigt wo der Connector nachgibt
"""
import FreeCAD as App
import FreeCADGui as Gui
import Part
import math
import os
# ============================================================
# PARAMETER — müssen identisch zu feldbett_connectors_v2.py sein!
# ============================================================
L = 35.0
BREITE_AA = 70.0
MODULE = 3
ROHR_DA = 25.0
ROHR_WAND = 2.0
HUELSE_DA = 32.0
HUELSE_WAND= 3.0
EINSTECK_TIEFE = 45.0
SPIEL = 0.4
LAST_KG = 200.0
SICHERHEIT = 2.0
STRECKGRENZE = 235.0 # N/mm² (S235 Stahl)
E_MODUL = 210000.0 # N/mm²
POISSON = 0.3
# ============================================================
# GEOMETRIE (identisch zu Connector-Script)
# ============================================================
hwA = BREITE_AA / 2.0
hwQ = L / 2.0
dZ = hwA - hwQ
legH = math.sqrt(0.75 * L**2 - dZ**2)
def norm(v):
l = math.sqrt(sum(x**2 for x in v))
return tuple(x/l for x in v)
vQ = (0.0, 0.0, 1.0)
vD = norm(( L/2, -legH, -dZ))
vDr = norm((-L/2, legH, dZ))
vB = norm((-L/2, -legH, dZ))
ROHR_DI = ROHR_DA - 2.0 * ROHR_WAND
HUELSE_DI = ROHR_DA + 2.0 * SPIEL
g = 9.81
F_gesamt = LAST_KG * SICHERHEIT * g # N, mit Sicherheit
F_pro_connector = F_gesamt / (MODULE * 4) # N pro Connector 2
# An Connector 2 greifen D und B an — je 2 Stutzen
F_pro_stutzen = F_pro_connector / 2.0 # N pro Stutzen
print("=" * 55)
print("FEM SIMULATION — CONNECTOR 2")
print("=" * 55)
print(f" Auslegungslast = {LAST_KG*SICHERHEIT:.0f} kg ({SICHERHEIT:.0f}× Sicherheit)")
print(f" Kraft pro Stutzen = {F_pro_stutzen:.1f} N")
print(f" Streckgrenze = {STRECKGRENZE:.0f} N/mm²")
print(f" Zul. Spannung = {STRECKGRENZE/SICHERHEIT:.0f} N/mm²")
print()
# ============================================================
# HILFSFUNKTIONEN
# ============================================================
def fv(t):
return App.Vector(t[0], t[1], t[2])
def rotation_to_dir(direction):
z = App.Vector(0, 0, 1)
d = App.Vector(*direction).normalize()
cross = z.cross(d)
dot = z.dot(d)
if cross.Length > 1e-6:
angle = math.degrees(math.acos(max(-1.0, min(1.0, dot))))
return App.Rotation(cross, angle)
elif dot < 0:
return App.Rotation(App.Vector(1, 0, 0), 180)
else:
return App.Rotation()
def make_huelse_solid(direction, origin=(0,0,0)):
"""Solide Hülse (für FEM — keine Bohrung, vereinfacht für Vernetzung)."""
outer = Part.makeCylinder(HUELSE_DA / 2.0, EINSTECK_TIEFE)
inner = Part.makeCylinder(HUELSE_DI / 2.0, EINSTECK_TIEFE + 1.0)
tube = outer.cut(inner)
rot = rotation_to_dir(direction)
tube.Placement = App.Placement(App.Vector(*origin), rot)
return tube
# ============================================================
# GEOMETRIE FÜR FEM AUFBAUEN
# Vereinfachter Connector 2: 4 Hülsen zusammengeschweißt
# (FEM arbeitet besser mit einem zusammenhängenden Solid)
# ============================================================
print("Erzeuge Connector-Geometrie für FEM...")
O = (0, 0, 0)
h_Ql = make_huelse_solid((0, 0, -1), O)
h_Qr = make_huelse_solid((0, 0, +1), O)
h_Dr = make_huelse_solid(vDr, O)
h_Bv = make_huelse_solid(vB, O)
# Alle zu einem Solid verschmelzen (wichtig für FEM — muss ein Body sein)
print(" Verschmelze Hülsen (kann einen Moment dauern)...")
try:
connector_solid = h_Ql.fuse(h_Qr).fuse(h_Dr).fuse(h_Bv)
# FEM braucht ein sauberes Solid ohne lose Faces
connector_solid = connector_solid.removeSplitter()
print(" ✓ Solid erstellt")
except Exception as e:
print(f" ⚠ Fuse-Fehler: {e}")
print(" Verwende h_Ql als Fallback für Test")
connector_solid = h_Ql
# ============================================================
# FEM DOKUMENT AUFSETZEN
# ============================================================
doc_name = "Feldbett_FEM"
if doc_name in App.listDocuments():
App.closeDocument(doc_name)
doc = App.newDocument(doc_name)
print("Erzeuge FEM-Struktur...")
# Geometrie-Objekt
geo_obj = doc.addObject("Part::Feature", "Connector2_Solid")
geo_obj.Shape = connector_solid
doc.recompute()
# ============================================================
# FEM ANALYSIS
# ============================================================
try:
import ObjectsFem
import FemGui
except ImportError as e:
print(f"FEHLER: FEM-Modul nicht verfügbar: {e}")
print("Stelle sicher dass FreeCAD mit FEM-Workbench installiert ist.")
raise
# Analysis-Container
analysis = ObjectsFem.makeAnalysis(doc, "Analysis")
# --- Material ---
mat_obj = ObjectsFem.makeMaterialSolid(doc, "Material_Stahl")
mat = mat_obj.Material
mat["Name"] = "Stahl_S235"
mat["YoungsModulus"] = f"{E_MODUL:.0f} MPa"
mat["PoissonRatio"] = f"{POISSON}"
mat["Density"] = "7900 kg/m^3"
mat["UltimateTensileStrength"]= f"{STRECKGRENZE:.0f} MPa"
mat["YieldStrength"] = f"{STRECKGRENZE:.0f} MPa"
mat_obj.Material = mat
mat_obj.References = [(geo_obj, "Solid1")]
analysis.addObject(mat_obj)
# --- Mesh (Vernetzung) ---
# Netfgen oder GMSH — FreeCAD nutzt intern Netgen
mesh_obj = doc.addObject("Fem::FemMeshShapeNetgenObject", "FEMMesh")
mesh_obj.Shape = geo_obj
mesh_obj.MaxSize = ROHR_DA / 2.0 # Elementgröße = halber Rohrdurchmesser
mesh_obj.MinSize = ROHR_WAND # Mindestgröße = Wandstärke
mesh_obj.Fineness = 3 # 1=sehr grob, 5=sehr fein (3=mittel, gut für Start)
mesh_obj.Optimize = True
mesh_obj.SecondOrder = True # Quadratische Elemente = genauer
analysis.addObject(mesh_obj)
print(f" Mesh: MaxSize={ROHR_DA/2:.1f}mm, Fineness=3 (mittel)")
# --- Einspannung (Fixed Constraint) ---
# Q-Stange an einem Ende festhalten (simuliert Befestigung am Bett-Rahmen)
# Face-Auswahl: Stirnfläche der Q-links-Hülse
fix = ObjectsFem.makeConstraintFixed(doc, "Einspannung_Q_Ende")
# Face muss manuell ausgewählt werden — wir setzen eine plausible Referenz
# (In der Praxis: in FreeCAD GUI die richtige Face anklicken)
fix.References = [(geo_obj, "Face1")] # ← ggf. in GUI anpassen
analysis.addObject(fix)
# --- Kraft an D-Stutzen ---
force_D = ObjectsFem.makeConstraintForce(doc, "Last_D_Stutzen")
force_D.Force = F_pro_stutzen # N
force_D.Direction = (geo_obj, ["Edge1"]) # Richtung entlang D-Achse
force_D.Reversed = False
# Kraftvektor in D-Richtung
force_D.DirectionVector = App.Vector(*vD)
force_D.References = [(geo_obj, "Face2")] # ← Stirnfläche D-Stutzen, in GUI anpassen
analysis.addObject(force_D)
# --- Kraft an B-Stutzen ---
force_B = ObjectsFem.makeConstraintForce(doc, "Last_B_Stutzen")
force_B.Force = F_pro_stutzen
force_B.DirectionVector = App.Vector(*vB)
force_B.References = [(geo_obj, "Face3")] # ← Stirnfläche B-Stutzen, in GUI anpassen
analysis.addObject(force_B)
# --- Solver (CalculiX) ---
solver = ObjectsFem.makeSolverCalculixCcxTools(doc, "SolverCcxTools")
solver.AnalysisType = "static"
solver.GeometricalNonlinearity = "linear"
solver.ThermoMechSteadyState = False
solver.MatrixSolverType = "default"
solver.IterationsControlParameterTimeUse = False
solver.SplitInputWriter = False
# CalculiX-Pfad automatisch suchen
ccx_paths = [
"/usr/bin/ccx", # Linux apt
"/usr/local/bin/ccx", # Linux/Mac brew
"C:/Program Files/FreeCAD/bin/ccx.exe", # Windows
"C:/Program Files (x86)/FreeCAD/bin/ccx.exe",
]
for path in ccx_paths:
if os.path.exists(path):
solver.ccxBinaryPath = path
print(f" ✓ CalculiX gefunden: {path}")
break
else:
print(" ⚠ CalculiX nicht automatisch gefunden.")
print(" → Manuell setzen: Edit → Preferences → FEM → CalculiX")
analysis.addObject(solver)
# ============================================================
# ABSCHLUSS & ANLEITUNG
# ============================================================
doc.recompute()
# FEM-Workbench aktivieren
try:
Gui.activateWorkbench("FemWorkbench")
FemGui.setActiveAnalysis(analysis)
except Exception:
pass
try:
Gui.activeDocument().activeView().fitAll()
except Exception:
pass
print()
print("=" * 55)
print("FEM-SETUP ABGESCHLOSSEN")
print("=" * 55)
print("""
NÄCHSTE SCHRITTE IN FREECAD:
1. FACES ANPASSEN (wichtig!):
Die Einspannungen und Lasten sind auf "Face1/2/3"
gesetzt — das sind Platzhalter.
→ Im Modellbaum: Doppelklick auf "Einspannung_Q_Ende"
→ Stirnfläche der Q-Hülse anklicken → OK
→ Gleiches für "Last_D_Stutzen" und "Last_B_Stutzen"
2. MESH ERZEUGEN:
→ Doppelklick auf "FEMMesh" im Modellbaum
"Mesh parameters" prüfen → OK
→ Vernetzung startet automatisch (dauert 10-30 Sek.)
3. SIMULATION STARTEN:
→ Doppelklick auf "SolverCcxTools"
→ Button "Write .inp file"
→ Button "Run CalculiX"
→ Warten (je nach Mesh 30 Sek. bis 5 Min.)
4. ERGEBNISSE ANZEIGEN:
→ Im Modellbaum erscheint "CCX_Results"
→ Menü: FEM → Postprocessing → Apply pipeline to result
→ Wähle "Von Mises Stress" oder "Displacement"
→ Farbskala zeigt Spannungsverteilung
""")
print(f"GRENZWERTE:")
print(f" Zulässige Spannung : {STRECKGRENZE/SICHERHEIT:.0f} N/mm² (grün im Plot)")
print(f" Streckgrenze : {STRECKGRENZE:.0f} N/mm² (orange = kritisch)")
print(f" Simulierte Last : {LAST_KG*SICHERHEIT:.0f} kg ({SICHERHEIT:.0f}× Sicherheit)")