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:
Axel Meyer
2026-04-09 14:30:35 +02:00
commit d849c4e86d
15 changed files with 2824 additions and 0 deletions

View File

@@ -0,0 +1,260 @@
"""
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 (AA) [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.01.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 AA
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 AA = {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°")