
摘要: 工程制图中大量重复操作消耗的时间远超设计本身——逐张填标题栏、手工排图框、批量导出PDF,每一项都是纯体力劳动。中望CAD通过COM接口暴露了完整的对象模型,可以用Python直接驱动绘图、编辑和导出的全流程。本文以三个真实工程场景为主线,给出从环境搭建到参数化批量出图的完整代码方案,附带错误处理与调试经验。
中望CAD在安装时默认注册COM组件,Python通过 win32com.client 或 comtypes 库即可获取应用程序对象。两者的区别在于:win32com 基于IDispatch后期绑定,代码简洁但无智能提示;comtypes 基于类型库早期绑定,支持IDE自动补全,但需要先通过 gen_dir 生成类型桩。
以下使用 win32com(兼容性最广,中望CAD 2020-2025 均经过验证)。
pip install pywin32 pandas openpyxlpandas 用于读取外部数据源(Excel/CSV),openpyxl 是 pandas 读取 .xlsx 的后端引擎。
import win32com.client
# 启动中望CAD或连接到已运行的实例
zwcad = win32com.client.Dispatch("ZWCAD.Application")
# 获取当前活动文档
doc = zwcad.ActiveDocument
# 获取模型空间(ModelSpace),所有图形对象在此创建
ms = doc.ModelSpace注意: Dispatch("ZWCAD.Application") 中的 ProgID 取决于中望CAD的版本。如果连接失败,尝试 "ZWCAD.Application.2025" 或 "ZWCAD.Application.2024"。可通过注册表 HKEY_CLASSES_ROOT 查看已安装版本的精确ProgID。如果中望CAD与AutoCAD共存且ProgID冲突,指定完整版本号即可隔离。
某电气项目包含42张原理图,每张需要插入A3图框并填写图号、图名、设计人和日期。Excel中已维护完整图纸清单:
图号 | 图名 | 设计 | 日期 |
|---|---|---|---|
EL-001 | 主接线图 | 张工 | 2025-06-20 |
EL-002 | 控制回路 | 张工 | 2025-06-20 |
... | ... | ... | ... |
import pandas as pd
# 读取Excel图纸清单
df = pd.read_excel("D:/Project/图纸清单.xlsx", sheet_name="Sheet1")
# 图框块名称(需在模板文件中预先定义为带属性的块参照)
BLOCK_NAME = "A3_TITLE_BLOCK"
for idx, row in df.iterrows():
# 图框插入点(每张图Y方向偏移420mm = A3图框高度+间距)
insert_point = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
[0.0, -idx * 420.0, 0.0])
# 插入图框块参照
block_ref = ms.InsertBlock(insert_point, BLOCK_NAME, 1, 1, 1, 0)
# 遍历块的属性并填充
attrs = block_ref.GetAttributes()
for attr in attrs:
tag = attr.TagString.upper()
if tag == "DWG_NO":
attr.TextString = str(row["图号"])
elif tag == "DWG_NAME":
attr.TextString = str(row["图名"])
elif tag == "DESIGNER":
attr.TextString = str(row["设计"])
elif tag == "DATE":
attr.TextString = str(row["日期"])
print(f"完成:{len(df)} 张图框已生成")
# 缩放到全部图形范围
zwcad.ZoomExtents()关键点: 图框块必须在模板文件(.dwt 或 .dwg)中预先定义为带属性的块参照。属性标签(TagString)在块定义阶段设定,上述代码中的 DWG_NO、DWG_NAME 等标签需要与块定义中的属性标签完全一致(代码中做了大写转换容错)。如果不确定属性标签名称,可以先在CAD中打开块编辑器逐个查看,或用以下代码打印所有属性标签:
for attr in block_ref.GetAttributes():
print(f"Tag: {attr.TagString}, Value: {attr.TextString}")建筑和结构图纸的第一步通常是绘制轴网——由水平和垂直轴线组成的网格,轴线间距和编号由设计参数决定。手工绘制一组 8×6 的轴网需要反复执行偏移、标注、编号操作,而用Python可以在三秒内生成。
{
"grid_name": "轴网-01",
"x_spacing": [4200, 3600, 3600, 4200],
"y_spacing": [6000, 2400, 6000],
"x_labels": ["1", "2", "3", "4", "5"],
"y_labels": ["A", "B", "C", "D"],
"extension": 2500,
"circle_radius": 400
}import json
with open("D:/Project/grid_config.json", "r", encoding="utf-8") as f:
cfg = json.load(f)
def add_line(start, end):
"""在中望CAD模型空间中添加直线"""
return ms.AddLine(
win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, list(start)),
win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, list(end))
)
def add_circle(center, radius):
"""添加圆(用于轴线编号)"""
return ms.AddCircle(
win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, list(center)),
radius
)
# 计算累积坐标
x_coords = [0]
for sp in cfg["x_spacing"]:
x_coords.append(x_coords[-1] + sp)
y_coords = [0]
for sp in cfg["y_spacing"]:
y_coords.append(y_coords[-1] + sp)
ext = cfg["extension"]
cir_r = cfg["circle_radius"]
# 绘制垂直轴线(沿X方向偏移)
for i, x in enumerate(x_coords):
y_min = y_coords[0] - ext
y_max = y_coords[-1] + ext
add_line((x, y_min, 0), (x, y_max, 0))
# 底部编号圆
add_circle((x, y_min - cir_r * 2, 0), cir_r)
# 绘制水平轴线(沿Y方向偏移)
for i, y in enumerate(y_coords):
x_min = x_coords[0] - ext
x_max = x_coords[-1] + ext
add_line((x_min, y, 0), (x_max, y, 0))
# 左侧编号圆
add_circle((x_min - cir_r * 2, y, 0), cir_r)
print("轴网绘制完成")设计思路: 轴网间距参数用JSON管理而非硬编码,原因有二:一是不同项目的轴网尺寸不同,修改配置文件比修改代码安全;二是JSON可以纳入版本管理(Git),设计变更时追溯轴网参数的历史记录。
图纸完成后最常见的体力劳动是将DWG逐张导出为PDF。中望CAD的导出方法需要指定打印范围,手动操作需要逐张框选。Python方案是遍历模型空间中的图框块,读取每个图框的边界坐标作为打印窗口,一键批量导出。
import os
output_dir = "D:/Project/PDF_Output"
os.makedirs(output_dir, exist_ok=True)
# 遍历模型空间中所有块参照
for entity in ms:
if entity.EntityName == "AcDbBlockReference":
name = entity.Name.upper()
if "TITLE_BLOCK" not in name and "A3" not in name and "A4" not in name:
continue # 跳过非图框块
# 获取图框包围盒
bbox = entity.GetBoundingBox()
min_pt = bbox[0] # 左下角
max_pt = bbox[1] # 右上角
# 获取图纸编号(从图框属性中提取)
dwg_no = "unknown"
for attr in entity.GetAttributes():
if attr.TagString.upper() == "DWG_NO":
dwg_no = attr.TextString
break
# 设置打印窗口并导出PDF
layout = doc.ActiveLayout
plot_cfg = doc.PlotConfigurations.Add(f"TEMP_{dwg_no}")
# 图幅尺寸判断(A3=420×297, A4=297×210)
width = max_pt[0] - min_pt[0]
height = max_pt[1] - min_pt[1]
paper_name = "A3" if width > 350 else "A4"
plot_cfg.CanonicalMediaName = paper_name
# 窗口打印范围
plot_cfg.PlotWindowArea(
win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
[min_pt[0], min_pt[1]]),
win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8,
[max_pt[0], max_pt[1]])
)
pdf_path = os.path.join(output_dir, f"{dwg_no}.pdf")
doc.PlotToFile(pdf_path, plot_cfg.ConfigName)
print(f"[{dwg_no}] → {pdf_path}")
print(f"PDF导出完成,文件数:{len(os.listdir(output_dir))}")实际使用中的两个坑:
一是中望CAD的打印配置在批量操作时不稳定,连续打印过多文件后偶尔出现内存泄漏导致COM连接中断。解决方法是在每10张图后插入 doc.Regen(0) 刷新图形数据库并手动调用垃圾回收。
二是DWG文件名与图框中的图号不一致时,PDF文件名会出错。建议在实际脚本开头加入一致性校验:遍历所有图框提取图号集合,与Excel清单做差集比对,发现不匹配时输出警告后暂停。
COM自动化开发中最难排查的不是语法错误,而是COM对象引用失效——Python端的变量指向了CAD中已被释放的对象,调用时报 COMError: (-2147417848, '被调用的对象已与其客户端断开连接')。
排查路径:
ActiveDocument 和 ModelSpace,避免因图形数据库更新导致引用失效。win32com.client.VARIANT 类型,直接传Python的tuple在部分COM方法中会导致类型不匹配的静默错误——对象被创建但位置落在原点。GetObject(None, "ZWCAD.Application") 连接到已运行实例(而不是每次Dispatch启动新实例),可以直观看到脚本每一步在CAD中的效果。本文基于中望CAD 2025 + Python 3.11 + pywin32 306 实测。代码中的VARIANT数组构造依赖 pythoncom 模块,该模块随 pywin32 一并安装,无需额外依赖。中望CAD可通过官网下载安装,支持 Windows 10/11 系统。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。