
对于需要批量操控 AutoCAD、跨版本联动、并行处理图纸的开发者,可参考下文,采用Python 通过 pywin32 统一控制多个实例与文档。
- 同时打开多个版本 AutoCAD 做兼容验证
- 多实例并行执行,提高批处理效率
- 同一实例内多文档顺序处理(批量绘图、标注、导出)
后台回复“多开”即可获取源码下载链接。
源代码安装与运行:
安装依赖pip install -r requirements.txt
运行实例:python examples/multi_thread_control.py
一、核心原理
1. COM 与 ProgID(多版本识别)
AutoCAD 每个版本都会在注册表注册 COM ProgID,例如:
`AutoCAD.Application.18`、`AutoCAD.Application.23`
通过注册表枚举所有 ProgID,从而识别系统安装了哪些版本。
2. ROT(Running Object Table,实例发现)
系统维护 ROT 来记录当前正在运行的 COM 对象。
从 ROT 枚举运行实例,如果失败,再逐个 ProgID 使用 `GetActiveObject` 连接。
3. COM 线程模型(多线程安全)
COM 是 STA(单线程单元)模型:
每个线程必须自己初始化 COM,并且不能跨线程共享 COM 对象。
因此需要封装 `ComThread`,保证每个线程内部 `CoInitialize()`。
二、源代码项目结构与职责



1、cadcom/com.py: COM 线程封装 `ComThread`,上下文 `com_initialized`
2、cadcom/rot.py: ROT 枚举与包装; `cadcom/autocad.py`;多版本 ProgID;运行实例发现与去重; 多文档管理
3、examples/list_instances.py: 列出已安装与运行实例;
4、examples/multi_thread_control.py; 多实例并行; 同实例多文档绘制圆
三、多版本识别:注册表枚举实现
def list_installed_progids(prefix: str = AUTOCAD_PROGID_PREFIX) -> list[str]:progids: list[str] = []with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, "") as root:index = 0while True:try:name = winreg.EnumKey(root, index)except OSError:breakindex += 1if name.startswith(prefix):progids.append(name)return sorted(set(progids))
- `HKEY_CLASSES_ROOT` 保存所有 COM ProgID
- 通过前缀筛选 AutoCAD 版本
- 返回所有已安装版本
四、多实例发现:ROT + 回退策略**
def list_running_instances() -> list[CadInstance]:instances: list[CadInstance] = []instance_keys: set[str] = set()for entry in iter_running(prefix=None):prog_id = _extract_prog_id(entry.display_name)if not prog_id.startswith(AUTOCAD_PROGID_PREFIX):continueadd_instance(entry.display_name, prog_id, entry.dispatch)if not instances:for prog_id in list_installed_progids(prefix=AUTOCAD_PROGID_PREFIX):try:app = win32com.client.GetActiveObject(prog_id)except Exception:continueadd_instance(f"{prog_id} (GetActiveObject)", prog_id, app)return instances
- 先从 ROT 读取运行实例
- 若 ROT 无结果,回退逐 ProgID 连接
- 同一实例可能被多个 ProgID 指向,使用 `HWND` 去重
五、多线程安全控制:ComThread 机制
class ComThread(threading.Thread):def run(self) -> None:pythoncom.CoInitialize()try:self._result.result = self._target(*self._args, **self._kwargs)finally:pythoncom.CoUninitialize()
- 每个线程进入时初始化 COM
- 退出时释放
- 防止“跨线程 COM 对象调用”的错误
六、示例 1:多实例并行绘制圆
import pythoncomimport win32com.clientfrom cadcom import ComThread, connect_by_index, list_running_instancesdef point(x, y, z):return win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, (x, y, z))def draw_circle(doc, radius):doc.ModelSpace.AddCircle(point(0.0, 0.0, 0.0), radius)def worker(index: int, radius: float) -> None:app = connect_by_index(index)doc = app.ActiveDocumentdraw_circle(doc, radius)running = list_running_instances()threads = []for i in range(len(running)):radius = 5.0 + i * 5.0t = ComThread(target=worker, args=(i, radius))t.start()threads.append(t)for t in threads:t.join()

七、示例 2:同一实例多文档绘制不同直径圆
import pythoncomimport win32com.clientfrom cadcom import connect_by_index, list_documents, activate_documentdef point(x, y, z):return win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, (x, y, z))def draw_circle(doc, radius):doc.ModelSpace.AddCircle(point(0.0, 0.0, 0.0), radius)app = connect_by_index(0)docs = list_documents(app)for info in docs:doc = activate_document(app, index=info.index)radius = 10.0 + info.index * 5.0draw_circle(doc, radius)print(f"文档[{info.index}] {info.name}: 直径 {radius * 2:.1f}")
- 同一实例内多文档必须先激活
- 操作顺序执行,避免同实例 UI/命令通道冲突

总结
通过 COM + ROT + ProgID + DispatchEx 的组合,
实现了 AutoCAD 的多版本识别、多实例并行、多文档精细控制,并且遵循 STA 模型保证线程安全。
这套结构可进一步扩展到更复杂的批处理和工程自动化场景。
