Visio 文件生成(.vsdx)
出的是 Visio 原生形状,双击即可继续手工编辑。
核心规矩:按放置宽度出图,字号 1:1
图的字号该按它最终放进文档的宽度定,不该按画布定。一张 420 mm 宽的图画了 9 pt 的字,插进 Word 正文宽 146.6 mm,缩放 35%,读者看到的是 3.1 pt——只有正文(12 pt)的四分之一,于是「字小、框大、四周一片空」。
- 先问清图放进文档后有多宽。Word 正文宽 146.6 mm(A4 页宽 210 减两侧 31.7 边距)。
- 页宽就排成 146.6 mm,字号 1:1,量到多少就是多少。
- 要按别的宽度放置时把
place_mm传给Diagram,机检会算缩放后的实际字号,低于 8 pt 判错误。
字号只写令牌,不写数字。六档:title 12 / h1 10.5 / h2 9.5 / body 9 / note 8.5 / micro 8(pt)。8 pt 是硬下限。
硬性前提:Windows + 已安装 Visio
本 skill 只实现了 COM 自动化一条路径,依赖本机装着 Microsoft Visio。没有 Visio 就没有可用的出图路径,这不是配置问题,也没有别的路由可退。
遇到没有 Visio 的机器,不要硬凑一版图交出去,也不要临时手写一个 .vsdx 打包器去顶。正确做法是明确告诉用户:本机缺少 Visio,本 skill 用不了,请换到装有 Visio 的机器上跑,或改用手工绘图。宁可交白卷,也不要交一版观感不达标的图。
先探测,看 Visio ProgID 一行:
python bin/visio.py doctor
要用装着 pywin32 与 Pillow 的那个解释器跑(机器上常有多套 Python,依赖只装在其中一套)。用错时 pywin32 那行报 [--] 缺失;此时若 Visio ProgID 为 已注册,就是解释器的问题,换解释器重跑,别下「Visio 不可用」的结论。
命令
python bin/visio.py dry 我的图.py # 调版式期间用:0.8 s 一轮,不启动 Visio、不写文件
python bin/visio.py run 我的图.py --out 出图目录
python bin/visio.py report 出图目录 # 汇总回执 + 生成 HTML 总览页
python bin/visio.py export 出图目录 --dpi 300
python bin/visio.py deepcheck 出图目录 # 重开 .vsdx 复查真实形状(可选,较慢)
python bin/visio.py extract 参考图.png # 照位图重画时先跑:量出框与连线
python bin/visio.py calibrate # 校验文字估算与实际渲染
dry 与 run 的机检结论一致,差别只是不建形状、不落盘,一轮 0.8 s 对 8~12 s。定稿再 run 一次。退出码、回执字段与全部检查项阈值见 references/delivery-contract.md。
预览 PNG 默认按 300 dpi 出,--dpi 600 或环境变量 VSDX_PNG_DPI=600 可调。Diagram.save() 落盘时会自动铺一层整页白底,导出范围因此恒等于整页,图边留白不会被裁掉。
脚本长什么样
import sys, os
sys.path.insert(0, "<skill 目录>")
from lib.vgraph import Diagram, C, shutdown
from lib import tokens as T
PLACE = 146.6 # 图放进文档的宽度
M = T.MARGIN
d = Diagram(PLACE, 100.0, place_mm=PLACE, name="图01-功能组成图")
# 标题:宽度随文字,再挪到居中
t = d.node("订单管理平台", x=M, y=M, token="title", bold=True,
fill=C.BRAND, line=C.BRAND_DK, color=C.WHITE, radius=2.6)
d.center_x(t, PLACE / 2.0)
# 一排等宽块:第三项传固定宽,整排就按这个宽对齐
lane = (PLACE - 2 * M - 3 * T.GAP_IN) / 4
heads = d.flow(t.b + T.TITLE_GAP,
[(c, {"size": "h2", "bold": True}, lane) for c in
("订单受理与审核", "库存调度与分配", "规则配置与维护",
"报表统计与导出")],
x0=M)
# 一列子项:单行宽度上限取列宽(超出自动折行),列内等高
nodes = d.stack(M, heads[0].b + T.GAP_BLOCK,
["线上订单\n受理审核", "线下订单\n受理审核"],
max_w=lane, uniform_h=True,
fill=C.F_BLUE, line=C.LINE_SOFT)
d.vgaps(nodes[0].cx, nodes, y_from=heads[0].b)
d.fit_page() # 页高收到内容底下,去掉图底空白
d.save("out/图01.vsdx", "out/图01.png")
出完图跑机检,有 error 就停下来修,别带着错误交付。
库速查
尺寸单位一律视觉毫米(图上量到多少,文档里就是多少),左上角原点。
| 方法 | 用途 |
|---|---|
Diagram(w, h, place_mm=, render_scale=, name=) |
建图。place_mm 只在机检里用 |
node(s, x, y, token=, w=, h=, max_w=) |
自适应框,宽高由文字解出。给 w 只解高,给 max_w 只限折行宽 |
flow(y, items, x0=, gap=, max_w=) |
横排一串,等高对齐,间距是净间距 |
stack(x, y, items, w=, max_w=, uniform_w=, uniform_h=) |
竖排一串,等宽(可等高) |
grid(x, y, rows, max_w=) |
按行排,每行取该行最高者 |
rect / pill / text / line / polyline / poly / diamond / ellipse |
基本图元 |
connect(a, b, mode=, sa=, sb=) / chain / trace |
连线。折线走位自动取两端锚点 |
swimlane / group / zone / band / entity / actor / uml_assoc |
泳道、分区、胶囊、E-R 实体、UML 参与者 |
label(x, y, w, h, s) |
压线说明,白底把线断开 |
move / center_x / align_x / align_y / bbox |
挪位与对位,同步挪 Visio 形状和机检登记 |
text_h / box_h / zone_h / row_w |
反推尺寸,别靠目测凑数 |
fit_page(pad=) |
页高收到内容底下(含连线),并同步平移图元保持位置 |
page_bg(color=) |
整页白底。save() 默认自动调用,bg=False 可关 |
save(vsdx, png, place_mm=, bg=) |
落盘 + 机检 + 写回执(回执默认落在 .vsdx 同目录) |
validate(place_mm=) |
只跑机检,不落盘 |
size 一律写令牌名或视觉 pt。fill 传颜色即视为实心(连线不许穿过);传 None 为空心。
框宽由版面栅格决定时(泳道头、同列等宽的流程框、跨列对齐的框组)传 grid=True,这类框的文字比框窄是版面使然,机检不再报「框内留白过多」。
版式不变量
排图时守住这几条,机检就不会亮红。
- 一条主路径。主路径之外的分支从最近的主路径节点引出,不要横穿。
- 同一列的框行数一致。等高排版遇到行数不一的框,短的会留下空框。
- 间距一律指净间距,即两框之间真正空出的距离,算法上取净间距而非中心距。横向 5 mm、纵向 3.5 mm 起步。
- 分区高度按内容算。用
zone_h([行高...]),算少了内容会溢过边框。 - 端口方向是契约。连线的首末段要垂直进出被指定的那一侧。
- 非矩形图元从真实顶点出发。菱形、多边形已记下顶点,
connect会自动用。 - 标签可以压线,不可以压字。压线的标签必须带白底,把线断开。
- 放不下时先改内容,再改版式。删掉可省的连线、收紧间距、把标签压短;最后才动字号,且不得低于 8 pt。
- 页高必须罩住全部内容。
fit_page()要放在最后一步,之后别再往下画东西;页高定稿后再加内容,图元会落到页外,打印时被裁掉。
细则与修复顺序见 references/layout-rules.md。
写脚本最容易踩的三条
- 文字框宽度不要贴边排,留 0.3~0.5 mm 余量。宽度按量算值算到与文字相等时,Visio 侧基本一定会折行,而量算说 1 行——这是「量算没问题、图上却多一行」的常见根因,机检的
tight-fit会报。做法:给文字量出的宽度统一加 0.35 mm 再参与分列与排版。 dry试算是预测,不能替代定稿那一次真出图。它不连 Visio,因此看不到字体是不是真的取到了(font-missing与font-fallback永不触发),也看不到 Visio 排版引擎与量算表的实际偏差。dry用来把版式调收敛,定稿仍要run一次并对渲染图核一遍。- 不要绕过库去手写 COM。COM 层的坑(
PinY对连线无效、DrawPolyline传 N 点只画 N−1 条边、导出分辨率与绝对路径、TEXTWIDTH不能用来量文字、字体 ID 必须用系统显示名等)库已处理,机制与修法见references/com-route.md。画布内也不要引入浏览器或布局求解器。
font-fallback(warn)表示 Visio 实际用上的字体不是字宽表的标定字体(微软雅黑),含拉丁或数字的框宽会被算得偏大。字宽表按 msyh.ttc 硬编码在 lib/metrics.py 里、运行时不读字体文件,补表方法写在表头注释里,详见 references/com-route.md 与 metrics.py 顶部「字体前提」。
照着一张位图参考图重画
手上有截图、拍屏或旧图时,流程固定成三步:先取证,再按交付宽度重排,最后靠机检收口。这条路线比「给我文字规格直接画」慢得多,慢的那部分全在取证——位图里没有结构信息,框在哪、什么颜色、连线怎么绕,都得从像素里量。量错一处,后面重排再精致也是错的,所以取证一次做全,别挤牙膏。
python bin/visio.py extract 参考图.png --min-area 3000 --json ref.json
输出填充块的包围盒与形态、按颜色分组的水平/垂直线段坐标、主要填充色。--json 把坐标落盘,排版脚本直接读,免得改一次版式重量一次。这是最重要的工具,别绕过它去手写逐点探测脚本:手写探测每问一个问题就一轮往返,十几个问题就是十几轮;extract 一次跑完约 5 s。
填充块的识别不预设色相(tinted() 只排除白底、墨色与红绿线色),粉、棕、紫都认。若改成按固定调色板挑块,换一套配色就会一个块都不报,等于沉默地漏掉整张图,别这么写。
拿到的坐标是证据,照着看只是印象。一张检验类流程图里有十几条红绿回退线,走向相似、跨列绕行,目测几乎必错,画完看着还挺像,要等对方逐条核对才暴露。定框宽也一样:若取证显示十几个框宽都差不多,说明原图是等宽排布,重排时就别让它们各自按文字自适应。观感上的判断(哪块是表头、哪段是标题行)仍要用眼睛核一次,但一次看够:把原图按行切几条横带、放大到能看清字,一轮读完再动手,不要看一眼写一版。
重排阶段有两条本库特有的传参约定,漏了会被机检刷屏:
- 泳道头、同列等宽的流程框一律传
grid=True,否则每个都报box-too-empty。 - 带说明行的框(标题行 + 说明行做成两个贴合
rect)要显式传pad_y=T.PAD_Y_TIGHT,宽度同步用PAD_X_TIGHT,否则报一条 1.4 mm 的假text-overflow。
四类高频机检问题(标签互压、折线首段过短、假溢出、共线重叠)的根因与修法见 references/from-bitmap.md,那里也写了怎么读取证输出、怎么按内容反算泳道宽。
性能与节奏
出图耗时基本等于「图元数 × 0.1 s」:每个带文字的框约 96 ms(要写十七八次 COM 单元格),每条连线约 27 ms,65 框 16 线的图约 8 s;机检与纯排版计算都在 1 ms 量级,可忽略。由此三条纪律:
- 调版式不要出图。出图的十几秒里 99% 是建形状,与版式对不对无关,用
dry试算。 - 不要为了省时间跳过机检。真正的浪费在反复试版式,不在出图。
- 要出多张图时放一个脚本里顺笔画(同进程复用实例,省掉每张 0.1~0.7 s 的起页成本),别拆成多次调用。
参考文档
| 文件 | 什么时候读 |
|---|---|
references/from-bitmap.md |
要照着一张位图参考图(截图、旧图)重画 |
references/design-system.md |
要定字号、间距、配色,或不确定某一档用在什么层级 |
references/layout-rules.md |
机检报了错、走线绕不开、分区高度算不准 |
references/com-route.md |
COM 调用失败、要接官方模具、要批量出图;实现细节与坑的总汇 |
references/delivery-contract.md |
要看命令退出码、回执字段、检查项阈值、报告话术 |