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,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 AA = {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: AD = {saege_winkel(vA, vD):.2f}° (jedes Teil um diesen Winkel sägen)")
print(f" Connector 2: QD = {saege_winkel(vQ, vDr):.2f}°")
print(f" Connector 2: QB = {saege_winkel(vQ, vB):.2f}°")
print(f" Connector 2: DB = {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")