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:
316
scripts/original/feldbett_connectors.py
Normal file
316
scripts/original/feldbett_connectors.py
Normal 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 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")
|
||||
Reference in New Issue
Block a user