☰
text-to-cad 实战:自然语言生成 CAD 模型与 STEP/DXF/URDF 导出
2026/10/8 5:40:36 网站建设 项目流程

1. 从一段文字到一张图纸:text-to-cad 到底在解决什么问题

如果你在机械设计、建筑结构或者工业制造行业待过一段时间,就一定经历过这样的场景:客户发来一段文字描述——“一个长宽高分别是 120mm、80mm、50mm 的铝合金外壳,四角做 R5 圆角,顶面开两个直径 20mm 的散热孔,孔间距 60mm”——然后你需要打开 CAD 软件,手动拉伸、倒角、打孔,一步步把这段文字变成三维模型。整个过程熟练工也得花十几分钟,复杂一点的零件半小时起步。text-to-cad 这个方向要干的事情,就是把这十几分钟甚至半小时的重复劳动压缩到几秒钟。

它的核心逻辑并不神秘:把自然语言描述解析成结构化的几何参数,再通过程序化建模接口生成 CAD 实体,最后导出成 STEP、DXF、URDF 这些下游工具能直接读取的格式。关键词里出现的 STEP 是三维实体交换格式,DXF 是二维图纸交换格式,URDF 是机器人仿真里描述连杆和关节的 XML 格式——这三个格式基本覆盖了从设计到仿真到出图的全链路。

这篇文章适合谁看?如果你是会写 Python 但不太懂 CAD 二次开发的工程师,或者你是懂设计但想用脚本批量处理图纸的技术人员,再或者你是做机器人仿真需要批量生成 URDF 模型的研究者,这篇内容都能给你一套可以直接上手跑的思路。我不会只讲概念,而是把参数解析、几何生成、格式导出、踩坑经验全部拆开讲清楚。

2. 自然语言到几何参数:解析层怎么设计才靠谱

2.1 为什么不能直接让大模型输出 CAD 命令

很多人第一反应是:既然有大语言模型,直接让它输出 AutoCAD 的 LISP 脚本或者 FreeCAD 的 Python 命令不就行了?我一开始也是这么想的,实测下来问题很大。大模型输出的代码看起来像那么回事,但经常出现坐标计算错误、单位混乱、实体布尔运算顺序不对等问题。更致命的是,它生成的代码不可复现——同样的输入,换个时间跑出来的结果可能不一样。

正确的做法是把任务拆成两步:第一步,让模型只负责把自然语言转成结构化的 JSON 参数;第二步,用确定性的代码根据 JSON 参数生成几何。这样模型只做它擅长的事情——理解语义和提取参数,几何计算交给精确的数学库来完成。

举个例子,输入“一个 120x80x50 的盒子,四角 R5 圆角,顶面两个直径 20mm 的孔,孔间距 60mm”,解析层应该输出这样的结构:

{ "type": "box", "dimensions": {"length": 120, "width": 80, "height": 50}, "unit": "mm", "fillets": {"edges": "vertical", "radius": 5}, "features": [ { "type": "hole", "face": "top", "diameter": 20, "count": 2, "spacing": 60, "position": "centered" } ] }

这个 JSON 就是解析层和几何层之间的契约。只要这个结构定义得足够清晰,后面生成几何的代码就是纯数学问题,不涉及任何模糊判断。

2.2 参数提取中最容易翻车的三个地方

第一个坑是单位推断。用户说“长 120”,到底是毫米还是厘米还是英寸?我的做法是在解析层强制要求单位,如果用户没写,默认按毫米处理,但在返回结果里加一个unit_assumed: true的标记,提醒用户确认。这个细节看起来小,但实际项目中因为单位搞错导致整个零件报废的情况我见过不止一次。

第二个坑是相对位置描述。“孔在顶面中间偏左 20mm”这种描述,不同的人理解不一样。偏左是从哪个方向看?是沿着长度方向还是宽度方向?我的处理方式是建立一个标准坐标系:以零件底面中心为原点,长度方向为 X 轴,宽度方向为 Y 轴,高度方向为 Z 轴。所有位置描述都转换到这个坐标系下的绝对坐标。如果用户的描述有歧义,解析层会返回一个ambiguity字段,列出可能的解释让用户选择。

第三个坑是特征顺序。先倒角再打孔和先打孔再倒角,结果可能完全不同。比如孔的位置如果刚好在圆角边上,顺序不同会导致孔被切掉一部分或者圆角失败。我的做法是在 JSON 里用数组顺序明确表示特征的应用顺序,并且在几何生成层严格按照这个顺序执行。

2.3 用 Pydantic 做参数校验的实操细节

解析层输出的 JSON 不能直接信任,必须做严格的校验。我用 Pydantic 定义了一套数据模型,每个字段都有类型约束和范围检查。比如直径必须是正数,圆角半径不能大于最小边长的一半,孔间距不能超过零件尺寸等。

from pydantic import BaseModel, Field, validator class Hole(BaseModel): diameter: float = Field(gt=0, le=500) count: int = Field(ge=1, le=100) spacing: float = Field(gt=0) @validator('spacing') def spacing_must_fit(cls, v, values): # 这里可以加更复杂的校验逻辑 return v

这样做的好处是,如果模型输出的参数不合理,会在解析阶段就报错,而不是等到几何生成时才发现问题。报错信息也能直接告诉用户哪个参数有问题,方便修正。

3. 几何生成引擎选型:FreeCAD、OpenCASCADE 还是 CadQuery

3.1 三个候选方案的实测对比

几何生成是整个流程的核心,选错工具后面会非常痛苦。我实际用过的三个方案是 FreeCAD 的 Python API、直接调用 OpenCASCADE 的 Python 绑定(pythonocc)、以及 CadQuery。下面这张表是我自己的实测对比:

维度FreeCAD APIpythonoccCadQuery
安装难度中等,需要装完整 FreeCAD较高,依赖多低,pip 直接装
建模代码简洁度一般,API 比较啰嗦低,需要手动管理拓扑高,链式调用很直观
STEP 导出支持支持支持
DXF 导出支持需要额外处理支持
布尔运算稳定性偶尔出问题稳定稳定
文档和社区丰富但分散一般活跃且集中
适合场景需要 GUI 交互底层定制纯脚本批量生成

我最终选了 CadQuery 作为主力方案,原因是它的代码写起来最像“描述几何”而不是“操作 CAD 软件”。比如生成一个带圆角和孔的盒子,CadQuery 的代码是这样的:

import cadquery as cq result = (cq.Workplane("XY") .box(120, 80, 50) .edges("|Z").fillet(5) .faces(">Z").workplane() .hole(20) .pushPoints([(-30, 0), (30, 0)]) .hole(20) )

这段代码的可读性非常好,基本上看一遍就知道在做什么。而且 CadQuery 底层用的就是 OpenCASCADE,几何内核的稳定性有保障。

3.2 圆角失败:最常见的几何生成报错

圆角操作是几何生成里最容易出问题的地方。当你对一个边做圆角时,如果相邻边的长度小于圆角半径,或者圆角面和其他特征发生干涉,操作就会失败。CadQuery 会抛出一个ValueError,但错误信息往往不够具体。

我的处理策略是分三步:第一,在参数校验阶段就检查圆角半径是否合理,比如半径不能超过最短边长的 40%;第二,在几何生成时用 try-except 捕获异常,如果圆角失败就自动降低半径重试,每次降低 20%,最多重试三次;第三,如果自动降级后仍然失败,就跳过圆角并在返回结果里加一个警告,告诉用户哪个边没能成功倒角。

def safe_fillet(workplane, edges, radius, max_retries=3): for i in range(max_retries): try: return workplane.edges(edges).fillet(radius) except ValueError: radius *= 0.8 return workplane # 放弃圆角,返回原始形状

这个策略在实际项目中救了我很多次。用户不需要知道底层发生了什么,他们只需要拿到一个能用的模型,哪怕圆角稍微小一点也比整个生成失败要好。

3.3 孔特征定位的坐标系陷阱

打孔的时候最容易搞混的是坐标系。CadQuery 的workplane()方法会创建一个新的局部坐标系,后续的hole()和pushPoints()都是在这个局部坐标系下操作的。如果你在顶面创建了工作平面,然后想在一个相对于零件中心的位置打孔,需要先想清楚局部坐标系的原点在哪里。

我的经验是:每次创建 workplane 之后,先用center()方法把原点移到面的中心,然后再用绝对坐标计算孔的位置。这样逻辑最清晰,不容易出错。比如顶面两个孔间距 60mm,那就是在 X 轴上 -30 和 +30 的位置各打一个孔。

还有一个细节:hole()方法默认是贯穿整个零件的。如果你只想打一个盲孔,需要指定深度参数。这个在文档里写得不明显,我一开始就踩过这个坑,打出来的孔直接把零件穿透了。

4. 导出格式的门道:STEP、DXF、URDF 各自怎么处理

4.1 STEP 导出:注意单位和工作坐标系

STEP 是三维实体交换的标准格式,几乎所有 CAD 软件都能读。CadQuery 导出 STEP 很简单:

cq.exporters.export(result, "output.step")

但有两个细节需要注意。第一,CadQuery 默认导出的单位是毫米,这符合大多数机械设计场景,但如果你需要英寸,需要在导出前对模型做缩放。第二,导出的坐标系是建模时的坐标系,如果下游软件期望不同的原点位置,需要在导出前做平移变换。

我遇到过一个实际问题:生成的模型导入到某个仿真软件后,位置总是偏的。排查了半天才发现,那个仿真软件默认把 STEP 文件的几何中心放在场景原点,而我的模型原点在底面中心。解决办法是在导出前把模型整体平移,让几何中心和原点重合。

4.2 DXF 导出:二维投影的坑比想象中多

DXF 主要用于二维图纸交换,从三维模型生成 DXF 需要先做投影。CadQuery 可以通过section()方法获取截面,然后导出为 DXF。但这里有个问题:截面得到的是一个面,而 DXF 需要的是轮廓线。

我的做法是先获取截面的外轮廓线,再导出:

section = result.section(0) # 在 Z=0 处截断 cq.exporters.export(section, "output.dxf")

实测下来,简单的形状没问题,但如果有内部孔洞,导出的 DXF 可能会丢失孔的信息。这时候需要手动提取所有轮廓线,包括外轮廓和内孔轮廓,然后分别导出。这个处理比较繁琐,但如果你的下游流程需要 DXF 来做激光切割或者线切割,这一步绕不开。

另外提醒一点:DXF 的版本很多,不同软件对版本的兼容性不一样。我一般导出为 R2010 版本,兼容性最好。CadQuery 的导出器默认版本可能不同,需要查一下文档确认。

4.3 URDF 生成:从几何模型到机器人描述文件

URDF 是机器人仿真里描述连杆和关节的格式,它本身不包含几何信息,而是引用外部的网格文件(通常是 STL 或 DAE)。所以从 text-to-cad 生成 URDF 的流程是:先生成三维几何,导出为 STL,然后生成引用这些 STL 的 URDF XML 文件。

URDF 的核心结构包括 link(连杆)和 joint(关节)。每个 link 包含视觉几何、碰撞几何和惯性参数。视觉几何和碰撞几何可以直接引用 STL 文件,惯性参数需要根据几何形状和材料密度计算。

<robot name="generated_part"> <link name="base_link"> <visual> <geometry> <mesh filename="base.stl"/> </geometry> </visual> <collision> <geometry> <mesh filename="base.stl"/> </geometry> </collision> <inertial> <mass value="0.5"/> <inertia ixx="0.001" ixy="0" ixz="0" iyy="0.001" iyz="0" izz="0.001"/> </inertial> </link> </robot>

惯性参数的计算是个容易忽略的点。如果只是做视觉演示,随便填一个值也能跑;但如果要做动力学仿真,惯性矩阵不对会导致结果完全错误。我的做法是用 CadQuery 获取模型的体积,乘以材料密度得到质量,然后根据几何形状用标准公式计算惯性矩。对于复杂形状,可以用网格划分工具做数值积分。

5. 批量生成与自动化:把单次生成变成流水线

5.1 从 Excel 参数表到批量 STEP 文件

实际工作中,更多时候不是处理单条文字描述,而是有一张 Excel 表格,每一行是一个零件的参数,需要批量生成所有模型。这个场景下,text-to-cad 的解析层可以简化——直接读 Excel 的列作为参数,跳过自然语言解析这一步。

我的做法是用 pandas 读 Excel,每一行转成一个 JSON 对象,然后循环调用几何生成函数。关键是要做好错误隔离:某一个零件生成失败不能影响其他零件。用 try-except 包住每次生成,失败的记录写到日志文件里,最后统一报告。

import pandas as pd df = pd.read_excel("parts.xlsx") results = [] for idx, row in df.iterrows(): try: params = row.to_dict() model = generate_model(params) cq.exporters.export(model, f"output/part_{idx}.step") results.append({"idx": idx, "status": "success"}) except Exception as e: results.append({"idx": idx, "status": "failed", "error": str(e)})

这个流程跑一百个零件大概两三分钟,比手动建模快太多了。而且参数表改一下重新跑就行,完全可复现。

5.2 文件命名和目录结构的规范

批量生成最容易乱的是文件管理。我的规范是:输出目录按日期分文件夹,文件名包含零件编号和关键参数。比如20250115/part_001_120x80x50.step。这样即使过了几个月回头看,也能一眼知道这个文件是什么。

另外建议在输出目录里放一个manifest.json,记录每个文件的生成时间、输入参数、使用的代码版本。这个习惯在出问题回溯时非常有用。

5.3 性能优化:什么时候该用并行

单次生成一个简单零件大概 0.5 到 2 秒,一百个零件串行跑也就两三分钟,没必要上并行。但如果零件复杂(比如有几百个特征),或者数量上千,串行就太慢了。这时候可以用 Python 的multiprocessing做并行。

需要注意的是,CadQuery 和 OpenCASCADE 不是线程安全的,所以不能用多线程,必须用多进程。每个进程独立初始化 CadQuery 环境,处理一部分零件。进程数一般设为 CPU 核心数,太多反而会因为上下文切换降低效率。

from multiprocessing import Pool def process_part(args): idx, params = args model = generate_model(params) cq.exporters.export(model, f"output/part_{idx}.step") return idx with Pool(processes=4) as pool: results = pool.map(process_part, enumerate(parts))

实测下来,4 个进程处理 200 个中等复杂度零件,总时间从 6 分钟降到不到 2 分钟,提升还是很明显的。

6. 实际项目中踩过的坑和应对策略

6.1 模型输出不稳定:同样的输入为什么结果不一样

这个问题困扰了我很久。同样的文字描述,有时候生成的模型是对的,有时候圆角位置偏了,有时候孔打穿了。排查后发现根源在自然语言解析层——大模型对同一句话的理解每次可能有细微差异,导致输出的 JSON 参数不一致。

解决办法是在解析层加缓存。把输入文本做哈希,如果之前处理过相同的文本,直接返回缓存的 JSON 结果。这样既保证了结果一致性,又减少了模型调用次数。缓存可以用简单的 JSON 文件实现,也可以用 SQLite。

另一个办法是在解析层的 prompt 里加 few-shot 示例,让模型输出格式更稳定。我一般会放三到五个示例,覆盖常见的零件类型和特征组合。这个投入很值得,能显著降低解析出错的概率。

6.2 复杂特征的组合爆炸

当零件同时包含拉伸、旋转、圆角、倒角、孔、槽等多种特征时,参数空间会急剧膨胀。我一开始试图用一个通用的 JSON schema 覆盖所有情况,结果 schema 越来越复杂,维护成本极高。

后来我换了个思路:把特征做成可组合的模块,每个模块有自己的参数定义和生成函数。JSON 里用一个数组表示特征序列,每个特征有自己的类型和参数。这样新增特征类型只需要加一个模块,不影响已有的代码。

{ "base": {"type": "box", "dimensions": [120, 80, 50]}, "features": [ {"type": "fillet", "edges": "vertical", "radius": 5}, {"type": "hole", "face": "top", "diameter": 20, "positions": [[-30, 0], [30, 0]]}, {"type": "chamfer", "edges": "bottom", "distance": 2} ] }

这个设计的好处是扩展性强,而且每个特征的生成逻辑可以独立测试,出了问题容易定位。

6.3 下游软件兼容性:STEP 文件打不开怎么办

生成的 STEP 文件在某些 CAD 软件里打不开,这是很常见的问题。原因通常有两个:一是 STEP 文件的版本不对,二是几何本身有缺陷(比如面没有闭合)。

版本问题好解决,CadQuery 支持指定 STEP 的协议版本,一般用 AP214 兼容性最好。几何缺陷比较麻烦,需要用 OpenCASCADE 的修复工具做一遍检查。CadQuery 提供了Shape.fix()方法,可以修复一些常见的几何问题。

model = model.fix() # 修复几何缺陷 cq.exporters.export(model, "output.step", "STEP", opt={"write_pcurves": False})

如果修复后仍然打不开,那可能是下游软件本身的问题。我遇到过某个国产 CAD 软件对 STEP 的支持不完整,同样的文件在 FreeCAD 里能打开,在那个软件里就报错。这种情况只能导出为其他格式(比如 IGES 或 STL)来绕过。

7. 关于 text-to-cad 后续扩展的一些个人想法

这个方向目前能做到的是“文字描述到参数化模型”的自动化,但离“任意文字描述到任意复杂模型”还有距离。我个人的判断是,短期内最现实的落地场景是标准化零件的批量生成——比如法兰、支架、外壳这类形状规整、参数有限的零件。这些场景下,解析层的准确率可以做到很高,几何生成也稳定。

再往远一点看,如果能结合草图识别和三维重建,让用户上传一张手绘草图或者参考照片,自动提取几何参数并生成模型,那适用场景会宽很多。但这涉及到计算机视觉和几何推理的深度结合,技术难度不小。

我在实际使用中最大的体会是:不要追求一步到位。先把一个细分场景做透,比如只处理“带孔和圆角的盒子类零件”,把解析准确率和生成稳定性做到 95% 以上,再逐步扩展特征类型。这样每一步都有可用的产出,而不是做一个大而全但什么都不精的系统。

另外一个小技巧:在解析层的 prompt 里明确告诉模型“如果描述中有不确定的地方,返回一个 clarification 字段而不是猜测”。这个改动让我的解析准确率提升了不少,因为模型不再强行猜测模糊描述,而是把问题抛回给用户确认。虽然多了一步交互,但避免了生成错误模型后返工的麻烦。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询