
1. 从一句话到三维模型text-to-cad 到底在解决什么问题第一次听到 “text-to-cad” 这个词很多人脑子里蹦出来的画面可能是对着电脑说一句“给我画个齿轮”然后 CAD 软件里就自动蹦出来一个三维模型。这个想象方向没错但实际落地远没有这么简单。text-to-cad 本质上是一套把自然语言描述转换成结构化三维几何数据的技术链路它的输出通常不是某个 CAD 软件的原生文件而是像 STEP、URDF 这类通用格式然后再由下游工具去消费。我最早接触这个方向是因为手头有一批机械臂的装配需求需要频繁生成不同尺寸的连杆和关节件。传统做法是在 CAD 软件里手动建模改一个参数就要重新画一遍效率极低。后来尝试用 Python 脚本驱动参数化建模再往后就自然走到了“能不能直接用文字描述来生成模型”这一步。实测下来text-to-cad 目前最成熟的落地场景集中在三类参数化零件批量生成、机器人 URDF 模型快速搭建、教学演示中的即时建模。它不适合替代精细的工业设计但非常适合做原型验证和重复性建模。这篇文章面向的是有一定 Python 基础、对 CAD 或机器人仿真有初步了解、想把这套流程跑通的从业者。我会从整体设计思路讲起拆解核心环节给出可直接复现的实操步骤最后把我踩过的坑和排查经验整理出来。全文围绕 text-to-cad 这条主线涉及 CAD、STEP、URDF、Python 四个关键词的实际用法。2. 整体设计思路为什么是 Python STEP URDF 这套组合2.1 核心链路的拆解逻辑text-to-cad 的完整链路可以拆成四段文本解析 → 参数提取 → 几何构建 → 格式导出。每一段都有多种技术选型我选择当前这套组合是基于实际约束反推出来的。文本解析这一段最直接的做法是调用大语言模型做意图识别和参数抽取。但如果你不想依赖外部服务也可以用正则加关键词匹配做轻量解析。我在早期版本里用过纯规则匹配对于“生成一个长 100mm、宽 50mm、高 30mm 的方块”这类结构化描述正则完全够用。但一旦描述变成“做一个比刚才那个高一点、窄一点的支架”规则就崩了。所以现在的做法是规则解析兜底模型解析处理模糊描述。参数提取之后进入几何构建。这里的选择很多可以用 FreeCAD 的 Python API可以用 CadQuery也可以用 OpenSCAD 的脚本模式。我最终选 CadQuery原因是它的 API 设计更接近“描述式建模”代码可读性强而且原生支持 STEP 导出。FreeCAD 的 API 功能更全但学习曲线陡脚本调试体验差。OpenSCAD 适合做纯几何但导出 STEP 需要额外转换多一道工序就多一个出错点。格式导出这一段STEP 和 URDF 是两条不同的路。STEP 是通用三维交换格式适合给下游 CAD 软件或 3D 打印切片工具用。URDF 是机器人描述格式适合导入 CoppeliaSim、Gazebo 这类仿真环境。两者的数据结构差异很大STEP 描述的是几何体本身URDF 描述的是连杆和关节的拓扑关系。所以 text-to-cad 在导出阶段需要根据目标格式做不同的后处理。2.2 为什么不用现成的 CAD 插件方案市面上有一些 CAD 插件号称支持自然语言建模我试过几款普遍问题是封闭性太强。你没法控制中间过程生成的模型参数不可追溯改一个尺寸就要重新描述一遍。而且很多插件绑定特定 CAD 版本换台机器就废了。自己用 Python 搭这套链路最大的好处是全流程可控。文本解析的规则可以自己调几何构建的参数可以自己定导出格式可以自己选。更重要的是整个流程可以版本化管理今天生成的模型和三个月后生成的模型只要输入描述一样输出就一样。这种可复现性在工程场景里比什么都重要。另一个考虑是批量处理能力。用插件你只能一个一个手动操作用 Python 脚本你可以写一个循环读一个 CSV 参数表一次性生成几百个不同尺寸的零件。我之前做一个支架系列12 个规格用脚本跑完不到 30 秒手动建模至少半天。2.3 方案的优势与边界这套方案的优势集中在三点可复现、可批量、可扩展。可复现前面说了可批量是 Python 脚本的天然优势可扩展是指你可以在任意环节插入自定义逻辑比如自动加圆角、自动检查干涉、自动生成装配体。但它的边界也很明显。第一不适合复杂曲面。CadQuery 和大多数脚本化建模工具一样擅长规则几何体遇到自由曲面就力不从心。第二不适合精细装配。URDF 导出的是简化模型碰撞体通常是包围盒或简单几何不能直接用于公差分析。第三文本解析的准确率有上限。再好的模型解析也有歧义比如“大一点”到底是多大没有上下文就没法确定。所以我的定位很明确text-to-cad 是原型工具和批量工具不是设计工具。用它来快速验证想法、生成仿真模型、批量出图效率极高用它来做最终交付的工业设计还差得远。3. 核心细节解析文本解析、几何构建与格式导出的关键要点3.1 文本解析从自然语言到参数字典文本解析的目标是把一句人话变成机器能读的参数字典。比如输入“生成一个长 100mm、宽 50mm、高 30mm 的方块”输出应该是{shape: box, length: 100, width: 50, height: 30, unit: mm}。用规则解析的话核心是关键词映射 数值提取。关键词映射负责识别形状类型比如“方块”“立方体”“box”都映射到box“圆柱”“圆管”“cylinder”都映射到cylinder。数值提取负责抓尺寸用正则匹配数字加单位的组合。这里有个细节单位统一。用户可能写 100mm也可能写 10cm还可能写 0.1m解析阶段必须统一转成毫米否则后面几何构建会乱。用模型解析的话核心是提示词设计。我用的提示词模板大致是这样的先给几个示例说明输入输出格式然后要求模型只输出 JSON不要加任何解释。实测下来给示例比不给示例的准确率高很多尤其是尺寸参数的提取。但模型解析有个坑它可能会编造参数。比如用户没说高度模型可能自己补一个默认值。所以我在解析之后加了一层校验检查必填参数是否齐全不齐全就报错让用户补充。注意文本解析阶段一定要做参数校验不能直接信任解析结果。我遇到过模型把“长 100”解析成length: 100但单位丢了的情况后面几何构建按默认单位跑结果差了 10 倍。3.2 几何构建CadQuery 的实操要点CadQuery 的建模逻辑是链式调用。比如画一个方块代码是这样的import cadquery as cq result cq.Workplane(XY).box(100, 50, 30)这行代码的意思是在 XY 平面上画一个长 100、宽 50、高 30 的方块。Workplane(XY)指定基准面box()是建模操作。如果要画圆柱result cq.Workplane(XY).circle(25).extrude(100)先画一个半径 25 的圆然后拉伸 100 的高度。注意这里的circle(25)是画圆extrude(100)是拉伸两个操作链式组合。CadQuery 的坐标系默认是 Z 轴向上X 轴向右Y 轴向前。这个和很多 CAD 软件的默认坐标系一致但和 URDF 的坐标系有差异。URDF 里通常 Z 轴向上X 轴向前Y 轴向左。所以在导出 URDF 的时候需要做坐标变换这个后面会细说。几何构建阶段有几个容易踩的坑。第一布尔运算的顺序。CadQuery 支持union()、cut()、intersect()等布尔操作但顺序不同结果可能不同。比如先并集再差集和先差集再并集结果可能完全不一样。我的经验是先做加法再做减法先把所有要加的形状并起来再统一减去要挖掉的部分。第二圆角的位置。CadQuery 的fillet()操作需要指定边边的选择用edges()加选择器。选择器写不好圆角就加错地方。我通常用edges(|Z)选平行于 Z 轴的边或者edges(Z)选 Z 坐标最大的边。第三单位问题。CadQuery 默认单位是毫米如果你输入的参数是米记得先转换。3.3 STEP 导出参数设置与常见问题STEP 导出在 CadQuery 里就一行代码cq.exporters.export(result, output.step)但这行代码背后有几个参数值得注意。export函数支持tolerance参数控制导出精度默认是 0.1。对于大多数零件0.1 够用如果零件尺寸很小比如微型齿轮建议调到 0.01。精度越高文件越大导出越慢需要权衡。STEP 导出最常见的问题是面丢失。尤其是做了复杂布尔运算之后导出的 STEP 文件在某些 CAD 软件里打开会发现缺面。这通常是几何体本身有微小缝隙或自相交导致的。解决办法是在导出前做一次几何修复CadQuery 没有直接的修复函数但可以用clean()方法清理冗余边和面。如果还不行就只能回退到布尔运算那一步检查是不是运算顺序有问题。另一个问题是颜色和材质丢失。STEP 格式本身支持颜色信息但 CadQuery 默认不导出颜色。如果需要带颜色的 STEP得手动设置color属性然后在导出时指定exportTypeSTEP并确保颜色信息被包含。不过大多数下游工具对 STEP 颜色的支持也不一致所以我的做法是颜色信息单独用 JSON 记录不依赖 STEP 文件本身。3.4 URDF 导出拓扑结构与坐标变换URDF 导出比 STEP 复杂得多因为 URDF 描述的不是单个几何体而是连杆和关节的树状结构。一个典型的 URDF 文件包含link和joint两类元素。link定义连杆的几何形状和惯性参数joint定义关节的类型、位置和父子关系。从 text-to-cad 的角度文本描述需要包含拓扑信息。比如“一个两连杆机械臂第一段长 200mm第二段长 150mm中间是旋转关节”解析出来应该是两个 link 和一个 joint。如果文本只描述了一个零件那导出 URDF 就没意义因为 URDF 的最小完整单元至少是一个 link。坐标变换是 URDF 导出的核心难点。CadQuery 的坐标系和 URDF 的坐标系不一致需要在导出时做转换。具体来说CadQuery 的 Z 轴向上URDF 通常也是 Z 轴向上但 X 和 Y 的方向可能不同。我的做法是在 CadQuery 里建模时就用 URDF 的坐标系约定这样导出时不需要额外变换。具体操作是在Workplane()里指定XZ平面而不是默认的XY平面然后调整拉伸方向。惯性参数是另一个坑。URDF 的inertial元素需要质量和惯性矩阵。text-to-cad 生成的几何体没有质量信息需要根据体积和密度估算。我通常假设材料是铝合金密度 2700 kg/m³然后根据体积算质量。惯性矩阵对于简单几何体有解析公式比如方块和圆柱的惯性矩阵可以直接算。对于复杂形状可以用trimesh库做数值积分。提示URDF 导出后一定要在仿真环境里验证。我遇到过惯性矩阵算错导致机械臂在仿真里疯狂抖动的情况排查了半天才发现是惯性矩阵的非对角元素符号搞反了。4. 实操过程从零搭建 text-to-cad 流水线4.1 环境准备与依赖安装先搞定 Python 环境。我推荐用 Python 3.8 或以上版本太老的版本有些库不支持。安装 CadQuery 最省事的方式是用 condaconda create -n text2cad python3.10 conda activate text2cad conda install -c conda-forge cadquery用 pip 也行但 CadQuery 依赖 OCCT 库pip 安装有时候会编译失败。conda 的预编译包更稳。除了 CadQuery还需要装trimesh用于惯性计算numpy用于数值处理lxml用于生成 URDF 的 XML 结构。pip install trimesh numpy lxml如果你打算用模型做文本解析还需要装对应的 SDK。不过这部分我建议先跳过用规则解析把流程跑通再考虑加模型。4.2 文本解析模块的实现先定义一个解析函数输入是字符串输出是参数字典。规则解析的核心是正则匹配import re def parse_text(text): text text.lower() params {} # 识别形状 if any(kw in text for kw in [方块, 立方体, box]): params[shape] box elif any(kw in text for kw in [圆柱, 圆管, cylinder]): params[shape] cylinder else: raise ValueError(无法识别形状类型) # 提取尺寸 dims re.findall(r(\d\.?\d*)\s*(mm|cm|m), text) # 统一转毫米 unit_map {mm: 1, cm: 10, m: 1000} dims_mm [float(v) * unit_map[u] for v, u in dims] if params[shape] box: if len(dims_mm) 3: raise ValueError(方块需要三个尺寸) params[length], params[width], params[height] dims_mm[:3] elif params[shape] cylinder: if len(dims_mm) 2: raise ValueError(圆柱需要半径和高度) params[radius], params[height] dims_mm[:2] return params这个函数能处理“长 100mm、宽 50mm、高 30mm 的方块”和“半径 25mm、高 100mm 的圆柱”。实测下来对于格式规整的描述准确率接近 100%。对于模糊描述比如“大一点的方块”这个函数会报错这时候就需要模型解析兜底。4.3 几何构建模块的实现拿到参数字典后构建几何体import cadquery as cq def build_geometry(params): if params[shape] box: result cq.Workplane(XY).box( params[length], params[width], params[height] ) elif params[shape] cylinder: result cq.Workplane(XY).circle( params[radius] ).extrude(params[height]) else: raise ValueError(不支持的形状) return result这段代码很直白但有个细节box()的参数顺序是长、宽、高对应 X、Y、Z 方向。circle().extrude()是先画圆再拉伸圆在 XY 平面拉伸方向是 Z 轴。如果你需要圆柱沿 X 轴方向得先旋转工作平面result cq.Workplane(YZ).circle(radius).extrude(height)Workplane(YZ)把基准面换成 YZ 平面拉伸方向就变成 X 轴了。4.4 STEP 导出与验证导出 STEP 并验证文件完整性def export_step(result, filepath): cq.exporters.export(result, filepath, tolerance0.01) print(fSTEP 文件已导出{filepath})导出后我习惯用 FreeCAD 打开检查一遍。如果 FreeCAD 能正常打开且没有缺面基本就没问题。另一个验证方法是重新导入 CadQueryimported cq.importers.importStep(output.step) print(imported.val().Volume())如果导入后的体积和原始体积一致说明导出没有丢几何。体积不一致就说明有问题需要回退检查。4.5 URDF 导出的完整流程URDF 导出需要先构建拓扑结构。假设我们要生成一个两连杆机械臂from lxml import etree def build_urdf(link_params_list, joint_params_list): robot etree.Element(robot, namearm) for i, link_params in enumerate(link_params_list): link etree.SubElement(robot, link, nameflink_{i}) # 几何 visual etree.SubElement(link, visual) geometry etree.SubElement(visual, geometry) box etree.SubElement(geometry, box) box.set(size, f{link_params[length]} {link_params[width]} {link_params[height]}) # 惯性 inertial etree.SubElement(link, inertial) mass etree.SubElement(inertial, mass) volume link_params[length] * link_params[width] * link_params[height] / 1e9 # mm³ 转 m³ mass.set(value, str(volume * 2700)) # 铝合金密度 for j, joint_params in enumerate(joint_params_list): joint etree.SubElement(robot, joint, namefjoint_{j}, typerevolute) parent etree.SubElement(joint, parent) parent.set(link, flink_{joint_params[parent]}) child etree.SubElement(joint, child) child.set(link, flink_{joint_params[child]}) origin etree.SubElement(joint, origin) origin.set(xyz, f{joint_params[x]} {joint_params[y]} {joint_params[z]}) axis etree.SubElement(joint, axis) axis.set(xyz, 0 0 1) return etree.tostring(robot, pretty_printTrue, xml_declarationTrue)这段代码生成一个包含两个 link 和一个 joint 的 URDF。注意质量计算体积单位是 mm³转 m³ 要除以 1e9然后乘以密度 2700 kg/m³。惯性矩阵这里省略了实际使用需要补上否则仿真可能不稳定。4.6 完整流水线的串联把上面几个模块串起来def text_to_cad(text, output_formatstep, output_pathoutput): params parse_text(text) geometry build_geometry(params) if output_format step: export_step(geometry, f{output_path}.step) elif output_format urdf: urdf_str build_urdf([params], []) with open(f{output_path}.urdf, w) as f: f.write(urdf_str) return f{output_path}.{output_format} # 测试 text_to_cad(生成一个长 100mm、宽 50mm、高 30mm 的方块, step, test_box)跑完这段代码当前目录下会生成test_box.step。用 FreeCAD 打开应该能看到一个 100x50x30 的方块。5. 常见问题与排查技巧实录5.1 文本解析类问题问题一单位识别错误。用户写“长 0.1m”解析成length: 0.1但单位没转后面按毫米处理结果小了 1000 倍。解决办法是在正则里强制捕获单位没有单位的数值直接报错不猜默认值。问题二同义词漏识别。用户写“长方体”规则里只写了“方块”和“立方体”就识别不了。解决办法是维护一个同义词表把所有可能的表达都列进去。我现在的同义词表有 20 多个词条覆盖了常见的中英文表达。问题三数值提取歧义。用户写“长 100 宽 50 高 30”没有单位正则匹配不到。解决办法是允许无单位数值但统一按毫米处理并在输出里标注“假设单位为毫米”。5.2 几何构建类问题问题一布尔运算失败。两个几何体做cut()操作时报错提示“布尔运算失败”。原因通常是两个几何体没有相交或者相交面有微小缝隙。解决办法是先检查两个几何体的位置关系确保它们确实有重叠区域。如果有缝隙把其中一个几何体稍微放大一点再运算。问题二圆角导致几何体破碎。给一个薄壁零件加圆角圆角半径大于壁厚几何体直接碎掉。解决办法是加圆角前检查半径是否合理圆角半径必须小于最小壁厚的一半。问题三导出 STEP 后体积不对。导出前体积是 1000 mm³导出后重新导入变成 999.8 mm³。这是精度问题tolerance设得太小导致的。把tolerance从 0.01 调到 0.1体积就一致了。精度和文件大小需要权衡不是越小越好。5.3 URDF 导出类问题问题一导入 CoppeliaSim 后模型位置不对。这是坐标系差异导致的。CadQuery 默认 Z 轴向上CoppeliaSim 也是 Z 轴向上但 X 和 Y 的方向可能差 90 度。解决办法是在 URDF 的origin里加旋转或者建模时就按 CoppeliaSim 的坐标系来。问题二仿真时机械臂抖动。惯性矩阵算错了。检查inertial里的质量值和惯性矩阵确保单位是 kg 和 kg·m²。我遇到过质量写成克的情况仿真里机械臂轻得像纸片一碰就飞。问题三关节类型不匹配。URDF 里写了revolute但仿真环境里需要continuous。解决办法是查仿真环境的文档确认支持的关节类型。CoppeliaSim 对revolute和continuous都支持但行为略有不同。5.4 常见问题速查表问题现象可能原因排查方法解决方案解析结果缺参数正则没匹配到打印解析中间结果补充同义词或放宽正则几何体构建报错参数类型不对检查参数字典强制类型转换STEP 导出缺面几何体有缝隙FreeCAD 打开检查清理几何体或调整布尔顺序URDF 导入失败XML 格式错误用 xmllint 校验检查标签闭合和属性仿真抖动惯性参数错误检查质量和惯性矩阵重新计算惯性参数单位不一致解析时没统一打印所有尺寸值强制转毫米提示排查问题时最有效的方法是在每一步打印中间结果。文本解析后打印参数字典几何构建后打印体积导出后打印文件大小。这样一旦出错能快速定位是哪一步的问题。5.5 独家避坑经验经验一不要追求一步到位。我一开始想直接做“文本到完整装配体”结果卡在拓扑解析上整整一周。后来退回到“文本到单个零件”先把链路跑通再逐步加拓扑。这个思路救了我很多时间。经验二参数校验比解析本身更重要。解析可以不准但校验必须严格。我现在的校验规则有十几条必填参数是否齐全、数值是否为正、尺寸是否在合理范围内、单位是否统一。校验不过就直接报错不往下走。这样虽然有时候会误报但避免了大量隐蔽错误。经验三STEP 和 URDF 分开处理。一开始我想用一套代码同时支持两种导出结果代码复杂度爆炸。后来拆成两个独立模块各自处理各自的格式转换代码清晰多了。虽然有一些重复逻辑但维护成本低很多。经验四版本锁定。CadQuery 的 API 在不同版本之间有变化我遇到过升级后box()参数顺序变了的情况。所以我现在用requirements.txt锁定版本不随便升级。生产环境稳定比新功能重要。经验五批量生成时加日志。批量跑 100 个零件中间某个失败了没有日志根本不知道是哪个。我现在的做法是每个零件生成时都写一行日志记录输入参数、输出路径、耗时、是否成功。出问题直接查日志不用重新跑一遍。6. 扩展方向与个人体会这套 text-to-cad 流水线跑通之后我陆续加了一些扩展功能。自动加圆角是一个在几何构建之后加一步检测所有锐边自动加 1mm 圆角。自动生成装配体是另一个读一个 CSV 文件每行是一个零件的描述自动生成所有零件并组装。自动检查干涉也做过用trimesh做碰撞检测两个零件有重叠就报警。这些扩展都不是必须的但加上之后整个流程的自动化程度提升很多。我现在做一个新零件系列从写描述到出 STEP 文件全程不超过 5 分钟。这在以前手动建模的时代是不可想象的。不过我也得说text-to-cad 目前的能力边界很清楚。它擅长规则几何、批量生成、快速原型不擅长自由曲面、精细装配、公差分析。如果你的需求落在前三个这套方案能帮你省大量时间如果落在后三个还是老老实实打开 CAD 软件手动建模。最后分享一个小技巧把常用形状做成模板。我在代码里预定义了十几种常用形状比如“法兰盘”“支架”“连杆”每种都有默认参数。用户描述的时候只需要说“生成一个法兰盘外径 100”其他参数用默认值。这样解析逻辑大大简化用户体验也好很多。模板化是 text-to-cad 从“能用”到“好用”的关键一步。