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>
404 lines
13 KiB
Python
404 lines
13 KiB
Python
"""
|
||
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).
|
||
""")
|