
1. 从一句话到三维模型text-to-cad 到底在解决什么问题第一次听到 “text-to-cad” 这个词很多人脑子里浮现的画面大概是对着电脑敲一行字比如“一个长宽高 100×60×30 的带圆角法兰底座”然后软件自动吐出一个可以旋转查看、可以导出加工的 3D 模型。这个画面放在五年前还像是科幻但放到今天它已经是一条能跑通的技术链路了。我最早接触这个方向是在做参数化零件批量生成的时候当时每天要手动在 CAD 里重复画几十个结构相似的支架改一个尺寸就要重新拉伸、打孔、倒角效率低到让人怀疑人生。后来我开始琢磨能不能用自然语言描述需求让程序自动生成对应的几何体再导出成 STEP 或者 URDF 这种下游能直接用的格式这就是 text-to-cad 的核心诉求。说白了text-to-cad 就是把自然语言描述转换成 CAD 可识别的三维模型数据的一套方法。它的输入是一段人话输出通常是 STEP、STL、URDF、G-code 这类格式文件。它解决的核心问题是降低三维建模的门槛同时把重复性的建模工作自动化。适合谁来参考三类人最需要一是做机械设计但被重复劳动折磨的工程师二是做机器人仿真需要批量生成 URDF 模型的开发者三是做数控加工需要从描述直接生成 G-code 的工艺人员。哪怕你只是刚学 CAD 制图入门的新手理解这套逻辑也能帮你建立“参数驱动几何”的思维而不是死记命令。我先把结论摆在这里text-to-cad 不是一个单一软件而是一条从语言解析到几何内核再到格式导出的流水线。它的技术栈通常包含四个环节——语义理解、参数提取、几何构建、格式转换。每个环节都有坑也都有成熟的替代方案。下面我按实际搭建的顺序把这条链路拆开讲清楚。2. 整体架构设计为什么这样搭而不是那样搭2.1 核心链路的四个环节与选型逻辑一条完整的 text-to-cad 流水线我习惯把它拆成四段语义解析层、参数映射层、几何生成层、格式导出层。这四层不是随便分的而是按照“信息从模糊到精确”的顺序排列的。语义解析层负责把“一个带四个安装孔的长方形板”这种描述拆解成结构化的意图参数映射层把“长”“宽”“孔距”这些词对应到具体的数值和变量名几何生成层调用 CAD 内核真正把点、线、面、体算出来格式导出层把内存里的几何体写成 STEP、URDF 或 G-code。为什么这么分因为每一层的失败模式完全不同。语义解析错了是 NLP 的问题参数映射错了是单位或命名的问题几何生成错了是内核 API 调用的问题导出错了是格式兼容性的问题。分层之后排查问题的时候能快速定位到是哪一层的锅。我见过有人把语义解析和几何生成写在一个函数里结果改一个孔的位置要把整段代码重读一遍维护成本极高。选型上语义解析层我推荐用基于规则的关键词匹配 正则提取作为起步方案而不是一上来就上大模型。原因很简单CAD 描述里的参数往往有固定模式比如“长 100 宽 60 高 30”“孔径 8 孔距 80”这些用正则就能稳定提取速度快、可解释、不花钱。等你遇到“一个看起来比较修长的支架”这种模糊描述时再考虑引入语言模型做意图补全。几何生成层则必须依赖成熟的 CAD 内核自己从零写几何运算是不现实的。2.2 几何内核的选择OpenCASCADE 还是别的几何生成层是整个链路里最硬的部分。你需要一个能处理边界表示B-rep的内核因为 STEP 格式本质上就是 B-rep 的标准化表达。目前开源方案里OpenCASCADE简称 OCCT是绕不开的选择。它支持实体建模、布尔运算、倒角、圆角、抽壳而且能直接导出 STEP。Python 环境下可以用pythonocc-core或者cadquery来调用它。CadQuery 是我个人最推荐的入口因为它把 OCCT 的底层 API 封装成了链式调用写起来像在描述几何关系而不是在调函数。比如画一个带孔的板子CadQuery 里就是Workplane().box(100,60,10).faces(Z).workplane().hole(8)可读性极强。相比之下直接调 OCCT 的 C 接口或者 pythonocc 的底层 API代码量会翻好几倍而且容易在拓扑命名上踩坑。那为什么不选 Blender 的 bpy 或者 FreeCAD 的脚本接口Blender 偏向网格建模导出的 STL 是三角面片做 3D 打印够用但要做精确的工程 STEP 就不合适了因为网格模型丢失了圆柱面、平面这些解析几何信息。FreeCAD 虽然也能脚本化但它的 Python API 稳定性一般版本之间经常变而且启动慢。CadQuery 专注在参数化实体建模这一件事上反而更稳。2.3 输出格式的取舍STEP、URDF、G-code 各管什么输出格式决定了你的模型下游能干什么这里必须讲清楚三者的区别因为很多人会混。STEP是工程领域的通用交换格式它记录的是精确的 B-rep 几何任何主流 CAD 软件中望 CAD、SolidWorks、Fusion 360都能打开并继续编辑。如果你做的是机械零件最终要出图纸或者给别人加工STEP 是首选。URDF是机器人描述格式它描述的不只是几何外形还包括关节类型、运动学树、惯性矩阵、碰撞体。URDF 导入 CoppeliaSim 或者 ROS 做仿真时光有外形不够还得有质量、惯量、关节轴向。所以从 text 生成 URDF比生成 STEP 多了一步“运动学语义”的映射——你得从描述里识别出“这个臂绕 Z 轴旋转”这类信息。G-code是数控加工指令它是刀具路径而不是几何模型。从 text 直接到 G-code中间必须经过“几何生成 加工策略规划”两步。也就是说你得先有实体模型再决定用多大的刀具、走什么路径、进给多少。这一步通常交给切片软件或者 CAM 软件做text-to-cad 负责把模型准备好就行。格式记录内容下游用途生成难度STEP精确 B-rep 几何机械设计、加工出图中STL三角网格3D 打印、快速预览低URDF几何 运动学 惯性机器人仿真高G-code刀具路径指令数控加工高需 CAM理解了这张表你就知道为什么 text-to-cad 不是一个“一键出结果”的工具而是一条需要根据目标格式调整中间环节的流水线。3. 核心细节解析从一句话到参数表的映射要点3.1 语义解析怎么把“人话”拆成结构化参数语义解析这一步最怕的是描述里的隐含信息。比如用户说“一个 M8 的安装孔”这里 M8 是螺纹规格对应底孔直径约 6.8mm但很多人写描述时不会说“底孔 6.8”只写“M8 孔”。如果你的解析器不认识 M 系列螺纹就会把 8 当成孔径直接做错。所以我在参数映射表里会专门维护一张标准件对照表把 M3、M4、M5、M6、M8 这些常用螺纹映射到底孔直径。再比如“倒角 C2”C 代表 45 度等边倒角2 是倒角边长。如果描述写“倒角 2×45°”意思一样但表达不同。解析器要能同时识别这两种写法。我的做法是用正则分组捕获把“C(\d)”和“(\d)\s*[×xX]\s*45”都映射到同一个chamfer_size变量。单位也是重灾区。有人写“长 100”默认是毫米有人写“长 10cm”那就得换算成 100mm。CAD 内核内部一般统一用毫米所以解析层必须做单位归一化。我通常会在解析后加一个校验步骤如果某个尺寸数值小于 0.1 或者大于 10000就弹出警告让人确认因为这两个边界之外大概率是单位搞错了。注意语义解析不要追求一次到位。先支持 20 个最常用的几何描述模式覆盖 80% 的日常需求剩下的边用边补。一上来就想支持所有自然语言表达只会陷入无穷无尽的边界情况。3.2 参数映射变量命名与默认值的设定技巧解析出参数之后下一步是把它们映射到几何构建代码里的变量。这里有个容易被忽视的点默认值的设计。用户描述往往不完整比如只说“一块板”没说厚度。这时候你不能报错退出而应该给一个合理的默认厚度比如 5mm同时在日志里标注“厚度未指定使用默认值 5mm”。这样用户看到结果后如果不满意知道该补哪个参数。变量命名我建议用领域通用缩写而不是自创。比如length、width、height、hole_dia、hole_pitch、fillet_r、chamfer_c。这样别人接手你的代码时不用猜。我见过有人用a、b、c当变量名过两周自己都忘了哪个是哪个。还有一个技巧是参数依赖检查。比如用户说“四个孔均布在板上”但没给孔距。这时候孔距可以由板长和边距推算出来hole_pitch length - 2 * edge_margin。如果边距也没给就用默认边距。这种“能推就推推不出再用默认”的策略能大幅减少用户需要输入的参数数量体验会好很多。3.3 几何构建布尔运算的顺序会直接影响成败几何构建阶段布尔运算的顺序非常关键。举个实际例子你要做一个“带四个安装孔和中心大孔的圆角矩形板”。正确的顺序是先做矩形板 → 倒圆角 → 打四个小孔 → 打中心大孔。如果你先打孔再倒圆角倒角操作可能会因为孔的存在而失败或者把孔边缘也倒掉。为什么因为 OCCT 的倒角算法需要识别连续的边。孔把板的边打断了倒角时拓扑关系变复杂容易报错。所以我的经验是先做外形特征再做去除特征。外形特征包括拉伸、旋转、倒角、圆角去除特征包括打孔、开槽、切除。这个顺序能避开大部分拓扑错误。另一个坑是圆角半径不能大于相邻边的一半。比如板厚 10mm你想在上下边缘倒 R6 的圆角那就会失败因为两个圆角加起来 12mm 超过了板厚。这时候要么减小圆角要么改板厚。程序里最好加一个预检查if fillet_r * 2 min_thickness: 提示用户调整。import cadquery as cq # 正确的构建顺序外形 - 圆角 - 打孔 result ( cq.Workplane(XY) .box(100, 60, 10) # 先做板 .edges(|Z).fillet(5) # 再倒竖边圆角 .faces(Z).workplane() # 选上表面 .rect(80, 40, forConstructionTrue) .vertices().hole(8) # 打四个安装孔 .faces(Z).workplane() .hole(30) # 打中心大孔 )这段代码里forConstructionTrue表示那个矩形只是用来定位顶点不参与实际几何。这是 CadQuery 里批量打孔的标准写法比手动算四个孔的坐标省事得多。4. 实操过程搭一条能跑通的 text-to-cad 流水线4.1 环境准备与依赖安装先把环境搭起来。我用的组合是 Python 3.10 CadQuery 2.4 正则解析。CadQuery 的安装用 conda 最省心因为它的 OCCT 依赖比较重pip 装有时候会缺库。conda create -n text2cad python3.10 conda activate text2cad conda install -c conda-forge cadquery装完之后验证一下import cadquery as cq print(cq.__version__)能打印出版本号就说明 OCCT 内核加载正常。如果报OCCT not found多半是 conda 环境没激活对或者平台架构不匹配。Windows 上建议用 conda-forge 渠道Linux 上同理。4.2 写一个最小可用的解析器下面这段代码是我实际用过的简化版解析器支持“长宽高 孔径 孔数”这几种描述。核心思路是用正则从文本里抓数字再映射到变量。import re def parse_description(text): params {} # 提取长宽高 dims re.findall(r(长|宽|高)\s*(\d(?:\.\d)?), text) for key, val in dims: mapping {长: length, 宽: width, 高: height} params[mapping[key]] float(val) # 提取孔径 hole re.search(r孔径?\s*(\d(?:\.\d)?), text) if hole: params[hole_dia] float(hole.group(1)) # 提取孔数 count re.search(r(\d)\s*个孔, text) if count: params[hole_count] int(count.group(1)) # 默认值填充 params.setdefault(length, 100) params.setdefault(width, 60) params.setdefault(height, 10) params.setdefault(hole_dia, 8) params.setdefault(hole_count, 4) return params测试一下text 一个长120宽80高15的板四个孔孔径10 print(parse_description(text)) # {length: 120.0, width: 80.0, height: 15.0, hole_dia: 10.0, hole_count: 4}这个解析器很粗糙但已经能覆盖一批常见描述了。关键是它的结构清晰你要加新参数就在里面加一条正则和一个默认值不会影响其他逻辑。4.3 从参数到几何构建函数怎么写拿到参数字典后构建几何的函数就很好写了。注意孔的位置要根据孔数动态计算。四个孔就均布在四个角两个孔就左右各一个。def build_model(params): L params[length] W params[width] H params[height] D params[hole_dia] N params[hole_count] # 边距取板宽的 1/6保证孔不贴边 margin W / 6 model cq.Workplane(XY).box(L, W, H) # 根据孔数决定打孔位置 if N 4: points [ (L/2 - margin, W/2 - margin), (-L/2 margin, W/2 - margin), (L/2 - margin, -W/2 margin), (-L/2 margin, -W/2 margin), ] elif N 2: points [(L/2 - margin, 0), (-L/2 margin, 0)] else: points [(0, 0)] model ( model.faces(Z).workplane() .pushPoints(points) .hole(D) ) return model这里pushPoints接受一个坐标列表比用rect加vertices更灵活因为孔的位置可以任意指定不限于矩形顶点。4.4 导出 STEP 与 URDF 的实操差异导出 STEP 很简单model.val().exportStep(output.step)一行搞定。STEP 文件可以用中望 CAD 或者任何支持 STEP 的软件打开尺寸精确能继续编辑。导出 URDF 就麻烦一些因为 URDF 是 XML 格式需要描述 link 和 joint。如果你的模型是单个刚体那只需要一个 link几何部分可以用 mesh 引用 STL 文件。流程是先把模型导出成 STL再写 URDF 文件引用这个 STL并补上惯性矩阵。# 导出 STL 供 URDF 引用 model.val().exportStl(link0.stl) urdf_content ?xml version1.0? robot namepart link namebase_link visual geometry mesh filenamelink0.stl/ /geometry /visual collision geometry mesh filenamelink0.stl/ /geometry /collision inertial mass value0.5/ inertia ixx0.001 ixy0 ixz0 iyy0.001 iyz0 izz0.001/ /inertial /link /robot with open(part.urdf, w) as f: f.write(urdf_content)惯性矩阵这里我用了占位值实际使用时需要根据材料和体积算。质量可以用体积乘以密度估算惯量可以用长方体公式近似。这一步如果做机器人仿真不能偷懒否则动力学表现会不对。4.5 G-code 生成的前置条件从 text 直接生成 G-code我的建议是不要跳过实体模型。正确路径是text → STEP/STL → CAM 软件 → G-code。你可以用 FreeCAD 的 Path 工作台或者专门的 CAM 工具做路径规划。text-to-cad 的职责到实体模型为止再往下是加工工艺的范畴涉及刀具选择、切削参数、装夹方式这些不是一段文字描述能完全确定的。如果你确实想自动化可以用 Python 调用 FreeCAD 的命令行模式做批量路径生成但前提是你已经有一套固定的加工策略模板。比如“所有平面用面铣所有孔用钻孔循环”这种模板化的策略可以脚本化但灵活性有限。5. 常见问题与排查技巧实录5.1 几何构建失败的典型原因现象可能原因排查方法倒角报错圆角半径超过相邻边一半减小半径或加厚材料打孔后模型消失孔径大于板宽检查孔径与板宽比例STEP 导出为空模型不是实体而是壳体用model.val().ShapeType()检查URDF 导入仿真软件后穿模碰撞体与视觉体不一致确认 collision 用的 mesh 正确单位不对模型巨大或极小解析时未归一化单位加数值范围校验我踩过最坑的一次是解析器把“M8 孔”里的 8 当成了孔径结果打出来的孔比预期大装配时螺栓直接穿过。后来我在解析器里加了一条规则遇到“M数字”的模式先查螺纹对照表查不到再当孔径处理。这个教训告诉我领域知识必须写进解析器不能指望通用正则解决所有问题。5.2 批量生成时的性能优化如果你要一次生成几百个模型比如做参数化零件库性能就成了问题。OCCT 的布尔运算比较慢一个带多个孔的模型可能要几百毫秒。优化思路有三个一是复用基础形状如果多个模型只是孔位不同可以先建一个基础板然后复制再打孔二是并行化用 Python 的multiprocessing把不同模型分到多个进程三是减少布尔运算次数能一次打多个孔就不要分多次打。from multiprocessing import Pool def generate_one(params): model build_model(params) model.val().exportStep(fpart_{params[id]}.step) return params[id] with Pool(8) as p: results p.map(generate_one, param_list)八核并行能把批量生成时间压到单核的六分之一左右实测有效。5.3 格式兼容性的避坑清单STEP 导出时选AP214协议兼容性比 AP203 好支持颜色和层信息。URDF 里的 mesh 路径用相对路径否则换台机器就找不到文件。STL 导出时注意公差设置公差太大会丢细节太小文件巨大。一般设 0.01mm 线性公差和 0.5 度角度公差比较平衡。G-code 生成前确认单位是毫米有些 CAM 默认英寸会出大问题。提示每次导出后用独立的查看器打开确认一遍。STEP 用中望 CAD 或 FreeCADSTL 用系统自带的 3D 查看器URDF 用 CoppeliaSim 导入测试。不要只看代码没报错就以为成功了格式问题往往在打开时才暴露。5.4 语义解析的边界处理经验最后分享几个语义解析的实战技巧。第一同义词表要持续维护。用户可能说“孔”“圆孔”“通孔”“安装孔”都指同一个东西解析器要能归一化。第二否定词要处理。“不要倒角”和“倒角”是相反的如果正则只抓“倒角”就会误判。第三数值范围要校验。长宽高出现负数或者零直接拒绝并提示。第四保留原始描述。把用户输入的原话存进日志出问题时能回溯是哪句话解析错了。这套东西我断断续续迭代了大半年从最初只能识别“长宽高”三个参数到现在能处理螺纹、倒角、孔阵列、简单运动学描述。最大的体会是text-to-cad 的价值不在于完全替代人工建模而在于把重复劳动压缩掉。一个需要画两小时的零件如果描述清楚程序十秒就能生成剩下的时间可以用来做真正需要创造力的设计工作。这个方向后续还可以往“描述修改后自动重新生成”和“多零件装配关系自动推导”扩展那又是另一个值得深挖的话题了。