Files
feldbett/scripts/original/feldbett_connectors_v2.py
Axel Meyer d849c4e86d 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>
2026-04-09 14:30:35 +02:00

437 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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")