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

261 lines
7.7 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 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°")