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>
261 lines
7.7 KiB
Python
261 lines
7.7 KiB
Python
"""
|
||
Feldbett Konstruktion - FreeCAD Python Script
|
||
=============================================
|
||
Ausführen in FreeCAD: Menü → Makro → Makro ausführen → diese Datei wählen
|
||
|
||
Koordinatensystem:
|
||
X = Längsrichtung (Liegeachse)
|
||
Y = Höhe
|
||
Z = Querrichtung (Breite)
|
||
|
||
Struktur:
|
||
A = Längsstangen (2x, durchgehend, Z=±hwA, Y=totalH)
|
||
Q = Querstreben (3x, quer in Z, kürzer als Breite, Y=legH)
|
||
D = Diagonalen (12x, A-Knoten → Q-Ende, 3D-diagonal)
|
||
B = Beine (12x, Q-Ende → Bodenknoten, gespiegelt zu D)
|
||
F = Füße (kurze senkrechte Stücke unter Bodenknoten)
|
||
"""
|
||
|
||
import FreeCAD as App
|
||
import FreeCADGui as Gui
|
||
import Part
|
||
import math
|
||
|
||
# ============================================================
|
||
# PARAMETER — hier anpassen
|
||
# ============================================================
|
||
|
||
L = 35.0 # Stangenlänge [mm] — ALLE Stangen gleich lang
|
||
BREITE_AA = 70.0 # Abstand zwischen den zwei Längsstangen (A–A) [mm]
|
||
MODULE = 3 # Anzahl Module
|
||
|
||
# Rohr-Parameter
|
||
ROHR_DA = 25.0 # Außendurchmesser Rohr [mm]
|
||
ROHR_WAND = 2.0 # Wandstärke [mm]
|
||
|
||
FUSS_LAENGE = 10.0 # Länge der Fußstücke nach unten [mm]
|
||
|
||
# Farben (R, G, B) je 0.0–1.0
|
||
FARBE_A = (0.20, 0.45, 0.75) # blau
|
||
FARBE_D = (0.06, 0.43, 0.34) # grün
|
||
FARBE_Q = (0.73, 0.46, 0.09) # amber
|
||
FARBE_B = (0.42, 0.25, 0.63) # lila
|
||
FARBE_F = (0.50, 0.50, 0.50) # grau
|
||
|
||
# ============================================================
|
||
# BERECHNETE GEOMETRIE
|
||
# ============================================================
|
||
|
||
hwA = BREITE_AA / 2.0 # halbe Breite A–A
|
||
hwQ = L / 2.0 # halbe Q-Länge (Q = L lang)
|
||
dZ = hwA - hwQ # Z-Einzug von A nach Q
|
||
|
||
legH2 = 0.75 * L**2 - dZ**2
|
||
if legH2 <= 0:
|
||
raise ValueError(
|
||
f"Geometrie unmöglich: L={L} zu kurz für Breite={BREITE_AA}. "
|
||
f"Mindest-L = {math.sqrt(dZ**2/0.75 + dZ**2/0.75*0.25):.1f} mm"
|
||
)
|
||
|
||
legH = math.sqrt(legH2) # Höhe einer Ebene (A→Q oder Q→Boden)
|
||
totalH = 2.0 * legH # Gesamthöhe
|
||
step = 2.0 * L # Abstand Modul-zu-Modul (inkl. Lücke)
|
||
aEnd = (MODULE - 1) * step + L # X-Ende der Längsstangen
|
||
|
||
# Kontrollrechnung D-Länge
|
||
dLen = math.sqrt((L/2)**2 + legH**2 + dZ**2)
|
||
|
||
print("=" * 50)
|
||
print("FELDBETT GEOMETRIE")
|
||
print("=" * 50)
|
||
print(f" Stangenlänge L = {L:.1f} mm")
|
||
print(f" Breite A–A = {BREITE_AA:.1f} mm")
|
||
print(f" Q-Länge = {L:.1f} mm (= L ✓)")
|
||
print(f" Liegelänge (A-Ende) = {aEnd:.1f} mm")
|
||
print(f" Höhe gesamt = {totalH:.1f} mm")
|
||
print(f" legH (pro Ebene) = {legH:.2f} mm")
|
||
print(f" D/B-Länge = {dLen:.2f} mm (sollte = {L:.1f})")
|
||
print(f" Module = {MODULE}")
|
||
print(f" Rohr DA/Wand = {ROHR_DA}/{ROHR_WAND} mm")
|
||
print()
|
||
|
||
# ============================================================
|
||
# HILFSFUNKTIONEN
|
||
# ============================================================
|
||
|
||
ROHR_DI = ROHR_DA - 2.0 * ROHR_WAND # Innendurchmesser
|
||
|
||
def make_tube(p1, p2, label, color):
|
||
"""
|
||
Erzeugt ein Hohlrohr von Punkt p1 nach p2.
|
||
p1, p2: FreeCAD.Vector
|
||
"""
|
||
direction = p2 - p1
|
||
length = direction.Length
|
||
|
||
if length < 0.01:
|
||
return None
|
||
|
||
# Außenzylinder
|
||
outer = Part.makeCylinder(ROHR_DA / 2.0, length)
|
||
# Innenzylinder (Hohlraum)
|
||
inner = Part.makeCylinder(ROHR_DI / 2.0, length)
|
||
tube = outer.cut(inner)
|
||
|
||
# Ausrichten: Zylinder liegt standardmäßig entlang Z-Achse
|
||
# → Rotation auf Zielrichtung
|
||
z_axis = App.Vector(0, 0, 1)
|
||
norm_dir = direction.normalize()
|
||
|
||
cross = z_axis.cross(norm_dir)
|
||
dot = z_axis.dot(norm_dir)
|
||
|
||
if cross.Length > 1e-6:
|
||
angle = math.degrees(math.acos(max(-1.0, min(1.0, dot))))
|
||
rot = App.Rotation(cross, angle)
|
||
elif dot < 0:
|
||
# 180° Rotation um beliebige Querachse
|
||
rot = App.Rotation(App.Vector(1, 0, 0), 180)
|
||
else:
|
||
rot = App.Rotation()
|
||
|
||
placement = App.Placement(p1, rot)
|
||
tube.Placement = placement
|
||
|
||
# FreeCAD Objekt anlegen
|
||
obj = doc.addObject("Part::Feature", label)
|
||
obj.Shape = tube
|
||
|
||
# Farbe setzen
|
||
if hasattr(obj, "ViewObject") and obj.ViewObject:
|
||
obj.ViewObject.ShapeColor = color
|
||
|
||
return obj
|
||
|
||
|
||
def vec(x, y, z):
|
||
return App.Vector(x, y, z)
|
||
|
||
|
||
# ============================================================
|
||
# DOKUMENT ANLEGEN
|
||
# ============================================================
|
||
|
||
doc_name = "Feldbett"
|
||
if doc_name in App.listDocuments():
|
||
App.closeDocument(doc_name)
|
||
|
||
doc = App.newDocument(doc_name)
|
||
|
||
# ============================================================
|
||
# LÄNGSSTANGEN A (2x durchgehend)
|
||
# ============================================================
|
||
|
||
make_tube(
|
||
vec(0, totalH, -hwA),
|
||
vec(aEnd, totalH, -hwA),
|
||
"A_links", FARBE_A
|
||
)
|
||
make_tube(
|
||
vec(0, totalH, +hwA),
|
||
vec(aEnd, totalH, +hwA),
|
||
"A_rechts", FARBE_A
|
||
)
|
||
|
||
# ============================================================
|
||
# MODULE
|
||
# ============================================================
|
||
|
||
for m in range(MODULE):
|
||
xStart = m * step
|
||
xAL = xStart # X linker A-Knoten
|
||
xAR = xStart + L # X rechter A-Knoten
|
||
xQ = xStart + L / 2 # X Mitte → Q-Position
|
||
|
||
suffix = f"_M{m+1}"
|
||
|
||
# --- Querstrebe Q ---
|
||
make_tube(
|
||
vec(xQ, legH, -hwQ),
|
||
vec(xQ, legH, +hwQ),
|
||
"Q" + suffix, FARBE_Q
|
||
)
|
||
|
||
# --- Diagonalen D (4 pro Modul) ---
|
||
# Von A-Knoten (oben, außen) nach Q-Ende (mitte, innen)
|
||
for side, zS in [("L", -1), ("R", +1)]:
|
||
zA = zS * hwA
|
||
zQe = zS * hwQ
|
||
|
||
# linker A-Knoten → Q-Ende
|
||
make_tube(
|
||
vec(xAL, totalH, zA),
|
||
vec(xQ, legH, zQe),
|
||
f"D_xAL_{side}{suffix}", FARBE_D
|
||
)
|
||
# rechter A-Knoten → Q-Ende
|
||
make_tube(
|
||
vec(xAR, totalH, zA),
|
||
vec(xQ, legH, zQe),
|
||
f"D_xAR_{side}{suffix}", FARBE_D
|
||
)
|
||
|
||
# --- Beine B (4 pro Modul, gespiegelt zu D) ---
|
||
for side, zS in [("L", -1), ("R", +1)]:
|
||
zA = zS * hwA
|
||
zQe = zS * hwQ
|
||
|
||
# Q-Ende → linker Bodenknoten
|
||
make_tube(
|
||
vec(xQ, legH, zQe),
|
||
vec(xAL, 0, zA),
|
||
f"B_xAL_{side}{suffix}", FARBE_B
|
||
)
|
||
# Q-Ende → rechter Bodenknoten
|
||
make_tube(
|
||
vec(xQ, legH, zQe),
|
||
vec(xAR, 0, zA),
|
||
f"B_xAR_{side}{suffix}", FARBE_B
|
||
)
|
||
|
||
# --- Füße F (4 pro Modul, senkrecht nach unten) ---
|
||
for xF in [xAL, xAR]:
|
||
for zS in [-1, +1]:
|
||
zA = zS * hwA
|
||
make_tube(
|
||
vec(xF, 0, zA),
|
||
vec(xF, -FUSS_LAENGE, zA),
|
||
f"F_x{int(xF)}_z{int(zA)}{suffix}", FARBE_F
|
||
)
|
||
|
||
# ============================================================
|
||
# ABSCHLUSS
|
||
# ============================================================
|
||
|
||
doc.recompute()
|
||
|
||
# Kamera auf Objekt ausrichten
|
||
try:
|
||
Gui.activeDocument().activeView().fitAll()
|
||
Gui.SendMsgToActiveView("ViewFit")
|
||
except Exception:
|
||
pass
|
||
|
||
print("Feldbett erfolgreich erstellt!")
|
||
print(f" Objekte im Dokument: {len(doc.Objects)}")
|
||
print()
|
||
print("STÜCKLISTE:")
|
||
print(f" A Längsstangen : 2 Stück à {aEnd:.0f} mm")
|
||
print(f" Q Querstreben : {MODULE} Stück à {L:.0f} mm")
|
||
print(f" D Diagonalen : {MODULE*4} Stück à {dLen:.1f} mm")
|
||
print(f" B Beine : {MODULE*4} Stück à {dLen:.1f} mm")
|
||
print(f" F Füße : {MODULE*4} Stück à {FUSS_LAENGE:.0f} mm")
|
||
print()
|
||
print("CONNECTOR-WINKEL:")
|
||
alpha_AD = math.degrees(math.acos(
|
||
abs(App.Vector(1,0,0).dot(App.Vector(L/2,-legH,-dZ).normalize()))
|
||
))
|
||
print(f" Connector 1 — Winkel A↔D : {alpha_AD:.1f}°")
|
||
print(f" Connector 2 — Winkel Q↔D : 60.0°")
|
||
print(f" Connector 2 — Winkel D↔B : 90.0°")
|