text-to-cad 实战:从自然语言到 STEP/URDF 的自动化建模流水线

📅 发布时间:2026/10/8 20:15:53
text-to-cad 实战:从自然语言到 STEP/URDF 的自动化建模流水线
1. 从一句话到三维模型text-to-cad 到底在解决什么问题第一次听到 text-to-cad 这个词我脑子里蹦出来的画面是对着电脑敲一行字比如“一个 80x60x10 的带四个 M4 沉头孔的安装板”然后软件自动吐出一个能直接进 CAM 加工的 STEP 文件。这个画面在几年前还属于科幻范畴但现在已经有一批开源项目和商业工具在往这个方向走了。text-to-cad 的核心命题非常直白——用自然语言描述替代手工建模操作让 CAD 模型的生成过程从“鼠标点击参数输入”变成“文本描述自动解析”。这件事为什么值得关注因为传统 CAD 建模的门槛其实不低。你得熟悉草图约束、特征树、布尔运算、基准面选择这一整套操作逻辑。一个熟练的机械工程师建一个中等复杂度的零件可能也要十几分钟到半小时。而如果只是需要一个标准件、一个简单的结构件、或者一个用于仿真验证的占位模型这个时间成本就显得很不划算。text-to-cad 瞄准的正是这个缝隙把重复性高、参数化程度高、结构相对规整的建模任务交给文本解析和程序化生成来完成。它适合谁来用我梳理了一下大概有三类人受益最明显。第一类是做仿真和机器人方向的工程师他们经常需要快速生成 URDF 模型用于运动学验证对几何精度要求没那么苛刻但对生成速度要求很高。第二类是做参数化设计的产品开发人员他们的模型本身就是由一组参数驱动的用文本描述反而比在 GUI 里拖拽更高效。第三类是需要批量生成 CAD 数据的场景比如做数据集、做模板库、做自动化测试手工建模完全不现实。关键词里出现了 STEP、URDF、G-code 这三个格式这其实暗示了 text-to-cad 的完整链路文本描述 → 几何内核生成实体 → 导出为 STEP通用 CAD 交换格式或 URDF机器人描述格式→ 必要时转 G-code 进入加工。这条链路打通了从想法到实物之间的路径就缩短了一大截。下面我会按照这个链路的逻辑把 text-to-cad 的整体设计思路、核心技术点、实操流程和踩坑经验逐一拆开讲。2. 整体设计思路为什么是“文本解析 几何内核 格式导出”三层架构2.1 核心架构的选型逻辑text-to-cad 的实现方式有很多种我见过用大语言模型直接生成 CAD 脚本的也见过用规则引擎做模板匹配的还有用扩散模型生成三维网格再转实体的。但如果你想要一个稳定、可控、可复现的方案目前最靠谱的架构还是三层分离文本解析层、几何生成层、格式导出层。为什么要把这三层分开因为每一层的技术栈和失败模式完全不同。文本解析层负责把自然语言转成结构化的参数描述这一层的难点在于语义理解的准确性和容错性。几何生成层负责根据参数调用几何内核比如 OpenCASCADE、CGAL、Manifold生成实体这一层的难点在于几何操作的鲁棒性。格式导出层负责把内部实体转成 STEP、URDF、STL 等目标格式这一层的难点在于格式规范的兼容性。三层分开之后任何一层的改动不会影响其他层调试和替换都方便得多。我试过把文本解析和几何生成揉在一起让大语言模型直接输出 OpenSCAD 代码或者 CadQuery 脚本。这样做的好处是开发速度快但问题也很明显生成的代码质量不稳定遇到复杂几何容易报错而且错误信息很难定位。后来改成三层架构之后文本解析层只负责输出 JSON 格式的参数描述几何生成层用固定的模板去消费这些参数稳定性提升了一个档次。2.2 文本解析层的设计考量文本解析层的核心任务是把“一个 80x60x10 的带四个 M4 沉头孔的安装板”这样的描述转成类似下面这样的结构化数据{ type: plate, dimensions: {length: 80, width: 60, thickness: 10}, features: [ { type: counterbore_hole, standard: M4, count: 4, positions: [[10,10], [70,10], [10,50], [70,50]] } ] }这个转换过程有两种主流做法。一种是基于大语言模型的语义解析用 prompt engineering 让模型输出结构化 JSON。另一种是基于规则的模式匹配用正则表达式和关键词词典来提取参数。两种做法各有优劣我的经验是混合使用先用规则匹配处理高频的标准描述匹配不到再走大语言模型兜底。为什么不全用大语言模型因为成本和延迟。规则匹配可以在毫秒级完成而且结果完全确定。大语言模型虽然灵活但每次调用都有延迟而且输出格式偶尔会漂移。对于 text-to-cad 这种需要频繁调用的场景规则匹配做主力、大语言模型做补充是比较务实的方案。2.3 几何生成层的技术选型几何生成层是整个系统的核心选型直接决定了能支持多复杂的几何。目前主流的几何内核有这几个内核名称语言优势劣势适用场景OpenCASCADEC功能最全支持 B-Rep学习曲线陡编译重工业级 CADCadQueryPython语法简洁基于 OCCT复杂曲面支持有限参数化零件OpenSCADC语法简单适合程序化只支持 CSG无 B-Rep简单结构件ManifoldC速度快鲁棒性好功能相对基础网格处理CGALC算法丰富偏学术工程化弱计算几何如果你要导出 STEP 文件那基本只能选 OpenCASCADE 或者基于它的 CadQuery。因为 STEP 是 B-Rep 格式需要内核支持精确的边界表示。如果你只需要 STL 或者网格格式那 OpenSCAD 和 Manifold 也够用。URDF 本身不限制几何格式但通常配合 STL 或 DAE 使用所以网格内核也能胜任。我个人的选择是CadQuery 作为主力几何生成库。原因有三第一它是 Python 的和文本解析层、导出层可以无缝集成第二它底层就是 OpenCASCADE导出的 STEP 质量有保障第三它的 API 设计比较符合直觉写参数化模板很顺手。2.4 格式导出层的兼容性处理导出层看起来简单实际上坑不少。STEP 导出相对标准但要注意单位制和坐标系约定。URDF 导出要处理 link 和 joint 的层级关系还要注意 mesh 文件的路径引用。G-code 导出则涉及 CAM 策略不是简单转换就能用的。这里重点说一下 URDF。URDF 是机器人描述格式它本身不包含几何体的精确定义而是引用外部 mesh 文件。所以 text-to-cad 生成 URDF 的流程是先生成几何实体导出为 STL再写 URDF 文件引用这些 STL。URDF 里的 joint 类型、轴向、限位这些参数也需要从文本描述里解析出来。比如“一个两连杆机械臂第一关节绕 Z 轴旋转第二关节绕 Y 轴旋转”这种描述就需要映射到 URDF 的 joint 定义。3. 核心细节解析文本到实体的关键映射规则3.1 尺寸与单位的解析策略自然语言里的尺寸描述非常灵活。“80x60x10”、“长80宽60厚10”、“80毫米乘60毫米乘10毫米”这些表达方式都得能识别。我的做法是维护一个单位词典和维度关键词表先把文本归一化再提取数值和单位。单位处理有个容易忽略的点CAD 内核通常用毫米作为默认单位但 URDF 用米。如果文本里写的是“0.08米 x 0.06米 x 0.01米”解析出来的数值是 0.08、0.06、0.01直接喂给 CadQuery 就会生成一个 0.08mm 的板子完全不对。所以解析层必须做单位换算统一转成毫米再传给几何层导出 URDF 时再转回米。注意单位换算是 text-to-cad 里最容易出错的环节之一。建议在解析层就统一到毫米并在日志里打印换算前后的数值方便排查。3.2 特征描述的语义映射“带四个 M4 沉头孔的安装板”这句话里包含了特征类型沉头孔、标准M4、数量四个、以及隐含的位置信息通常均匀分布或四角分布。解析层需要把这些信息都提取出来。M4 沉头孔的标准尺寸是有国标可查的孔径 4.5mm沉头直径 9.4mm沉头深度 2.5mm。这些数据应该内置在标准件库里而不是让大语言模型去猜。我见过一些实现直接用大语言模型生成孔径数值结果每次生成的都不一样完全不可用。标准件的尺寸必须查表不能靠模型生成。位置信息的处理更复杂。“四角分布”需要根据板的尺寸计算四个角的坐标还要考虑边距。“均匀分布”需要根据数量和板宽计算间距。这些计算逻辑应该封装成独立的函数输入板尺寸和特征参数输出坐标列表。3.3 几何特征的布尔运算顺序多个特征叠加时布尔运算的顺序会影响最终结果。比如先打孔再倒角和先倒角再打孔结果可能不同。text-to-cad 需要有一套确定的运算顺序规则。我的做法是按特征类型分层处理先做基础实体拉伸、旋转再做减材特征孔、槽、切除最后做修饰特征倒角、圆角。同一层内的特征按解析顺序依次执行。这个规则简单但有效能覆盖大部分常见场景。如果文本描述里没有明确顺序就按这个默认规则走。如果用户明确指定了顺序比如“先倒角再打孔”那解析层需要识别这种顺序描述并调整执行序列。这个功能实现起来不难但需要额外的语义解析逻辑。3.4 坐标系与基准面的约定CAD 建模离不开坐标系。text-to-cad 需要约定一个默认坐标系并在文本描述涉及方向时做映射。我的约定是Z 轴向上XY 平面为基准面原点在基础实体的几何中心或左下角根据实体类型决定。对于“安装板”这类零件原点放在底面中心比较合理。对于“连杆”这类零件原点放在一端比较合理。这些约定应该写在文档里并在解析层做对应的偏移处理。如果用户描述里提到了“以左下角为原点”解析层需要识别并覆盖默认约定。URDF 的坐标系约定又不一样它通常要求每个 link 的坐标系在关节处。所以从 CAD 实体到 URDF link 的转换需要做坐标变换。这个变换矩阵的计算是 URDF 导出里最容易出错的地方建议用可视化工具验证。4. 实操过程从零搭建一个 text-to-cad 流水线4.1 环境准备与依赖安装先说一下环境。我用的技术栈是 Python 3.10 CadQuery 2.4 FastAPI做服务接口 Pydantic做数据校验。CadQuery 的安装稍微麻烦一点因为它依赖 OpenCASCADE 的 Python 绑定。# 创建虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装 CadQuery pip install cadquery # 安装其他依赖 pip install fastapi uvicorn pydantic python-multipartCadQuery 在 Linux 和 macOS 上安装比较顺利Windows 上偶尔会遇到 OCCT 动态库找不到的问题。如果遇到可以试试用 conda 安装conda install -c conda-forge cadquery安装完成后跑一个简单的测试import cadquery as cq # 生成一个 80x60x10 的板 result cq.Workplane(XY).box(80, 60, 10) cq.exporters.export(result, test.step) print(STEP 文件导出成功)如果这一步能跑通说明几何内核没问题。4.2 文本解析模块的实现文本解析模块我分成两部分规则匹配器和大语言模型兜底。规则匹配器用正则表达式和关键词词典处理高频的标准描述。大语言模型兜底用 API 调用处理规则匹配不了的复杂描述。规则匹配器的核心是一个模式列表import re PATTERNS [ { name: box, regex: r(\d(?:\.\d)?)\s*[x×乘]\s*(\d(?:\.\d)?)\s*[x×乘]\s*(\d(?:\.\d)?), handler: lambda m: { type: box, dimensions: { length: float(m.group(1)), width: float(m.group(2)), height: float(m.group(3)) } } }, { name: hole, regex: r(\d)\s*个\s*(M\d)\s*(沉头孔|通孔|螺纹孔), handler: lambda m: { type: hole, count: int(m.group(1)), standard: m.group(2), hole_type: m.group(3) } } ]这个模式列表可以不断扩充。每遇到一种新的描述方式就加一条规则。规则匹配的好处是结果确定、速度快、可调试。大语言模型兜底的部分我用的是 prompt 模板PROMPT_TEMPLATE 你是一个 CAD 参数解析器。请把下面的自然语言描述转成 JSON 格式的参数。 描述{description} 输出格式 {{ type: 实体类型, dimensions: {{length: 数值, width: 数值, height: 数值}}, features: [{{type: 特征类型, params: {{...}}}}] }} 只输出 JSON不要输出其他内容。 调用大语言模型的时候要注意输出必须做 JSON 校验解析失败就重试或者降级到默认参数。我一般会设置最多重试 3 次3 次都失败就返回错误信息给用户。4.3 几何生成模块的实现几何生成模块接收解析后的参数调用 CadQuery 生成实体。我把它设计成模板函数的形式每种实体类型对应一个函数import cadquery as cq def generate_plate(params): 生成安装板 dims params[dimensions] thickness dims[height] # 基础板 plate cq.Workplane(XY).box( dims[length], dims[width], thickness ) # 处理孔特征 for feature in params.get(features, []): if feature[type] hole: plate add_holes(plate, feature, dims) return plate def add_holes(plate, feature, dims): 在板上添加孔 count feature[count] standard feature[standard] # 查标准件表获取孔径 hole_dia STANDARD_HOLES[standard][hole_diameter] # 计算孔位四角分布 margin 10 # 边距 positions [ (-dims[length]/2 margin, -dims[width]/2 margin), (dims[length]/2 - margin, -dims[width]/2 margin), (-dims[length]/2 margin, dims[width]/2 - margin), (dims[length]/2 - margin, dims[width]/2 - margin) ] # 打孔 plate plate.faces(Z).workplane().pushPoints(positions).hole(hole_dia) return plate标准件表是一个字典内置常见的螺纹孔尺寸STANDARD_HOLES { M3: {hole_diameter: 3.4, counterbore_dia: 6.8, counterbore_depth: 1.8}, M4: {hole_diameter: 4.5, counterbore_dia: 9.4, counterbore_depth: 2.5}, M5: {hole_diameter: 5.5, counterbore_dia: 11.0, counterbore_depth: 3.0}, M6: {hole_diameter: 6.6, counterbore_dia: 13.0, counterbore_depth: 3.5}, }这些数据来自机械设计手册是经过验证的。千万不要让大语言模型去生成这些数值它可能会给出看似合理但实际上错误的尺寸。4.4 STEP 导出的参数配置STEP 导出看起来简单但有几个参数需要注意from cadquery import exporters exporters.export( result, output.step, exportTypeSTEP, opt{ write_pcurves: False, # 不写参数曲线减小文件体积 precision_mode: 1, # 精度模式1 为中等精度 unit: MM # 单位设为毫米 } )write_pcurves设为 False 可以显著减小 STEP 文件体积代价是丢失一些曲线信息。如果下游需要做精确的曲面分析可以设为 True。precision_mode控制导出精度0 最高、2 最低一般用 1 就够了。导出之后建议用 FreeCAD 或者在线 STEP 查看器打开验证一下。我遇到过导出的 STEP 在 CadQuery 里看着正常但在其他软件里打开发现孔的位置偏了的情况。后来发现是坐标系变换的问题在导出前加了一步result result.translate((0, 0, -thickness/2))把实体移到正确的位置就好了。4.5 URDF 导出的完整流程URDF 导出比 STEP 复杂因为涉及 link 和 joint 的层级结构。假设我们要生成一个两连杆机械臂的 URDF流程是这样的第一步生成每个 link 的几何体并导出为 STL# Link 1: 基座 link1 cq.Workplane(XY).box(100, 100, 20) exporters.export(link1, link1.stl) # Link 2: 连杆 link2 cq.Workplane(XY).box(200, 30, 30) exporters.export(link2, link2.stl)第二步写 URDF 文件?xml version1.0? robot nametwo_link_arm link namebase_link visual geometry mesh filenamelink1.stl scale0.001 0.001 0.001/ /geometry /visual collision geometry mesh filenamelink1.stl scale0.001 0.001 0.001/ /geometry /collision /link link namelink2 visual geometry mesh filenamelink2.stl scale0.001 0.001 0.001/ /geometry /visual /link joint namejoint1 typerevolute parent linkbase_link/ child linklink2/ origin xyz0 0 0.02 rpy0 0 0/ axis xyz0 0 1/ limit lower-3.14 upper3.14 effort10 velocity1.0/ /joint /robot注意scale0.001 0.001 0.001这一行因为 STL 是毫米单位URDF 是米单位所以需要缩放 0.001。这个缩放因子很容易漏掉漏掉之后模型会大 1000 倍。提示URDF 里的 mesh 路径可以是相对路径也可以是package://开头的 ROS 包路径。如果要在 CoppeliaSim 里导入建议用相对路径并把 STL 和 URDF 放在同一目录下。4.6 在 CoppeliaSim 中验证 URDFURDF 生成之后最好在仿真环境里验证一下。CoppeliaSim以前叫 V-REP对 URDF 的支持比较好导入流程也简单。打开 CoppeliaSim点击菜单栏的Plugins → URDF import → Import URDF选择生成的 URDF 文件。如果一切正常你会看到模型出现在场景里并且关节可以拖动。导入时常见的几个问题模型不可见通常是 mesh 路径不对检查 STL 文件是否在 URDF 同目录下。模型比例不对检查 scale 参数毫米转米应该是 0.001。关节不能动检查 joint 的 type 和 axis 定义revolute 关节需要指定 axis。模型位置偏移检查 origin 的 xyz 和 rpy 参数这是关节坐标系相对于父 link 的变换。我踩过的一个坑是URDF 里的 origin 是关节坐标系相对于父 link 坐标系的变换不是相对于世界坐标系的。如果搞混了模型会出现在奇怪的位置。建议先用简单的单连杆模型验证确认坐标系约定之后再上复杂模型。5. 常见问题与排查技巧实录5.1 文本解析类问题问题一数值提取错误。比如“80x60x10”被解析成“80、60、10”但“M4”里的“4”也被当成尺寸提取了。解决办法是在正则表达式里加边界条件M 开头的标准件编号单独处理不参与尺寸提取。问题二单位混淆。用户写“0.08米”解析成 0.08但没做单位换算结果生成 0.08mm 的实体。解决办法是在解析层强制做单位归一化所有尺寸统一转毫米并在日志里记录换算过程。问题三特征遗漏。用户描述里提到了“带倒角”但解析层没有对应的模式特征被忽略。解决办法是维护一个未识别特征日志把解析不了的描述片段记录下来定期分析并补充模式。5.2 几何生成类问题问题一布尔运算失败。打孔时孔的位置超出了板的边界导致布尔运算报错。解决办法是在生成孔之前做边界检查如果孔位超出边界就调整位置或者报错提示。问题二实体不是有效的 B-Rep。某些几何操作可能生成非流形实体导致 STEP 导出失败。解决办法是在导出前做isValid()检查如果无效就尝试修复或者简化几何。问题三圆角/倒角失败。圆角半径过大超过了相邻边的长度导致操作失败。解决办法是限制圆角半径的最大值或者捕获异常并降级处理不做圆角。5.3 格式导出类问题问题一STEP 文件在其他软件里打开异常。通常是单位或者坐标系的问题。解决办法是在导出时明确指定单位并在导出前把实体移到标准位置。问题二URDF 在 CoppeliaSim 里导入失败。常见原因是 XML 格式错误或者 mesh 路径不对。解决办法是用xmllint检查 XML 格式用绝对路径测试 mesh 引用。问题三STL 文件太大。默认的 STL 导出精度太高导致文件体积过大。解决办法是调整导出精度参数或者用二进制 STL 格式。5.4 常见问题速查表问题现象可能原因排查方法解决方案解析结果为空描述不符合任何模式打印原始描述和匹配日志补充模式或走大模型兜底尺寸偏大/偏小单位换算错误检查解析后的数值和单位统一转毫米孔位置不对坐标系约定不一致可视化检查实体统一原点约定STEP 导出失败实体无效调用 isValid() 检查修复或简化几何URDF 导入失败XML 格式错误xmllint 校验修正 XML模型比例不对scale 参数遗漏检查 URDF 里的 scale设为 0.001关节不能动joint 定义错误检查 type 和 axis修正 joint 定义5.5 独家避坑技巧技巧一先用简单模型验证全链路。不要一上来就搞复杂模型。先用一个最简单的立方体跑通“文本→解析→生成→导出→验证”的全流程确认每一环都没问题再逐步增加复杂度。技巧二保留中间结果。解析后的 JSON、生成的实体、导出的文件都保留下来。出问题的时候可以快速定位是哪一环出了问题。技巧三建立回归测试集。收集一批典型的文本描述和对应的期望输出每次修改代码后跑一遍回归测试确保没有破坏已有功能。技巧四日志要详细。在解析层、生成层、导出层都加详细日志记录输入、输出、耗时、异常。出问题的时候日志是第一手线索。技巧五版本锁定。CadQuery 和 OpenCASCADE 的版本更新可能引入行为变化建议锁定版本号避免意外升级导致的问题。6. 扩展方向从 text-to-cad 到 text-to-manufacturingtext-to-cad 本身已经很有用了但它只是更大图景的一部分。往上游走可以接语音输入让用户直接说话就能生成模型。往下游走可以接CAM 模块自动生成 G-code 进入加工。中间还可以接仿真验证自动检查模型的力学性能或者运动学可行性。G-code 生成是另一个有意思的方向。从 CAD 到 G-code 通常需要经过 CAM 软件设置刀具、切削参数、走刀策略。如果 text-to-cad 生成的模型本身带有加工特征信息比如哪些面需要精加工、哪些孔需要攻丝那 CAM 环节也可以部分自动化。当然这涉及的知识面比纯几何生成要广得多需要补充切削参数库和刀具库。另一个扩展方向是批量生成。比如你需要生成 100 个不同尺寸的安装板用于做数据集或者做参数扫描。text-to-cad 可以接受一个参数表批量生成对应的 STEP 文件。这个场景在仿真和机器学习领域很有用。还有一个方向是与现有 CAD 软件集成。比如生成 FreeCAD 的 Python 脚本或者生成 SolidWorks 的宏。这样用户可以在自己熟悉的 CAD 环境里继续编辑生成的模型而不是被锁定在 text-to-cad 的工具链里。我在实际使用中发现text-to-cad 最适合的场景不是替代 CAD 工程师而是辅助快速原型设计和批量生成。它把建模的门槛降低到了“会描述需求”的程度但复杂曲面、自由造型、精密配合这些任务还是得靠传统 CAD 工具。把 text-to-cad 定位成“快速草稿工具”而不是“全自动建模神器”心态会好很多预期也会更合理。最后分享一个小技巧如果你要生成一批相似的模型可以把公共参数抽出来做成模板只把变化的部分留给文本描述。比如“安装板长度 {L}宽度 {W}厚度 10四个 M4 孔”然后批量替换 L 和 W 的值。这样比每次写完整描述效率高得多也更不容易出错。