PPT 数据转移(固定位置映射,保目标版式)
场景路由
- MotoMotion 吊牌同步(Spec Sheet → Hang Tags,吊牌已有完整页面):用
scripts/motomotion_sync.py。 - MotoMotion 吊牌生成(Spec Sheet → Hang Tags,吊牌只有 1 页模板):用
scripts/motomotion_build.py从模板克隆生成整套吊牌。 - SofaWorks 吊牌批量生成/复检(spec 文件夹,每款一个 spec pptx):用
scripts/sofaworks_build.py
生成 +scripts/sofaworks_check.py复检,规则与坑见references/sofaworks_hang_tags.md。 - MotoMotion 吊牌批量检查(二维码着重):用
scripts/motomotion_qr_check.py --spec-dir <Spec目录> --tag-dir <吊牌目录>(依赖 opencv-python-headless 解码二维码)。
二维码规则(用户 2026-09-28):同一 Spec 页的几款沙发(不管几款)吊牌必须用同一个码;
注意"同 spec 页"≠"同型号"(同型号可有多个 spec 页,各页各码),须按页级行映射分组后比对,
不能按型号分组(会误报)。 - 一致性检查(Spec 与 Hang Tags 对照,找出输入错误):走下方「一致性检查」,结果按简版规范输出。
- 其它任意 PPT 转移:走下方「通用工作流程」。
规则细节统一见 references/motomotion_hang_tags.md(MotoMotion)与
references/sofaworks_hang_tags.md(SofaWorks)。
MotoMotion 吊牌同步(专用)
# 先干跑看计划(不匹配、缺页、图片对应全部列出,不写盘)
python scripts/motomotion_sync.py --spec SPEC.pptx --tags TAGS.pptx --output OUT.pptx --dry-run
# 把计划给用户确认后正式执行
python scripts/motomotion_sync.py --spec SPEC.pptx --tags TAGS.pptx --output OUT.pptx
# 只同步指定型号
python scripts/motomotion_sync.py ... --models M00480,M00336
MotoMotion 吊牌生成(build 模式)
# 模板只有 1 页横版:
python scripts/motomotion_build.py --spec SPEC.pptx --tags 模板.pptx --output OUT.pptx [--plan plan.json] [--dry-run]
# --tags 为多页人工成品文件(横版+lift 混排):
python scripts/motomotion_build.py --spec SPEC.pptx --tags 成品.pptx --tpl-slide 1 --output OUT.pptx [--plan plan.json] [--dry-run]
--tpl-slide N:横版模板页码(1-based)。多页成品文件必填;结束时所有被
克隆过的模板源页自动删除,未涉及的成品原页原样保留(可做"lift 页重生成、
横版页保人工原样"的混合版)。- 默认一款一页(整表 + 最大图);3 行 3 图且宽高比可区分时自动拆 3 页。
- 产品图自适应占满空白区(v1.1+):描述框/二维码下缘以下、左栏背景块以内、
原图片框底边为止,内容 ≤ 区域 95%(--pic-fill可调)。宽矮型沙发不再
缩在原小图片框里(2026-09-22 用户反馈修复)。 - 宽高比无法区分款式时:用 inspect/导出脚本把候选图导出为 PNG,AI 看图识别款式后把分配写进 plan.json 的
splits;特殊分组拆分(如 6 行模块 + 3 图)同样由 plan 指定。 - lift 竖版页(v1.2+):模板池优先取
--tags文件自身——右侧表格 ≤2 行
(表头+1 SKU;横版整表为 8 行)的页;生成时优先同名型号页(版式/desc
行数天然匹配),同名多页全部纳入删除集合防重复。图片保几何只换图源
(重排放大会出血画布外,2026-09-23 对比人工成品发现)。无同名页时退回
desc 行数最接近的候选页;也可--vtpl指定外部文件/目录。找不到时结尾
汇总列出全部未生成 lift 款,不要硬塞进横向模板。 - 执行前必须 dry-run 并把计划给用户确认;输出一律为新文件(原模板文件不动)。
- 生成后自检对比(v1.2+ 惯例):生成版 vs 人工成品逐字段比对(型号/描述/
cover/表格逐格/图片几何 >0.8cm),差异回查 Spec 定真源——Spec 是数据真源,
人工版可能有笔误(实例:M00369 Pack D30.3 被人工误写 30.5,生成版正确)。
两个脚本共有能力:按型号匹配页(大小写归一化)、型号大字与 SKU 单元格统一大写、三款拆页(按表格行序、图片按可见宽高比匹配款式)、竖排 lift chair 版式提取、表格砍列与行数自适应、图片 blob+裁剪转移并居中放入现有图片框、幂等写入(内容不变不动)。
SofaWorks 批量生成(spec 文件夹 → 吊牌)
场景:Sofaworks--XXXX 展会 文件夹里每款一个 spec pptx(25.4×19.05cm 小页,
1-N 页 = N 个 cover 配色变体),用多页成品吊牌文件(如 2026-9-18)做模板批量生成。
用 scripts/sofaworks_build.py:
# 先 dry-run 看全部型号/页数/版式计划,给用户确认后去掉 --dry-run 批量执行
python scripts/sofaworks_build.py --spec-dir <spec文件夹> \
--tags <模板吊牌.pptx> --out-dir <输出文件夹> [--dry-run] [--models S4189B,...]
- spec 解析(与 MotoMotion 不同的专用逻辑):型号在
TextBox 9;描述+Cover 在
object 2(Leather:/Fabric:/Cover:/PU:/Leather/PU Match:/Fabric/PU Match: 行 →
cover,含 Loading 行丢弃,其余行并入 desc——skill 原 COVER_RE 不认
Fabric/PU Match:,sofaworks_build 用自己的 COVER_SW_RE);表格 7 列,
第 2 列 Description 与第 7 列 Landed Price 丢弃;logo 固定名Picture 8要排除。 - 版式规则:SKU 行 ≤1 → 竖版(模板池里表格 ≤2 行的页,按产品图宽高比选),
>1 → 横版(整表自适应行数 + 最大图填充空白区)。型号/SKU 默认去前缀 S
(--keep-s保留,模板 4105A 风格)。 - 套系页拆分(v1.4.6 修订,SKU 编码见下):同页表格含 Sofa+Loveseat 行 -> 按图数拆:
- spec 页 ≥3 张图:拆 单椅→双人→三人 三个竖版页;双人/三人以装箱宽度定(宽=三人);
图片映射:spec 上最上图=三人位,剩余最左=单椅、再余=双人。 - spec 页 2 张图:拆 两页 —— 单椅页(Chair 行 + 全部脚蹬行,脚蹬跟单椅,
图=单椅+脚蹬图)+ 三人位页(仅 Sofa 行,图=沙发大图)。
-3/C3 Loveseat 行剔除(v1.4.6,用户规则"检查所有的-3"):spec 页上没有 loveseat
独立照片时该行不出现在任何输出页(4120K/4119P/4093A 等 14 页)。 - 描述规则(v1.4.6):多款页中单椅页用表格行描述,其它页(双人/三人/整合页)
用 spec object 2 标题(如 "Stationary Modular Seating Options")。 - 单椅+多脚蹬页拆两张(v1.4.6,ottoman_pages):页含单椅行 + ≥2 脚蹬行且 ≥2 图
-> 大脚蹬(desc 含 Square/Cocktail Ottoman)单独一页(图=右侧大脚蹬照)、
小脚蹬跟单椅页(图=左侧单椅组合照;4111E/4181E-s2/4189B-s2/s4/4190C-s2)。 - 不够分则图片框留空;非套系多行页维持横版整表。拆页规则统一封装在
suite_pages(),build 与 check 必须共用。 - 模块组合页拆分(v1.4.5):同页含 -5(LAF 模块)与 -6(RAF 模块)行、页上 ≥2 图且有
-4 单椅行 -> 拆 单椅(-4)→双人(-5+Console[14X]+-6)→三人(-5+Armless[7]+-6);三人和双人位
都含 -5-6(S4116A 7 个位置 5 行表的场景);双人位仅当存在 Console 行才出
(S4062S 无 Console 只拆单椅+三人)。pics=1 的模块横版页不拆(S4104H 等用户确认正确)。 - 二维码防遮挡(v1.4.6,fix_qr_overlap):模板 QR 组合 top=35.5、描述框底 33.4,
描述渲染超 2 行会压住二维码 -> 按(行数-2)*2.6cm下移二维码(行数按
split_desc_lines 拆段后每 54 字符折一行估算,S4173B 三行长描述案例)。 - SKU 编码规则(真源
\\hhcfs0003\...\Vivy\资料\SKU.pdf,扫描件需提图识别):
Item No:0/28=固定沙发、1=沙发、3=Loveseat、4=单椅(4S=Stationary Chair 单独页)、
5/6=左右模块、7/7M/8=Armless、9X=Wedge、14X=Console、22X=Ottoman(跟单椅)、
25/28=Sofa/Sleeper、99=组合;后缀 2P/3P/3PZG=功能,MFMJ=音频件。解析 SKU 用
_item_no()(sku 第 2 段)。 - cover 去 "(All TG)"(v1.4.5 修复):ALLTG_RE 之前定义了但没套用,S4116A 等带
"(All TG)" 的 cover 原样漏出——parse_spec_slide里 cover 赋值必须过 ALLTG_RE。 - COVER/面料文本框一行化(v1.4.7,用户 2026-09-28 规则):cover 文本能一行放下就显示一行
(M00297C "Trento Pewter w/ Trento Dove Piping" 曾在窄框里折成两行)。写入 cover 后调
transfer.fit_cover_one_line(sh, slide_w_emu):实测文本渲染宽(PIL 按运行字体度量;
未知字体退 Arial 近似,COM 实测 NanumMyeongjo 差 ≤6% 故近似余量取 12%),放得下才加宽
文本框;全宽仍放不下保持折行;手动多段/换行不动。三个写入点都要接(build 的 fill_page、
sync 的 write_page、sofaworks 的 fill_vertical),且不依赖 cover 文本是否变化(幂等,
重跑旧文件自动补修)。注意parse_one_page传的是假 prs 对象,parse_tag_pages里
slide_width 必须getattr(prs, "slide_width", None)容错。 - 网络盘 ACL 坑:共享目录可能「允许新建、禁止修改/删除」,
覆盖旧文件会 Permission denied——先试写探权限,不行就整套装进带日期的新文件夹。 - 单行页描述以表格行描述为准(v1.4.1):spec 单椅页
object 2标题常是行描述的截断
(S3797K 标题 "Double Power Recliner"、行 "Double Power Recliner with Headrest"),只取标题会
丢 "with ..." 段——首批 69 款曾因此 21 页出错(19 个文件)。规则:行描述以标题首行开头且更长
→ 用行描述;两个不同叫法("Tilt Recliner" vs "Chair with Tilt & Footrest")不是截断,保持标题。
sofaworks_build.py与sofaworks_check.py必须同规则,否则复检误报。 - 图片大小 SOP(v1.4.6 改为固定区域居中):产品图不锁模板小框——统一落在
固定区域:水平居中于 x=26.3cm,区域内(top 39.1 ~ bottom 73.9cm)等比适配并
垂直居中,最大 52.1×34.8cm(sop_resize())。ar=1.5 占满区域(与人工成品
一致);宽图(如 S4121D 转角沙发 ar2.96)不再底边出血切脚、上方不留大空白
(用户报错"有些图片不在固定的位置上")。竖版(fill_vertical)与横版(fill_page
后补排)都走此 SOP;只改几何不动 blob/srcRect,复检 content_ar/MD5 不受影响。 - 图片映射启发式先目检再批量:35 个多图页 82 张图逐张目检证明
"最上=沙发/剩余最左=单椅/再余=双人"当前批次全对,但 spec 图片位置没有固定
规律(单椅图有左有右、常出血到页外)——新批次必须先导出图片 blob 目检
(contact sheet 拼图+大标签)确认映射后再批量生成。 - 输出:每个 spec 一个同名 pptx 到
--out-dir(用户确认过的惯例:spec 文件夹下
建Hang Tags-成品子文件夹)。 - 关键坑:规划与构建必须用同一个 Presentation 对象——分两个对象会跨包克隆,
保存时 slideLayouts/media 全部重复条目,PowerPoint 打不开。 - 生成后视觉自检:
scripts/pptx_export_png.py <file.pptx> <outdir>用 PowerPoint
COM 导 PNG(本机需装 Office;脚本结束会app.Quit(),否则残留进程锁文件导致后续覆盖写报
Permission denied),AI 看图核验版式/字段。 - 一致性复检(v1.3.0):
scripts/sofaworks_check.py(改BASE/TAGS两个路径常量),
spec 文件夹 vsHang Tags-成品全量核对:页数配对 / 型号 / Cover / 描述双格式 / 表格逐格
(按行序 positional 比对——同 SKU 多行是模块款常态,按 SKU 建字典会误报)/ 版式类型 /
产品图 MD5 归属 + content_ar(比内容比例而非形状 ar——spec 原图自带 srcRect 裁剪)/
spec 页漏生成。输出sw_check_result.json+ 分类清单;spec 同 SKU 值冲突单列「待确认」。 - 一致性检查可复用 dump_structure.py(任意 pptx 逐页形状+表格全文 dump)。
- 合并版输出(v1.4.5):
scripts/sofaworks_merge.py用 PowerPoint COM
Slides.InsertFromFile把Hang Tags-成品下全部 pptx 按文件名序合并为
Hang Tags-成品-合并版.pptx(COM 插入完整保留版式/媒体;每次改版后需重跑)。
build/check 扫描 spec 目录时必须跳过Hang Tags*前缀文件——合并版/成品
放回同目录会把 5 列吊牌表当 spec 解析直接崩。 - 交付顺带两件:① 改过解析规则后要么全量重跑、要么全量复检(实例:只重跑发现的那一款,
同写法的 4 款带错出厂);② 成品交付路径以用户当场指定为准(2026-09-24 起 SofaWorks
项目用户明确:不再同步网络盘,只改本地Hang Tags-成品;此前网络盘同步惯例对该项目作废)。 - 完整规则/坑清单见
references/sofaworks_hang_tags.md。
一致性检查(Spec vs Hang Tags)
以 Spec Sheet 为基准,逐字段核对 Hang Tags 是否有输入错误。只查不改,不改动任何原始文件。
检查维度(按优先级):
1. 字段级逐格比对——SKU 为主键对齐后,比 SKU / 描述 / Cover / 装箱尺寸。只查页数/型号是查不出单元格抄错的(曾漏掉 M00469 SKU 抄错页),必须落到单元格。
2. 描述双格式——应为两段(产品定性 + with ...),合并成一段即缺断行。
3. 页面对齐——型号错位(页面型号与 SKU 不符)、缺页、多余页。
4. 结构语义——同型号多页、多行组合拆页、配件行(SS-2.0/Accessory)不是错误,判定前先看 spec 行语义,不要只看计数。
输出格式(强制):一律按 references/check_report_format.md 的简版表格规范输出,不用论述性长文。
自动化入口(v1.4.0):python scripts/motomotion_check.py --spec SPEC.pptx --tags TAGS.pptx
[--out r.json](v1.4 起改为命令行传参,不再改路径常量)。输出 JSON + 打印分类清单:
字段错误 / 页序差异 / cover 备注 / 待确认。判定规则(全是实测踩坑后固化,改前先看为什么):
- 按型号整组配对:把该型号的全部吊牌行(含拆页)汇成一个池,再四级匹配 spec 行——SKU 精确
(同 SKU 多候选按 Cover 择优)→ 描述一致 → 款式唯一 → 剩余非配件行顺序兜底。避免"spec 6 行表被
拆到两页 / 行序不同"引发的错位连锁误报(未做此修正前,C5 一类模块款会一次误报 40+ 条)。
- 重复型号必须逐 spec 页配对(M00440 的 3H/2H、M70017 两种 Cover、B0008 两页),不能"所有页比
所有页",否则互相误报(曾一次误报 8 条)。
- 行数统计只比"本 spec 页对应的吊牌页",不能拿整型号池比单页行数。
- 配件行豁免:SKU 以 SS-/Accessor 开头,或四个尺寸列全空的行 → 不计入行数/缺行
(spec 有、吊牌按惯例不写)。
- 型号/SKU 不符单列一类:页面型号 ≠ 表格 SKU 前缀,抓"页内容串了别款"(实例:C2 p24 型号写
M00035 内容实为 M00111;C2 p4 表格 SKU 仍是 M00460)。
- 页序差异单列:人工惯例 单椅→双人→三人、主件优先,与 spec 表格行序不同属版式顺序,不是抄错。
- Cover 用分段值判定:spec cover 按 / 拆段后命中任一段即正确(分款误报防护);spec 用 &
代替 / 时标签不完整,归"待确认"而非错误。
- 描述按相似度分流:字面高度相似=疑似抄错(进错误清单);一方包含另一方/差异大=待确认。
spec 页描述与表格行描述自身不一致时(M50001m、M50017),错不在吊牌 → 待确认并建议回改 Spec。
- 行级描述只有拆页页有:整表页的吊牌表格无 Description 列 → 整表页描述对 spec object 2;
拆页描述对 spec 行描述。
- 归因看文件时间:Spec 比吊牌新时,差异可能是"Spec 后改"(实例:C6 全套 SKU 漏 VC 后缀 +
描述漏 & Ventilated Seat,疑 Spec 后加 Ventilated 版本)。
- 出报告按 references/check_report_format.md 写 HTML 再转 DOCX;真实错误 / 描述断行 / 待确认 /
已排除 / 优先级五节分清,误报要进"已排除"并写理由,不要混进错误清单。
目录批量入口(v1.3.0):scripts/motomotion_check_dir.py --spec-dir <目录> --tag-dir <目录>
[--only CONTAINER1,VN] [--qr-subdir 二维码嵌入版] [--out-json r.json]——按文件名"容器标识"
(Container 1/2/3/5/6、VN、MM…)自动配对多个 spec/吊牌文件,逐对调用 check_pair;没有对应
spec 的吊牌文件走跨 spec 核对(只保留双方都有的型号),用来区分"抄错"与"对不上号"。
--qr-subdir 用于复核派生副本(如"二维码嵌入版"):结果应与主目录完全一致,可证明二维码插入
没有动过数据。SofaWorks 侧的目录批量复检用 sofaworks_check.py(见上)。
通用工作流程(严格按序执行)
-
盘点双方结构 —— 分别对源和目标运行
scripts/inspect_ppt.py,拿到每页形状的索引、id、坐标(cm)和内容预览:
bash python scripts/inspect_ppt.py 源.pptx --json src_structure.json python scripts/inspect_ppt.py 目标.pptx --json dst_structure.json
(运行环境与 pip 依赖见references/pptx_gotchas.md,依赖 python-pptx。) -
建立映射 —— 复制
assets/mapping.example.json为mapping.json,按坐标和内容预览逐项确认「源形状 → 目标形状」的对应关系,写成 rules。规则类型:
-text:文字转移,保留目标 run/段落格式;
-table:单元格逐一转移,用 offset 跳过双方表头/首列;
-image:替换目标图片的二进制(位置/裁剪/边框保留),要求同图片格式。
形状定位优先用索引(与 inspect 输出一致),长期重复任务改用shape_id防索引漂移。 -
与用户核对映射 —— 转移前把 rules 整理成表格给用户确认(源页/源形状预览 → 目标页/目标形状预览),确认后再执行。
-
执行转移 ——
python scripts/transfer.py mapping.json。所有规则成功才保存输出文件;任一失败则逐条报错且不写盘。 -
冒烟验证 —— 打开输出文件,抽查首条与末条规则对应页面:内容已更新、目标版式未变。
边界与注意
- 只处理顶层形状;组合(GROUP)内部元素需先取消组合或单独处理,见
references/pptx_gotchas.md。 - 表格含合并单元格时先 inspect 确认结构再写规则。
- 绝不覆盖源/目标原文件,一律输出到新文件。
- 详细坑点(格式保留原理、图片 blob 替换、坐标系)见
references/pptx_gotchas.md。 - 检查报告输出规范(简版表格) 见
references/check_report_format.md。
安装与维护
- 真源 = 已安装目录
C:\Users\jie.wu\.workbuddy\skills\ppt-data-transfer(2026-09-22 起桌面项目目录已清理,只留 PPT 成品;此前"桌面源目录优先"的规则作废)。 - 运行环境:
C:\Users\jie.wu\.workbuddy\binaries\python\envs\default\Scripts\python.exe(已装 python-pptx + Pillow)。 - 重打包:
python scripts/repack.py --output <目标目录>\ppt-data-transfer.zip --exclude check_report_sample.docx
(纯 zipfile,无 tar 依赖,自动跳过__pycache__)。平台不允许.docx等文件类型
(assets/check_report_sample.docx 是本地参考样例,发布必须排除)。 - 发布到 SkillHub:
~/.local/bin/skillhub publish <zip> --changelog "..."(需先skillhub login --key skh_...;
SKILL.md 已含 slug/version,改动后递增版本号)。注意:本机 Git Bash 下~/.local/bin/skillhubwrapper 会因
#!/usr/bin/env bash失效,直接用
<managed-python> C:/Users/jie.wu/.skillhub/skills_store_cli.py publish <zip> --changelog "..."即可;
目录形式发布会被 docx 拦下,必须发 zip。 - 版本历史:1.4.8 = MotoMotion 二维码批量检查(motomotion_qr_check.py:同 Spec 页必须同码;
页级行映射分组防"同型号多 spec 页"误报;cv2.QRCodeDetector 多策略解码;5 列老款 spec 兼容;
实测 8 对 305 页:主文件 7 个全合规、老款替换 46 处同型号跨页共用码待确认);
1.4.7 = COVER 面料文本框一行化(fit_cover_one_line 写入 transfer.py 共享,
build/sync/sofaworks 三处接入;PIL 量宽 + COM 实测校准近似余量;M00297C 用户报错修复);
1.4.6 = 四条用户新规(69 文件全量重建,165 页复检 0 错误):
① 两图套系三人位页剔除 -3/C3 Loveseat 行(spec 无 loveseat 独立照片就不出,
"检查所有的-3");② 多款页描述规则——单椅页用表格行描述,其它页(双人/三人/
整合页)用 object 2 标题;③ 单椅+多脚蹬页拆两张——大脚蹬(Square/Cocktail)
单独一页、小脚蹬跟单椅(ottoman_pages;照片约定:单椅组合在左、大脚蹬在右);
④ 描述渲染超 2 行时二维码组合下移防遮挡(fix_qr_overlap,每超 1 行 2.6cm);
另修 SOP 为固定区域居中(39.1~73.9cm 内等比适配,宽图如 4121D ar2.96
不再底边出血切脚)+ ALLTG 正则容忍 "All TG )"(cover 残留 ")" 修复);
图片映射验证:35 个多图页 82 张图逐张目检,"最上=沙发/最左=单椅"启发式全对,
不可靠的是位置假设本身——遇到新批次的套系页必须先导图目检再批量;
1.4.5 = -22 脚蹬跟单椅(两图套系单椅页 2 行)+ 模块组合拆页
(-5/-6:三人位=-5+Armless+-6、双人位=-5+Console+-6,S4116A 等 7 页)+
cover 去 "(All TG)" 修复 + SKU 编码规则(SKU.pdf)+ sofaworks_merge.py 合并版;
1.4.4 = 图片大小 SOP(产品图统一 52.1×34.8cm 左下区,sop_resize,
全量 69 文件重建复检 0 错误);1.4.3 = 两图套系页改拆两页(单椅 + 其余行全部整合到三人位多行表,
suite_pages() 统一 build/check 规则;15 文件重建,全量复检 0 错误;交付只落本地);
1.4.2 = 套系页拆三款(classify_suite/suite_pics,S4065H 等 21 文件 22 页;
sofaworks_check 改为期望页清单+按 型号+SKU 配对,图片 md5 精确到指定原图);
1.4.1 = 单行页描述改用表格行描述(修 "with ..." 段丢失,build+check 同规则;
坑清单第 8 条);1.4.0 = SofaWorks 面料行写法修复(Leather/PU Match、Fabric/PU Match)+ 4 款重生成;
1.3.1 = 维护说明补充(发布排除 docx);
1.3.0 = 新增 SofaWorks 批量生成(sofaworks_build.py)+ 全量复检(sofaworks_check.py、
motomotion_check_dir.py)、SofaWorks 规则参考文档、pptx_export_png.py 退出修复(2026-09-23);
1.2.0 = MotoMotion build/lift 版式;1.1.0 = 图片自适应占满空白区。