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>
317 lines
9.2 KiB
Python
317 lines
9.2 KiB
Python
"""
|
||
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 A–A [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")
|