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

360 lines
12 KiB
Python
Raw Permalink 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 Struktur v2 — FreeCAD Python Script
=============================================
Zeigt das komplette Feldbett mit allen Rohren in Realgröße.
A-Stangen sind segmentiert (je 350mm), Stift-Verbinder angedeutet.
Ausführen: FreeCAD → Makro → Makro ausführen → diese Datei wählen
Koordinatensystem:
X = Längsrichtung (Liegeachse)
Y = Höhe (oben = Liegefläche)
Z = Querrichtung (Breite)
Rohrtriplet "Komfortabel":
Standard : Alu 25×1.5 mm (Strukturrohre)
Stift : Alu 18×1.0 mm (Inline-Verbinder A-Stangen)
Hülse : Stahl 33.7×2.5 mm (Konnektoren C1/C2)
"""
import FreeCAD as App
import FreeCADGui as Gui
import Part
import math
# ============================================================
# PARAMETER
# ============================================================
# Geometrie
L = 350.0 # Stangenlänge [mm] — ALLE Stangen gleich
BREITE_AA = 700.0 # Abstand zwischen Längsstangen (AA) [mm]
MODULE = 3 # Anzahl Module
# Rohre — Triplet "Komfortabel"
# Standard (Alu 25×1.5)
STD_DA = 25.0 # Außendurchmesser [mm]
STD_WAND = 1.5 # Wandstärke [mm]
STD_DI = STD_DA - 2.0 * STD_WAND # 22.0 mm
# Stift-Verbinder (Alu 18×1.0)
STIFT_DA = 18.0
STIFT_WAND = 1.0
STIFT_DI = STIFT_DA - 2.0 * STIFT_WAND
STIFT_LAENGE = 100.0 # 50mm Einstecktiefe pro Seite
# Hülse / Konnektor (Stahl 33.7×2.5)
HUEL_DA = 33.7
HUEL_WAND = 2.5
HUEL_DI = HUEL_DA - 2.0 * HUEL_WAND # 28.7 mm
HUEL_LAENGE = 40.0 # Länge pro Hülsenstück
# Farben (R, G, B) je 0.01.0
FARBE_A = (0.20, 0.45, 0.75) # blau — Längsstangen
FARBE_D = (0.06, 0.43, 0.34) # grün — Diagonalen
FARBE_Q = (0.73, 0.46, 0.09) # amber — Querstreben
FARBE_B = (0.42, 0.25, 0.63) # lila — Beine
FARBE_STIFT = (0.85, 0.55, 0.15) # orange — Stift-Verbinder
FARBE_C1 = (0.85, 0.35, 0.15) # rot-orange — Connector 1
FARBE_C2 = (0.15, 0.55, 0.75) # blau-grün — Connector 2
# ============================================================
# BERECHNETE GEOMETRIE
# ============================================================
hwA = BREITE_AA / 2.0 # halbe Breite AA
hwQ = L / 2.0 # halbe Q-Länge (Q = L)
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"Erhöhe L oder reduziere BREITE_AA."
)
legH = math.sqrt(legH2) # Höhe pro Ebene (A→Q oder Q→Boden)
totalH = 2.0 * legH # Gesamthöhe
step = 2.0 * L # Modul-Abstand in X
aEnd = (MODULE - 1) * step + L # X-Ende der Längsstangen
# Kontrollrechnung D/B-Länge
dLen = math.sqrt((L / 2)**2 + legH**2 + dZ**2)
# A-Stangen Segmente
a_per_side = int(aEnd / L)
print("=" * 55)
print("FELDBETT STRUKTUR v2")
print("=" * 55)
print(f" Stangenlänge L = {L:.0f} mm")
print(f" Breite AA = {BREITE_AA:.0f} mm")
print(f" Liegelänge = {aEnd:.0f} mm = {aEnd/10:.0f} cm")
print(f" Höhe gesamt = {totalH:.0f} mm = {totalH/10:.1f} cm")
print(f" legH (pro Ebene) = {legH:.1f} mm")
print(f" D/B-Länge = {dLen:.1f} mm (soll = {L:.0f})")
print(f" Module = {MODULE}")
print(f" A-Segmente pro Seite = {a_per_side}")
print(f"\n Standard : {STD_DA}×{STD_WAND} mm (Alu)")
print(f" Stift : {STIFT_DA}×{STIFT_WAND} mm (Alu)")
print(f" Hülse : {HUEL_DA}×{HUEL_WAND} mm (Stahl)")
print(f" Spiel Hülse↔Std = {HUEL_DI - STD_DA:.1f} mm")
print(f" Spiel Std↔Stift = {STD_DI - STIFT_DA:.1f} mm")
print()
# ============================================================
# HILFSFUNKTIONEN
# ============================================================
def make_tube(p1, p2, da, wand, label, color, group=None):
"""Hohlrohr von p1 nach p2."""
direction = p2 - p1
length = direction.Length
if length < 0.01:
return None
di = da - 2.0 * wand
outer = Part.makeCylinder(da / 2.0, length)
inner = Part.makeCylinder(di / 2.0, length)
tube = outer.cut(inner)
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:
rot = App.Rotation(App.Vector(1, 0, 0), 180)
else:
rot = App.Rotation()
tube.Placement = App.Placement(p1, rot)
obj = doc.addObject("Part::Feature", label)
obj.Shape = tube
if hasattr(obj, "ViewObject") and obj.ViewObject:
obj.ViewObject.ShapeColor = color
if group:
group.addObject(obj)
return obj
def vec(x, y, z):
return App.Vector(x, y, z)
def make_group(name):
return doc.addObject("App::DocumentObjectGroup", name)
# ============================================================
# DOKUMENT ANLEGEN
# ============================================================
doc_name = "Feldbett_v2"
if doc_name in App.listDocuments():
App.closeDocument(doc_name)
doc = App.newDocument(doc_name)
grp_A = make_group("A_Laengsstangen")
grp_Q = make_group("Q_Querstreben")
grp_D = make_group("D_Diagonalen")
grp_B = make_group("B_Beine")
grp_stift = make_group("Stift_Verbinder")
grp_c1 = make_group("Connector_1_Huelsen")
grp_c2 = make_group("Connector_2_Huelsen")
# ============================================================
# A — LÄNGSSTANGEN (segmentiert, 2× je a_per_side Stücke)
# ============================================================
for side, zS in [("L", -1), ("R", +1)]:
zA = zS * hwA
for seg in range(a_per_side):
x0 = seg * L
x1 = x0 + L
make_tube(
vec(x0, totalH, zA),
vec(x1, totalH, zA),
STD_DA, STD_WAND,
f"A_{side}_seg{seg+1}", FARBE_A, grp_A
)
# ============================================================
# STIFT-VERBINDER (A-Stangen inline, zwischen Segmenten)
# ============================================================
for side, zS in [("L", -1), ("R", +1)]:
zA = zS * hwA
for joint in range(a_per_side - 1):
xJ = (joint + 1) * L # Stoßstelle
x0 = xJ - STIFT_LAENGE / 2.0
x1 = xJ + STIFT_LAENGE / 2.0
make_tube(
vec(x0, totalH, zA),
vec(x1, totalH, zA),
STIFT_DA, STIFT_WAND,
f"Stift_{side}_{joint+1}", FARBE_STIFT, grp_stift
)
# ============================================================
# MODULE — Q, D, B + Konnektor-Hülsen
# ============================================================
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
suf = f"_M{m+1}"
# --- Q Querstrebe ---
make_tube(
vec(xQ, legH, -hwQ),
vec(xQ, legH, +hwQ),
STD_DA, STD_WAND,
"Q" + suf, FARBE_Q, grp_Q
)
# --- D Diagonalen (4 pro Modul) ---
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),
STD_DA, STD_WAND,
f"D_AL_{side}{suf}", FARBE_D, grp_D
)
# rechter A-Knoten → Q-Ende
make_tube(
vec(xAR, totalH, zA),
vec(xQ, legH, zQe),
STD_DA, STD_WAND,
f"D_AR_{side}{suf}", FARBE_D, grp_D
)
# --- B Beine (4 pro Modul, gespiegelt) ---
for side, zS in [("L", -1), ("R", +1)]:
zA = zS * hwA
zQe = zS * hwQ
make_tube(
vec(xQ, legH, zQe),
vec(xAL, 0, zA),
STD_DA, STD_WAND,
f"B_AL_{side}{suf}", FARBE_B, grp_B
)
make_tube(
vec(xQ, legH, zQe),
vec(xAR, 0, zA),
STD_DA, STD_WAND,
f"B_AR_{side}{suf}", FARBE_B, grp_B
)
# --- Connector 2 Hülsen (an Q-Enden) ---
# Jedes Q-Ende bekommt 4 Hülsen: Q-links, Q-rechts, D(aufwärts), B(abwärts)
for zS in [-1, +1]:
zA = zS * hwA
zQe = zS * hwQ
qEnd = vec(xQ, legH, zQe)
side_label = "L" if zS < 0 else "R"
# Richtungsvektoren vom Q-Ende aus
# Q verläuft in ±Z → Hülsen zeigen in +Z und -Z
for qdir, qlabel in [((0,0,-1), "Ql"), ((0,0,+1), "Qr")]:
d = App.Vector(*qdir).normalize()
p1 = qEnd
p2 = vec(qEnd.x + d.x*HUEL_LAENGE, qEnd.y + d.y*HUEL_LAENGE, qEnd.z + d.z*HUEL_LAENGE)
make_tube(p1, p2, HUEL_DA, HUEL_WAND,
f"C2_{qlabel}_{side_label}{suf}", FARBE_C2, grp_c2)
# D-Richtung (vom Q-Ende zum A-Knoten oben = umgekehrte D-Richtung)
# Wir nehmen die Richtung zum linken A-Knoten als Referenz
vDr = vec(xAL - xQ, totalH - legH, zA - zQe).normalize()
p2 = vec(qEnd.x + vDr.x*HUEL_LAENGE, qEnd.y + vDr.y*HUEL_LAENGE, qEnd.z + vDr.z*HUEL_LAENGE)
make_tube(qEnd, p2, HUEL_DA, HUEL_WAND,
f"C2_D_{side_label}{suf}", FARBE_C2, grp_c2)
# B-Richtung (vom Q-Ende zum Bodenknoten)
vB = vec(xAL - xQ, 0 - legH, zA - zQe).normalize()
p2 = vec(qEnd.x + vB.x*HUEL_LAENGE, qEnd.y + vB.y*HUEL_LAENGE, qEnd.z + vB.z*HUEL_LAENGE)
make_tube(qEnd, p2, HUEL_DA, HUEL_WAND,
f"C2_B_{side_label}{suf}", FARBE_C2, grp_c2)
# --- Connector 1 Hülsen (an A-Knoten oben und Fußpunkten unten) ---
for xF in [xAL, xAR]:
for side, zS in [("L", -1), ("R", +1)]:
zA = zS * hwA
zQe = zS * hwQ
xi = int(xF)
# === C1 oben: am A-Knoten, verbindet A-Stange mit D-Diagonale ===
aNode = vec(xF, totalH, zA)
# Hülse entlang A (±X)
make_tube(
vec(xF - HUEL_LAENGE/2, totalH, zA),
vec(xF + HUEL_LAENGE/2, totalH, zA),
HUEL_DA, HUEL_WAND,
f"C1o_A_x{xi}_{side}{suf}", FARBE_C1, grp_c1
)
# Hülse entlang D (nach unten zum Q-Ende)
vD = vec(xQ - xF, legH - totalH, zQe - zA).normalize()
p2 = vec(aNode.x + vD.x*HUEL_LAENGE, aNode.y + vD.y*HUEL_LAENGE, aNode.z + vD.z*HUEL_LAENGE)
make_tube(aNode, p2, HUEL_DA, HUEL_WAND,
f"C1o_D_x{xi}_{side}{suf}", FARBE_C1, grp_c1)
# === C1 unten (Fuß): am Bodenknoten, verbindet B-Bein mit Bodenkontakt ===
fNode = vec(xF, 0, zA)
# Hülse für B (nach oben zum Q-Ende)
vBup = vec(xQ - xF, legH, zQe - zA).normalize()
p2 = vec(fNode.x + vBup.x*HUEL_LAENGE, fNode.y + vBup.y*HUEL_LAENGE, fNode.z + vBup.z*HUEL_LAENGE)
make_tube(fNode, p2, HUEL_DA, HUEL_WAND,
f"C1u_B_x{xi}_{side}{suf}", FARBE_C1, grp_c1)
# Hülse als Fuß (nach unten, senkrecht)
make_tube(
vec(xF, 0, zA),
vec(xF, -HUEL_LAENGE, zA),
HUEL_DA, HUEL_WAND,
f"C1u_F_x{xi}_{side}{suf}", FARBE_C1, grp_c1
)
# ============================================================
# ABSCHLUSS
# ============================================================
doc.recompute()
try:
Gui.activeDocument().activeView().fitAll()
Gui.SendMsgToActiveView("ViewFit")
except Exception:
pass
# Stückliste
print("STÜCKLISTE:")
print(f" {a_per_side * 2:>3}× A Längsstangen (Alu {STD_DA}×{STD_WAND} × {L:.0f}mm)")
print(f" {MODULE:>3}× Q Querstreben (Alu {STD_DA}×{STD_WAND} × {L:.0f}mm)")
print(f" {MODULE*4:>3}× D Diagonalen (Alu {STD_DA}×{STD_WAND} × {L:.0f}mm)")
print(f" {MODULE*4:>3}× B Beine (Alu {STD_DA}×{STD_WAND} × {L:.0f}mm)")
print(f" {'-'*50}")
print(f" {a_per_side*2 + MODULE + MODULE*8:>3}× Standardrohre gesamt")
print()
print(f" {(a_per_side-1)*2:>3}× Stift-Verbinder (Alu {STIFT_DA}×{STIFT_WAND} × {STIFT_LAENGE:.0f}mm)")
print(f" {24:>3}× Connector 1 (je 2 Hülsen Stahl {HUEL_DA}×{HUEL_WAND} × {HUEL_LAENGE:.0f}mm)")
print(f" { 6:>3}× Connector 2 (je 4 Hülsen Stahl {HUEL_DA}×{HUEL_WAND} × {HUEL_LAENGE:.0f}mm)")
print()
print(f" Objekte im Dokument: {len(doc.Objects)}")
print("\nFeldbett v2 erfolgreich erstellt!")