This commit is contained in:
breitenbach76 2026-06-09 08:21:31 +02:00
parent 4b031fd98a
commit e7e7912211
5 changed files with 55 additions and 70 deletions

View file

@ -1,41 +1,35 @@
# RACI-Konsolen-Board (rund) — Blender-Generator (bpy) · v2
# RACI-Konsolen-Board (rund) — FUNKTIONS-BLANK · Blender-Generator (bpy)
# SLC-Workshop Tabletop · 1 Blender-Unit = 1 mm
# Runde Puck-Flaeche, zentrale Acryl-Chip-Mulde (Ø40), 10 Sockel in 4 RACI-Sektoren
# (R3 A1 C4 I2), gleich grosse Sektor-Woerter tangential um die Sockel, Kartenhalter
# oben (Aussparung), Phasenname (DESIGN) unten im Innenkreis, Design-Blau.
# Lauf: Scripting -> Open -> Run | blender -b -P raci-board.py
# Ziel: korrekt & schlicht (Maße/Passungen stimmen). Der edle Look wird SEPARAT
# interaktiv draufmodelliert. Hier nur: runde Platte, Chip-Mulde, 10 Sockel in
# 4 klar getrennten RACI-Sektoren (R3 A1 C4 I2), Kartenschlitz, lesbare Labels.
import bpy, math, os
# ----------------------------- Parameter (mm) -----------------------------
R_BOARD, BASE_H = 90.0, 12.0
EDGE_BEVEL, EDGE_SEG = 1.6, 4
EDGE_BEVEL, EDGE_SEG = 1.4, 3
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
SOCK_LEAD = 0.6
RING_R = 64.0
RING_R = 62.0
PHASE_NAME = "DESIGN"
PHASE_COLOR = (0.184, 0.502, 0.788, 1) # #2f80c9 Design-Blau
PHASE_NAME, PHASE_COLOR = "DESIGN", (0.184, 0.502, 0.788, 1) # #2f80c9
# Sektoren: Name, Wort-Mittenwinkel, Sockel-Winkel (Grad, 90=oben). Top frei fuer Karte.
# Sektor: Name, Label-Mittenwinkel, Sockel-Winkel (Grad; 90=oben, Top frei fuer Karte).
# Lücken zwischen den Sektoren (34-36°) > Lücken innerhalb (28°) -> Gruppen klar sichtbar.
SECTORS = [
("RESPONSIBLE", 165, [135, 165, 195]),
("ACCOUNTABLE", 50, [50]),
("CONSULTED", -20, [25, -5, -35, -65]),
("INFORMED", -110, [-95, -125]),
("RESPONSIBLE", 174, [146, 174, 202]),
("ACCOUNTABLE", 58, [58]),
("CONSULTED", -20, [22, -6, -34, -62]),
("INFORMED", -110, [-96, -124]),
]
DIVIDERS = [37.5, -80, -145] # Grenzen zwischen Sektoren (nicht im Karten-Spalt)
RIDGE_H, RIDGE_W = 2.6, 3.4
WORD_R = RING_R + SOCK_D/2 + 8 # Labels ausserhalb der Sockel
WORD_SIZE, WORD_DEP = 6.0, 0.9
DESIGN_SIZE, DESIGN_DEP, DESIGN_POS = 9.0, 1.0, (0, -32)
WORD_SIZE, WORD_DEP = 5.0, 0.8
WORD_R = RING_R + SOCK_D/2 + 9 # ausserhalb der Sockel
DESIGN_SIZE, DESIGN_DEP = 9.0, 1.0
DESIGN_POS = (0, -38) # unten im Innenkreis, gegenueber Kartenhalter
CARD_CY, CARD_BW, CARD_BD, CARD_BH = 70.0, 76.0, 20.0, 16.0
CARD_CY, CARD_BW, CARD_BD, CARD_BH = 72.0, 76.0, 18.0, 14.0
SLOT_W, SLOT_T, SLOT_TILT = 63.0, 4.0, 12.0
TOP = BASE_H
@ -46,8 +40,7 @@ def _outdir():
try: return os.path.dirname(os.path.abspath(__file__))
except NameError: return os.path.expanduser("~")
HERE = _outdir()
STL_OUT = os.path.join(HERE, "raci-board.stl")
PNG_OUT = os.path.join(HERE, "raci_preview.png")
STL_OUT, PNG_OUT = os.path.join(HERE, "raci-board.stl"), os.path.join(HERE, "raci_preview.png")
print("Ausgabe-Ordner:", HERE)
# ----------------------------- Helfer -----------------------------
@ -71,8 +64,7 @@ def boolean(obj, tool, op='DIFFERENCE'):
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)
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
@ -93,7 +85,6 @@ def engrave(board, body, x, y, rotz=0, size=WORD_SIZE, dep=WORD_DEP):
# ----------------------------- Aufbau -----------------------------
clear_scene()
# Runde Platte + Kartenblock (oben), beide gefast, vereinen
base = cyl(R_BOARD*2, BASE_H, (0, 0, BASE_H/2), verts=160)
apply_bevel(base)
block = cube(CARD_BW, CARD_BD, BASE_H + CARD_BH, (0, CARD_CY, (BASE_H + CARD_BH)/2))
@ -104,34 +95,26 @@ boolean(base, block, 'UNION')
boolean(base, cyl(CHIP_D, 6, (0, 0, TOP - CHIP_DEP + 3)), 'DIFFERENCE')
boolean(base, cyl(NOTCH_D, 6, (0, -CHIP_D/2, TOP - CHIP_DEP + 3)), 'DIFFERENCE')
# Sockelmulden (mit Einfuehr-Fase)
# Sockelmulden (4 Sektoren, ueber Luecken getrennt)
for _, _, angles in SECTORS:
for a in angles:
x = RING_R*math.cos(math.radians(a)); y = RING_R*math.sin(math.radians(a))
boolean(base, cyl(SOCK_D, 6, (x, y, TOP - SOCK_DEP + 3)), 'DIFFERENCE')
boolean(base, cyl(SOCK_D, 6, (RING_R*math.cos(math.radians(a)),
RING_R*math.sin(math.radians(a)), TOP - SOCK_DEP + 3)), 'DIFFERENCE')
# Sektor-Trennstege (erhaben)
ri, ro = CHIP_D/2 + 3, RING_R + SOCK_D/2 + 4
for a in DIVIDERS:
rmid = (ri+ro)/2
r = cube(ro-ri, RIDGE_W, RIDGE_H, (rmid*math.cos(math.radians(a)),
rmid*math.sin(math.radians(a)), TOP + RIDGE_H/2))
r.rotation_euler = (0, 0, math.radians(a)); bpy.ops.object.transform_apply(rotation=False)
boolean(base, r, 'UNION')
# Action-Card-Schlitz (oben offen, nach hinten geneigt)
# Action-Card-Schlitz (oben offen, leicht nach hinten geneigt)
slot = cube(SLOT_W, SLOT_T, 40, (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 (rund)
# dezente Rand-Linie
rim = cyl((R_BOARD-7)*2, 1.4, (0, 0, TOP-0.7))
boolean(rim, cyl((R_BOARD-8.6)*2, 2.0, (0, 0, TOP-0.7)), 'DIFFERENCE')
boolean(base, rim, 'DIFFERENCE')
# Sektor-Woerter: gleich gross, tangential um die Sockel
# Sektor-Labels (gleich gross): links/rechts vertikal, oben/unten waagerecht -> lesbar & passt
for name, wc, _ in SECTORS:
rot = (wc - 90) if math.sin(math.radians(wc)) >= 0 else (wc + 90) # lesbar tangential
c = math.cos(math.radians(wc))
rot = -90 if c > 0.7 else (90 if c < -0.7 else 0)
engrave(base, name, WORD_R*math.cos(math.radians(wc)), WORD_R*math.sin(math.radians(wc)), rot)
# Phasenname unten im Innenkreis
@ -143,7 +126,7 @@ except Exception:
try: bpy.ops.object.shade_flat()
except Exception: pass
# ----------------------------- Material (Design-Blau) -----------------------------
# Material (Kontext-Farbe; fuer den Blank zweitrangig)
mat = bpy.data.materials.new("Phase"); mat.use_nodes = True
bsdf = next((n for n in mat.node_tree.nodes if n.type == 'BSDF_PRINCIPLED'), None)
if bsdf:
@ -161,15 +144,14 @@ try:
except Exception: pass
bpy.ops.object.light_add(type='SUN', location=(140, -180, 260)); bpy.context.object.data.energy = 3.5
bpy.ops.object.light_add(type='AREA', location=(-140, -60, 180))
bpy.context.object.data.energy = 5000; bpy.context.object.data.size = 260
bpy.context.object.data.energy = 6000; bpy.context.object.data.size = 280
bpy.ops.object.empty_add(location=(0, 0, 5)); tgt = bpy.context.object
bpy.ops.object.camera_add(location=(215, -270, 250)); cam = bpy.context.object
bpy.ops.object.camera_add(location=(235, -295, 270)); cam = bpy.context.object
cam.data.lens = 50
con = cam.constraints.new('TRACK_TO'); con.target = tgt
con.track_axis = 'TRACK_NEGATIVE_Z'; con.up_axis = 'UP_Y'
sc.camera = cam
sc.render.resolution_x, sc.render.resolution_y = 1500, 1100
sc.render.filepath = PNG_OUT
sc.render.resolution_x, sc.render.resolution_y = 1500, 1100; sc.render.filepath = PNG_OUT
try: sc.render.engine = 'BLENDER_EEVEE_NEXT'
except Exception:
try: sc.render.engine = 'BLENDER_EEVEE'