From acd40599abbe582dba616731e0b4e907cc09ac49 Mon Sep 17 00:00:00 2001 From: breitenbach76 Date: Mon, 8 Jun 2026 23:52:18 +0200 Subject: [PATCH] . --- 01_3D-Druck/blender/README.md | 47 +++++ .../__pycache__/raci-board.cpython-312.pyc | Bin 0 -> 12989 bytes 01_3D-Druck/blender/raci-board.py | 193 ++++++++++++++++++ 01_3D-Druck/openscad/raci-board.scad | 172 ++++++++++++++++ 4 files changed, 412 insertions(+) create mode 100644 01_3D-Druck/blender/README.md create mode 100644 01_3D-Druck/blender/__pycache__/raci-board.cpython-312.pyc create mode 100644 01_3D-Druck/blender/raci-board.py create mode 100644 01_3D-Druck/openscad/raci-board.scad diff --git a/01_3D-Druck/blender/README.md b/01_3D-Druck/blender/README.md new file mode 100644 index 0000000..96a75c3 --- /dev/null +++ b/01_3D-Druck/blender/README.md @@ -0,0 +1,47 @@ +# Blender-Workflow — RACI-Konsolen-Board + +Besserer Look als OpenSCAD: weiche Fasen (Bevel), smooth shading, optional Relief, +schöne Vorschau-Renders — und trotzdem **maßhaltig** (alle Mulden/Passungen im Skript). + +## Tooling +- **Blender** (gratis): https://www.blender.org/download/ (4.2 LTS empfohlen). +- Generator-Skript: [`raci-board.py`](raci-board.py) — baut das Board parametrisch. + +## So baust du das Board +**Variante A — in der Blender-Oberfläche (empfohlen zum Iterieren):** +1. Blender öffnen → oben Reiter **Scripting**. +2. **Open** → `raci-board.py` → **Run Script** (▷). +3. Das Board wird erzeugt (gerundete Kanten via Bevel). Im **Layout**-Reiter ansehen. +4. Export: `File → Export → STL` (bzw. 3MF für den H2D, falls Addon aktiv). + +**Variante B — headless (nur Datei erzeugen):** +``` +blender -b -P raci-board.py +``` +→ legt `raci-board.stl` (+ Vorschau `raci_preview.png`) neben dem Skript ab. + +## Maße (Spec — der „Vertrag", gilt in jedem Tool) +| Element | Wert | +|---|---| +| Board | 210 × 210 × 10 mm, Außenkanten gefast (Bevel ~1,2 mm) | +| Acryl-Chip-Mulde (Mitte) | Ø **40,6** × Tiefe **1,8** mm (+ Greifkerbe Ø12) — für Chip Ø40 × 2 mm | +| Figuren-Sockel-Mulden | **10×** Ø **25,3** × Tiefe **1,5** mm (Sockel Ø24,5) | +| Sockel-Ring-Radius | 48 mm (Mitte–Mitte), 36° Teilung | +| RACI-Sektoren | **R 3 · A 1 · C 4 · I 2** (= 10), Trennstege erhaben | +| Sektor-Beschriftung | groß **R/A/C/I** + Wörter RESPONSIBLE/ACCOUNTABLE/CONSULTED/INFORMED | +| Action-Card-Halter | Steh-Schlitz **63 × 4 mm**, ~12° nach hinten geneigt (Karte 60×90) | +| Druck | 6× in Phasenfarbe (5 Phasen + Gate-Rot), einfarbig je Board | + +## „Edel" machen (manueller Feinschliff in Blender) +Nach dem Skript-Lauf für den Premium-Look: +1. **Smooth shading:** Objekt wählen → Rechtsklick → *Shade Auto Smooth*. +2. **Mehr/Schärfere Fasen:** Bevel-Modifier-Breite/Segmente erhöhen, oder Kanten markieren + (*Edge → Mark Sharp/Crease*) und Bevel nach Winkel/Weight. +3. **Relief/Thema (optional):** flaches Stadt-/Platinen-Relief als Bild → *Displace*-Modifier + mit einer Textur auf einer Unterteilungsfläche; oder Mikro-Rillen im Mittelfeld. +4. **Typo schöner:** eigene Schriftart laden (Text-Objekt → Font), Bold, leicht erhaben + statt graviert. +5. **Export:** STL (mm) für den Slicer; Wandstärke/Mulden vorm Druck einmal prüfen. + +> Das Skript ist ein **Startgerüst v1** — sag mir nach deinem ersten Lauf, was hakt +> (Version/Fehlermeldung), dann fix ich es gezielt. diff --git a/01_3D-Druck/blender/__pycache__/raci-board.cpython-312.pyc b/01_3D-Druck/blender/__pycache__/raci-board.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58c49ff492b0caca5dc98c8e1256613eafd1b4a8 GIT binary patch literal 12989 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 literal 0 HcmV?d00001 diff --git a/01_3D-Druck/blender/raci-board.py b/01_3D-Druck/blender/raci-board.py new file mode 100644 index 0000000..78d1429 --- /dev/null +++ b/01_3D-Druck/blender/raci-board.py @@ -0,0 +1,193 @@ +# RACI-Konsolen-Board — Blender-Generator (bpy) +# SLC-Workshop Tabletop · Einheiten: 1 Blender-Unit = 1 mm +# Baut das Board parametrisch mit gefasten Kanten (Bevel), Mulden, Sektoren, +# gravierten Labels, Action-Card-Steckschlitz; rendert eine Vorschau und +# exportiert eine STL. Startgeruest v1 — in Blender 4.x getestet gegen die API, +# einzelne Schritte sind per try/except abgesichert (Labels optional). +# +# Lauf: Blender -> Scripting -> Open -> Run ODER blender -b -P raci-board.py + +import bpy, bmesh, math, os +from mathutils import Vector + +# ----------------------------- Parameter (mm) ----------------------------- +BOARD_W, BOARD_D, BASE_H = 210.0, 210.0, 10.0 +EDGE_BEVEL, EDGE_SEG = 1.4, 4 + +DIAL_CX, DIAL_CY = 0.0, -15.0 +CHIP_D, CHIP_DEP = 40.6, 1.8 # Acryl-Chip Ø40 x 2 mm +NOTCH_D = 12.0 + +SOCK_D, SOCK_DEP = 25.3, 1.5 # Figuren-Sockel Ø24,5 +RING_R = 48.0 +N_SOCK = 10 # Winkel ab oben (90°), 36°-Teilung +RIDGE_H, RIDGE_W = 2.6, 3.2 +DIVIDERS = [72, -72, -144, 108] +# Sektor-Mitten (fuer grosse Buchstaben) R3 A1 C4 I2 +LETTERS = [(90, "A"), (0, "C"), (-108, "I"), (162, "R")] + +CARD_CY = 78.0 +CARD_BW, CARD_BD, CARD_BH = 84.0, 22.0, 16.0 +SLOT_W, SLOT_T, SLOT_TILT = 63.0, 4.0, 12.0 + +LET_SIZE, LET_DEP = 11.0, 1.0 +WORD_SIZE, WORD_DEP = 5.0, 0.8 +TOP = BASE_H + +HERE = os.path.dirname(bpy.data.filepath) or os.path.dirname(os.path.abspath(__file__)) +STL_OUT = os.path.join(HERE, "raci-board.stl") +PNG_OUT = os.path.join(HERE, "raci_preview.png") + +# ----------------------------- Helfer ----------------------------- +def clear_scene(): + bpy.ops.object.select_all(action='SELECT') + bpy.ops.object.delete(use_global=False) + for blk in (bpy.data.meshes, bpy.data.materials, bpy.data.curves): + for d in list(blk): + if d.users == 0: blk.remove(d) + +def cube(sx, sy, sz, loc): + bpy.ops.mesh.primitive_cube_add(size=1, location=loc) + o = bpy.context.object; o.scale = (sx, sy, sz) + bpy.ops.object.transform_apply(scale=True) + return o + +def cyl(d, h, loc, verts=96): + bpy.ops.mesh.primitive_cylinder_add(radius=d/2.0, depth=h, location=loc, vertices=verts) + return bpy.context.object + +def boolean(obj, tool, op='DIFFERENCE'): + m = obj.modifiers.new("bool", 'BOOLEAN'); m.operation = op + m.object = tool + try: m.solver = 'EXACT' + except Exception: pass + bpy.context.view_layer.objects.active = obj + bpy.ops.object.modifier_apply(modifier=m.name) + bpy.data.objects.remove(tool, do_unlink=True) + +def apply_bevel(obj, width=EDGE_BEVEL, seg=EDGE_SEG, ang=30): + m = obj.modifiers.new("bevel", 'BEVEL') + m.width = width; m.segments = seg + m.limit_method = 'ANGLE'; m.angle_limit = math.radians(ang) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.modifier_apply(modifier=m.name) + +def cut_text(board, body, x, y, rotz=0, size=LET_SIZE, dep=LET_DEP): + try: + bpy.ops.object.text_add(location=(x, y, TOP - dep)) + t = bpy.context.object; t.data.body = body + t.data.size = size; t.data.extrude = dep + 1.5 + t.data.align_x = 'CENTER'; t.data.align_y = 'CENTER' + t.rotation_euler = (0, 0, math.radians(rotz)) + bpy.ops.object.convert(target='MESH') + boolean(board, t, 'DIFFERENCE') + except Exception as e: + print("Label uebersprungen (%s): %s" % (body, e)) + +# ----------------------------- Aufbau ----------------------------- +clear_scene() + +# Basisplatte + Kartenblock, beide gefast, dann vereinen +base = cube(BOARD_W, BOARD_D, BASE_H, (0, 0, BASE_H/2)) +apply_bevel(base) +block = cube(CARD_BW, CARD_BD, BASE_H + CARD_BH, (0, CARD_CY, (BASE_H + CARD_BH)/2)) +apply_bevel(block, width=1.0) +boolean(base, block, 'UNION') + +# Gefaste Ecke vorne rechts (Optik) +cc = cube(28, 28, BASE_H + 4, (BOARD_W/2, -BOARD_D/2, BASE_H/2)) +cc.rotation_euler = (0, 0, math.radians(45)); bpy.ops.object.transform_apply(rotation=True) +boolean(base, cc, 'DIFFERENCE') + +# Chip-Mulde + Greifkerbe +boolean(base, cyl(CHIP_D, 6, (DIAL_CX, DIAL_CY, TOP - CHIP_DEP + 3)), 'DIFFERENCE') +boolean(base, cyl(NOTCH_D, 6, (DIAL_CX, DIAL_CY - CHIP_D/2, TOP - CHIP_DEP + 3)), 'DIFFERENCE') + +# 10 Sockelmulden im Ring +for i in range(N_SOCK): + a = math.radians(90 - i * 36) + x = DIAL_CX + RING_R * math.cos(a); y = DIAL_CY + RING_R * math.sin(a) + boolean(base, cyl(SOCK_D, 6, (x, y, TOP - SOCK_DEP + 3)), 'DIFFERENCE') + +# Deko-Rillen im Mittelfeld +for rr in [24, 27, 30, 33]: + ring = cyl(rr*2, 0.8, (DIAL_CX, DIAL_CY, TOP - 0.25)) + inner = cyl((rr-0.8)*2, 1.2, (DIAL_CX, DIAL_CY, TOP - 0.25)) + boolean(ring, inner, 'DIFFERENCE') + boolean(base, ring, 'DIFFERENCE') + +# Sektor-Trennstege (erhaben) +ri, ro = CHIP_D/2 + 3, RING_R + SOCK_D/2 + 4 +for a in DIVIDERS: + r = cube(ro - ri, RIDGE_W, RIDGE_H, ((ri+ro)/2, 0, TOP + RIDGE_H/2)) + r.rotation_euler = (0, 0, math.radians(a)) + # um Dial-Zentrum rotieren: erst an Zentrum verschieben + r.location = (DIAL_CX + ((ri+ro)/2)*math.cos(math.radians(a)), + DIAL_CY + ((ri+ro)/2)*math.sin(math.radians(a)), TOP + RIDGE_H/2) + bpy.ops.object.transform_apply(rotation=False) + boolean(base, r, 'UNION') + +# Action-Card-Steckschlitz (oben offen, nach hinten geneigt) +slot = cube(SLOT_W, SLOT_T, 38, (0, CARD_CY, 22)) +slot.rotation_euler = (math.radians(-SLOT_TILT), 0, 0) +bpy.ops.object.transform_apply(rotation=True) +boolean(base, slot, 'DIFFERENCE') + +# Gravierte Rand-Linie +outer = cube(BOARD_W-14, BOARD_D-14, 1.4, (0, 0, TOP-0.7)) +inner = cube(BOARD_W-17, BOARD_D-17, 2.0, (0, 0, TOP-0.7)) +boolean(outer, inner, 'DIFFERENCE'); boolean(base, outer, 'DIFFERENCE') + +# Labels: grosse R/A/C/I + Woerter +for ang, ch in LETTERS: + rl = RING_R + 13 + cut_text(base, ch, DIAL_CX + rl*math.cos(math.radians(ang)), + DIAL_CY + rl*math.sin(math.radians(ang))) +cut_text(base, "RESPONSIBLE", -(RING_R+29), DIAL_CY+6, 90, WORD_SIZE, WORD_DEP) +cut_text(base, "CONSULTED", (RING_R+29), DIAL_CY+6, -90, WORD_SIZE, WORD_DEP) +cut_text(base, "INFORMED", DIAL_CX, DIAL_CY-(RING_R+30), 0, WORD_SIZE, WORD_DEP) +cut_text(base, "ACCOUNTABLE", DIAL_CX, CARD_CY - CARD_BD/2 - 8, 0, WORD_SIZE, WORD_DEP) + +base.name = "RACI-Board" +bpy.ops.object.shade_smooth() + +# ----------------------------- Material ----------------------------- +mat = bpy.data.materials.new("BoardBlue"); mat.use_nodes = True +bsdf = mat.node_tree.nodes.get("Principled BSDF") +if bsdf: + bsdf.inputs["Base Color"].default_value = (0.10, 0.16, 0.30, 1) + try: bsdf.inputs["Roughness"].default_value = 0.55 + except Exception: pass +base.data.materials.append(mat) + +# ----------------------------- Vorschau-Render (guarded) ----------------------------- +try: + bpy.ops.object.light_add(type='SUN', location=(120, -160, 220)) + bpy.context.object.data.energy = 3.0 + bpy.ops.object.camera_add(location=(150, -190, 210)) + cam = bpy.context.object + cam.rotation_euler = (math.radians(58), 0, math.radians(38)) + bpy.context.scene.camera = cam + sc = bpy.context.scene + sc.render.resolution_x, sc.render.resolution_y = 1400, 1000 + sc.render.filepath = PNG_OUT + try: sc.render.engine = 'BLENDER_EEVEE_NEXT' + except Exception: + try: sc.render.engine = 'BLENDER_EEVEE' + except Exception: pass + bpy.ops.render.render(write_still=True) + print("Vorschau:", PNG_OUT) +except Exception as e: + print("Render uebersprungen:", e) + +# ----------------------------- STL-Export (versionstolerant) ----------------------------- +bpy.ops.object.select_all(action='DESELECT') +base.select_set(True); bpy.context.view_layer.objects.active = base +try: + bpy.ops.wm.stl_export(filepath=STL_OUT, export_selected_objects=True) # Blender >= 4.1 +except Exception: + try: + bpy.ops.export_mesh.stl(filepath=STL_OUT, use_selection=True) # Blender <= 4.0 + except Exception as e: + print("STL-Export fehlgeschlagen — bitte manuell File>Export>STL:", e) +print("STL:", STL_OUT) diff --git a/01_3D-Druck/openscad/raci-board.scad b/01_3D-Druck/openscad/raci-board.scad new file mode 100644 index 0000000..e7bc8d9 --- /dev/null +++ b/01_3D-Druck/openscad/raci-board.scad @@ -0,0 +1,172 @@ +// RACI-Konsolen-Board — SLC-Workshop Tabletop · Einheiten: mm +// Quadratisches Premium-Board: zentrale Acryl-Chip-Mulde (Stations-/Gate-ID), +// 10 Figurenmulden im Ring, gruppiert in 4 RACI-Sektoren (R3 · A1 · C4 · I2), +// erhabene Trennstege, gravierte Sektor-Buchstaben + Wörter, gravierte Rand-Linie, +// stehender Action-Card-Halter (60x90) hinten. +// Druck: 6x in den Phasenfarben (5 Phasen + Gate-Rot). Einfarbig je Board (Filament). + +/* [Board] */ +board_w = 210; +board_d = 210; +base_h = 10; +corner_r = 8; +cut_corner = 22; // gefaste Ecke (vorne rechts), Optik + +/* [Dial / Mitte] */ +dial_cx = 0; +dial_cy = -15; // Scheibe etwas nach vorn -> Platz fuer Card-Halter hinten +chip_d = 40.6; // Acryl-Chip Ø40 + Spiel +chip_dep = 1.8; // Chip 2 mm steht ~0,2 vor (greifbar) +notch_r = 6; // Greifkerbe + +/* [Sockel] — Figuren-Sockel Ø24,5 -> Mulde Ø25,3 */ +sock_d = 25.3; +sock_dep = 1.5; +sock_lead = 0.6; +ring_R = 48; // Radius Sockelkreis (Mitte-Mitte) + +/* [Sektor-Trennstege] */ +ridge_h = 2.6; +ridge_w = 3.2; + +/* [Gravur] */ +let_size = 11; // grosse R/A/C/I +let_dep = 0.9; +word_size = 5; // RESPONSIBLE ... +word_dep = 0.7; +border_inset = 7; +border_w = 1.6; +border_dep= 0.7; + +/* [Action-Card-Halter] (Karte 60x90, steht leicht nach hinten geneigt) */ +card_w = 84; // Plinthen-Breite +card_block_d = 22; +card_block_h = 16; +card_cy = 78; // Position hinten +slot_w = 63; // Kartenbreite 60 + Spiel +slot_t = 4; // Kartendicke + Spiel +slot_tilt = 12; // Grad Neigung nach hinten + +$fn = 64; + +// 10 Sockel: Winkel ab oben (90°) im Uhrzeigersinn, 36° Schritt. +// Gruppen: A=[0] C=[1..4] I=[5,6] R=[7,8,9] +function ang(i) = 90 - i*36; +GROUP_A = [0]; +GROUP_C = [1,2,3,4]; +GROUP_I = [5,6]; +GROUP_R = [7,8,9]; +// Sektor-Mittenwinkel (fuer Buchstaben) und Grenzwinkel (fuer Trennstege) +A_mid = 90; C_mid = 0; I_mid = -108; R_mid = 162; +DIVIDERS = [72, -72, -144, 108]; + +module rrect(w, d, h, r) { + linear_extrude(h) offset(r) offset(-r) square([w, d], center=true); +} + +module base_slab() { + difference() { + rrect(board_w, board_d, base_h, corner_r); + // gefaste Ecke vorne rechts + translate([board_w/2, -board_d/2, -1]) + rotate([0,0,45]) cube([cut_corner, cut_corner, base_h+2], center=true); + } +} + +module figure_socket(a, r=ring_R) { + x = dial_cx + r*cos(a); y = dial_cy + r*sin(a); + translate([x, y, base_h - sock_dep]) { + cylinder(d=sock_d, h=sock_dep + 0.2); + translate([0,0,sock_dep - sock_lead]) + cylinder(d1=sock_d, d2=sock_d + 2*sock_lead, h=sock_lead + 0.1); + } +} + +module chip_well() { + translate([dial_cx, dial_cy, base_h - chip_dep]) cylinder(d=chip_d, h=chip_dep + 0.2); + // Greifkerbe Richtung vorn + translate([dial_cx, dial_cy - chip_d/2, base_h - chip_dep]) + cylinder(r=notch_r, h=chip_dep + 0.2); +} + +module grooves() { // konzentrische Deko-Rillen im Innenfeld + for (rr = [23 : 2 : 34]) + translate([dial_cx, dial_cy, base_h - 0.5]) + linear_extrude(0.6) + difference() { circle(r=rr); circle(r=rr-0.8); } +} + +module dividers() { + for (a = DIVIDERS) { + ri = chip_d/2 + 3; ro = ring_R + sock_d/2 + 4; + translate([dial_cx, dial_cy, base_h]) + rotate([0,0,a]) + translate([(ri+ro)/2, 0, ridge_h/2]) + cube([ro-ri, ridge_w, ridge_h], center=true); + } +} + +module letter(a, ch) { + rl = ring_R + 13; + x = dial_cx + rl*cos(a); y = dial_cy + rl*sin(a); + translate([x, y, base_h - let_dep]) + linear_extrude(let_dep + 0.1) + text(ch, size=let_size, halign="center", valign="center", + font="DejaVu Sans:style=Bold"); +} + +module word(txt, x, y, rot) { + translate([x, y, base_h - word_dep]) + rotate([0,0,rot]) + linear_extrude(word_dep + 0.1) + text(txt, size=word_size, halign="center", valign="center"); +} + +module border_channel() { + translate([0,0, base_h - border_dep]) + linear_extrude(border_dep + 0.1) + difference() { + offset(-border_inset) offset(corner_r) offset(-corner_r) square([board_w, board_d], center=true); + offset(-border_inset-border_w) offset(corner_r) offset(-corner_r) square([board_w, board_d], center=true); + } +} + +module card_block() { + translate([0, card_cy, 0]) rrect(card_w, card_block_d, base_h + card_block_h, 4); +} +module card_slot() { + // oben offener Schlitz, leicht nach HINTEN geneigt; Boden bleibt ~3 mm stehen + translate([0, card_cy, 22]) + rotate([-slot_tilt, 0, 0]) + cube([slot_w, slot_t, 38], center=true); +} + +module raci_board() { + difference() { + union() { + base_slab(); + dividers(); + card_block(); + } + chip_well(); + grooves(); + for (i=[0:9]) figure_socket(ang(i)); + // grosse Buchstaben + letter(A_mid, "A"); + letter(C_mid, "C"); + letter(I_mid, "I"); + letter(R_mid, "R"); + // Woerter + word("RESPONSIBLE", -ring_R-29, dial_cy+6, 90); + word("CONSULTED", ring_R+29, dial_cy+6, -90); + word("INFORMED", dial_cx, dial_cy - ring_R - 30, 0); + word("ACCOUNTABLE", dial_cx, card_cy - card_block_d/2 - 7, 0); + border_channel(); + card_slot(); + } +} + +raci_board(); + +echo(board=[board_w, board_d, base_h], sockets=10, raci="R3 A1 C4 I2", + chip_well=chip_d, sock=sock_d);