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:
403
scripts/feldbett_konnektoren.py
Normal file
403
scripts/feldbett_konnektoren.py
Normal file
@@ -0,0 +1,403 @@
|
||||
"""
|
||||
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).
|
||||
""")
|
||||
Reference in New Issue
Block a user