恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
text-to-cad实战:从自然语言到STEP/URDF/G-code的分层转换与避坑指南
首页
资讯中心
/
text-to-cad实战:从自然语言到STEP/URDF/G-code的分层转换与避坑指南
text-to-cad实战:从自然语言到STEP/URDF/G-code的分层转换与避坑指南
发布时间:2026/10/8 5:41:19
1. 从一句话到三维模型text-to-cad 到底在解决什么问题第一次听到 text-to-cad 这个词我脑子里蹦出来的画面是对着电脑敲一行字比如“一个 80x40x20 的铝合金外壳四角带 M4 沉头孔壁厚 3 毫米”然后软件啪一下把 STEP 文件吐出来。这个画面在几年前还属于科幻范畴但现在已经有一批工具和方案在往这个方向靠了。text-to-cad 本质上是一类把自然语言描述转换成 CAD 可识别几何模型的技术路线它的输出通常不是某个私有格式而是 STEP、URDF、G-code 这类通用性强的中间格式。为什么是这几个格式因为 STEP 是工业界公认的实体模型交换标准URDF 是机器人领域描述连杆和关节的通用语言G-code 则是从模型走向加工的最后一步。换句话说text-to-cad 想打通的是“语言描述 → 几何定义 → 可制造/可仿真文件”这条链路。这件事的价值在哪儿我举个自己踩过的场景。做非标设备的时候经常需要根据客户口头描述快速出一个初步模型去对尺寸。传统流程是听懂需求 → 打开 CAD 软件 → 草图 → 拉伸 → 打孔 → 导出。一套下来熟练工也得十几分钟而且中间任何一处尺寸理解错了返工成本很高。text-to-cad 的思路是把“草图→拉伸→打孔”这些重复性劳动交给程序人只负责用语言把关键约束说清楚。它解决的不是“替代设计师”而是“把设计师从重复建模里捞出来”。适合关注这个方向的人其实挺杂的做机械设计的想提效做机器人仿真的想快速生成 URDF做 3D 打印的想从文字直接出 G-code甚至做教育的想让学生用自然语言理解几何约束。不同背景的人关注点不一样但核心诉求是一致的——降低从想法到模型的门槛。我在这篇文章里会把这套东西拆开讲整体设计思路怎么定、核心格式之间怎么转换、实操环节怎么落地、以及我实际折腾过程中遇到的一堆坑。内容会涉及 STEP、URDF、G-code 的具体处理方式也会给一些可以直接抄的参数和代码片段。不管你是刚接触 CAD 二次开发还是已经在做参数化建模想往智能化靠应该都能找到能用的东西。2. 整体设计思路为什么不是“直接生成”而是“分层转换”2.1 核心架构的选型逻辑很多人一开始会想能不能训练一个模型输入文字直接输出 STEP 文件我试过类似思路结论是现阶段不现实。原因在于 STEP 文件本质上是一堆 NURBS 曲面和拓扑关系的集合它的数据结构极其严谨一个实体的边界表示BRep要求面、边、点之间的连接关系完全自洽。让语言模型直接生成这种结构化数据相当于让它逐字节写一个二进制文件错误率会高到没法用。所以目前靠谱的方案都是分层的自然语言 → 中间参数化表示 → 几何内核操作 → 目标格式导出。这个中间层可以是 JSON 描述的参数表也可以是 Python 脚本还可以是 OpenSCAD 那种声明式代码。我自己的方案选的是“自然语言 → 结构化参数 JSON → CadQuery/OpenCASCADE 内核 → STEP/STL → 后处理”。为什么选 CadQuery因为它基于 OpenCASCADE而 OpenCASCADE 是工业级几何内核对 STEP 的支持非常成熟。另一个原因是 CadQuery 的 API 是链式的写起来接近自然语言的逻辑顺序比如Workplane().box(80,40,20).faces(Z).workplane().hole(4)这种表达很容易和语言模型输出的结构对应上。相比之下直接调 OpenCASCADE 的 C 接口学习曲线太陡用 FreeCAD 的脚本又受限于它的对象模型。CadQuery 在灵活性和稳定性之间取了一个不错的平衡点。这里有个关键决策点中间表示到底用“参数表”还是“代码”。参数表的好处是安全模型只能从预定义的模板里选参数不会生成乱七八糟的几何坏处是灵活性差遇到模板没覆盖的形状就抓瞎。代码的好处是灵活理论上能表达任意几何坏处是语言模型生成的代码可能跑不通甚至可能写出危险操作。我的做法是混合常见形状走参数表模板复杂形状走代码生成但代码在执行前会经过一层 AST 静态检查禁止文件读写和网络调用。这个检查步骤很关键我后面在避坑部分会详细说。2.2 格式转换链的设计与取舍从几何内核出来之后往哪个格式走取决于下游用途。STEP 适合做精确的实体交换它的优势是保留了完整的 BRep 信息任何主流 CAD 软件都能读而且精度无损。但 STEP 有个问题它不包含装配约束和运动学信息你拿到的是一个静态的几何体。如果下游是机器人仿真就需要 URDF。URDF 描述的是连杆link和关节joint的树状结构每个 link 有自己的几何形状和惯性参数每个 joint 有类型旋转、平移、固定和轴向。从 STEP 到 URDF 不是简单的格式转换而是要把一个整体几何拆成多个 link再定义它们之间的关节关系。这一步目前没有全自动的可靠方案我的做法是在参数 JSON 里就显式定义好 link 和 joint 的结构生成 STEP 的同时也生成 URDF两者共享同一套尺寸参数。G-code 则是另一条路。它不关心几何的精确表示只关心刀具轨迹。从模型到 G-code 通常要经过切片3D 打印或 CAM 刀路规划减材加工。text-to-cad 在这个环节的价值是语言描述里可以直接包含加工参数比如“层高 0.2填充 20%PLA 材料”这些参数和几何参数一起进入 JSON最后喂给切片引擎。我实测下来用 CadQuery 生成 STL再用 CuraEngine 的命令行模式切片整条链路可以完全脚本化。但要注意G-code 和 STEP 的精度要求完全不同STL 的网格精度如果设得太低薄壁特征会丢失打出来就是残次品。目标格式核心用途关键优势主要限制STEP精确实体交换无损 BRep通用性强无装配运动信息URDF机器人仿真含关节与惯性需手动拆分 linkG-code加工制造直接驱动机床依赖切片/CAM 参数STL3D 打印/可视化简单通用网格精度有损2.3 语言理解层的设计边界语言理解这块我的建议是不要追求“大而全”。很多人一上来就想让系统理解“一个复杂的机械臂”结果发现模型根本不知道从哪儿下手。正确的做法是限定领域词汇表。比如你做的是板金件那就把“折弯”“冲孔”“沉头”“翻边”这些词定义清楚每个词对应一组几何操作和默认参数。语言模型的任务只是把用户的话映射到这些预定义操作上而不是自由发挥。我试过用纯 prompt 让模型输出 CadQuery 代码结果它经常发明一些不存在的 API或者把hole和cboreHole搞混。后来改成“先分类到操作模板再填参数”稳定性大幅提升。另一个边界是尺寸推理。用户说“一个差不多手掌大的盒子”这个“手掌大”到底是多大我的处理方式是维护一个常用尺寸映射表比如“手掌大”映射到 100mm 左右“拇指粗”映射到 20mm同时在输出里明确标注“按 100mm 估算如需精确请指定数值”。这样既给了结果又留了修正余地。千万不要让模型自己瞎猜一个精确到小数点后三位的数字那是在制造虚假精度。3. 核心细节解析STEP、URDF、G-code 的处理要点3.1 STEP 文件的生成与校验用 CadQuery 导出 STEP 就一行代码cq.exporters.export(result, output.step)。但这一行背后有几个坑。第一个坑是单位。CadQuery 默认单位是毫米但 STEP 文件本身不强制单位有些软件读的时候会按英寸解释。我遇到过导出的模型在另一个软件里大了 25.4 倍就是因为单位没对齐。解决办法是在导出前显式设置cq.Unit或者在文件头里写入单位声明。第二个坑是实体有效性。如果几何操作产生了自相交或零厚度面STEP 文件可能能写出来但读的时候会报错。我的习惯是在导出前跑一遍result.val().isValid()不通过就回退到上一个有效状态。还有一个实际问题是文件大小。一个带很多小孔和圆角的零件STEP 文件可能几十兆。如果只是做可视化没必要用 STEP转成 STL 或 glTF 更合适。我一般会在 JSON 里加一个output_format字段根据用途自动选格式。如果是给下游 CAD 做进一步编辑必须 STEP如果是给网页预览glTF 足够如果是 3D 打印STL 加 G-code。import cadquery as cq # 从参数构建模型 length, width, height 80.0, 40.0, 20.0 hole_dia 4.0 result ( cq.Workplane(XY) .box(length, width, height) .faces(Z) .workplane() .rect(length - 10, width - 10, forConstructionTrue) .vertices() .hole(hole_dia) ) # 校验实体有效性 if not result.val().isValid(): raise ValueError(生成的实体无效请检查参数) # 导出 STEP cq.exporters.export(result, bracket.step)这段代码里forConstructionTrue是个容易忽略的点。它表示这个矩形只用来定位顶点不参与实际几何。如果不加这个参数矩形会被当成实体的一部分切出来结果就完全不对了。这种细节在文档里往往一笔带过但实际用的时候不知道就会卡很久。3.2 URDF 的关节定义与惯性参数URDF 的难点不在几何而在关节和惯性。几何部分可以直接从 STEP 或 STL 引用 mesh 文件但关节类型、轴向、限位这些必须显式定义。我见过很多人把 URDF 写成了一堆固定关节的集合结果导入仿真环境后机器人根本动不了。正确的做法是先想清楚运动链哪个 link 是父、哪个是子、关节绕哪个轴转、转多少度。这些信息在 text-to-cad 的 JSON 里就应该定义好而不是等到生成 URDF 时再补。惯性参数是另一个大坑。URDF 要求每个 link 有质量和惯性矩阵。如果随便填一个值仿真时会出现奇怪的抖动或者直接飞出去。我的做法是用几何体积乘以材料密度估算质量惯性矩阵用近似公式算。比如一个长方体绕中心轴的惯性矩是m*(w^2h^2)/12。虽然不精确但比瞎填强得多。如果对精度要求高可以用 FreeCAD 或 Blender 算完再填进去。link namebase_link visual geometry mesh filenamebase.stl scale0.001 0.001 0.001/ /geometry /visual collision geometry mesh filenamebase.stl scale0.001 0.001 0.001/ /geometry /collision inertial mass value0.5/ inertia ixx0.001 ixy0 ixz0 iyy0.001 iyz0 izz0.001/ /inertial /link注意scale那个 0.001。STL 通常以毫米为单位导出但 URDF 的标准单位是米所以必须缩放。这个坑我踩过不止一次导入后模型大得像个房子排查半天才发现是单位问题。3.3 G-code 的切片参数与路径规划从模型到 G-code核心是切片参数。层高、壁厚、填充密度、打印速度、温度这些参数直接决定成品质量。text-to-cad 在这个环节的价值是把语言描述里的加工意图翻译成切片参数。比如用户说“要结实一点”那就把填充密度从 20% 提到 40%壁厚从 2 层加到 3 层。说“表面要光滑”那就把层高降到 0.1mm。这些映射关系需要事先定义好不能指望切片软件自己理解。我用的是 CuraEngine 的命令行模式因为它可以完全脚本化不需要打开图形界面。调用方式大概是CuraEngine slice -j fdmprinter.def.json -l model.stl -o output.gcode -s layer_height0.2 -s infill_sparse_density20 -s wall_line_count3这里-j指定打印机定义文件-l指定输入模型-s覆盖具体参数。实测下来这套流程跑通之后从文字到 G-code 可以做到全自动。但有个前提模型必须是流形manifold的也就是没有破面、没有自相交。STL 如果是从有问题的 STEP 转过来的切片时会出现空洞或者错层。所以我在导出 STL 之前一定会做一次网格修复用trimesh或者admesh都行。4. 实操过程从一句话到可加工文件的完整链路4.1 环境搭建与依赖安装先把环境说清楚。我用的主力是 Python 3.10CadQuery 2.4CuraEngine 5.xtrimesh 3.x。CadQuery 的安装推荐用 conda因为它的依赖里有 OpenCASCADE 的二进制包pip 装有时候会缺库。conda create -n text2cad python3.10 conda activate text2cad conda install -c conda-forge cadquery pip install trimesh urdfpyCuraEngine 需要单独下载编译好的二进制放到 PATH 里就行。如果你用的是 Windows注意把它的路径加到系统环境变量不然命令行调用会找不到。我一开始在 Windows 上折腾了半天后来发现是路径里有空格导致解析出错换到没有空格的目录就好了。语言理解层我用的是本地部署的小模型加规则引擎不依赖外部接口。这样做的好处是响应快、数据不出本地坏处是理解能力有限。如果你的场景词汇比较固定规则引擎加小模型完全够用。如果场景很开放那可能需要更大的模型但那就涉及到算力和部署成本的问题了得自己权衡。4.2 参数 JSON 的结构设计整个链路的核心是那个参数 JSON。它既是语言理解的输出也是几何生成的输入。我的结构大概长这样{ part_name: mounting_bracket, units: mm, geometry: { type: box_with_holes, length: 80, width: 40, height: 20, holes: [ {position: corners, diameter: 4, depth: through} ] }, material: aluminum_6061, output: [step, stl], manufacturing: { process: cnc, tolerance: 0.1 } }这个结构的好处是清晰、可校验。geometry.type决定了用哪个模板holes数组描述了孔的位置和尺寸output决定导出哪些格式。语言理解层的任务就是把“一个 80 乘 40 乘 20 的铝板四角打 4 毫米通孔”映射成这个 JSON。映射规则可以写得很细比如“通孔”对应depth: through“沉头”对应type: counterbore并附带沉头直径和深度。我建议在 JSON 里加一个confidence字段记录每个参数的可信度。如果某个尺寸是从模糊描述里猜的就标低一点生成后在界面上高亮提示用户确认。这个做法在实际使用中能减少很多“我以为你懂我意思”的扯皮。4.3 几何生成与格式导出的完整脚本把上面的东西串起来主流程大概是这样import json import cadquery as cq from cadquery import exporters def build_from_json(params): geo params[geometry] if geo[type] box_with_holes: result cq.Workplane(XY).box(geo[length], geo[width], geo[height]) if geo.get(holes): result ( result.faces(Z).workplane() .rect(geo[length] - 10, geo[width] - 10, forConstructionTrue) .vertices() .hole(geo[holes][0][diameter]) ) else: raise ValueError(f不支持的几何类型: {geo[type]}) if not result.val().isValid(): raise ValueError(几何无效) return result def export_all(model, params, basename): outputs params.get(output, [step]) if step in outputs: exporters.export(model, f{basename}.step) if stl in outputs: exporters.export(model, f{basename}.stl, tolerance0.01) if urdf in outputs: generate_urdf(model, params, f{basename}.urdf) if gcode in outputs: slice_to_gcode(f{basename}.stl, params, f{basename}.gcode)这里tolerance0.01是 STL 的网格精度单位是毫米。这个值越小网格越细文件越大。对于大多数 3D 打印件0.01 到 0.05 之间够用了。如果零件有很小的特征比如 0.5mm 的细缝那得设到 0.005 以下不然缝就糊住了。4.4 从 STL 到 G-code 的切片实操切片这一步我单独拎出来说因为它涉及的参数最多也最容易出问题。以 CuraEngine 为例一个典型的调用需要指定打印机定义、材料定义和一堆覆盖参数。我一般会把常用配置写成 JSON 文件调用时直接引用。CuraEngine slice \ -j fdmprinter.def.json \ -l bracket.stl \ -o bracket.gcode \ -s layer_height0.2 \ -s wall_line_count3 \ -s infill_sparse_density20 \ -s material_print_temperature210 \ -s material_bed_temperature60 \ -s speed_print50跑完之后一定要检查 G-code 的开头和结尾。开头应该有归零和预热指令结尾应该有回抽和关机指令。如果这些不对打印机可能直接撞头或者堵嘴。我习惯用gcode-parser之类的工具快速扫一遍确认没有异常指令。还有一个实际经验不同品牌的打印机对 G-code 的方言支持不一样。比如有的机器用M104设温度有的用M109并等待。如果你的 G-code 是给特定机器用的最好在切片配置里选对机型或者手动改一下起始和结束代码。这个没有通用解只能针对具体设备调。5. 常见问题与排查技巧实录5.1 几何生成阶段的典型报错问题一实体无效isValid 返回 False。最常见的原因是布尔运算产生了零厚度面或者自相交。比如在一个面上打孔孔的位置正好在边缘上切出来的面就退化了。解决办法是调整孔的位置留出至少一个壁厚的余量。如果必须贴边那就把孔改成缺口用切除而不是打孔。问题二圆角失败。对一条边做圆角如果相邻面的夹角太小或者边长不够圆角会失败。CadQuery 的fillet会直接抛异常。我的做法是先用edges()选中要倒角的边检查它的长度是否大于圆角半径的两倍不满足就跳过或者减小半径。问题三导出 STEP 后文件打不开。十有八九是单位或者编码问题。先确认导出时单位设置正确再检查文件路径里有没有中文或特殊字符。有些老版本的 CAD 软件对 UTF-8 路径支持不好换成纯英文路径就能解决。5.2 URDF 导入仿真环境的常见坑URDF 导入 CoppeliaSim 或者 Gazebo 时最常见的问题是 mesh 路径不对。URDF 里的filename可以是相对路径也可以是绝对路径但不同仿真器对相对路径的解析基准不一样。CoppeliaSim 通常相对于 URDF 文件所在目录Gazebo 则相对于某个模型包路径。我的做法是统一用package://前缀然后在仿真环境里配置好包路径映射。如果嫌麻烦直接用绝对路径也行但换机器就得改。另一个坑是关节轴向。URDF 里关节的axis是相对于 joint 坐标系的不是相对于世界坐标系。如果你定义了一个绕 Z 轴旋转的关节但 joint 坐标系本身被旋转过那实际转轴就不是世界 Z 轴了。这个在简单模型里不容易发现一旦模型复杂起来运动就会变得很奇怪。排查方法是把 URDF 导入后手动拖一下关节看运动方向是否符合预期。5.3 G-code 打印失败的排查清单现象可能原因排查方法第一层不粘平台温度低/调平不准提高 bed 温度重新调平中途断层耗材卡住/温度波动检查送丝机构稳定热端温度尺寸偏大材料收缩/步进校准校准 E 步调整收缩补偿表面粗糙层高太大/速度太快降低层高和打印速度孔洞堵死填充重叠/网格问题检查 STL 流形性调整填充重叠率这张表是我自己打印失败多次之后总结的基本上覆盖了八成以上的常见问题。其中“孔洞堵死”这个特别隐蔽因为模型看起来没问题切片预览也正常但打出来孔就是实的。后来发现是 STL 网格在孔的位置有微小破面切片引擎把孔当成了实体。用trimesh的repair功能修一下就好了。5.4 语言理解层的避坑心得语言理解这块最大的坑是“过度自信”。模型对模糊描述会给出一个看起来很确定的答案但实际上它是在猜。比如“打几个孔”它可能默认打四个但用户心里想的是六个。我的做法是在输出 JSON 里加一个assumptions数组把所有猜测都列出来生成后显示给用户确认。这个做法看起来麻烦但比事后返工强得多。另一个坑是单位混用。用户可能说“长 8 厘米”也可能说“长 80 毫米”还可能说“长 8 公分”。这些都得映射到统一的毫米单位。我维护了一个单位映射表覆盖厘米、毫米、米、英寸、公分这些常见说法。英寸转毫米是 25.4这个系数一定要记准不然差之毫厘谬以千里。6. 工具选型与扩展思路6.1 几何内核的对比与选择OpenCASCADE 是功能最全的但学习曲线陡。CadQuery 封装了它用起来舒服很多。FreeCAD 的 Part 模块也是基于 OpenCASCADE但它的脚本接口更偏向于操作文档对象不如 CadQuery 直接。如果你要做的是建筑或土木方向的模型那可能更适合用 Blender 的脚本或者 IfcOpenShell。选哪个内核取决于你的几何复杂度和团队的技术栈。我的建议是机械零件用 CadQuery建筑用 IfcOpenShell可视化用 trimesh 或 Blender。6.2 从 text-to-cad 到 text-to-cam 的延伸text-to-cad 的下一步自然是 text-to-cam。既然语言能描述几何那也能描述加工策略。比如“先粗铣再精铣粗铣留 0.5 余量精铣转速 8000”这些完全可以结构化后喂给 CAM 引擎。FreeCAD 的 Path 工作台支持脚本化生成刀路理论上可以和 text-to-cad 的 JSON 对接。我试过一个简单的例子从 JSON 读取零件尺寸和加工参数自动生成一个平面铣削的 G-code。虽然离实用还有距离但方向是通的。6.3 批量处理与自动化集成如果你有一堆相似的零件要处理text-to-cad 的批量能力就体现出来了。把每个零件的描述写成一行文字批量转成 JSON再批量生成模型和 G-code。我用这个方式处理过一批支架类零件二十多个变体从描述到 G-code 全部自动完成只花了不到十分钟。如果手动建模一个下午都搞不定。批量处理的关键是模板要足够通用参数要足够灵活。如果每个零件都要改代码那就失去自动化的意义了。7. 我个人在实际操作中的几点体会这套东西我断断续续折腾了大半年最大的体会是不要追求一步到位。一开始我想做一个“万能”的 text-to-cad 系统什么都能生成结果什么都做不好。后来把范围缩到“板类零件带孔”反而稳定了。先在一个窄领域里做到能用再慢慢扩展比一开始就铺大摊子靠谱得多。第二个体会是校验比生成更重要。生成一个模型不难难的是保证它是对的。我现在花在校验上的时间比生成还多实体有效性检查、尺寸范围检查、单位一致性检查、网格流形性检查。这些检查看起来繁琐但能挡住绝大多数低级错误。没有校验的自动化就是在批量制造垃圾。第三个体会是保留人工干预的入口。全自动听起来很酷但实际用的时候用户总会有一些系统理解不了的需求。这时候如果能方便地手动改参数、改代码体验会好很多。我的做法是生成的 JSON 和 Python 脚本都保留下来用户可以直接编辑后重新生成。这样系统是“辅助”而不是“替代”接受度会高很多。最后分享一个小技巧如果你也在做类似的东西建议把每次生成的输入描述、中间 JSON、输出文件和校验结果都存下来。攒够几百条之后你会发现哪些描述容易理解错、哪些参数经常需要调整。这些数据是优化系统的金矿比拍脑袋想规则有用得多。我现在就有一个这样的日志库每次遇到新的描述方式就加一条慢慢地把覆盖率提上去了。