This commit is contained in:
marcus hinz
2026-06-12 10:40:47 +02:00
commit 52ce98626c
15 changed files with 903 additions and 0 deletions
+245
View File
@@ -0,0 +1,245 @@
#!/usr/bin/env python3
"""Workshop-Folien 'Vault Technischer Impuls': ruhig, visuell, wenig Text."""
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE
# ---- Palette ---------------------------------------------------------------
INK = RGBColor(0x2A, 0x2F, 0x3A)
NAVY = RGBColor(0x1F, 0x3A, 0x5F)
ACCENT = RGBColor(0xC0, 0x49, 0x2F)
MUTE = RGBColor(0x8A, 0x93, 0xA0)
SOFT = RGBColor(0xE9, 0xEC, 0xEF) # heller Kasten
SOFT2 = RGBColor(0xDD, 0xE5, 0xEC) # heller Kasten blau
LINEC = RGBColor(0xC3, 0xC9, 0xD1)
BG = RGBColor(0xFF, 0xFF, 0xFF)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
F = "Calibri"
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
SW, SH = prs.slide_width, prs.slide_height
BLANK = prs.slide_layouts[6]
MX = Inches(0.95)
def slide(bg=BG):
s = prs.slides.add_slide(BLANK)
r = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SW, SH)
r.fill.solid(); r.fill.fore_color.rgb = bg; r.line.fill.background()
r.shadow.inherit = False
s.shapes._spTree.remove(r._element); s.shapes._spTree.insert(2, r._element)
return s
def shape(s, kind, x, y, w, h, fill=None, line=None, lw=Pt(1)):
sh = s.shapes.add_shape(kind, x, y, w, h)
if fill is None: sh.fill.background()
else: sh.fill.solid(); sh.fill.fore_color.rgb = fill
if line is None: sh.line.fill.background()
else: sh.line.color.rgb = line; sh.line.width = lw
sh.shadow.inherit = False
return sh
def box(s, x, y, w, h, text="", fill=SOFT, tc=INK, size=15, bold=False,
rounded=True, line=None, sub=None, sub_c=None):
kind = MSO_SHAPE.ROUNDED_RECTANGLE if rounded else MSO_SHAPE.RECTANGLE
sh = shape(s, kind, x, y, w, h, fill=fill, line=line)
tf = sh.text_frame; tf.word_wrap = True
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
tf.margin_top = tf.margin_bottom = Pt(2)
p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER
r = p.add_run(); r.text = text
r.font.size = Pt(size); r.font.bold = bold; r.font.name = F; r.font.color.rgb = tc
if sub:
p2 = tf.add_paragraph(); p2.alignment = PP_ALIGN.CENTER; p2.space_before = Pt(2)
r2 = p2.add_run(); r2.text = sub
r2.font.size = Pt(11); r2.font.name = F; r2.font.color.rgb = sub_c or MUTE
return sh
def text(s, x, y, w, h, runs, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP,
leading=1.1, space=0):
tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame
tf.word_wrap = True; tf.vertical_anchor = anchor
tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0)
p = tf.paragraphs[0]; p.alignment = align; p.line_spacing = leading; p.space_after = Pt(space)
for t, kw in runs:
r = p.add_run(); r.text = t
r.font.size = Pt(kw.get("size", 16)); r.font.bold = kw.get("bold", False)
r.font.italic = kw.get("italic", False); r.font.name = F
r.font.color.rgb = kw.get("color", INK)
return tb
def kicker(s, t):
tb = text(s, MX, Inches(0.62), Inches(11), Inches(0.4),
[(t.upper(), {"size": 12.5, "color": ACCENT, "bold": True})])
tb.text_frame.paragraphs[0].runs[0].font._rPr.set('spc', '240')
def head(s, t):
text(s, MX, Inches(1.0), Inches(11.4), Inches(0.9),
[(t, {"size": 32, "color": NAVY, "bold": True})])
shape(s, MSO_SHAPE.RECTANGLE, MX, Inches(1.78), Inches(0.6), Pt(4), fill=ACCENT)
def caption(s, t):
text(s, MX, Inches(6.62), Inches(11.4), Inches(0.6),
[(t, {"size": 16, "color": MUTE})], leading=1.15)
def vline(s, x, y, h, color=LINEC):
shape(s, MSO_SHAPE.RECTANGLE, x, y, Pt(1.6), h, fill=color)
# ===========================================================================
# TITEL
# ===========================================================================
s = slide(NAVY)
shape(s, MSO_SHAPE.RECTANGLE, 0, 0, Inches(0.24), SH, fill=ACCENT)
shape(s, MSO_SHAPE.RECTANGLE, MX, Inches(2.7), Inches(0.85), Pt(5), fill=ACCENT)
text(s, MX, Inches(1.7), Inches(10), Inches(0.5),
[("TECHNISCHER IMPULS", {"size": 14, "color": RGBColor(0xC6,0xD1,0xDE), "bold": True})]
).text_frame.paragraphs[0].runs[0].font._rPr.set('spc', '280')
text(s, MX, Inches(3.05), Inches(11.4), Inches(2.0),
[("LIS wohin", {"size": 50, "color": WHITE, "bold": True})])
text(s, MX, Inches(4.05), Inches(11.4), Inches(2.0),
[("technisch denken?", {"size": 50, "color": WHITE, "bold": True})])
# ===========================================================================
# FOLIE 1 Produkt oder Projekt (Balken-Bild)
# ===========================================================================
s = slide()
kicker(s, "Diagnose")
head(s, "Produkt oder Projekt?")
base = Inches(5.45) # Grundlinie
bw, gap = Inches(0.52), Inches(0.16)
def bars(cx_start, heights, colors, label):
n = len(heights)
for i, (hh, col) in enumerate(zip(heights, colors)):
x = cx_start + i * (bw + gap)
h = Inches(hh)
shape(s, MSO_SHAPE.ROUNDED_RECTANGLE, x, base - h, bw, h, fill=col)
total = n * bw + (n - 1) * gap
# Grundlinie
shape(s, MSO_SHAPE.RECTANGLE, cx_start, base, total, Pt(2), fill=LINEC)
text(s, cx_start, base + Inches(0.18), total, Inches(0.4),
[(label, {"size": 18, "color": NAVY, "bold": True})], align=PP_ALIGN.CENTER)
bars(Inches(1.5), [0.45, 0.5, 2.7, 0.5, 0.45],
[MUTE, MUTE, ACCENT, MUTE, MUTE], "Projekt")
bars(Inches(8.0), [1.7, 2.0, 1.8, 2.1, 1.7],
[NAVY, NAVY, NAVY, NAVY, NAVY], "Produkt")
# kleine neutrale Unterschriften
text(s, Inches(1.5), base + Inches(0.6), Inches(3.42), Inches(0.4),
[("tief in einer Dimension", {"size": 13, "color": MUTE})], align=PP_ALIGN.CENTER)
text(s, Inches(8.0), base + Inches(0.6), Inches(3.42), Inches(0.4),
[("breit über viele", {"size": 13, "color": MUTE})], align=PP_ALIGN.CENTER)
# ===========================================================================
# FOLIE 2 Eine Schicht dazwischen (Layer-Diagramm)
# ===========================================================================
s = slide()
kicker(s, "Architektur")
head(s, "Eine Schicht dazwischen")
cxL, cxR = MX, Inches(12.38)
full_w = cxR - cxL
# oben: Gruppe / Prozesse
top_y = Inches(2.2)
chips = ["Reporting", "DFÜ / Anlieferung", "Fusion"]
cw = Inches(3.2); cg = (full_w - 3 * cw) / 2
for i, c in enumerate(chips):
box(s, cxL + i * (cw + cg), top_y, cw, Inches(0.72), c,
fill=BG, tc=NAVY, size=15, bold=True, line=LINEC)
# Mitte: die Schicht (Hero)
mid_y = Inches(3.55)
box(s, cxL, mid_y, full_w, Inches(0.95),
"Integrations- & Abstraktionsschicht",
fill=NAVY, tc=WHITE, size=20, bold=True)
# unten: Systeme
bot_y = Inches(5.15)
labs = [("Labor A", "LIS"), ("Labor B", "LIS"), ("Vault", "LIS"), ("Labor C", "eigenes LIS")]
lw = Inches(2.65); lg = (full_w - 4 * lw) / 3
for i, (nm, sub) in enumerate(labs):
x = cxL + i * (lw + lg)
box(s, x, bot_y, lw, Inches(0.95), nm, fill=SOFT, tc=INK, size=16, bold=True,
sub=sub, sub_c=MUTE)
# Verbinder Schicht -> System
vline(s, x + lw / 2, mid_y + Inches(0.95), bot_y - (mid_y + Inches(0.95)))
# Verbinder oben -> Schicht
for i in range(3):
cx = cxL + i * (cw + cg) + cw / 2
vline(s, cx, top_y + Inches(0.72), mid_y - (top_y + Inches(0.72)))
caption(s, "Labore docken an und behalten ihr LIS. Die Schicht verbindet.")
# ===========================================================================
# FOLIE 3 Zustand oder Ereignis (Event Sourcing, visuell)
# ===========================================================================
s = slide()
kicker(s, "Modell")
head(s, "Zustand oder Ereignis")
cxL, cxR = MX, Inches(12.38)
full_w = cxR - cxL
# --- obere Spur: Zustand ---
ay = Inches(2.35)
text(s, cxL, ay - Inches(0.05), Inches(3), Inches(0.4),
[("Zustand", {"size": 15, "color": MUTE, "bold": True})])
zy = ay + Inches(0.42)
box(s, cxL, zy, Inches(2.4), Inches(0.95), "Probe", fill=SOFT, tc=INK, size=16, bold=True)
# Pfeil "überschreiben"
shape(s, MSO_SHAPE.RIGHT_ARROW, cxL + Inches(2.65), zy + Inches(0.27),
Inches(1.5), Inches(0.42), fill=LINEC)
text(s, cxL + Inches(2.55), zy - Inches(0.18), Inches(1.7), Inches(0.3),
[("überschreiben", {"size": 11, "color": MUTE, "italic": True})], align=PP_ALIGN.CENTER)
box(s, cxL + Inches(4.4), zy, Inches(2.7), Inches(0.95), "1 Abrechnung",
fill=BG, tc=NAVY, size=16, bold=True, line=LINEC)
# Trennlinie
shape(s, MSO_SHAPE.RECTANGLE, cxL, Inches(4.25), full_w, Pt(1.2), fill=LINEC)
# --- untere Spur: Ereignisse ---
by = Inches(4.55)
text(s, cxL, by - Inches(0.05), Inches(3), Inches(0.4),
[("Ereignisse", {"size": 15, "color": MUTE, "bold": True})])
ey = by + Inches(0.42)
ev = ["Eingang", "Messung", "Abrechnung", "Abrechnung"]
ew = Inches(1.85); eg = Inches(0.32)
for i, e in enumerate(ev):
x = cxL + i * (ew + eg)
col = ACCENT if e == "Abrechnung" else NAVY
box(s, x, ey, ew, Inches(0.85), e, fill=col, tc=WHITE, size=14, bold=True)
if i < len(ev) - 1:
shape(s, MSO_SHAPE.CHEVRON, x + ew + Inches(0.02), ey + Inches(0.24),
Inches(0.28), Inches(0.38), fill=LINEC)
# Pfeil zum abgeleiteten Zustand
last_x = cxL + 4 * (ew + eg)
shape(s, MSO_SHAPE.RIGHT_ARROW, last_x - Inches(0.05), ey + Inches(0.22),
Inches(0.7), Inches(0.42), fill=LINEC)
box(s, last_x + Inches(0.75), ey - Inches(0.05), Inches(2.7), Inches(0.95),
"Zustand", fill=SOFT2, tc=NAVY, size=15, bold=True,
sub="abgeleitet", sub_c=MUTE)
caption(s, "Der Zustand entsteht aus den Ereignissen Mehrfaches inklusive.")
out = "/home/marcuh/claude-projects/limbach/Vault_Impuls_Workshop.pptx"
prs.save(out)
print("saved", out)