资讯详情 text-to-cad实战:从自然语言到STEP/URDF的自动化建模流水线
📅 2026/10/8 11:54:25
1. 从一句话到三维模型text-to-cad 到底在解决什么问题第一次听到 text-to-cad 这个词我脑子里蹦出来的画面是对着电脑敲一行字比如“一个 80x60x10 的带四个 M4 沉头孔的法兰盘”然后软件啪一下把 STEP 文件吐出来。这个画面在几年前还属于科幻范畴但现在已经有一批开源项目和商业工具在往这个方向走了。text-to-cad 的核心命题非常直白——用自然语言描述替代手工建模操作直接生成可用的 CAD 几何数据。它要解决的不是“建模好不好看”的问题而是“从想法到可制造文件之间那段最枯燥的重复劳动”。传统 CAD 工作流是什么样的你打开 SolidWorks、Fusion 360 或者中望 CAD新建草图画线标注尺寸拉伸倒角打孔装配导出 STEP。一个中等复杂度的零件熟练工也得折腾半小时到几小时。如果是参数化系列件比如不同长度的支架、不同孔径的法兰那更是复制粘贴改尺寸的无限循环。text-to-cad 想干的事情就是把这套“人肉翻译”过程交给程序你说人话它出几何。这个方向涉及的技术栈其实相当杂。前端是自然语言理解中间是几何内核和参数化建模引擎后端要输出标准格式——STEP 用于通用三维交换URDF 用于机器人仿真G-code 用于加工制造。热搜词里出现的“urdf导入coppeliasim”就说明很多人拿到模型后的下一步是丢进仿真环境里跑运动学验证。而“cad切地形”“cad图纸合并”“python批量对cad修改”这些词则暴露了另一层需求大家不只是想要新模型还想让程序去操作已有的 CAD 数据。适合关注这个方向的人大概分三类。第一类是机械工程师和产品设计师天天跟 CAD 打交道想从重复建模里解放出来。第二类是机器人方向的开发者需要快速生成 URDF 模型做仿真手搓 link 和 joint 太痛苦。第三类是做自动化工具链的开发者想把 CAD 生成能力集成到自己的系统里比如根据订单参数自动出图。不管你是哪一类理解 text-to-cad 的底层逻辑和实操路径都比单纯等一个“完美工具”要实在得多。2. 拆解 text-to-cad 的技术链路从文字到几何到底经历了什么2.1 自然语言解析把“人话”翻译成“机器能懂的参数”text-to-cad 的第一步永远是对输入文本做结构化解析。你说“一个长 100 宽 50 高 20 的长方体中心有一个直径 10 的通孔”程序需要提取出形状类型是长方体尺寸参数是 100/50/20特征是通孔孔位在中心孔径 10。这个过程在技术上有几种实现路径。早期做法是基于规则模板匹配。开发者预先定义一堆正则表达式和关键词映射比如“长方体”对应 box“直径”对应 diameter“通孔”对应 through_hole。这种方案的好处是可控、可预测坏处是稍微换个说法就歇菜。你说“方块”它可能认识说“六面体”就懵了。我试过用这种方案做一个简单的支架生成器结果用户输入“打个洞”和“开个孔”我得写两条规则维护成本随场景线性增长。现在更主流的做法是用大语言模型做语义解析输出结构化的 JSON 或 DSL。比如输入“一个 80x60 的板四角各有一个 M4 沉头孔板厚 5”模型输出可能是{ shape: plate, width: 80, height: 60, thickness: 5, features: [ { type: counterbore_hole, thread: M4, count: 4, position: corners } ] }这个 JSON 就是后续几何生成的“施工图纸”。关键在于LLM 的输出必须被严格约束在预定义的 schema 里否则下游代码没法解析。我一般会用 function calling 或者 JSON mode 来强制格式同时在 prompt 里把可用的特征类型、参数范围、单位约定写清楚。单位这块特别容易翻车——用户说“10 个厚”到底是 10mm 还是 10cm我的做法是默认毫米但在解析结果里显式标注 unit 字段遇到歧义就报错让用户确认。注意自然语言解析阶段最大的坑不是模型不够聪明而是用户描述本身有歧义。“中间打个孔”里的“中间”是几何中心还是视觉中心“大一点”是多大我的经验是宁可多问一句也不要猜。在工具设计上解析完先展示结构化参数让用户确认比直接出模型再返工要高效得多。2.2 几何内核选型为什么 STEP 和 URDF 是两条不同的路解析出参数之后下一步是生成真正的几何体。这里就涉及到几何内核的选择而选择的核心依据是输出格式的用途。如果你的目标是造东西——3D 打印、CNC 加工、钣金折弯——那你需要的是 B-rep边界表示几何输出 STEP 或 IGES。STEP 文件里存的是精确的曲面和实体信息加工软件能直接读。常用的开源几何内核有 OpenCASCADEOCCTPython 里通过 pythonocc 或 cadquery 调用。CadQuery 这个库我用得比较多它用链式 API 描述几何写起来像这样import cadquery as cq result (cq.Workplane(XY) .box(80, 60, 5) .faces(Z).workplane() .rect(60, 40, forConstructionTrue) .vertices() .cboreHole(4.5, 8, 2) .edges(|Z).fillet(2)) cq.exporters.export(result, plate.step)这段代码生成一个 80x60x5 的板四角有 M4 沉头孔立边倒 R2 圆角导出 STEP。CadQuery 底层就是 OCCT所以出来的 STEP 质量跟商业软件是一个级别的。但如果你的目标是机器人仿真情况就不一样了。URDFUnified Robot Description Format本质上是一个 XML 文件它描述的是连杆和关节的拓扑关系而不是精确的实体几何。URDF 里的 link 可以引用 STL 或 DAE 做视觉网格但它的核心是 joint 的类型、轴向、限位。热搜词里“urdf导入coppeliasim”之所以常见就是因为 CoppeliaSim 这类仿真器需要 URDF 来定义机器人的运动学结构。text-to-cad 在机器人场景下的典型用法是用户描述“一个六轴机械臂基座高 200大臂长 300小臂长 250末端法兰直径 50”系统生成对应的 URDF 文件每个 link 附带简化的 STL 碰撞体。这里几何精度不是首要的关节坐标系的位置和朝向才是。我踩过的坑是用 CadQuery 生成的漂亮实体直接转 URDF结果 joint origin 对不上仿真里机械臂直接飞出去。后来学乖了URDF 生成必须单独处理坐标系变换不能指望几何库自动搞定。输出格式核心用途几何类型推荐工具链STEP加工制造、通用交换B-rep 精确实体CadQuery OCCTURDF机器人仿真、运动学拓扑 简化网格手写 XML trimeshG-code3D 打印、CNC切片路径从 STEP/STL 切片生成STL快速原型、可视化三角网格从 B-rep 网格化2.3 参数化建模引擎text-to-cad 的“心脏”几何内核负责“画”参数化建模引擎负责“怎么画”。这两者的关系有点像 CPU 和操作系统——内核提供基础运算能力引擎提供组织逻辑。在 text-to-cad 场景下参数化引擎要解决的核心问题是如何把解析出来的参数映射到可复用的建模操作序列上。我自己的做法是建立一个“特征库”。每个特征是一个函数输入参数字典输出几何操作。比如through_hole(diameter, position)、fillet(radius, edge_selector)、pattern_linear(count, spacing, direction)。当解析器输出 JSON 后引擎按顺序调用这些特征函数逐步构建模型。这种架构的好处是新增一种特征只需要写一个函数不用改主流程。但这里有个隐藏的难点特征之间的依赖顺序。先打孔再倒角和先倒角再打孔结果可能完全不同。用户描述里往往不包含顺序信息引擎需要自己推断。我的策略是定义一个默认优先级基础形状 布尔运算 孔/槽 倒角/圆角 阵列。这个顺序覆盖了大多数机械零件的建模习惯但遇到特殊情况还是得让用户显式指定。另一个坑是尺寸链和约束。用户说“孔在板的正中间”这是一个约束不是绝对坐标。如果板宽 80孔位就是 x40。但如果用户后面又说“板宽改成 100”孔位应该自动跟着变。这就要求引擎维护一个参数依赖图而不是把数值写死。CadQuery 的 Workplane 体系天然支持这种相对定位但如果你用底层 OCCT API 直接操作就得自己维护这套逻辑。3. 动手实现一个最小可用的 text-to-cad 流水线3.1 环境搭建与依赖选择要跑通一个 text-to-cad 的最小闭环你需要的核心依赖其实不多。我推荐的技术栈是 Python CadQuery 一个大语言模型 API。CadQuery 负责几何LLM 负责语义解析中间用 JSON 做胶水。安装 CadQuery 最省事的方式是用 condaconda create -n text2cad python3.11 conda activate text2cad conda install -c conda-forge cadquerypip 也能装但 OCCT 的二进制依赖在 pip 下偶尔会出问题conda-forge 的包经过充分测试稳得多。装完之后跑一句import cadquery; print(cadquery.__version__)验证一下能打印版本号就说明几何内核正常加载了。LLM 这块你可以用任何提供结构化输出能力的接口。关键是要能强制 JSON 格式并且支持 function calling 或者 JSON mode。我在 prompt 里会定义一个“特征清单”把支持的形状和特征类型列清楚让模型只能从清单里选。这样虽然牺牲了一点灵活性但换来了下游解析的确定性。提示如果你不想依赖外部 API也可以本地跑一个小模型做解析但实测下来7B 级别的模型在参数提取准确率上跟大模型差距明显尤其是涉及多特征组合和单位换算的时候。我的建议是解析用大模型几何生成用本地库这样既保证了理解能力又保证了数据不出本地。3.2 从文本到 JSON解析器的实现细节解析器的核心是一个精心设计的 prompt。我不会直接把用户输入丢给模型而是先做一轮预处理统一单位表述“毫米”“mm”“个厚”都归一成 mm提取数值和单位的配对标记可能的歧义点。预处理之后再把清洗过的文本和特征 schema 一起送给模型。Schema 的定义很关键。我用 Pydantic 定义了一个PartSpec模型from pydantic import BaseModel, Field from typing import List, Optional, Literal class HoleFeature(BaseModel): type: Literal[through_hole, blind_hole, counterbore, countersink] diameter: float Field(description孔径单位 mm) depth: Optional[float] Field(defaultNone, description盲孔深度通孔不填) position: List[float] Field(description孔中心坐标 [x, y]) count: int Field(default1) class PartSpec(BaseModel): shape: Literal[box, cylinder, plate, flange] dimensions: dict features: List[HoleFeature] [] fillets: List[dict] [] material: Optional[str] None把 Pydantic 的 JSON schema 塞进 prompt模型输出的 JSON 就能直接被PartSpec.model_validate_json()解析。如果校验失败就把错误信息回传给模型让它重试一般两轮之内都能修好。实测下来最容易出错的字段是position。用户说“四角各一个孔”模型有时候输出[[0,0],[80,0],[0,60],[80,60]]有时候输出[[5,5],[75,5],[5,55],[75,55]]。前者是角点后者是内缩了 5mm。我的处理方式是在 schema 里加一个position_mode字段可选corner、edge_center、center、explicit让模型显式声明定位模式然后由几何生成器根据板件尺寸计算实际坐标。这样就把“理解”和“计算”分开了各司其职。3.3 几何生成与导出把 JSON 变成 STEP 文件拿到PartSpec之后几何生成就是按部就班的映射。我写了一个build_part(spec)函数用 match-case 分发到不同的形状构建器def build_part(spec: PartSpec): if spec.shape plate: wp cq.Workplane(XY).box( spec.dimensions[width], spec.dimensions[height], spec.dimensions[thickness] ) elif spec.shape cylinder: wp cq.Workplane(XY).circle( spec.dimensions[diameter] / 2 ).extrude(spec.dimensions[height]) for hole in spec.features: wp wp.faces(Z).workplane().pushPoints( [tuple(hole.position)] ).hole(hole.diameter) for fillet in spec.fillets: wp wp.edges(fillet[selector]).fillet(fillet[radius]) return wp导出 STEP 就一行cq.exporters.export(build_part(spec), output.step)如果你需要 URDF思路类似但输出的是 XML。我会用trimesh把 CadQuery 的实体转成 STL 网格然后手写 URDF 的 link 和 joint 标签。这里的关键是 joint origin 的计算——它通常位于两个 link 的交接面中心需要根据几何尺寸手动推算。我一般会在 PartSpec 里额外加一个joints字段让用户或模型显式指定关节位置而不是从几何反推。G-code 的生成则更靠后一步。你需要先把 STEP 或 STL 导入切片软件比如 Cura 的引擎或者 PrusaSlicer 的命令行设置层高、填充率、支撑等参数再导出 G-code。text-to-cad 在这个环节能做的是自动填充切片参数比如根据模型高度推荐层高根据悬垂角度决定是否加支撑。但切片本身还是交给专业工具更靠谱。4. 实操中绕不开的坑常见问题与排查实录4.1 几何生成失败的典型原因问题一布尔运算报错提示“无法计算交集”。这通常发生在孔特征的位置刚好落在实体边缘或者面上。比如板宽 80孔位 x80孔径 10孔的一半在实体外面。OCCT 在这种情况下会直接抛异常。我的处理方式是在生成前做一次边界检查孔中心到最近边的距离必须大于孔径的一半加上一个安全余量我一般用 0.5mm。如果校验不过要么调整孔位要么报错让用户改描述。问题二倒角半径过大导致几何退化。用户说“所有边倒 R5”但板厚只有 3mm倒角半径超过厚度的一半几何就崩了。CadQuery 会报Standard_Failure。我的做法是在 fillet 之前计算可用边的最大安全半径取min(用户指定值, 最小相邻面尺寸/2 - 0.1)然后给用户一个警告说半径被自动调整了。问题三STEP 导出后文件打不开。这种情况多半是几何体不是有效的实体solid而是壳shell或者面face。CadQuery 的export对非实体几何容错性差。排查方法是先wp.val().isValid()检查如果返回 False用wp.clean()尝试修复还不行就得回溯建模步骤看哪一步产生了开放边。4.2 单位与坐标系的隐形陷阱单位问题我在前面提过但值得再强调一次。CAD 领域默认单位是毫米但用户输入可能混用厘米、英寸、甚至“分”英制螺纹里的分。我的解析器里维护了一个单位映射表遇到“寸”要区分是英寸还是市寸——这在中文语境下真的会发生。最稳妥的做法是在 prompt 里明确要求模型输出时统一转成毫米并在 JSON 里带上unit: mm字段做二次确认。坐标系的问题更隐蔽。CadQuery 默认 Z 轴向上但有些机器人仿真环境比如 ROS默认 Z 轴向上、X 轴向前而 URDF 的 joint axis 又可能是任意方向。我遇到过生成的 URDF 导入 CoppeliaSim 后机械臂朝下长的情况排查半天发现是 joint axis 的默认值没设对。后来我在 URDF 生成模板里强制指定每个 joint 的 axis 和 origin不再依赖默认值。常见问题典型报错排查思路解决手段布尔运算失败Standard_Failure检查孔位是否越界加边界校验自动内缩倒角退化BRep_API: command not done检查半径与壁厚关系自动限制最大半径STEP 无效文件打开为空检查是否为有效实体isValid() clean()URDF 姿态错乱仿真中模型朝向异常检查 joint axis 和 origin显式指定坐标系单位混乱模型尺寸差 10 倍或 25.4 倍回溯输入单位表述统一转 mm 显式标注4.3 性能优化批量生成时怎么不卡死如果你要批量生成几十上百个零件比如根据订单表自动出图性能就成了问题。CadQuery 单次建模加导出 STEP 大概在 0.5 到 2 秒之间取决于复杂度。一百个零件就是几分钟还能接受。但如果你在循环里反复创建Workplane对象而不释放内存会涨得很快。我的优化手段有三个。第一复用几何内核的上下文不要在每次迭代时重新初始化。第二把导出操作放到单独的进程池里并行跑CadQuery 的 GIL 释放做得不错多进程能线性提速。第三对于只需要 STL 的场景降低网格精度tolerance从 0.01 调到 0.1导出速度能快好几倍文件也小很多。还有一个容易被忽略的点临时文件清理。CadQuery 在网格化和布尔运算时会生成中间文件如果不清理跑几百个零件后磁盘就满了。我在代码里加了tempfile.TemporaryDirectory()上下文管理器确保每次生成后中间产物自动删除。5. 从模型到制造text-to-cad 的下游衔接5.1 STEP 之后的加工链路生成 STEP 只是第一步真正要造出来还得经过 CAM 编程。text-to-cad 在这个环节的价值是自动标注和工艺信息附加。比如在生成法兰盘的时候顺便把孔的公差等级、表面粗糙度、材料牌号写进 STEP 的 PMIProduct Manufacturing Information里。这样 CAM 工程师拿到文件就知道哪些孔要铰哪些面要磨。PMI 的写入在 OCCT 里有 API 支持但用起来比较繁琐。我的简化做法是生成一个伴随的 JSON 工艺文件里面列出每个特征的加工要求STEP 文件只负责几何。加工厂那边如果只要几何就给 STEP如果要完整信息就把 JSON 一起打包。这种“几何元数据”的分离方案在实际协作中反而更灵活。5.2 URDF 与机器人仿真的对接细节URDF 导入 CoppeliaSim 或者 Gazebo 之后最常见的需求是验证运动范围和碰撞。text-to-cad 生成的 URDF 如果 link 的碰撞体太精细仿真会跑得很慢。我的经验是碰撞体用简化几何——长方体、圆柱体、球体——而不是原始网格。视觉网格可以用 STL 保持好看碰撞网格单独生成一套低模。具体操作上我会在 PartSpec 里加一个collision_geometry字段让模型或用户指定每个 link 的碰撞体类型和尺寸。如果没指定就根据 link 的包围盒自动生成一个长方体。这样导入仿真环境后运动学计算快碰撞检测也不会因为网格太密而卡顿。另外URDF 的 joint limit 一定要设。我见过太多生成的 URDF 因为没设限位仿真里关节转起来跟风车一样。限位值可以从用户描述里提取比如“大臂俯仰范围 -30 到 90 度”也可以给一个保守的默认值比如 ±180 度让用户在仿真里再调。5.3 G-code 生成中的参数决策从 STEP 到 G-code中间隔着切片。text-to-cad 能做的是根据几何特征自动推荐切片参数。比如检测到模型有悬垂面就自动开启支撑检测到薄壁特征就减小层高提高精度检测到大量小孔就降低打印速度保证冷却。我写过一个简单的规则引擎输入是模型的包围盒尺寸、最小特征尺寸、悬垂角度分布输出是推荐的层高、壁厚、填充率、支撑角度。这套规则不复杂但能省掉大量手动调参的时间。实测下来对于常见的机械零件自动推荐的参数跟有经验的工程师手动设置的差距在 10% 以内打印成功率也差不多。注意G-code 生成涉及具体的打印机参数不同机器的喷嘴直径、热床尺寸、材料收缩率都不一样。text-to-cad 输出的应该是“参数建议”而不是“最终 G-code”最终切片还是要在目标机器的切片软件里做一次。直接拿通用 G-code 去打印翻车概率很高。6. 我对 text-to-cad 落地的一些真实体会这个方向我断断续续跟了一年多最大的感受是text-to-cad 的瓶颈不在 AI而在几何的严谨性。语言模型可以把“一个带法兰的轴”解析得头头是道但几何内核不会因为你的描述合理就给你一个有效的实体。尺寸矛盾、特征冲突、拓扑错误这些在手工建模时会被工程师的直觉规避掉但在自动生成里必须用代码显式处理。另一个体会是不要追求“一句话出复杂装配体”。我试过让模型直接生成多零件装配的 STEP结果坐标系乱成一锅粥。后来改成“一句话出一个零件装配关系用额外描述定义”成功率大幅提升。text-to-cad 现阶段最适合的场景是参数化系列件和标准结构件——法兰、支架、板件、轴套、齿轮坯。这些零件特征明确、参数有限、几何规则自动生成的准确率能到 90% 以上。最后分享一个实用技巧在解析器和几何生成器之间加一层“预览渲染”。用 CadQuery 的exporters.export生成一张 PNG 缩略图让用户在下载 STEP 之前先看一眼。这一步能过滤掉大部分因为描述歧义导致的错误用户看到图不对改一句话重新生成比下载文件再打开软件检查要快得多。我现在的流程里预览图是默认开启的STEP 下载反而成了第二步操作。这个方向还在快速演进几何内核的 Python 绑定越来越成熟语言模型的结构化输出能力也在提升。如果你手头有重复建模的活儿不妨从最简单的板件和轴类零件开始试跑通一个闭环之后再往复杂特征扩展。踩坑是必然的但每解决一个几何报错你的自动化流水线就稳一分。