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