Files
feldbett/scripts/original/feldbett_connectors.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

317 lines
9.2 KiB
Python
Raw 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 - 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")