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,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 AA [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")