aps-agent/video/gen_slides.py

105 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""绘制演示视频用 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("全部幻灯片绘制完成")