一、痛点引入
发数节点到了。工艺要 STEP,供应商要 IGES,3D 打印那边要 STL,质量部要 CGR 做轻量化评审——同一批 200 个模型,四种格式。
你的操作是:文件 另存为 下拉选格式 找目录 保存 关窗口 下一个。一个模型四次,200 个模型就是 800 次点击,中间还要应付"文件已存在,是否覆盖"的弹窗。运气不好点错一次,输出目录里混进两个同名文件,交付前还得返工核对。
更麻烦的是:转换完谁成功了、谁失败了、哪个文件导出是 0 字节,全靠人工翻文件夹。这不是你手速慢,是 CATIA 的"另存为"本来就是给人单条操作设计的,不是给批量交付设计的。
今天教你用 Python COM API,一次扫描目录、自动转换、输出带耗时和体积的交付清单——200 个模型 8 分钟跑完,人可以去干别的。
二、核心思路
CATIA 的 Document 对象带一个 ExportData(iFileName, iType) 方法,本质就是"另存为"的编程版本:告诉它输出路径和格式代号,一次调用完成转换。整个链路只需要三步——打开文档、调 ExportData、关闭文档。
真正的工程难点不在转换本身,而在批量:递归扫目录、屏蔽弹窗、异常隔离(一个坏文件不能中断整批)、结果留痕。
Python脚本 COM接口 CATIA.Application
├─ DisplayFileAlerts = False 屏蔽"是否覆盖"弹窗
├─ RefreshDisplay = False 批量期间不刷屏,提速30%+
├─ os.walk() 递归扫描 .CATPart / .CATProduct
├─ for f in files:
│ ├─ Documents.Open(f) 必须用绝对路径
│ ├─ doc.ExportData(out, "stp")
│ ├─ os.path.getsize() 校验非0字节
│ ├─ except: 记录失败原因,continue(不中断整批)
│ └─ finally: doc.Close()
└─ csv.writer 输出交付清单(耗时/体积/状态)你需要准备:
- Python 3.7+、
pywin32 - 本机已装 CATIA V5(脚本会复用已启动的会话,没有则自动拉起)
- 一批待转换的 .CATPart / .CATProduct
三、环境搭建(2分钟)
pip install pywin32验证 COM 通道和导出方法是否可用:
import win32com.client
catia = win32com.client.Dispatch("CATIA.Application")
catia.Visible = True
doc = catia.ActiveDocument
print(f"当前文档: {doc.Name}")
print(f"ExportData 方法可用: {hasattr(doc, 'ExportData')}")
print("转换接口就绪。")能打出文档名和 True,说明 ExportData 在当前版本可用。老版本(V5R20 之前)如果只有 ExportAs,把调用替换成 doc.ExportAs(out_file, "stp") 即可,参数一致。
四、核心代码
# -*- coding: utf-8 -*-
"""CATIA 批量格式转换:.CATPart / .CATProduct -> STEP / IGES / STL"""
import os, time, csv
import win32com.client as win32
SRC_DIR = r"D:\Project\Models" # 源模型目录(递归扫描)
OUT_DIR = r"D:\Project\Deliver" # 输出目录
FORMAT = "stp" # stp / igs / stl / cgr / 3dxml
EXT_MAP = {"stp": ".stp", "igs": ".igs", "stl": ".stl",
"cgr": ".cgr", "3dxml": ".3dxml"}
SRC_EXT = (".catpart", ".catproduct")
def connect_catia():
"""连接(或拉起)CATIA,并关掉批量场景下的干扰项"""
catia = win32.Dispatch("CATIA.Application")
catia.Visible = True
catia.DisplayFileAlerts = False # 屏蔽"文件已存在/是否保存"弹窗,关键
catia.RefreshDisplay = False # 批量期间不刷新图形区,明显提速
return catia
def scan_models(src_dir):
"""递归扫描目录下所有 CATIA 模型,返回绝对路径列表"""
found = []
for root, _, names in os.walk(src_dir):
for n in names:
if n.lower().endswith(SRC_EXT):
found.append(os.path.join(root, n))
return sorted(found)
def convert_one(catia, src_file, out_dir, fmt):
"""转换单个文件,返回 (是否成功, 输出路径, 体积KB, 耗时s, 错误信息)"""
base = os.path.splitext(os.path.basename(src_file))[0]
out_file = os.path.join(out_dir, base + EXT_MAP[fmt])
os.makedirs(out_dir, exist_ok=True)
t0 = time.time()
doc = None
try:
doc = catia.Documents.Open(src_file) # 必须绝对路径,相对路径会报找不到文件
doc.ExportData(out_file, fmt) # 核心:一行完成格式转换
cost = round(time.time() - t0, 2)
size = os.path.getsize(out_file) / 1024 if os.path.exists(out_file) else 0
if size == 0: # 0字节=静默失败,必须拦下来
return False, out_file, 0, cost, "导出为0字节"
return True, out_file, round(size, 1), cost, ""
except Exception as e:
return False, out_file, 0, round(time.time() - t0, 2), str(e)[:120]
finally:
if doc is not None:
doc.Close() # 不关会累积占内存,几百个文件后CATIA卡死
def main():
catia = connect_catia()
files = scan_models(SRC_DIR)
print(f"扫描到 {len(files)} 个模型,开始转换...")
report = [("源文件", "输出文件", "体积KB", "耗时s", "状态", "错误信息")]
ok = fail = 0
for i, f in enumerate(files, 1):
success, out, size, cost, err = convert_one(catia, f, OUT_DIR, FORMAT)
if success:
ok += 1
print(f"[{i}/{len(files)}] OK {os.path.basename(f)} {size}KB {cost}s")
else:
fail += 1
print(f"[{i}/{len(files)}] FAIL {os.path.basename(f)} {err}")
report.append((f, out, size, cost, "成功" if success else "失败", err))
with open(os.path.join(OUT_DIR, "转换清单.csv"), "w",
newline="", encoding="utf-8-sig") as fp:
csv.writer(fp).writerows(report)
catia.RefreshDisplay = True # 恢复刷新,否则后续手动操作界面不更新
print(f"\n完成:成功 {ok} 个,失败 {fail} 个,清单已输出到 {OUT_DIR}")
if __name__ == "__main__":
main()代码解析:
ExportData(out_file, fmt):转换的唯一核心方法。fmt用格式代号字符串,stp/igs/stl/cgr/3dxml都支持,不需要装任何转换器。DisplayFileAlerts = False:批量场景的命门。不关的话,重名文件弹一次窗,脚本就卡在那里等你点,所谓的"自动化"直接变成"半自动"。finally: doc.Close():每个文件转完必须关。CATIA 打开几百个文档不释放,内存会一路涨到崩溃。size == 0判定:CATIA 转换失败时有时不抛异常,只生成一个空文件。只靠 try/except 会漏判,必须加体积校验。
五、效果对比
| 对比项 | 传统方法(手动另存为) | 自动化脚本 |
|---|---|---|
| 200个模型耗时 | 约 4 小时 | 约 8 分钟 |
| 人工操作次数 | 800+ 次点击 | 1 次运行 |
| 弹窗中断 | 每次重名都要点 | 全程无干扰 |
| 失败可追溯 | 靠人翻文件夹 | CSV 清单带错误原因 |
| 换格式重做 | 全部重来一遍 | 改一个变量重跑 |
| 出错率 | 高(漏转/重名/选错目录) | 极低(路径由代码生成) |
六、进阶用法
技巧1:保留源目录层级,让交付包结构对齐
上面的版本把所有输出平铺在一个目录里,源目录有 机身段/、系统件/ 子目录时结构会丢。加三行就能镜像原结构:
def convert_one(catia, src_file, out_dir, src_root, fmt):
rel = os.path.relpath(os.path.dirname(src_file), src_root) # 相对路径
out_sub = os.path.join(out_dir, rel) # 镜像到输出目录
os.makedirs(out_sub, exist_ok=True)
out_file = os.path.join(
out_sub, os.path.splitext(os.path.basename(src_file))[0] + EXT_MAP[fmt])
...交付包直接 zip 发给外协,对方解压后的目录结构和你的一致,不用再口头解释"这个文件原来在哪层"。
技巧2:长批处理的稳定性兜底
跑 500 个以上文件时,个别老模型会触发 CATIA 内部异常导致会话变"僵尸"。两个实用兜底:
# 1) 每 100 个文件重启一次 CATIA 会话,释放累积内存
if i % 100 == 0:
catia.Quit()
time.sleep(3)
catia = connect_catia()
# 2) 装配体导出前强制更新,避免导出到陈旧几何
doc = catia.Documents.Open(src_file)
if hasattr(doc, "Product"): # 是装配体
doc.Product.Update()
else:
doc.Part.Update()
doc.ExportData(out_file, fmt)另外提醒一句:STL 的弦高精度由 CATIA 选项(工具 选项 常规 性能和精度)控制,脚本本身不改它。交付前先手动导出一个确认精度满足要求,再跑批量,否则 200 个文件全是粗糙网格,返工成本更高。
关注回复「批量转换」获取完整源码 + 转换清单 CSV 模板
_栏目:开发实战 | 发布日期:2026-09-07 | 字数:约 1150 字 | 系列:CATIA Python COM API 入门 #11_