#!/usr/bin/env python3 """Workshop-Folien 'Vault – Technischer Impuls': ruhig, visuell, wenig Text.""" import os 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 = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Vault_Impuls_Workshop.pptx") prs.save(out) print("saved", out)