app foldee

This commit is contained in:
marcus.hinz
2026-06-14 12:13:49 +02:00
parent 6d29263392
commit b30e4a7d31
73 changed files with 994 additions and 175 deletions
Vendored
BIN
View File
Binary file not shown.
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
.build/
*.app/
+23
View File
@@ -0,0 +1,23 @@
{
"pins" : [
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser",
"state" : {
"revision" : "6a52f3251125d74daf04fcbd5e6f08a75d074382",
"version" : "1.8.2"
}
},
{
"identity" : "swiftterm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/migueldeicaza/SwiftTerm.git",
"state" : {
"branch" : "main",
"revision" : "24a68bcadc479d945c7ca32f21ac0a8ab895c690"
}
}
],
"version" : 2
}
+20
View File
@@ -0,0 +1,20 @@
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MiniTerm",
platforms: [
.macOS(.v13)
],
dependencies: [
// Pin to a tag instead of `branch` if you prefer reproducible builds,
// e.g. .package(url: "...", from: "1.2.0")
.package(url: "https://github.com/migueldeicaza/SwiftTerm.git", branch: "main")
],
targets: [
.executableTarget(
name: "MiniTerm",
dependencies: ["SwiftTerm"]
)
]
)
+99
View File
@@ -0,0 +1,99 @@
# MiniTerm — winzige eigenständige Terminal-Apps für macOS
Ein minimaler, auf **SwiftTerm** basierender Terminal-Emulator. Sinn der Sache:
jede erzeugte `.app` ist eine **eigene macOS-App mit eigener Bundle-ID** — also
**eigenes Dock-Icon und eigener ⌘-Tab-Eintrag**. Genau das, was sich unter macOS
mit `--class` (wie bei Alacritty/Ghostty unter Linux) *nicht* lösen lässt.
Jede App startet in einem konfigurierbaren **Arbeitsverzeichnis** und führt einen
konfigurierbaren **Befehl** aus (z. B. `claude`). Für Claude Code reicht das
locker — SwiftTerm ist ein vollwertiger xterm-256color-kompatibler Emulator.
## Voraussetzungen
- macOS 13+ und **Xcode** bzw. die Command Line Tools (`xcode-select --install`)
- Swift (kommt mit Xcode)
## Schnellstart
```bash
cd MiniTerm
chmod +x make-app.sh
./make-app.sh \
--name "Claude · Projekt A" \
--bundle-id com.deinname.claude.projekta \
--workdir ~/code/projekt-a \
--command "claude"
```
Beim ersten Lauf wird das Binary einmal gebaut (`swift build -c release`,
lädt SwiftTerm). Danach liegt die App in `~/Applications/Claude · Projekt A.app`
— per Doppelklick starten, Dock-Icon erscheint als eigene App.
## Mehrere Apps (eigene Icons/Configs)
Einfach pro App einmal aufrufen. Das Binary wird nur einmal kompiliert und
wiederverwendet:
```bash
./make-app.sh --name "Claude · Webshop" --bundle-id com.deinname.claude.webshop \
--workdir ~/code/webshop --command "claude" --icon ./icons/shop.icns
./make-app.sh --name "Claude · Infra" --bundle-id com.deinname.claude.infra \
--workdir ~/code/infra --command "claude" --icon ./icons/infra.icns
./make-app.sh --name "Scratch Shell" --bundle-id com.deinname.scratch \
--workdir ~ --command "" # leer = einfache Shell
```
## Optionen
| Flag | Pflicht | Bedeutung |
|----------------|---------|--------------------------------------------------------|
| `--name` | ja | Anzeigename + Dateiname der App |
| `--bundle-id` | ja | Eindeutige Bundle-ID (gibt die eigene App-Identität) |
| `--workdir` | nein | Startverzeichnis (`~` wird expandiert; leer = Home) |
| `--command` | nein | Befehl beim Start (leer = nur eine Login-Shell) |
| `--font-size` | nein | Schriftgröße (Default 13) |
| `--font` | nein | Schriftfamilie (Default Menlo; Fallback System-Mono) |
| `--icon` | nein | PNG/JPG (wird automatisch konvertiert) oder `.icns` |
| `--out` | nein | Zielordner (Default `~/Applications`) |
## Icon aus einem PNG
`--icon` nimmt direkt ein **PNG** (oder JPG) — das Skript erzeugt daraus
automatisch alle nötigen Größen und das `.icns` (via `sips`/`iconutil`):
```bash
./make-app.sh --name "Claude · Infra" --bundle-id com.deinname.claude.infra \
--workdir ~/code/infra --command "claude" --icon ~/Downloads/infra.png
```
Am besten ein **quadratisches** Bild nehmen, sonst verzerrt `sips` es. Eine
fertige `.icns`-Datei kannst du weiterhin auch direkt angeben.
## Wie die Konfiguration funktioniert
Das kompilierte Binary ist für alle Apps identisch. Die Unterschiede stecken in
der `Info.plist` jedes Bundles (Schlüssel `MTCommand`, `MTWorkingDirectory`,
`MTWindowTitle`, `MTFontSize`, `MTFontName`). Das Programm liest sie beim Start aus
`Bundle.main`. So genügt ein Build für beliebig viele Apps.
## Hinweise
- Der Befehl läuft immer über eine **Login-Shell** (`zsh -l`), damit PATH stimmt
(Homebrew, `~/.local/bin` usw.) und `claude` gefunden wird.
- Nach Beenden des Befehls fällt das Fenster in eine normale Shell zurück
(`exec zsh -l`), schließt sich also nicht sofort.
- Die App ist **ad-hoc signiert**. Beim ersten Start ggf. Rechtsklick → „Öffnen",
falls Gatekeeper meckert.
- SwiftTerm-Version: im `Package.swift` auf `branch: "main"`. Für reproduzierbare
Builds dort auf einen festen Tag (`from: "..."`) umstellen.
## Tastatur
- **Enter** sendet wie gewohnt einen Wagenrücklauf (`\r`) → Eingabe absenden.
- **Shift+Enter** sendet einen Zeilenumbruch (`\n`) → für mehrzeilige Eingaben,
z. B. in Claude Code. Gilt auch für Enter auf dem Ziffernblock.
- ⌘C / ⌘V funktionieren über das Edit-Menü.
@@ -0,0 +1,213 @@
import AppKit
import SwiftTerm
// MARK: - Configuration
//
// Every value is read from the app bundle's Info.plist, so a single compiled
// binary can power many different .app bundles (each with its own command,
// working directory, title and icon). See make-app.sh.
//
// MTCommand String Command to run, e.g. "claude". Empty = plain shell.
// MTWorkingDirectory String Directory to start in. "~" is expanded. Empty = home.
// MTFontSize Number Font point size (default 13).
// MTFontName String Font family name (default "Menlo"). Falls back to
// the system monospaced font if the name is missing.
// MTWindowTitle String Window title (default = CFBundleName).
//
struct AppConfig {
let command: String
let workingDirectory: String?
let fontSize: CGFloat
let fontName: String
let windowTitle: String
static func load() -> AppConfig {
let info = Bundle.main.infoDictionary ?? [:]
let cmd = ((info["MTCommand"] as? String) ?? "")
.trimmingCharacters(in: .whitespacesAndNewlines)
var workdir: String? = nil
if let raw = info["MTWorkingDirectory"] as? String,
!raw.trimmingCharacters(in: .whitespaces).isEmpty {
workdir = (raw as NSString).expandingTildeInPath
}
let size = (info["MTFontSize"] as? NSNumber)?.doubleValue ?? 13.0
let fontName = ((info["MTFontName"] as? String) ?? "")
.trimmingCharacters(in: .whitespacesAndNewlines)
let title = (info["MTWindowTitle"] as? String)
?? (info["CFBundleName"] as? String)
?? "Terminal"
return AppConfig(command: cmd,
workingDirectory: workdir,
fontSize: CGFloat(size),
fontName: fontName.isEmpty ? "Menlo" : fontName,
windowTitle: title)
}
}
// MARK: - App delegate
final class AppDelegate: NSObject, NSApplicationDelegate, LocalProcessTerminalViewDelegate {
private var window: NSWindow!
private var terminalView: LocalProcessTerminalView!
private var keyMonitor: Any?
private let config = AppConfig.load()
func applicationDidFinishLaunching(_ notification: Notification) {
let frame = NSRect(x: 0, y: 0, width: 900, height: 560)
terminalView = LocalProcessTerminalView(frame: frame)
terminalView.processDelegate = self
terminalView.font = resolvedFont()
installKeyMonitor()
window = NSWindow(
contentRect: frame,
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered,
defer: false)
window.title = config.windowTitle
window.contentView = terminalView
window.setFrameAutosaveName("MiniTermMain")
window.center()
window.makeKeyAndOrderFront(nil)
window.makeFirstResponder(terminalView)
setupMenu()
launchProcess()
}
private func launchProcess() {
let shell = userShell()
// We always go through a LOGIN shell (-l) so PATH is set up exactly like
// in a normal terminal (Homebrew, ~/.local/bin, etc.). Without this,
// tools like `claude` are often "command not found".
let args: [String]
if config.command.isEmpty {
args = ["-l"]
} else {
// Run the requested command, then hand control to an interactive
// login shell so the window stays open after the command exits.
args = ["-l", "-c", "\(config.command); exec \(shell) -l"]
}
let env = Terminal.getEnvironmentVariables(termName: "xterm-256color", trueColor: true)
terminalView.startProcess(
executable: shell,
args: args,
environment: env,
execName: nil,
currentDirectory: config.workingDirectory)
}
// Intercept Shift+Return (and Shift+Enter on the numpad) before it reaches
// the terminal and send a line feed (\n) instead of the normal carriage
// return (\r). This lets CLIs like Claude Code insert a newline in
// multi-line input while plain Enter still submits. We use a local event
// monitor because SwiftTerm's keyDown(with:) is `public`, not `open`, so it
// can't be overridden from outside the module.
private func installKeyMonitor() {
keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
guard let self = self else { return event }
// Only act when our window is focused and the keyboard focus is in
// the terminal (the view itself or one of its subviews).
guard self.window.isKeyWindow else { return event }
if let responder = self.window.firstResponder as? NSView,
!(responder === self.terminalView || responder.isDescendant(of: self.terminalView)) {
return event
}
let isReturn = (event.keyCode == 36 || event.keyCode == 76)
let mods = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
if isReturn && mods == .shift {
self.terminalView.send(txt: "\n")
return nil // consume the event
}
return event
}
}
// Resolve the configured font, falling back gracefully so the app never
// launches with a broken/empty font if the name doesn't exist on the system.
private func resolvedFont() -> NSFont {
if let f = NSFont(name: config.fontName, size: config.fontSize) {
return f
}
if let menlo = NSFont(name: "Menlo", size: config.fontSize) {
return menlo
}
return NSFont.monospacedSystemFont(ofSize: config.fontSize, weight: .regular)
}
private func userShell() -> String {
if let shell = ProcessInfo.processInfo.environment["SHELL"], !shell.isEmpty {
return shell
}
return "/bin/zsh"
}
private func setupMenu() {
let mainMenu = NSMenu()
// Application menu
let appItem = NSMenuItem()
mainMenu.addItem(appItem)
let appMenu = NSMenu()
let name = config.windowTitle
appMenu.addItem(withTitle: "Hide \(name)",
action: #selector(NSApplication.hide(_:)),
keyEquivalent: "h")
appMenu.addItem(NSMenuItem.separator())
appMenu.addItem(withTitle: "Quit \(name)",
action: #selector(NSApplication.terminate(_:)),
keyEquivalent: "q")
appItem.submenu = appMenu
// Edit menu (enables C / V in the terminal)
let editItem = NSMenuItem()
mainMenu.addItem(editItem)
let editMenu = NSMenu(title: "Edit")
editMenu.addItem(withTitle: "Copy",
action: #selector(NSText.copy(_:)),
keyEquivalent: "c")
editMenu.addItem(withTitle: "Paste",
action: #selector(NSText.paste(_:)),
keyEquivalent: "v")
editItem.submenu = editMenu
NSApp.mainMenu = mainMenu
}
// MARK: LocalProcessTerminalViewDelegate
func sizeChanged(source: LocalProcessTerminalView, newCols: Int, newRows: Int) {}
func setTerminalTitle(source: LocalProcessTerminalView, title: String) {
// Keep a stable window title. Uncomment to follow the shell's title:
// window.title = title.isEmpty ? config.windowTitle : title
}
func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {}
func processTerminated(source: TerminalView, exitCode: Int32?) {
NSApp.terminate(nil)
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}
// MARK: - Entry point
let app = NSApplication.shared
app.setActivationPolicy(.regular) // a real Dock app, not a background process
let delegate = AppDelegate()
app.delegate = delegate
app.activate(ignoringOtherApps: true)
app.run()
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env bash
#
# make-app.sh — build a standalone macOS .app from the MiniTerm binary.
#
# Each generated .app has its OWN bundle id, so macOS treats it as a separate
# application: separate Dock icon, separate ⌘-Tab entry. Run this script once
# per app you want.
#
# Usage:
# ./make-app.sh \
# --name "Claude · Projekt A" \
# --bundle-id com.yourname.claude.projekta \
# --workdir ~/code/project-a \
# --command "claude" \
# [--font-size 13] \
# [--font "JetBrains Mono"] \
# [--icon ./icons/projekta.png] \ # .png/.jpg (auto-converted) or .icns
# [--out ~/Applications]
#
# Build the binary fresh? The script runs `swift build -c release` automatically
# the first time and reuses the result for further apps.
set -euo pipefail
# ---- defaults -------------------------------------------------------------
NAME=""
BUNDLE_ID=""
WORKDIR=""
COMMAND=""
FONT_SIZE="13"
FONT_NAME="Menlo"
ICON=""
OUTDIR="$HOME/Applications"
# ---- parse args -----------------------------------------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
--name) NAME="$2"; shift 2 ;;
--bundle-id) BUNDLE_ID="$2"; shift 2 ;;
--workdir) WORKDIR="$2"; shift 2 ;;
--command) COMMAND="$2"; shift 2 ;;
--font-size) FONT_SIZE="$2"; shift 2 ;;
--font) FONT_NAME="$2"; shift 2 ;;
--icon) ICON="$2"; shift 2 ;;
--out) OUTDIR="$2"; shift 2 ;;
-h|--help)
grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
if [[ -z "$NAME" || -z "$BUNDLE_ID" ]]; then
echo "Error: --name and --bundle-id are required." >&2
echo "Run './make-app.sh --help' for usage." >&2
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# ---- build binary once ----------------------------------------------------
BIN=".build/release/MiniTerm"
if [[ ! -f "$BIN" ]]; then
echo "==> Building MiniTerm (release)…"
swift build -c release
fi
# expand ~ in workdir
WORKDIR="${WORKDIR/#\~/$HOME}"
# ---- assemble the .app bundle ---------------------------------------------
APP="$OUTDIR/$NAME.app"
echo "==> Creating $APP"
rm -rf "$APP"
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
cp "$BIN" "$APP/Contents/MacOS/MiniTerm"
chmod +x "$APP/Contents/MacOS/MiniTerm"
# Build an .icns from a square source image (PNG/JPG/…) using macOS tools.
make_icns_from_image() {
local src="$1" dest="$2"
local iconset
iconset="$(mktemp -d)/icon.iconset"
mkdir -p "$iconset"
# All sizes Apple expects in an .iconset
local sizes=(16 32 128 256 512)
for s in "${sizes[@]}"; do
sips -z "$s" "$s" "$src" --out "$iconset/icon_${s}x${s}.png" >/dev/null
sips -z "$((s*2))" "$((s*2))" "$src" --out "$iconset/icon_${s}x${s}@2x.png" >/dev/null
done
iconutil -c icns "$iconset" -o "$dest"
rm -rf "$(dirname "$iconset")"
}
ICON_KEY=""
if [[ -n "$ICON" ]]; then
if [[ ! -f "$ICON" ]]; then
echo " (warning: --icon file not found: $ICON; skipping icon)" >&2
elif [[ "$ICON" == *.icns ]]; then
cp "$ICON" "$APP/Contents/Resources/AppIcon.icns"
ICON_KEY="<key>CFBundleIconFile</key><string>AppIcon</string>"
elif command -v sips >/dev/null 2>&1 && command -v iconutil >/dev/null 2>&1; then
echo "==> Converting $ICON to .icns…"
if make_icns_from_image "$ICON" "$APP/Contents/Resources/AppIcon.icns"; then
ICON_KEY="<key>CFBundleIconFile</key><string>AppIcon</string>"
else
echo " (warning: icon conversion failed; building without icon)" >&2
fi
else
echo " (warning: sips/iconutil not available; pass a .icns file instead)" >&2
fi
fi
cat > "$APP/Contents/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key><string>${NAME}</string>
<key>CFBundleDisplayName</key><string>${NAME}</string>
<key>CFBundleExecutable</key><string>MiniTerm</string>
<key>CFBundleIdentifier</key><string>${BUNDLE_ID}</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleVersion</key><string>1</string>
<key>CFBundleShortVersionString</key><string>1.0</string>
<key>LSMinimumSystemVersion</key><string>13.0</string>
<key>NSHighResolutionCapable</key><true/>
${ICON_KEY}
<key>MTCommand</key><string>${COMMAND}</string>
<key>MTWorkingDirectory</key><string>${WORKDIR}</string>
<key>MTWindowTitle</key><string>${NAME}</string>
<key>MTFontSize</key><integer>${FONT_SIZE}</integer>
<key>MTFontName</key><string>${FONT_NAME}</string>
</dict>
</plist>
PLIST
# ---- ad-hoc codesign (helps on Apple Silicon / Gatekeeper) ----------------
if command -v codesign >/dev/null 2>&1; then
codesign --force --deep --sign - "$APP" >/dev/null 2>&1 \
&& echo "==> Ad-hoc signed." \
|| echo " (codesign skipped/failed — app still runs)"
fi
echo "==> Done: $APP"
echo " Command : ${COMMAND:-<plain shell>}"
echo " Workdir : ${WORKDIR:-<home>}"
echo " Font : ${FONT_NAME} ${FONT_SIZE}pt"
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

-1
View File
@@ -1 +0,0 @@
,marcuh,mach12,12.06.2026 09:55,file:///home/marcuh/.config/libreoffice/4;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Gemeinsames adesso-Styling für die Workshop-Folien.
Teal->Blau-Verlauf, Fira Sans, weiße Shapes + Coral-Akzente,
Header (Pill + Titel) und schlanke Fußzeile (Seitenzahl + adesso)."""
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
from pptx.oxml.ns import qn
# ---- adesso Palette --------------------------------------------------------
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
INK = RGBColor(0x0E, 0x2A, 0x47) # tiefes Navy Text auf hellen Boxen / dunkle Füllungen
BLUE = RGBColor(0x00, 0x6E, 0xC7)
TEAL = RGBColor(0x28, 0xDC, 0xAA)
GREEN = RGBColor(0x76, 0xC8, 0x00)
CORAL = RGBColor(0xFF, 0x98, 0x68)
PINK = RGBColor(0xF5, 0x66, 0xBA)
TAUPE = RGBColor(0x88, 0x7D, 0x75)
LIGHT = RGBColor(0xD7, 0xE6, 0xF4) # helle, gedämpfte Schrift/Linien auf dem Verlauf
MUTE = RGBColor(0x6B, 0x77, 0x85) # mittleres Grau Text auf weißen Flächen
RECF = RGBColor(0xE6, 0xEF, 0xF8) # helle Kästchen in weißen Boxen
F = "Fira Sans"
FC = "Fira Sans Condensed"
def alpha(shape, opacity_pct):
"""Deckkraft (0-100) auf die Solid-Füllung setzen."""
srgb = shape._element.spPr.find(qn('a:solidFill')).find(qn('a:srgbClr'))
srgb.append(srgb.makeelement(qn('a:alpha'), {'val': str(int(opacity_pct * 1000))}))
def line_alpha(shape, opacity_pct):
ln = shape._element.spPr.find(qn('a:ln'))
srgb = ln.find(qn('a:solidFill')).find(qn('a:srgbClr'))
srgb.append(srgb.makeelement(qn('a:alpha'), {'val': str(int(opacity_pct * 1000))}))
def paint_background(s, width, height):
"""Teal->Blau-Verlauf als Folienhintergrund."""
bgr = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, width, height)
bgr.line.fill.background(); bgr.shadow.inherit = False
bgr.fill.gradient()
try:
bgr.fill.gradient_angle = 90.0
except Exception:
pass
st = bgr.fill.gradient_stops
st[0].position = 0.0; st[0].color.rgb = TEAL
st[1].position = 1.0; st[1].color.rgb = BLUE
return bgr
def chrome(s, title, page):
"""Header (Pill + Titel) + schlanke Fußzeile (Seitenzahl + adesso)."""
pill = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
Inches(0.55), Inches(0.46), Inches(0.5), Inches(0.26))
pill.fill.background(); pill.line.color.rgb = WHITE; pill.line.width = Pt(1.5)
pill.shadow.inherit = False
try: pill.adjustments[0] = 0.5
except Exception: pass
def _t(x, y, w, h, t, size, color, bold=False, align=PP_ALIGN.LEFT, font=FC):
tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame
tf.word_wrap = True; tf.margin_left = tf.margin_right = Pt(0)
tf.margin_top = tf.margin_bottom = Pt(0)
p = tf.paragraphs[0]; p.alignment = align
r = p.add_run(); r.text = t
r.font.size = Pt(size); r.font.bold = bold; r.font.name = font; r.font.color.rgb = color
_t(Inches(1.25), Inches(0.34), Inches(11), Inches(0.7), title, 30, WHITE, bold=True)
_t(Inches(0.55), Inches(7.06), Inches(1.0), Inches(0.3), str(page), 11, LIGHT)
_t(Inches(10.95), Inches(6.99), Inches(1.85), Inches(0.4), "adesso", 17, WHITE,
bold=True, align=PP_ALIGN.RIGHT)
+2 -1
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Workshop-Folien 'Vault Technischer Impuls': ruhig, visuell, wenig Text.""" """Workshop-Folien 'Vault Technischer Impuls': ruhig, visuell, wenig Text."""
import os
from pptx import Presentation from pptx import Presentation
from pptx.util import Inches, Pt from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor from pptx.dml.color import RGBColor
@@ -240,6 +241,6 @@ box(s, last_x + Inches(0.75), ey - Inches(0.05), Inches(2.7), Inches(0.95),
caption(s, "Der Zustand entsteht aus den Ereignissen Mehrfaches inklusive.") caption(s, "Der Zustand entsteht aus den Ereignissen Mehrfaches inklusive.")
out = "/home/marcuh/claude-projects/limbach/Vault_Impuls_Workshop.pptx" out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Vault_Impuls_Workshop.pptx")
prs.save(out) prs.save(out)
print("saved", out) print("saved", out)
+95 -82
View File
@@ -1,31 +1,21 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Folie 2 Grenzen der Vault-Integration. Kette: Auftrag -> altes LIS -> """Folie 2 Grenzen der Vault-Integration (adesso-Styling).
Schattenbuchhaltung/Metadaten -> Vault (am Ende). Der Layer wird zum SPOT.""" Kette: Auftrag -> Auftragsannahme -> Schattenbuchhaltung (SPOT) -> Vault ->
Zusammenführung."""
import os
from pptx import Presentation from pptx import Presentation
from pptx.util import Inches, Pt from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE from pptx.enum.shapes import MSO_SHAPE
from adesso_style import (WHITE, INK, CORAL, LIGHT, RECF, F, FC,
INK = RGBColor(0x2A, 0x2F, 0x3A) alpha, line_alpha, paint_background, chrome)
NAVY = RGBColor(0x1F, 0x3A, 0x5F)
ACCENT = RGBColor(0xC0, 0x49, 0x2F)
MUTE = RGBColor(0x8A, 0x93, 0xA0)
LINEC = RGBColor(0xC3, 0xC9, 0xD1)
FRAME = RGBColor(0xF4, 0xF6, 0xF8)
TINT_R = RGBColor(0xF6, 0xE5, 0xDF)
BG = RGBColor(0xFF, 0xFF, 0xFF)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
F = "Calibri"
prs = Presentation() prs = Presentation()
prs.slide_width = Inches(13.333) prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5) prs.slide_height = Inches(7.5)
s = prs.slides.add_slide(prs.slide_layouts[6]) s = prs.slides.add_slide(prs.slide_layouts[6])
bgr = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height)
bgr.fill.solid(); bgr.fill.fore_color.rgb = BG; bgr.line.fill.background()
bgr.shadow.inherit = False
def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0): def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0):
@@ -39,62 +29,62 @@ def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0):
return sh return sh
def boxtext(sh, t, size=14, color=INK, bold=False): def boxtext(sh, t, size=14, color=INK, bold=False, font=F):
tf = sh.text_frame; tf.word_wrap = True tf = sh.text_frame; tf.word_wrap = True
tf.vertical_anchor = MSO_ANCHOR.MIDDLE tf.vertical_anchor = MSO_ANCHOR.MIDDLE
tf.margin_left = tf.margin_right = Pt(4); tf.margin_top = tf.margin_bottom = Pt(1) tf.margin_left = tf.margin_right = Pt(4); tf.margin_top = tf.margin_bottom = Pt(1)
p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER
r = p.add_run(); r.text = t r = p.add_run(); r.text = t
r.font.size = Pt(size); r.font.bold = bold; r.font.name = F; r.font.color.rgb = color r.font.size = Pt(size); r.font.bold = bold; r.font.name = font; r.font.color.rgb = color
def txt(x, y, w, h, t, size=14, color=INK, bold=False, italic=False, def txt(x, y, w, h, t, size=14, color=INK, bold=False, italic=False,
align=PP_ALIGN.LEFT, spc=None, lead=1.1, anchor=MSO_ANCHOR.TOP): align=PP_ALIGN.LEFT, spc=None, lead=1.1, anchor=MSO_ANCHOR.TOP, font=F):
tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame
tf.word_wrap = True; tf.vertical_anchor = anchor tf.word_wrap = True; tf.vertical_anchor = anchor
tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0)
p = tf.paragraphs[0]; p.alignment = align; p.line_spacing = lead p = tf.paragraphs[0]; p.alignment = align; p.line_spacing = lead
r = p.add_run(); r.text = t r = p.add_run(); r.text = t
r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic
r.font.name = F; r.font.color.rgb = color r.font.name = font; r.font.color.rgb = color
if spc: r.font._rPr.set('spc', str(spc)) if spc: r.font._rPr.set('spc', str(spc))
return tb return tb
# ---- Kopf ------------------------------------------------------------------ paint_background(s, prs.slide_width, prs.slide_height)
txt(Inches(0.95), Inches(0.32), Inches(8), Inches(0.35), "INTEGRATION", chrome(s, "Grenzen der Vault-Integration?", 2)
size=12.5, color=ACCENT, bold=True, spc=240)
txt(Inches(0.95), Inches(0.62), Inches(11.5), Inches(0.85), "Grenzen der Vault-Integration?",
size=31, color=NAVY, bold=True)
shp(MSO_SHAPE.RECTANGLE, Inches(0.95), Inches(1.34), Inches(0.6), Pt(4), fill=ACCENT)
# ---- Linke Spalte ---------------------------------------------------------- # ---- Linke Spalte ----------------------------------------------------------
def claim(y, t, bold=False, sub=None): def claim(y, t, bold=False, sub=None):
shp(MSO_SHAPE.RECTANGLE, Inches(0.95), y + Inches(0.08), Inches(0.14), Inches(0.14), fill=ACCENT) shp(MSO_SHAPE.RECTANGLE, Inches(0.95), y + Inches(0.08), Inches(0.14), Inches(0.14), fill=CORAL)
txt(Inches(1.28), y, Inches(4.5), Inches(0.4), t, size=16.5, color=INK, bold=bold) txt(Inches(1.28), y, Inches(4.5), Inches(0.4), t, size=16.5, color=WHITE, bold=bold)
if sub: if sub:
txt(Inches(1.28), y + Inches(0.34), Inches(4.5), Inches(0.32), sub, txt(Inches(1.28), y + Inches(0.34), Inches(4.5), Inches(0.32), sub,
size=12.5, color=MUTE, italic=True) size=12.5, color=LIGHT, italic=True)
claim(Inches(2.2), "Middleware kapselt den Rand den Kern nicht", bold=True) claim(Inches(2.2), "Middleware kapselt den Rand den Kern nicht", bold=True)
claim(Inches(2.95), "1:n geht nur als Schattenmodell", claim(Inches(2.95), "Mischaufträge nur als Schattenmodell",
sub="aufwändig · fragil · fehleranfällig") sub="aufwändig · fragil · fehleranfällig")
claim(Inches(3.95), "die Fachwahrheit verlässt den Kern") claim(Inches(3.95), "die Fachwahrheit verlässt den Kern")
shp(MSO_SHAPE.RECTANGLE, Inches(0.95), Inches(4.95), Inches(0.5), Pt(3.5), fill=ACCENT) shp(MSO_SHAPE.RECTANGLE, Inches(0.95), Inches(4.95), Inches(0.5), Pt(3.5), fill=CORAL)
txt(Inches(0.95), Inches(5.12), Inches(4.85), Inches(1.1), txt(Inches(0.95), Inches(5.12), Inches(4.85), Inches(1.1),
"Die Schattenbuchhaltung wird zum Single Point of Truth nicht Vault.", "Die Schattenbuchhaltung wird zum Single Point of Truth nicht Vault.",
size=19, color=NAVY, bold=True, lead=1.08) size=19, color=WHITE, bold=True, lead=1.08, font=FC)
txt(Inches(0.95), Inches(6.45), Inches(4.85), Inches(0.4), txt(Inches(0.95), Inches(6.45), Inches(4.85), Inches(0.4),
"Genau die Rolle, die Vault halten müsste.", size=13.5, color=MUTE, italic=True) "Genau die Rolle, die Vault halten müsste.", size=13.5, color=LIGHT, italic=True)
# =========================================================================== # ===========================================================================
# RECHTS: Kette Auftrag -> altes LIS -> Layer (SPOT) -> Vault (Ende) # RECHTS: Kette Auftrag -> Auftragsannahme -> SPOT -> Vault -> Zusammenführung
# =========================================================================== # ===========================================================================
fx, fy, fw, fh = Inches(6.25), Inches(2.4), Inches(5.9), Inches(4.55) fx, fy, fw, fh = Inches(6.25), Inches(2.1), Inches(5.9), Inches(4.72)
shp(MSO_SHAPE.ROUNDED_RECTANGLE, fx, fy, fw, fh, fill=FRAME, line=LINEC, lw=Pt(1.5)) panel = shp(MSO_SHAPE.ROUNDED_RECTANGLE, fx, fy, fw, fh, fill=WHITE)
txt(fx + Inches(0.3), fy + Inches(0.14), fw - Inches(0.6), Inches(0.3), alpha(panel, 10)
"Auftragsweg hin und zurück durch den Layer", size=12.5, color=NAVY, bold=True) panel.line.color.rgb = WHITE; panel.line.width = Pt(1)
line_alpha(panel, 35)
txt(fx + Inches(0.3), fy + Inches(0.12), fw - Inches(0.6), Inches(0.3),
"Auftragsweg hin und zurück durch den Layer", size=12.5, color=WHITE, bold=True)
cxc = fx + Inches(2.05) cxc = fx + Inches(2.05)
bw = Inches(2.7) bw = Inches(2.7)
@@ -102,60 +92,83 @@ bx = cxc - bw / 2
def down(y, label=None): def down(y, label=None):
shp(MSO_SHAPE.DOWN_ARROW, cxc - Inches(0.11), y, Inches(0.22), Inches(0.26), fill=MUTE) shp(MSO_SHAPE.DOWN_ARROW, cxc - Inches(0.11), y, Inches(0.22), Inches(0.22), fill=LIGHT)
if label: if label:
txt(cxc + Inches(0.22), y - Inches(0.02), Inches(1.6), Inches(0.3), label, txt(cxc + Inches(0.22), y - Inches(0.02), Inches(1.7), Inches(0.3), label,
size=11, color=MUTE, italic=True, anchor=MSO_ANCHOR.MIDDLE) size=11, color=LIGHT, italic=True, anchor=MSO_ANCHOR.MIDDLE)
# 1) Auftrag # 1) Auftrag
b = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, fy + Inches(0.52), bw, Inches(0.48), fill=BG, line=LINEC) b = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, fy + Inches(0.44), bw, Inches(0.40), fill=WHITE)
boxtext(b, "Auftrag · Mischauftrag (1:n)", size=12.5, color=INK, bold=True) boxtext(b, "Auftrag · Mischauftrag", size=12.5, color=INK, bold=True)
down(fy + Inches(1.02)) down(fy + Inches(0.86))
# 2) Altes LIS # 2) Auftragsannahme (ersetzt altes LIS)
b = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, fy + Inches(1.3), bw, Inches(0.5), fill=NAVY) b = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, fy + Inches(1.06), bw, Inches(0.40), fill=WHITE)
boxtext(b, "Altes LIS · nimmt 1:n an", size=13, color=WHITE, bold=True) boxtext(b, "Auftragsannahme", size=13, color=INK, bold=True)
down(fy + Inches(1.82), "Magic") down(fy + Inches(1.48), "Magic")
# 3) Schattenbuchhaltung = SPOT # 3) Schattenbuchhaltung = Rahmen um den Vault-Kasten
b = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx - Inches(0.15), fy + Inches(2.1), bw + Inches(0.3), frx, fry = bx - Inches(0.22), fy + Inches(1.72)
Inches(0.78), fill=TINT_R, line=ACCENT, lw=Pt(2)) frw, frh = bw + Inches(0.50), Inches(2.22)
txt(bx - Inches(0.15), fy + Inches(2.18), bw + Inches(0.3), Inches(0.32), frame = shp(MSO_SHAPE.ROUNDED_RECTANGLE, frx, fry, frw, frh,
"Schattenbuchhaltung / Metadaten", size=13, color=ACCENT, bold=True, align=PP_ALIGN.CENTER) fill=RGBColor(0xFF, 0xE3, 0x9B))
txt(bx - Inches(0.15), fy + Inches(2.52), bw + Inches(0.3), Inches(0.3), "Mapping 1 ↔ 3", alpha(frame, 22)
size=12, color=INK, align=PP_ALIGN.CENTER) frame.line.color.rgb = CORAL; frame.line.width = Pt(2)
# aktiver SPOT-Stern
shp(MSO_SHAPE.STAR_5_POINT, fx + fw - Inches(0.78), fy + Inches(2.18), Inches(0.5), Inches(0.5),
fill=ACCENT)
txt(fx + fw - Inches(2.55), fy + Inches(2.7), Inches(1.75), Inches(0.28), "Single Point of Truth",
size=10.5, color=ACCENT, bold=True, align=PP_ALIGN.RIGHT)
down(fy + Inches(2.9), "1 → 3, Vault-kompatibel")
# 4) Vault am Ende # Überschrift + Positions-Mapping (oben im Rahmen)
b = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, fy + Inches(3.18), bw, Inches(0.82), fill=NAVY) txt(frx, fy + Inches(1.78), frw, Inches(0.28), "Schattenbuchhaltung",
txt(bx, fy + Inches(3.24), bw, Inches(0.3), "Vault · 1:1 · Speicher", size=13, color=CORAL, bold=True, align=PP_ALIGN.CENTER)
size=13, color=WHITE, bold=True, align=PP_ALIGN.CENTER) txt(frx, fy + Inches(2.07), frw, Inches(0.24), "Positions-Mapping",
size=11.5, color=WHITE, align=PP_ALIGN.CENTER)
# aktiver SPOT-Stern (rechts neben dem Rahmen)
shp(MSO_SHAPE.STAR_5_POINT, Inches(10.75), fy + Inches(1.80), Inches(0.5), Inches(0.5), fill=CORAL)
txt(Inches(9.95), fy + Inches(2.32), Inches(2.1), Inches(0.28), "Single Point of Truth",
size=10.5, color=CORAL, bold=True, align=PP_ALIGN.CENTER)
down(fy + Inches(2.33))
# 4) Vault speichert (aufgesplittet) im Rahmen
b = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, fy + Inches(2.50), bw, Inches(0.70), fill=WHITE)
txt(bx, fy + Inches(2.55), bw, Inches(0.3), "Vault · 1:1 · Speicher",
size=13, color=INK, bold=True, align=PP_ALIGN.CENTER)
for k in range(3): for k in range(3):
shp(MSO_SHAPE.ROUNDED_RECTANGLE, cxc - Inches(1.08) + k * Inches(0.78), shp(MSO_SHAPE.ROUNDED_RECTANGLE, cxc - Inches(1.08) + k * Inches(0.78),
fy + Inches(3.58), Inches(0.62), Inches(0.3), fill=RGBColor(0x34, 0x4E, 0x70)) fy + Inches(2.87), Inches(0.62), Inches(0.3), fill=RECF)
txt(cxc - Inches(1.08) + k * Inches(0.78), fy + Inches(3.58), Inches(0.62), Inches(0.3), txt(cxc - Inches(1.08) + k * Inches(0.78), fy + Inches(2.87), Inches(0.62), Inches(0.3),
"Rec", size=9.5, color=WHITE, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE) "Rec", size=9.5, color=INK, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# Geist-Stern (durchgestrichen) bei Vault # Geist-Stern (durchgestrichen) die Wahrheit müsste hier liegen
gx = fx + fw - Inches(0.74) gx = Inches(10.79)
shp(MSO_SHAPE.STAR_5_POINT, gx, fy + Inches(3.32), Inches(0.44), Inches(0.44), gs = shp(MSO_SHAPE.STAR_5_POINT, gx, fy + Inches(2.55), Inches(0.42), Inches(0.42))
fill=BG, line=LINEC, lw=Pt(1.25)) gs.fill.background(); gs.line.color.rgb = LIGHT; gs.line.width = Pt(1.25)
shp(MSO_SHAPE.RECTANGLE, gx - Inches(0.02), fy + Inches(3.52), Inches(0.48), Pt(2), shp(MSO_SHAPE.RECTANGLE, gx - Inches(0.02), fy + Inches(2.73), Inches(0.46), Pt(2),
fill=ACCENT, rot=20) fill=CORAL, rot=20)
txt(fx + fw - Inches(2.45), fy + Inches(3.3), Inches(1.65), Inches(0.4), txt(Inches(9.95), fy + Inches(3.02), Inches(2.1), Inches(0.3), "müsste hier liegen",
"müsste hier liegen", size=10, color=MUTE, align=PP_ALIGN.RIGHT) size=10, color=LIGHT, align=PP_ALIGN.CENTER)
down(fy + Inches(3.22))
# Rückweg (Lesen) Pfeil links nach oben # 5) Daten zusammenführen (unten im Rahmen)
shp(MSO_SHAPE.UP_ARROW, fx + Inches(0.34), fy + Inches(1.45), Inches(0.26), Inches(2.45), txt(frx, fy + Inches(3.42), frw, Inches(0.28), "Daten Zusammenführen",
fill=RGBColor(0xD8, 0xCB, 0xC6)) size=12.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
txt(fx + Inches(0.1), fy + Inches(3.95), Inches(1.55), Inches(0.5),
"Lesen: zurück, wieder LIS-kompatibel", size=9.5, color=ACCENT, align=PP_ALIGN.LEFT, lead=1.0)
out = "/home/marcuh/claude-projects/limbach/Folie2.pptx" # 6) Weiterverarbeitung grauer Pfeil + weißer Kasten unter dem Rahmen
shp(MSO_SHAPE.DOWN_ARROW, cxc - Inches(0.11), fy + Inches(4.00), Inches(0.22), Inches(0.22),
fill=RGBColor(0x8C, 0x8C, 0x8C))
b = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bx, fy + Inches(4.26), bw, Inches(0.40), fill=WHITE)
boxtext(b, "Weiterverarbeitung", size=12.5, color=INK, bold=True)
# Rückweg (Lesen) Pfeil links nach oben, Label vertikal daneben
ua = shp(MSO_SHAPE.UP_ARROW, fx + Inches(0.08), fy + Inches(1.55), Inches(0.26), Inches(2.30),
fill=LIGHT)
alpha(ua, 55)
lt = s.shapes.add_textbox(fx - Inches(0.95), fy + Inches(2.40), Inches(2.2), Inches(0.3))
ltf = lt.text_frame; ltf.word_wrap = False
ltf.margin_left = ltf.margin_right = ltf.margin_top = ltf.margin_bottom = Pt(0)
lp = ltf.paragraphs[0]; lp.alignment = PP_ALIGN.CENTER
lr = lp.add_run(); lr.text = "Lesen: zurück durch den Layer"
lr.font.size = Pt(9.5); lr.font.name = F; lr.font.color.rgb = CORAL
lt.rotation = 270
out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie2.pptx")
prs.save(out) prs.save(out)
print("saved", out) print("saved", out)
+62 -47
View File
@@ -2,35 +2,32 @@
"""Folie 3 LIS mit Umsystemen (Adapter, links/rechts) und andockenden Modulen """Folie 3 LIS mit Umsystemen (Adapter, links/rechts) und andockenden Modulen
(unten), unter einer Steuerungs-/Governance-Schicht; weitere LIS angedeutet.""" (unten), unter einer Steuerungs-/Governance-Schicht; weitere LIS angedeutet."""
import os
from pptx import Presentation from pptx import Presentation
from pptx.util import Inches, Pt from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE from pptx.enum.shapes import MSO_SHAPE
from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, MUTE, RECF,
F, FC, paint_background, chrome)
INK = RGBColor(0x2A, 0x2F, 0x3A) # Rollen-Mapping auf die adesso-Palette
NAVY = RGBColor(0x1F, 0x3A, 0x5F) NAVY = INK # dunkle Strukturelemente (Bar, LIS-Kern)
ACCENT = RGBColor(0xC0, 0x49, 0x2F) ACCENT = CORAL # Adapter-Linien
TEAL = RGBColor(0x2F, 0x8F, 0x83) AMBER = GREEN # zweites Modul
AMBER = RGBColor(0xA9, 0x73, 0x22) LINEC = LIGHT # Verbinder / feine Linien
MUTE = RGBColor(0x8A, 0x93, 0xA0) HINT = RGBColor(0xE6, 0xEF, 0xF8) # helle "weitere LIS"-Karten
LINEC = RGBColor(0xC3, 0xC9, 0xD1) HINTL = RGBColor(0xC4, 0xD6, 0xE8)
HINT = RGBColor(0xEE, 0xF1, 0xF4) TINT_T = WHITE # Modul-Boxen weiß
HINTL = RGBColor(0xCB, 0xD2, 0xDA) TINT_A = WHITE
TINT_T = RGBColor(0xDD, 0xEC, 0xEA) UMS = WHITE # Umsystem-Boxen weiß
TINT_A = RGBColor(0xF3, 0xE8, 0xD2) BG = WHITE
UMS = RGBColor(0xEC, 0xEE, 0xF1)
BG = RGBColor(0xFF, 0xFF, 0xFF)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
F = "Calibri"
prs = Presentation() prs = Presentation()
prs.slide_width = Inches(13.333) prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5) prs.slide_height = Inches(7.5)
s = prs.slides.add_slide(prs.slide_layouts[6]) s = prs.slides.add_slide(prs.slide_layouts[6])
bgr = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height) paint_background(s, prs.slide_width, prs.slide_height)
bgr.fill.solid(); bgr.fill.fore_color.rgb = BG; bgr.line.fill.background()
bgr.shadow.inherit = False
def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0): def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0):
@@ -75,11 +72,7 @@ def vline(x, y, h, color=LINEC, weight=Pt(1.4)):
# ---- Kopf ------------------------------------------------------------------ # ---- Kopf ------------------------------------------------------------------
txt(Inches(0.95), Inches(0.32), Inches(8), Inches(0.35), "ARCHITEKTUR", chrome(s, "Offen und gesteuert", 3)
size=12.5, color=ACCENT, bold=True, spc=240)
txt(Inches(0.95), Inches(0.62), Inches(11), Inches(0.85), "Offen und gesteuert",
size=31, color=NAVY, bold=True)
shp(MSO_SHAPE.RECTANGLE, Inches(0.95), Inches(1.34), Inches(0.6), Pt(4), fill=ACCENT)
x0, x1 = Inches(0.95), Inches(12.38) x0, x1 = Inches(0.95), Inches(12.38)
@@ -144,6 +137,14 @@ core_y, core_h = Inches(4.3), Inches(1.2)
core_cx = core_x + core_w / 2 core_cx = core_x + core_w / 2
core_by = core_y + core_h core_by = core_y + core_h
# Adapter-Schicht: echte Schicht als Hülle um den Kern (Trennung)
lay_pad_x, lay_pad_t = Inches(0.24), Inches(0.30)
lay_x = core_x - lay_pad_x
lay_w = core_w + lay_pad_x * 2
lay_y = core_y - lay_pad_t
lay_h = core_h + lay_pad_t
lay_right = lay_x + lay_w
def adapter(xc, yc): def adapter(xc, yc):
shp(MSO_SHAPE.ROUNDED_RECTANGLE, xc - Inches(0.10), yc - Inches(0.11), shp(MSO_SHAPE.ROUNDED_RECTANGLE, xc - Inches(0.10), yc - Inches(0.11),
@@ -185,8 +186,12 @@ def icon_dfu(ix, iy): # DFÜ
fill=ACCENT) fill=ACCENT)
# Governance -> LIS g = Inches(0.10) # Abstand Adapter-Linie <-> LIS
vline(core_cx, gov_y + Inches(0.07), core_y - (gov_y + Inches(0.07)), color=LINEC, weight=Pt(1)) th = Inches(0.08) # Dicke der roten Adapter-Linie
# Governance -> obere Adapter-Linie am Kern
vline(core_cx, gov_y + Inches(0.07), (core_y - g - th) - (gov_y + Inches(0.07)),
color=LINEC, weight=Pt(1))
uw, uh = Inches(1.55), Inches(0.6) uw, uh = Inches(1.55), Inches(0.6)
@@ -198,37 +203,50 @@ def ums_box(bx, yc, name, icon_fn):
size=12, color=INK, align=PP_ALIGN.LEFT) size=12, color=INK, align=PP_ALIGN.LEFT)
# Umsysteme links # Umsysteme links -> Adapter-Schichten am linken Kernrand
lx = Inches(0.8) lx = Inches(0.8)
ums_box(lx, Inches(4.6), "Anlieferung", icon_truck) ums_box(lx, Inches(4.6), "Anlieferung", icon_truck)
ums_box(lx, Inches(5.2), "DFÜ", icon_dfu) ums_box(lx, Inches(5.2), "DFÜ", icon_dfu)
for yc in (Inches(4.6), Inches(5.2)): for yc in (Inches(4.6), Inches(5.2)):
hline(lx + uw, yc, core_x - (lx + uw)) hline(lx + uw, yc, (core_x - g - th) - (lx + uw))
adapter(core_x - Inches(0.16), yc)
# Umsystem rechts # Umsystem rechts -> Adapter-Linie am rechten Kernrand
gx = Inches(6.65) gx = Inches(6.65)
ums_box(gx, Inches(4.9), "Geräte", icon_device) ums_box(gx, Inches(4.9), "Geräte", icon_device)
hline(core_x + core_w, Inches(4.9), gx - (core_x + core_w)) hline(core_x + core_w + g + th, Inches(4.9), gx - (core_x + core_w + g + th))
adapter(core_x + core_w + Inches(0.16), Inches(4.9))
# LIS (kompakt, solide) # ---- LIS-Kern -------------------------------------------------------------
shp(MSO_SHAPE.ROUNDED_RECTANGLE, core_x, core_y, core_w, core_h, fill=NAVY) shp(MSO_SHAPE.ROUNDED_RECTANGLE, core_x, core_y, core_w, core_h, fill=NAVY)
sh = shp(MSO_SHAPE.ROUNDED_RECTANGLE, core_x, core_y, core_w, core_h, fill=NAVY) sh = shp(MSO_SHAPE.ROUNDED_RECTANGLE, core_x, core_y, core_w, core_h, fill=NAVY)
boxtext(sh, "LIS", size=22, color=WHITE, bold=True) boxtext(sh, "LIS", size=22, color=WHITE, bold=True)
# Module unten (andockend)
# ---- 5 Adapter-Linien je Schnittstelle eine, mit Abstand zum Kern -------
def rbar_v(outer_x, yc, h=Inches(0.55)): # vertikal, linker/rechter Rand
shp(MSO_SHAPE.RECTANGLE, outer_x, yc - h / 2, th, h, fill=ACCENT)
def rbar_h(xc, outer_y, w): # horizontal, oben/unten
shp(MSO_SHAPE.RECTANGLE, xc - w / 2, outer_y, w, th, fill=ACCENT)
rbar_v(core_x - g - th, Inches(4.6)) # 1) Anlieferung
rbar_v(core_x - g - th, Inches(5.2)) # 2) DFÜ
rbar_h(core_cx, core_y - g - th, Inches(1.15)) # 3) nach oben (Steuerung)
rbar_v(core_x + core_w + g, Inches(4.9)) # 4) Geräte
rbar_h(core_cx, core_by + g, core_w - Inches(0.2)) # 5) Fachliche Verarbeitung
# Fachliche Verarbeitung (unten): Module an der unteren Adapter-Linie
mw, mh = Inches(1.7), Inches(0.6) mw, mh = Inches(1.7), Inches(0.6)
my = Inches(6.05) my = Inches(6.05)
for nm, fill, tc, mcx in [("Hochdurchsatz", TINT_T, TEAL, Inches(3.85)), for nm, fill, tc, mcx in [("Hochdurchsatz", TINT_T, TEAL, Inches(3.85)),
("Spezial", TINT_A, AMBER, Inches(5.85))]: ("Spezial", TINT_A, AMBER, Inches(5.85))]:
vline(mcx, core_by, my - core_by, color=LINEC, weight=Pt(1)) vline(mcx, core_by + g + th, my - (core_by + g + th), color=LINEC, weight=Pt(1))
dock(mcx, core_by + (my - core_by) / 2) sh = shp(MSO_SHAPE.ROUNDED_RECTANGLE, mcx - mw / 2, my, mw, mh, fill=fill, line=tc, lw=Pt(1.5))
sh = shp(MSO_SHAPE.ROUNDED_RECTANGLE, mcx - mw / 2, my, mw, mh, fill=fill, line=tc, lw=Pt(1.25)) boxtext(sh, nm, size=12.5, color=INK, bold=True)
boxtext(sh, nm, size=12.5, color=tc, bold=True)
txt(Inches(2.9), my + mh + Inches(0.12), Inches(4.0), Inches(0.3), "eigene Module", txt(Inches(2.9), my + mh + Inches(0.12), Inches(4.0), Inches(0.3), "Fachliche Verarbeitung",
size=12, color=MUTE, bold=True, align=PP_ALIGN.CENTER) size=12, color=LIGHT, bold=True, align=PP_ALIGN.CENTER)
# =========================================================================== # ===========================================================================
@@ -252,17 +270,14 @@ mini_lis(Inches(10.0), Inches(4.75), faded=False)
vline(Inches(10.0) + hw_ / 2, gov_y + Inches(0.07), Inches(4.75) - (gov_y + Inches(0.07)), vline(Inches(10.0) + hw_ / 2, gov_y + Inches(0.07), Inches(4.75) - (gov_y + Inches(0.07)),
color=LINEC, weight=Pt(1)) color=LINEC, weight=Pt(1))
txt(Inches(9.9), Inches(4.35) + hh_ + Inches(0.2), Inches(2.4), Inches(0.35), "weitere LIS", txt(Inches(9.9), Inches(4.35) + hh_ + Inches(0.2), Inches(2.4), Inches(0.35), "weitere LIS",
size=13, color=MUTE, align=PP_ALIGN.CENTER) size=13, color=LIGHT, align=PP_ALIGN.CENTER)
# ---- Mini-Legende ---------------------------------------------------------- # ---- Mini-Legende ----------------------------------------------------------
ly = Inches(7.05) ly = Inches(7.05)
adapter(Inches(7.35), ly) shp(MSO_SHAPE.RECTANGLE, Inches(7.55), ly - Inches(0.11), th, Inches(0.22), fill=ACCENT)
txt(Inches(7.55), ly - Inches(0.16), Inches(2.1), Inches(0.32), "Adapter (Umsysteme)", txt(Inches(7.8), ly - Inches(0.16), Inches(3.2), Inches(0.32),
size=11.5, color=MUTE) "Adapter-Schicht je Schnittstelle eine", size=11.5, color=LIGHT)
dock(Inches(9.65), ly)
txt(Inches(9.85), ly - Inches(0.16), Inches(2.1), Inches(0.32), "Andocken (Module)",
size=11.5, color=MUTE)
out = "/home/marcuh/claude-projects/limbach/Folie3.pptx" out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie3.pptx")
prs.save(out) prs.save(out)
print("saved", out) print("saved", out)
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""Folie 4 (eingehängt nach Folie 3) Architektur der Modularisierung:
Modularer Monolith vs. Microservices, anschaulich im Containerumfeld.
Links: 1 Container, Module mit In-Process-Calls, 1 geteilte DB.
Rechts: N Container, Services über Netzwerk, DB je Service.
Unten: kompakte Gegenüberstellung der wesentlichen Unterschiede."""
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
from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, MUTE, RECF,
F, FC, paint_background, chrome)
NAVY = INK
ACCENT = CORAL
LINEC = LIGHT
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
s = prs.slides.add_slide(prs.slide_layouts[6])
paint_background(s, prs.slide_width, prs.slide_height)
def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0):
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
if rot: sh.rotation = rot
sh.shadow.inherit = False
return sh
def boxtext(sh, t, size=14, color=INK, bold=False):
tf = sh.text_frame; tf.word_wrap = True
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
tf.margin_left = tf.margin_right = Pt(3); tf.margin_top = tf.margin_bottom = Pt(1)
p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER
r = p.add_run(); r.text = t
r.font.size = Pt(size); r.font.bold = bold; r.font.name = F; r.font.color.rgb = color
def txt(x, y, w, h, t, size=14, color=INK, bold=False, italic=False,
align=PP_ALIGN.LEFT, spc=None):
tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame
tf.word_wrap = True; tf.vertical_anchor = MSO_ANCHOR.MIDDLE
tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0)
p = tf.paragraphs[0]; p.alignment = align
r = p.add_run(); r.text = t
r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic
r.font.name = F; r.font.color.rgb = color
if spc: r.font._rPr.set('spc', str(spc))
return tb
def vline(x, y, h, color=LINEC, weight=Pt(1.4)):
shp(MSO_SHAPE.RECTANGLE, x - weight / 2, y, weight, h, fill=color)
def hline(x, y, w, color=LINEC, weight=Pt(1.4)):
shp(MSO_SHAPE.RECTANGLE, x, y - weight / 2, w, weight, fill=color)
# ---- Kopf + Kernaussage ----------------------------------------------------
chrome(s, "Architektur der Modularisierung", 4)
txt(Inches(1.25), Inches(0.98), Inches(11.6), Inches(0.34),
"Gleiche fachlichen Schnitte der Unterschied ist die Laufzeit- und Deployment-Grenze.",
size=14, color=LIGHT, italic=True)
DOT = [TEAL, GREEN, BLUE] # gleiche Module/Services beidseitig farblich verankert
LET = ["A", "B", "C"]
# ===========================================================================
# Spalten-Titel
# ===========================================================================
cL = Inches(3.6) # Mittelpunkt linke Spalte
cR = Inches(9.75) # Mittelpunkt rechte Spalte
txt(Inches(0.95), Inches(1.5), Inches(5.3), Inches(0.32), "Modularer Monolith",
size=18, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
txt(Inches(0.95), Inches(1.84), Inches(5.3), Inches(0.26), "1 Deployable · 1 Prozess",
size=11.5, color=LIGHT, align=PP_ALIGN.CENTER)
txt(Inches(7.0), Inches(1.5), Inches(5.5), Inches(0.32), "Microservices",
size=18, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
txt(Inches(7.0), Inches(1.84), Inches(5.5), Inches(0.26), "N Deployables · N Prozesse",
size=11.5, color=LIGHT, align=PP_ALIGN.CENTER)
# ===========================================================================
# LINKS Modularer Monolith: 1 Container, Module, geteilte DB
# ===========================================================================
cx_x, cx_w, cx_y, cx_h = Inches(0.95), Inches(5.3), Inches(2.2), Inches(1.6)
shp(MSO_SHAPE.ROUNDED_RECTANGLE, cx_x, cx_y, cx_w, cx_h, line=WHITE, lw=Pt(1.5))
txt(cx_x + Inches(0.15), cx_y + Inches(0.05), Inches(2.0), Inches(0.26), "1 Container",
size=11, color=LIGHT, bold=True)
mw, mh, my = Inches(1.35), Inches(0.8), Inches(2.62)
mcx = [Inches(2.05), Inches(3.6), Inches(5.15)]
for i, mx in enumerate(mcx):
sh = shp(MSO_SHAPE.ROUNDED_RECTANGLE, mx - mw / 2, my, mw, mh, fill=WHITE, line=LINEC, lw=Pt(1))
boxtext(sh, "Modul " + LET[i], size=13, color=INK, bold=True)
shp(MSO_SHAPE.OVAL, mx - Inches(0.07), my + Inches(0.12), Inches(0.14), Inches(0.14), fill=DOT[i])
# In-Process-Pfeile zwischen den Modulen
for ax in (Inches(2.725), Inches(4.275)):
shp(MSO_SHAPE.RIGHT_ARROW, ax, my + mh / 2 - Inches(0.09), Inches(0.2), Inches(0.18), fill=ACCENT)
txt(cx_x, cx_y + cx_h - Inches(0.34), cx_w, Inches(0.26), "In-Process-Aufrufe · Nanosekunden",
size=11, color=LIGHT, align=PP_ALIGN.CENTER)
# geteilte Datenbank
db_w, db_h, db_y = Inches(1.5), Inches(0.95), Inches(4.0)
vline(cL, cx_y + cx_h, db_y - (cx_y + cx_h), color=LINEC, weight=Pt(1.4))
sh = shp(MSO_SHAPE.CAN, cL - db_w / 2, db_y, db_w, db_h, fill=WHITE, line=LINEC, lw=Pt(1))
boxtext(sh, "DB", size=14, color=INK, bold=True)
txt(cx_x, db_y + db_h + Inches(0.06), cx_w, Inches(0.28), "1 geteilte Datenbank",
size=12.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
# ===========================================================================
# RECHTS Microservices: Netzwerk-Bus, N Container, DB je Service
# ===========================================================================
bar_x, bar_w, bar_y, bar_h = Inches(7.1), Inches(5.3), Inches(2.2), Inches(0.26)
bar = shp(MSO_SHAPE.ROUNDED_RECTANGLE, bar_x, bar_y, bar_w, bar_h, fill=NAVY)
boxtext(bar, "Netzwerk-Grenze · HTTP / gRPC / Events", size=11, color=WHITE, bold=True)
scx = [Inches(7.95), Inches(9.75), Inches(11.55)]
sc_w, sc_h, sc_y = Inches(1.5), Inches(1.2), Inches(2.62)
sdb_w, sdb_h, sdb_y = Inches(0.85), Inches(0.7), Inches(3.97)
for i, cx in enumerate(scx):
vline(cx, bar_y + bar_h, sc_y - (bar_y + bar_h), color=LINEC, weight=Pt(1.1))
shp(MSO_SHAPE.ROUNDED_RECTANGLE, cx - sc_w / 2, sc_y, sc_w, sc_h, line=WHITE, lw=Pt(1.5))
sh = shp(MSO_SHAPE.ROUNDED_RECTANGLE, cx - Inches(0.55), sc_y + Inches(0.3),
Inches(1.1), Inches(0.62), fill=WHITE, line=LINEC, lw=Pt(1))
boxtext(sh, "Svc " + LET[i], size=12.5, color=INK, bold=True)
shp(MSO_SHAPE.OVAL, cx - Inches(0.06), sc_y + Inches(0.4), Inches(0.12), Inches(0.12), fill=DOT[i])
# eigene DB je Service
vline(cx, sc_y + sc_h, sdb_y - (sc_y + sc_h), color=LINEC, weight=Pt(1.1))
sh = shp(MSO_SHAPE.CAN, cx - sdb_w / 2, sdb_y, sdb_w, sdb_h, fill=WHITE, line=LINEC, lw=Pt(1))
boxtext(sh, "DB", size=11, color=INK, bold=True)
txt(bar_x, sdb_y + sdb_h + Inches(0.06), bar_w, Inches(0.28), "Datenbank je Service",
size=12.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
# ===========================================================================
# Trenner + vs.
# ===========================================================================
vline(Inches(6.65), Inches(1.55), Inches(3.5), color=LINEC, weight=Pt(1))
vs = shp(MSO_SHAPE.OVAL, Inches(6.65) - Inches(0.31), Inches(3.05), Inches(0.62), Inches(0.46),
line=WHITE, lw=Pt(1.5))
vs.fill.background()
boxtext(vs, "vs.", size=13, color=WHITE, bold=True)
# ===========================================================================
# Gegenüberstellung wesentliche Unterschiede
# ===========================================================================
rows = [
("Skalierung", "als Ganzes (mehr Replicas)", "je Service gezielt"),
("Datenhaltung", "1 geteilte DB", "DB je Service"),
("Deployment", "gemeinsam, 1 Pipeline", "unabhängig je Service"),
("Konsistenz", "ACID, lokal", "eventual / Saga"),
]
ry = Inches(5.5)
rh = Inches(0.33)
chip_w = Inches(1.95)
for i, (dim, mono, ms) in enumerate(rows):
y = ry + rh * i
chip = shp(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(6.65) - chip_w / 2, y, chip_w, Inches(0.28),
line=LINEC, lw=Pt(0.75))
chip.fill.background()
boxtext(chip, dim, size=11, color=LIGHT, bold=True)
txt(Inches(2.5), y - Inches(0.02), Inches(3.1), Inches(0.32), mono,
size=12, color=WHITE, align=PP_ALIGN.RIGHT)
txt(Inches(7.72), y - Inches(0.02), Inches(3.1), Inches(0.32), ms,
size=12, color=WHITE, align=PP_ALIGN.LEFT)
out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie3b.pptx")
prs.save(out)
print("saved", out)
+33 -42
View File
@@ -1,36 +1,33 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Folie 4 Eventstream als Quelle; Aussagen links, Lupen rechts, Strecke unten.""" """Folie 4 Eventstream als Quelle; Aussagen links, Lupen rechts, Strecke unten
(adesso-Styling)."""
import os
from pptx import Presentation from pptx import Presentation
from pptx.util import Inches, Pt from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE from pptx.enum.shapes import MSO_SHAPE
from adesso_style import (WHITE, INK, BLUE, TEAL, GREEN, CORAL, LIGHT, RECF, F, FC,
paint_background, chrome)
INK = RGBColor(0x2A, 0x2F, 0x3A) # Rollen-Mapping auf die adesso-Palette
NAVY = RGBColor(0x1F, 0x3A, 0x5F) NAVY = INK # dunkle Füllungen (Lupe, Maschine)
ACCENT = RGBColor(0xC0, 0x49, 0x2F) ACCENT = CORAL
TEAL = RGBColor(0x2F, 0x8F, 0x83) AMBER = GREEN
AMBER = RGBColor(0xC8, 0x8A, 0x3A) GLASS = RECF
MUTE = RGBColor(0x8A, 0x93, 0xA0) BELT = RECF
GLASS = RGBColor(0xF2, 0xF5, 0xF8) DARK = INK
BELT = RGBColor(0xD7, 0xDC, 0xE2) LINEC = LIGHT
DARK = RGBColor(0x33, 0x39, 0x44) THIN = LIGHT
LINEC = RGBColor(0xC3, 0xC9, 0xD1) STREAM = INK
THIN = RGBColor(0xDD, 0xE2, 0xE8) MID = RGBColor(0x34, 0x4E, 0x70)
STREAM = RGBColor(0x24, 0x37, 0x52)
BG = RGBColor(0xFF, 0xFF, 0xFF)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
F = "Calibri"
prs = Presentation() prs = Presentation()
prs.slide_width = Inches(13.333) prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5) prs.slide_height = Inches(7.5)
SW, SH = prs.slide_width, prs.slide_height SW, SH = prs.slide_width, prs.slide_height
s = prs.slides.add_slide(prs.slide_layouts[6]) s = prs.slides.add_slide(prs.slide_layouts[6])
bgr = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SW, SH)
bgr.fill.solid(); bgr.fill.fore_color.rgb = BG; bgr.line.fill.background()
bgr.shadow.inherit = False
def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0): def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0):
@@ -44,25 +41,21 @@ def shp(kind, x, y, w, h, fill=None, line=None, lw=Pt(1), rot=0):
return sh return sh
def txt(x, y, w, h, t, size=16, color=INK, bold=False, italic=False, def txt(x, y, w, h, t, size=16, color=WHITE, bold=False, italic=False,
align=PP_ALIGN.LEFT, spc=None): align=PP_ALIGN.LEFT, spc=None, font=F):
tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame tb = s.shapes.add_textbox(x, y, w, h); tf = tb.text_frame
tf.word_wrap = True; tf.vertical_anchor = MSO_ANCHOR.MIDDLE tf.word_wrap = True; tf.vertical_anchor = MSO_ANCHOR.MIDDLE
tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0) tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = Pt(0)
p = tf.paragraphs[0]; p.alignment = align p = tf.paragraphs[0]; p.alignment = align
r = p.add_run(); r.text = t r = p.add_run(); r.text = t
r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic r.font.size = Pt(size); r.font.bold = bold; r.font.italic = italic
r.font.name = F; r.font.color.rgb = color r.font.name = font; r.font.color.rgb = color
if spc: r.font._rPr.set('spc', str(spc)) if spc: r.font._rPr.set('spc', str(spc))
return tb return tb
# ---- Kopf (ganz oben) ------------------------------------------------------ paint_background(s, SW, SH)
txt(Inches(0.95), Inches(0.32), Inches(8), Inches(0.35), "MODELL", chrome(s, "Zustand oder Ereignis", 5)
size=12.5, color=ACCENT, bold=True, spc=240)
txt(Inches(0.95), Inches(0.62), Inches(11), Inches(0.85), "Zustand oder Ereignis",
size=31, color=NAVY, bold=True)
shp(MSO_SHAPE.RECTANGLE, Inches(0.95), Inches(1.34), Inches(0.6), Pt(4), fill=ACCENT)
x0, x1 = Inches(1.0), Inches(12.33) x0, x1 = Inches(1.0), Inches(12.33)
beltw = x1 - x0 beltw = x1 - x0
@@ -77,25 +70,23 @@ cy = Inches(1.95)
for i, c in enumerate(claims): for i, c in enumerate(claims):
y = cy + Inches(0.6) * i y = cy + Inches(0.6) * i
shp(MSO_SHAPE.RECTANGLE, Inches(0.95), y + Inches(0.1), Inches(0.14), Inches(0.14), fill=ACCENT) shp(MSO_SHAPE.RECTANGLE, Inches(0.95), y + Inches(0.1), Inches(0.14), Inches(0.14), fill=ACCENT)
bold = (i == 0) txt(Inches(1.28), y, Inches(4.6), Inches(0.45), c, size=16.5, color=WHITE, bold=(i == 0))
txt(Inches(1.28), y, Inches(4.6), Inches(0.45), c,
size=16.5, color=INK, bold=bold)
# =========================================================================== # ===========================================================================
# LUPEN (Projektionen) nach rechts gerückt # LUPEN (Projektionen)
# =========================================================================== # ===========================================================================
def lupe(cx, label, label_w): def lupe(cx, label, label_w):
cy = Inches(2.45) cyy = Inches(2.45)
d = Inches(0.9) d = Inches(0.9)
shp(MSO_SHAPE.OVAL, cx - d / 2, cy - d / 2, d, d, fill=WHITE, line=NAVY, lw=Pt(4)) shp(MSO_SHAPE.OVAL, cx - d / 2, cyy - d / 2, d, d, fill=WHITE, line=NAVY, lw=Pt(4))
shp(MSO_SHAPE.OVAL, cx - d / 2 + Inches(0.13), cy - d / 2 + Inches(0.12), shp(MSO_SHAPE.OVAL, cx - d / 2 + Inches(0.13), cyy - d / 2 + Inches(0.12),
Inches(0.17), Inches(0.13), fill=RGBColor(0xED,0xF1,0xF5)) Inches(0.17), Inches(0.13), fill=RECF)
hw, hh = Inches(0.48), Inches(0.15) hw, hh = Inches(0.48), Inches(0.15)
hcx, hcy = cx + d * 0.52, cy + d * 0.52 hcx, hcy = cx + d * 0.52, cyy + d * 0.52
shp(MSO_SHAPE.ROUNDED_RECTANGLE, hcx - hw / 2, hcy - hh / 2, hw, hh, fill=NAVY, rot=45) shp(MSO_SHAPE.ROUNDED_RECTANGLE, hcx - hw / 2, hcy - hh / 2, hw, hh, fill=NAVY, rot=45)
txt(cx - label_w / 2, cy + d / 2 + Inches(0.16), label_w, Inches(0.6), txt(cx - label_w / 2, cyy + d / 2 + Inches(0.16), label_w, Inches(0.6),
label, size=14, color=NAVY, bold=True, align=PP_ALIGN.CENTER) label, size=14, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
return cy + d / 2 return cyy + d / 2
lupes = [ lupes = [
@@ -133,7 +124,7 @@ mx, mw = Inches(5.45), Inches(2.45)
my, mh = Inches(5.2), Inches(1.5) my, mh = Inches(5.2), Inches(1.5)
shp(MSO_SHAPE.ROUNDED_RECTANGLE, mx, my, mw, mh, fill=NAVY) shp(MSO_SHAPE.ROUNDED_RECTANGLE, mx, my, mw, mh, fill=NAVY)
shp(MSO_SHAPE.ROUNDED_RECTANGLE, mx + Inches(0.3), my + Inches(0.22), shp(MSO_SHAPE.ROUNDED_RECTANGLE, mx + Inches(0.3), my + Inches(0.22),
mw - Inches(0.6), Inches(0.32), fill=RGBColor(0x34,0x4E,0x70)) mw - Inches(0.6), Inches(0.32), fill=MID)
shp(MSO_SHAPE.OVAL, mx + Inches(0.35), my + Inches(0.74), Inches(0.15), Inches(0.15), fill=TEAL) shp(MSO_SHAPE.OVAL, mx + Inches(0.35), my + Inches(0.74), Inches(0.15), Inches(0.15), fill=TEAL)
shp(MSO_SHAPE.OVAL, mx + Inches(0.6), my + Inches(0.74), Inches(0.15), Inches(0.15), fill=AMBER) shp(MSO_SHAPE.OVAL, mx + Inches(0.6), my + Inches(0.74), Inches(0.15), Inches(0.15), fill=AMBER)
shp(MSO_SHAPE.RECTANGLE, mx - Inches(0.02), belt_top - Inches(0.02), shp(MSO_SHAPE.RECTANGLE, mx - Inches(0.02), belt_top - Inches(0.02),
@@ -165,6 +156,6 @@ for xv in pre_x + post_x:
shp(MSO_SHAPE.OVAL, Inches(xv) - Inches(0.05), acx - Inches(0.05), shp(MSO_SHAPE.OVAL, Inches(xv) - Inches(0.05), acx - Inches(0.05),
Inches(0.1), Inches(0.1), fill=AMBER) Inches(0.1), Inches(0.1), fill=AMBER)
out = "/home/marcuh/claude-projects/limbach/Folie4.pptx" out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Folie4.pptx")
prs.save(out) prs.save(out)
print("saved", out) print("saved", out)
+2
View File
@@ -1 +1,3 @@
- [Limbach Workshop / Outline](limbach-workshop-outline.md) — Wissensaufbau in Outline für Workshop 15./16.06.2026 - [Limbach Workshop / Outline](limbach-workshop-outline.md) — Wissensaufbau in Outline für Workshop 15./16.06.2026
- [adesso Styling](adesso-styling.md) — Corporate-Template-Quelle & Design-Tokens für Folien 24 (adesso_style.py)
- [Verbotene Wörter](verbotene-woerter.md) — „Pointe", „der Hit", „der Clou" & reißerische Wörter nie verwenden
+18
View File
@@ -0,0 +1,18 @@
---
name: adesso-styling
description: Corporate-Template-Quelle und Design-Tokens für das adesso-Styling der Workshop-Folien
metadata:
node_type: memory
type: reference
originSessionId: 38920718-90c7-440b-acfe-8a06dc15263b
---
Die Workshop-Folien (Folie 24) wurden auf das **adesso-Corporate-Template** umgestylt.
- **Referenz-Deck:** `/Users/marcus.hinz/Documents/schnzz.pptx` (adesso-Masterdeck, 96 Folien; LibreOffice exportiert nur die ~19 sichtbaren).
- **Design-Tokens** (extrahiert aus dem Theme): Fonts **Fira Sans Condensed** (Titel) / **Fira Sans** (Body); Palette Blau `#006EC7`, Purpur `#461EBE`, Grün `#76C800`, Teal `#28DCAA`, Coral `#FF9868`, Pink `#F566BA`, Taupe `#887D75`.
- **Look:** Teal→Blau-Verlauf, weiße Shapes + Text, Coral-Akzente; Header = Pill-Marker + Titel; schlanke Fußzeile = Seitenzahl + „adesso".
Implementiert im geteilten Modul `adesso_style.py` (von `folie2/3/4.py` importiert). Logo wird als Text „adesso" gesetzt (echtes Asset `image2.svg` im Template ist weiß-auf-weiß ohne Alpha → nicht direkt nutzbar).
Siehe [[limbach-workshop-outline]].
+17
View File
@@ -0,0 +1,17 @@
---
name: verbotene-woerter
description: Wörter, die in Folien/Texten für den User niemals verwendet werden dürfen
metadata:
type: feedback
---
Reißerische / werbliche „Aha"-Wörter sind verboten weder in Folieninhalten noch in der Konversation. Konkret untersagt:
- **„Pointe"**
- **„der Hit"**
- **„der Clou"**
- sinngleiche Effekthascherei (z. B. „der Knaller", „das Highlight", „der Wow-Effekt", „die Sensation")
**Why:** Ausdrückliche, mit Nachdruck formulierte Vorgabe des Users; passt nicht zum sachlichen Ton.
**How to apply:** Komplett vermeiden. Stattdessen nüchtern: „Kernaussage", „das Entscheidende", „Fazit", „der zentrale Vorteil", „der wesentliche Unterschied". Liste bei weiteren Hinweisen ergänzen.
+3 -2
View File
@@ -2,13 +2,14 @@
"""Fügt Folie2, Folie3, Folie4 zu einer Datei zusammen (Reihenfolge 2, 3, 4).""" """Fügt Folie2, Folie3, Folie4 zu einer Datei zusammen (Reihenfolge 2, 3, 4)."""
import copy import copy
import os
from pptx import Presentation from pptx import Presentation
base = "/home/marcuh/claude-projects/limbach/" base = os.path.dirname(os.path.abspath(__file__)) + "/"
dst = Presentation(base + "Folie2.pptx") # Seite 1 = Folie 2 dst = Presentation(base + "Folie2.pptx") # Seite 1 = Folie 2
blank = dst.slide_layouts[6] blank = dst.slide_layouts[6]
for fname in ("Folie3.pptx", "Folie4.pptx"): # Seiten 2 und 3 for fname in ("Folie3.pptx", "Folie3b.pptx", "Folie4.pptx"): # Seiten 2, 3, 4, 5
src = Presentation(base + fname) src = Presentation(base + fname)
new_slide = dst.slides.add_slide(blank) new_slide = dst.slides.add_slide(blank)
for shape in src.slides[0].shapes: for shape in src.slides[0].shapes: