"""绘制演示视频用 1080p 幻灯片(深色科技风 + 品牌橙)。""" import os from PIL import Image, ImageDraw, ImageFont from pathlib import Path WS = Path(__file__).resolve().parent W, H = 1920, 1080 BG = (13, 25, 42) # 深藏青 BG2 = (21, 37, 60) # 卡片底色 ORANGE = (232, 107, 32) # 品牌橙(与应用主题一致) WHITE = (240, 244, 248) GREY = (150, 165, 185) FONT = os.environ.get("APS_VIDEO_FONT_REGULAR") or "msyh.ttc" FONT_B = os.environ.get("APS_VIDEO_FONT_BOLD") or "msyhbd.ttc" def font(size, bold=False): return ImageFont.truetype(FONT_B if bold else FONT, size) SLIDES = [ dict(kind="cover", title="从“人排产”到“智能体排产”", sub="工业智核 APS · 计划排产智能体", note="产品汇报演示"), dict(kicker="01 · 为什么做", title="排产:制造业数字化最后一块硬骨头", bullets=["约束复杂 —— 物料齐套、设备产能、模具寿命、班组技能、换型耗时,牵一发动全身", "变化频繁 —— 紧急插单、设备故障、物料延期,计划永远赶不上变化", "经验依赖 —— 老师傅的判断无法沉淀、无法复制、无法传承", "大语言模型的成熟,给出了全新解法"]), dict(kicker="02 · 智能体闭环", title="听得懂 · 算得准 · 管得住", bullets=["感知 —— 自然语言下达指令:「试排一版交期优先」", "决策 —— 意图翻译成命令,驱动运筹学引擎真实求解", "执行 —— 甘特 / 负荷 / 交期三视图实时呈现,左说右动", "留痕 —— 每次操作进入哈希链式审计台账,可校验可回放"]), dict(kicker="03 · 数据准备", title="把工厂“搬”进系统,只需一次导入", bullets=["订单、物料、BOM、工艺路线、设备台账 —— 普通 Excel 即可", "自动识别表角色、表头映射、数据校验", "传统 APS 最耗时的数据初始化,在这里一次完成"]), dict(kicker="04 · 一句话排产", title="「试排一版交期优先」", bullets=["几秒钟产出排产版本:订单 × 设备 × 工序 × 时间一目了然", "甘特图 / 设备负荷 / 交期看板三视图联动", "数字全部来自引擎确定性计算 —— 大模型不得编造任何数字"]), dict(kicker="05 · 多策略对比", title="机器穷举,人来拍板", bullets=["交期优先 / 产能均衡 / 换型最小化 —— 沙盒并行试排", "准时率、换型次数、设备利用率逐项对照,最优项高亮", "一键采用,右侧沙盘即刻刷新,正式数据零风险"]), dict(kicker="06 · 插单与动态调度", title="L1 → L4 分级动态调度", bullets=["紧急插单:先局部修复、评估影响面,而非硬塞引发连锁延期", "设备故障:秒级池内换机 → 短窗重排 → 日重排 → 全局重排", "每级重排附带影响评估与确认卡 —— 拍板权永远在人"]), dict(kicker="07 · 柔性排产", title="能力池 + 虚拟产线(差异化能力)", bullets=["打破“设备固定属于产线”假设,按订单动态组线、用完释放", "正排保开工 / 倒排保交期 / 瓶颈锚定保利用率", "设备停机即刻重算瓶颈产能,负载预警自动色标"]), dict(kicker="08 · 图纸智能解析", title="让 CAD 图纸自己“开口说话”", bullets=["DXF 图纸导入:自动识别图号与版本,逐图元解析", "提取物料 / BOM 引用 / 工艺信息为结构化候选清单", "图纸是证据不是命令 —— AI 负责提取,人负责批准"]), dict(kicker="09 · 门禁与审计", title="智能体的每一步“想动”,都要人“放行”", bullets=["P0–P3 四级门禁:只读自由 / 试排轻提示 / 关键写确认 / 外部二次确认", "违反硬约束的计划,任何人都无法发布", "哈希链审计:谁决定的、依据是什么、当时看到了什么,随时可查"]), dict(kicker="10 · 沉淀与报告", title="把经验沉淀为组织资产", bullets=["检查点时间线:每个决策节点自动存档,随时回滚", "KPI 仪表盘:利用率 / 准时率 / 在制 / 延误一屏汇总", "一键生成 Word 排产报告,数字来自冻结快照,不经 AI 改写"]), dict(kind="cover", title="让排产听得懂人话,让决策算得清利害", sub="工业智核 APS · 计划排产智能体", note="汇报完毕 · 谢谢大家"), ] def draw_cover(img, d, s): d.rectangle([0, H - 18, W, H], fill=ORANGE) d.rectangle([0, 0, W, 14], fill=ORANGE) tw = d.textlength(s["title"], font=font(72, True)) d.text(((W - tw) / 2, H / 2 - 130), s["title"], font=font(72, True), fill=WHITE) sw = d.textlength(s["sub"], font=font(40)) d.text(((W - sw) / 2, H / 2 + 10), s["sub"], font=font(40), fill=ORANGE) nw = d.textlength(s["note"], font=font(30)) d.text(((W - nw) / 2, H / 2 + 110), s["note"], font=font(30), fill=GREY) def draw_content(img, d, s, idx): d.rectangle([0, 0, W, 14], fill=ORANGE) d.rectangle([96, 120, 106, 260], fill=ORANGE) d.text((136, 118), s["kicker"], font=font(34, True), fill=ORANGE) d.text((136, 172), s["title"], font=font(56, True), fill=WHITE) y = 340 for b in s["bullets"]: d.rounded_rectangle([96, y - 18, W - 96, y + 78], radius=16, fill=BG2) d.ellipse([132, y + 18, 156, y + 42], fill=ORANGE) d.text((190, y), b, font=font(34), fill=WHITE) y += 128 footer = "工业智核 APS · 计划排产智能体" d.text((96, H - 72), footer, font=font(24), fill=GREY) num = f"{idx:02d} / {len(SLIDES):02d}" d.text((W - 96 - d.textlength(num, font=font(24)), H - 72), num, font=font(24), fill=GREY) for i, s in enumerate(SLIDES, 1): img = Image.new("RGB", (W, H), BG) d = ImageDraw.Draw(img) if s.get("kind") == "cover": draw_cover(img, d, s) else: draw_content(img, d, s, i) img.save(WS / "slides" / f"slide{i:02d}.png") print(f"slide{i:02d}.png ✓") print("全部幻灯片绘制完成")