
1. 从一句话到三维模型text-to-cad 到底在解决什么问题第一次听到 text-to-cad 这个词我的反应和大多数人一样用一句话就能生成 CAD 模型这靠谱吗毕竟在传统制造业和机械设计圈子里CAD 建模一直是个门槛不低的手艺活——你得熟悉草图约束、拉伸旋转、布尔运算还得对尺寸公差有概念。一个刚入行的结构工程师光是学会用主流 CAD 软件画出一个合格的零件图可能就得花上几个月。但 text-to-cad 想做的事情恰恰是把这段学习曲线给拉直。它的核心逻辑是你用自然语言描述一个零件的形状、尺寸和特征系统自动生成对应的 CAD 文件通常是 STEP 格式有时候也会输出 URDF 用于机器人仿真甚至 G-code 用于加工。这背后涉及的技术栈相当复杂包括大语言模型对文本的语义理解、参数化建模的几何内核调用、以及格式转换和验证环节。我之所以对这个方向感兴趣是因为在实际工作中遇到过太多“重复建模”的场景。比如做非标自动化设备经常需要画一些标准件法兰盘、支架、连接板、齿轮坯料。这些东西形状规整参数明确但每次都要手动在 CAD 里一步步画效率很低。如果能把常用零件的描述写成模板让系统自动生成那省下来的时间相当可观。text-to-cad 适合谁来了解我认为有三类人值得关注一是机械设计工程师想提升标准件和简单零件的建模效率二是机器人方向的开发者需要快速生成 URDF 模型做仿真验证三是做 CAD 二次开发的程序员想了解如何把自然语言处理和几何建模结合起来。哪怕你只是刚接触 CAD 制图入门的新手理解这个思路也能帮你更好地理解参数化建模的本质。2. 核心技术拆解text-to-cad 背后的四层架构2.1 自然语言理解层把“人话”翻译成几何参数这一层是整个系统的入口也是最容易出问题的地方。用户说“一个直径 50 毫米、厚度 10 毫米的圆盘中心有一个直径 10 毫米的通孔”系统需要从中提取出形状类型是圆柱体、外径 50、厚度 10、中心孔直径 10、孔贯穿整个厚度。听起来简单但自然语言里充满了歧义。比如“厚度”可能被说成“高度”“长度”“Z 向尺寸”甚至“有多厚”。如果用户说“一个法兰盘”系统还得知道法兰盘通常包含盘体和螺栓孔阵列这需要领域知识的注入。目前主流的做法是微调一个大语言模型让它专门做“文本到参数”的抽取。训练数据通常是一堆“描述-参数”配对比如“边长 20 的正方体”对应{shape: cube, size: 20}。但光靠 LLM 还不够因为 LLM 对数值的精度处理并不理想有时候会把 50 写成 5.0 或者 500。所以实际系统里通常会加一层规则校验比如检测到“毫米”单位后对数值做范围约束——机械零件的尺寸一般在 0.1 到 1000 毫米之间超出这个范围就触发人工确认。我在测试类似系统时发现一个坑中文描述里的“个”和“毫米”经常被混淆。比如“直径 50 个毫米”这种口语化表达模型有时候会解析成 50 个零件。解决办法是在预处理阶段做同义词归一化把“个毫米”“毫米个”统一成“毫米”。这个细节在官方文档里通常不会写但实际部署时非常关键。2.2 参数化几何生成层调用几何内核才是硬功夫拿到参数之后下一步是生成真正的三维几何。这里绕不开几何内核常见的有 OpenCASCADE、Parasolid、ACIS 等。OpenCASCADE 是开源方案里最成熟的选择支持 B-rep 表示能输出 STEP 格式。text-to-cad 系统通常会封装一套“建模原语”比如make_cylinder(radius, height)、make_box(length, width, height)、cut_hole(shape, diameter, position)然后根据解析出的参数调用这些原语。但这里有个容易被忽视的问题特征顺序。比如“一个长方体上面挖一个圆孔再在孔里加一个圆柱销”这个顺序不能乱。如果先加圆柱销再挖孔销就被挖掉了。所以系统需要理解操作的先后逻辑通常用抽象语法树来表示建模步骤再按树的后序遍历执行。我在实际项目中见过因为特征顺序错误导致模型完全不对的案例排查了半天才发现是解析器把“然后”和“并且”当成了同一种连接词。另一个难点是尺寸约束的传播。比如“一个 L 形支架竖板高度是横板长度的两倍横板长度等于孔间距”这种关联约束需要系统维护一个参数依赖图。OpenCASCADE 本身不直接提供约束求解器得自己实现或者集成像 SolveSpace 这样的约束求解库。这也是为什么很多 text-to-cad 原型只能处理简单零件一旦涉及多特征关联就歇菜了。2.3 格式转换与验证层STEP、URDF、G-code 各有各的坑生成几何之后输出格式的选择取决于下游用途。STEP 是通用交换格式适合给 CAD 软件打开继续编辑URDF 是机器人描述格式包含连杆和关节信息适合导入 CoppeliaSim 做仿真G-code 是数控加工指令适合直接上机床。STEP 输出相对直接OpenCASCADE 有现成的STEPControl_Writer。但要注意单位问题STEP 文件默认单位是毫米但有些系统会写成米导致导入后模型缩小 1000 倍。我遇到过好几次客户反馈“模型怎么这么小”最后发现是单位没设置对。解决办法是在写入前显式设置Interface_Static::SetCVal(xstep.cascade.unit, MM)。URDF 输出就麻烦多了。URDF 本质上是描述机器人运动学的 XML 格式需要定义 link 和 joint。如果 text-to-cad 生成的只是一个静态零件转 URDF 时得把它拆成至少一个 base_link如果有运动副还得加 joint。更头疼的是惯性矩阵的计算——URDF 里的 inertial 标签需要质量和转动惯量而 CAD 模型通常只有几何没有材料属性。常见做法是假设一个密度值比如钢的 7850 kg/m³然后调用几何库计算体积和惯性张量。这个假设在仿真里可能带来误差但对于快速验证来说够用了。G-code 输出则是另一条路。它不关心 B-rep 表示而是把模型切片成层生成刀具路径。text-to-cad 直接生成 G-code 的场景比较少通常是先生成 STEP 或 STL再用切片软件处理。但如果目标是简单的 2.5 轴加工比如钻孔和铣平面也可以直接从参数生成 G-code。这里的关键是刀具半径补偿和进给速度的设定需要根据材料类型查表。2.4 交互与迭代层一次生成不对怎么办text-to-cad 不可能一次就生成完全符合预期的模型。用户看到结果后通常会说“孔再大一点”“厚度改成 15”“加一个倒角”。所以系统需要支持增量修改。实现方式有两种一种是重新解析完整描述覆盖之前的参数另一种是维护一个参数状态机只更新变化的字段。前者实现简单但容易丢失上下文比如用户说“把孔改成 12”系统不知道是哪个孔。后者需要做指代消解难度更大但体验更好。我在实际使用中更倾向于第一种的变体让用户用结构化表单修改参数而不是继续用自然语言。因为自然语言在多轮对话里太容易产生歧义而参数表单一目了然。还有一个实用技巧保存每次生成的参数和对应的 STEP 文件建立一个“描述-模型”对的历史记录。这样用户可以说“回到上一版”或者“在第三版的基础上改”。这个功能在团队协作时特别有用相当于给 CAD 建模加了版本控制。3. 动手实操从零搭建一个简易 text-to-cad 流水线3.1 环境准备与依赖安装先说明一下这里搭建的是一个最小可用的原型目的是理解整个流程不是生产级系统。你需要准备 Python 3.9 以上环境主要依赖三个库cadquery基于 OpenCASCADE 的 Python 建模库、transformers跑语言模型、trimesh做格式转换和验证。安装命令如下pip install cadquery transformers trimeshCadQuery 是我强烈推荐的工具它把 OpenCASCADE 的 C 接口封装成了 Python 的链式调用写起来很直观。比如画一个圆柱体import cadquery as cq result cq.Workplane(XY).circle(25).extrude(10)这两行就生成了一个半径 25、高度 10 的圆柱。如果要加中心孔result result.faces(Z).workplane().hole(5)faces(Z)选中顶面workplane()建立工作平面hole(5)挖一个直径 5 的孔。这种链式写法比传统 CAD 的 GUI 操作更适合程序化生成。语言模型部分可以用一个小的预训练模型做微调也可以直接调用现成的 API。为了演示方便我这里用一个基于规则加关键词匹配的“伪解析器”实际项目中再替换成真正的 LLM。3.2 文本解析器的实现细节解析器的任务是把中文描述转成结构化的建模指令。我设计了一个简单的 JSON 格式{ shape: cylinder, params: { radius: 25, height: 10 }, features: [ {type: hole, diameter: 5, position: center, through: true} ] }解析逻辑分三步先分词并提取数值和单位再识别形状关键词最后匹配特征描述。数值提取用正则表达式import re def extract_dimensions(text): pattern r(\d(?:\.\d)?)\s*(?:毫米|mm|个毫米) matches re.findall(pattern, text) return [float(m) for m in matches]形状识别用关键词映射表SHAPE_KEYWORDS { 圆柱: cylinder, 圆盘: cylinder, 方块: box, 长方体: box, 正方体: box, 球: sphere }特征识别类似检测“孔”“倒角”“圆角”“螺纹”等词。这里有个经验中文里“挖一个孔”和“打一个孔”是一个意思但“攻一个螺纹孔”就多了一层螺纹特征。如果系统不支持螺纹建模至少要能识别出来并提示用户“当前版本不支持螺纹已生成光孔”。3.3 几何生成与 STEP 导出拿到结构化指令后用 CadQuery 执行建模。下面是一个完整的生成函数def build_model(instruction): shape instruction[shape] params instruction[params] if shape cylinder: result cq.Workplane(XY).circle(params[radius]).extrude(params[height]) elif shape box: result cq.Workplane(XY).box(params[length], params[width], params[height]) else: raise ValueError(f不支持的形状: {shape}) for feature in instruction.get(features, []): if feature[type] hole: result result.faces(Z).workplane().hole(feature[diameter]) return result导出 STEP 文件def export_step(model, filename): cq.exporters.export(model, filename, exportTypeSTEP)这里有个细节CadQuery 导出的 STEP 默认单位是毫米但如果你在建模时用了英寸导出后单位会混乱。建议全程用毫米避免换算。3.4 URDF 转换的关键步骤把 CadQuery 模型转成 URDF需要计算质量和惯性矩阵。假设材料是钢密度 7850 kg/m³def to_urdf(model, namepart, density7850): volume model.val().Volume() # 单位是 mm³ mass volume * 1e-9 * density # 转成 m³ 再乘密度 # 计算惯性张量这里用近似值 # 实际项目中应该用 trimesh 或 OpenCASCADE 的惯性计算功能 inertia compute_inertia(model, mass) urdf_template f?xml version1.0? robot name{name} link namebase_link inertial mass value{mass}/ inertia ixx{inertia[0]} ixy0 ixz0 iyy{inertia[1]} iyz0 izz{inertia[2]}/ /inertial visual geometry mesh filename{name}.stl/ /geometry /visual /link /robot return urdf_template注意 URDF 里的 mesh 文件需要单独导出 STL而且路径要写对。导入 CoppeliaSim 时如果 STL 路径是相对路径得确保 URDF 和 STL 在同一目录下。3.5 G-code 生成的简化方案对于简单的钻孔和铣削操作可以直接生成 G-code。比如一个直径 10 的孔用直径 8 的铣刀需要走螺旋下刀def generate_gcode_for_hole(diameter, depth, tool_diameter): radius (diameter - tool_diameter) / 2 lines [] lines.append(G21 ; 单位设为毫米) lines.append(G90 ; 绝对坐标) lines.append(G0 Z5 ; 抬刀到安全高度) lines.append(fG0 X{radius} Y0 ; 移动到起始位置) step_down 1 # 每层下刀 1mm z 0 while z -depth: z - step_down lines.append(fG1 Z{z} F100 ; 下刀) lines.append(fG2 X{radius} Y0 I{-radius} J0 F300 ; 圆弧插补) lines.append(G0 Z5 ; 抬刀) return \n.join(lines)这个 G-code 是简化版实际加工还需要考虑刀具补偿、冷却液开关、主轴转速等。但作为原型验证已经能跑通流程了。4. 踩坑实录text-to-cad 常见问题与排查技巧4.1 模型生成失败或形状错误最常见的问题是生成的模型和预期完全不一样。比如用户说“一个圆环”系统生成了一个实心圆柱。排查思路是先从解析结果入手打印出结构化的 JSON看看shape字段是不是torus而不是cylinder。如果解析正确但建模错误检查 CadQuery 的 API 调用——torus需要两个半径参数少一个就会报错。另一个高频问题是布尔运算失败。比如在圆柱上挖孔如果孔的位置正好在边缘OpenCASCADE 可能因为几何退化而失败。解决办法是给孔的位置加一个安全边距或者用cutThruAll()代替hole()后者对边界情况处理更好。注意CadQuery 的hole()默认是贯穿孔但如果工作平面选错了可能只挖了一半。建议每次挖孔后调用model.val().Volume()检查体积变化确认材料确实被移除了。4.2 STEP 文件导入其他 CAD 软件后显示异常有时候 STEP 文件在 CadQuery 里看着正常导入 SolidWorks 或中望 CAD 后却出现破面或丢失特征。这通常是 B-rep 精度问题。OpenCASCADE 的默认精度是 1e-7 米对于小尺寸零件可能不够。可以在导出前设置cq.exporters.export(model, part.step, exportTypeSTEP, tolerance1e-3)另外STEP 有 AP203 和 AP214 两个常用协议AP214 对颜色和层的信息支持更好。如果下游需要这些信息导出时指定write_pcurvesTrue。4.3 URDF 导入 CoppeliaSim 后模型不可见这个问题我遇到过好几次原因通常有三个一是 STL 文件路径不对URDF 里写的是绝对路径但换机器后失效二是单位问题URDF 默认单位是米但 STL 是毫米导致模型缩小 1000 倍看不见三是坐标系没对齐模型在视野外。排查步骤先用trimesh加载 STL 确认文件本身没问题再检查 URDF 里的mesh filename路径最后在 CoppeliaSim 里按CtrlShiftF打开所有可视对象看看模型是不是跑到很远的地方了。如果是单位问题在 URDF 的mesh标签里加scale0.001 0.001 0.001。4.4 自然语言描述中的歧义处理用户说“一个 50 的圆”这个 50 是直径还是半径在机械制图里默认是直径但口语里经常混用。我的做法是在解析器里加一条规则如果描述里出现“直径”“外径”“D”等词按直径处理如果出现“半径”“R”等词按半径处理如果都没有默认按直径并在返回结果里提示“已按直径 50 处理如需半径请说明”。另一个歧义是“厚度”和“高度”。对于圆盘类零件厚度是 Z 向尺寸对于长方体高度也是 Z 向。但如果用户说“一个 100x50x20 的板”这三个数分别对应长宽高顺序不能乱。我通常按“长宽高”的顺序解析并在文档里明确说明。4.5 性能优化批量生成时的内存管理如果需要批量生成几百个零件CadQuery 默认会把所有模型留在内存里很容易爆。解决办法是每生成一个就导出并释放import gc for desc in descriptions: model build_model(parse(desc)) export_step(model, f{desc[name]}.step) del model gc.collect()另外OpenCASCADE 的BRepTools会缓存一些中间结果可以用BRepTools.Clean()手动清理。实测下来加上这两步之后批量生成 500 个零件的内存占用从 8GB 降到了 1.5GB 左右。5. 工具选型与生态对比哪条路更适合你5.1 CadQuery vs FreeCAD vs OpenSCAD这三个是 text-to-cad 领域最常被提到的开源工具但定位差别很大。工具语言几何内核输出格式适合场景CadQueryPythonOpenCASCADESTEP, STL, DXF程序化建模复杂 B-repFreeCADPython/COpenCASCADESTEP, IGES, STL完整 CAD 功能GUI 操作OpenSCAD自有 DSLCGALSTL, DXF简单几何CSG 风格CadQuery 的优势在于 Python 生态可以无缝对接 numpy、scipy 做参数优化。FreeCAD 功能最全但 API 比较冗长适合做二次开发而不是快速原型。OpenSCAD 的 DSL 很简洁但不支持 B-rep输出只有网格精度有限。如果你要做 text-to-cad我建议从 CadQuery 入手因为它的链式 API 最接近自然语言的描述逻辑。比如“一个圆柱顶面挖孔”直接对应.circle().extrude().faces(Z).hole()解析器生成代码时很直观。5.2 语言模型的选择本地跑还是调 API如果只是做原型验证用规则匹配就够了。但要处理复杂的自然语言描述还是得上 LLM。选择上有两条路本地部署开源模型如 ChatGLM、Qwen或者调用云端 API。本地部署的好处是数据不出内网适合有保密要求的场景。但需要 GPU推理速度也慢。云端 API 速度快、效果好但按 token 收费批量生成时成本不低。我的折中方案是用本地小模型做初步解析把置信度低的描述发给云端大模型做二次确认。这样既控制了成本又保证了准确率。5.3 与现有 CAD 工作流的集成text-to-cad 生成的 STEP 文件可以直接导入主流 CAD 软件继续编辑。但如果你想让整个流程自动化比如“生成模型后自动出工程图”就需要用到 CAD 的 API。中望 CAD 和 AutoCAD 都支持 Python 脚本SolidWorks 有 VBA 接口。一个实用的集成方式是用 text-to-cad 生成基础模型导出 STEP再用 CAD 软件的脚本接口打开、添加标注、导出 PDF。这样就把自然语言建模和传统 CAD 出图串起来了。我在一个非标设备项目里试过这个流程原本需要 2 小时画的支架从描述到出图压缩到了 15 分钟。6. 从原型到生产还需要补哪些课6.1 公差与配合的自动标注text-to-cad 目前只能生成名义尺寸但实际加工需要公差。比如“直径 50 的孔配直径 50 的轴”实际得标成 H7/g6 的配合。这需要系统理解配合制度并根据使用场景自动推荐公差等级。目前这块基本靠人工但可以做一个规则库滑动配合用 H7/g6过盈配合用 H7/p6等等。6.2 材料与工艺的关联同样的形状不同材料对应不同的加工工艺。塑料件可以注塑复杂形状没问题金属件如果是切削加工内直角就得改成圆角。text-to-cad 如果能在生成模型时就考虑工艺约束比如自动给内角加 R 角会大大减少后续修改的工作量。6.3 版本管理与协作在团队里用 text-to-cad需要解决“谁改了什么”的问题。我的做法是把每次生成的描述文本和参数 JSON 一起存进 GitSTEP 文件作为二进制附件。这样每次修改都有记录还能对比不同版本的参数差异。对于 CAD 图纸合并这种需求也可以基于参数做自动合并而不是手动对齐图层。6.4 与 PLM/PDM 系统的对接企业里通常有 PLM 系统管理物料和 BOM。text-to-cad 生成的零件如果能量产就需要自动创建物料号、填写属性、关联到产品结构。这需要调用 PLM 的 API把生成的 STEP 文件和元数据一起上传。目前这块没有标准方案得根据具体系统定制。7. 一些实操心得与避坑建议先说一个我踩过的坑不要试图让 text-to-cad 生成复杂装配体。单个零件的生成已经够难了装配涉及配合关系、运动约束、干涉检查自然语言描述很难说清楚。我的建议是把装配拆成单个零件分别生成再在 CAD 里手动装配。这样虽然多了一步但可靠性高得多。另一个心得是关于描述模板的。与其让用户自由发挥不如提供一套结构化模板比如“形状圆柱外径高度特征中心通孔直径 __”。用户填空比写自由文本准确率高很多。我在内部工具里就是这么做的解析成功率从 60% 提升到了 95% 以上。还有一点生成的模型一定要做几何有效性检查。OpenCASCADE 提供了BRepCheck_Analyzer可以检测自相交、开放边、无效面等问题。我见过太多案例模型看着没问题一导入仿真软件就报错。加一行检查代码能省下大量排查时间。from OCC.Core.BRepCheck import BRepCheck_Analyzer analyzer BRepCheck_Analyzer(model.val().wrapped) if not analyzer.IsValid(): print(模型存在几何错误请检查参数)最后分享一个提高解析准确率的小技巧在用户输入描述后先让系统回显解析结果比如“我将生成一个直径 50、高度 10 的圆柱中心有直径 5 的通孔确认吗”用户确认后再执行建模。这一步看似多余但能拦截大部分解析错误避免生成一堆废模型。我在实际使用中加上确认步骤后返工率下降了七成左右。这个方向还在快速演进目前的开源方案已经能覆盖不少简单场景但离“说一句话就出成品”还有距离。不过对于标准件和简单零件的快速建模来说text-to-cad 的思路已经能实实在在省下时间了。如果你也在做类似的事情建议从 CadQuery 加规则解析起步跑通流程后再逐步引入语言模型这样每一步都可控不会一上来就被复杂的依赖关系劝退。