""" 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")