
“text-to-cad”这个词我算是眼睁睁看着它从一个论文演示型概念变成我们这帮做自动化设计工具的人手里真正能干活的流水线。它要解决的事情特别直白你脑子里想好一个零件用一句人话把它描述出来系统直接给你一个能编辑、能出图、能拿去加工的三维CAD模型。注意这里说的是CAD模型不是渲染图不是网格糊出来的雕塑。它得精确到毫米参数可以改特征树得清清楚楚导出STEP之后丢到CAM里能直接开干。这个方向适合谁来折腾一类是像我这样天天跟标准件、非标件打交道的工程师重复建模建到怀疑人生想用文本批量生成。另一类是做自动化设计工具的开发者想在产品里加一个“自然语言建模”入口。还有一类就是刚接触CAD二次开发的新人拿它当学习参数化建模和几何内核的绝佳练手项目。它不能替代你做完整的结构设计但能把你从“画一个法兰盘还要重复点十次布尔运算”这种破事里解放出来。接下来说说我在做这个方向时的真实思路、踩过的坑以及一套能跑通的最小流程。我的做法可能不是最前沿的但足够扎实希望能给你省点时间。1. 项目整体思路先想清楚输出的是“模型”还是“参数”1.1 我理解的“text-to-cad”在做什么如果只盯着“文字变3D模型”这个表层很容易做歪。很多人一上来就想着“生成网格”觉得有个STL文件交差就行。但在真实工程场景里网格模型几乎毫无价值。你要的是一个带有参数化特征历史的模型比如“外径80mm的法兰盘”用户要改外径改完螺栓孔阵列自动跟着动工程图自动更新。这才是CAD的价值所在。所以真正的系统链路是自然语言 - 语义解析 - 结构化参数 - 特征化建模 - 可编辑模型文件。中间任何一步脱离了“参数化”和“特征化”最后产出的东西就是个一次性塑料壳子中看不中用。我自己在做模拟项目X的时候定的目标是这样一个输入做一个外径80mm、内径40mm、厚度12mm的法兰盘在直径70mm的螺栓圆上均匀分布6个直径6.5mm的通孔外圈倒角1mm。输出要求是一个带特征树的STEP文件能打开后看到“盘体”、“中心孔”、“螺栓孔阵列”、“外圈倒角”这些特征每个特征参数都可以单独修改。这个目标听起来不大但真做起来牵扯到的东西比想象中多得多。1.2 为什么我放弃了“端到端生成网格”的路线在早期调研时我也试过直接让大模型输出一些网格生成指令或者用深度学习方法从文本直接回归出三维点云。结论是这条路在工程上走不通。首要问题是精度。CAD模型的精度要求是微米到毫米级网格模型哪怕是0.1mm的误差装配时都会出问题。其次是拓扑可控性。网格生成经常出现自相交、非流行边、裂缝这些在渲染场景里无伤大雅在CAM和CAE里就是灾难。再一个是可编辑性。网格没有特征概念用户想把某个孔的直径从6.5改成8你没法在网格上点一下说“这个孔变大一点”。你得重新生成。我后来把方案改成了“大语言模型做语义解析程序化建模做几何生成”的混合架构。为什么这么做道理很简单语义理解是模糊的适合交给大模型几何构建是精确的适合交给确定性的程序。两边各干各擅长的事每一层都可以单独测试、单独调优出了问题能定位到底是你解析错了还是建模错了。这就像做菜大模型负责读菜谱告诉你“盐少许”大概是2克你自己负责开火下锅精确控制时间和温度。你不能指望读菜谱的顺便把菜也炒了。1.3 我采用的分层架构整个系统我拆成了三层第一层是语义解析层。负责把自然语言转成结构化JSON比如关键的实体“法兰盘”、“中心孔”、“螺栓孔”属性“直径”、“厚度”、“数量”关系“均匀分布”、“同心”、“偏移”。我用了正则表达式做兜底抽数字和单位用大模型做意图理解和关系抽取。第二层是几何构建层。拿到结构化参数后先把参数做合法性校验再映射到具体几何特征的构造函数上。比如“外径80mm”映射成cylinder(radius40, height12)“中心孔”映射成另一个圆柱然后做布尔减运算。第三层是验证与输出层。生成完几何体后要检查体积是否合理、有没有退化面、有没有干涉最后导出成STEP或者STL。这一层很多人忽略但恰恰是工程落地最关键的一步。没有验证的自动建模就像没测试就上线的代码早晚出事。这个分层架构最大的好处是每层都可以独立演进。今天换一个大模型不影响几何构建层明天想把输出格式换成DWG只需要改输出层。我在实际改造中发现这套结构的可维护性远远好于一开始那个“一把梭”的端到端方案。2. 核心原理把一句话变成一棵特征树2.1 文本解析不是简单的“抽参数”一开始我天真地以为文本解析就是拿着正则表达式匹配数字和单位。比如看到“80mm”就抓出80看到“6个”就抓出6。但真正跑起来才发现自然语言里的坑多得很。第一用户不会只报数字。他会说“比这个再厚一点”、“孔稍微靠外”、“标准M6螺丝孔”这些语义靠正则根本玩不转。第二几何关系描述复杂。“在直径70mm的螺栓圆上均匀分布”这句话要解析出“分布模式是圆周阵列阵列直径是70阵列数量是6”这个结构化信息靠简单规则会疯掉。第三参数之间存在隐含依赖。比如“M6螺丝孔”隐含通孔直径6.5mm螺纹底孔5.0mm这些知识藏在工程规范里不在文本里。所以我的解析策略是正则打底大模型主力。具体做法是先定义好一个中间Schema大模型的任务只有一个把自然语言塞进这个Schema里不许自由发挥。我的提示词大概是这样约束的{ part_name: 法兰盘, features: [ { feature_type: main_body, parameters: {outer_diameter: 80, inner_diameter: 40, thickness: 12, unit: mm} }, { feature_type: circular_pattern_holes, parameters: {pattern_diameter: 70, hole_diameter: 6.5, count: 6, unit: mm} }, { feature_type: chamfer, parameters: {target: outer_edges, size: 1, unit: mm} } ] }这个JSON就是中间语言。大模型负责把“在直径70mm的螺栓圆上均匀分布6个直径6.5mm的通孔”翻译成上面那个circular_pattern_holes对象。然后我再用确定性代码去消费这个JSON生成几何。这样即便大模型偶尔抽风我也可以在JSON校验阶段把它拦下来。这里有个关键技巧一定要给大模型提供好few-shot示例明确告诉它“均匀分布”就是“circular_pattern”“通孔”就是“through_hole”单位缺失时默认用毫米。另外遇到不确定的参数宁可在JSON里返回null让系统提示用户也不要自己瞎猜。我见过太多因为“大模型自信地补齐了单位”导致整件废掉的案例。2.2 参数化建模特征树才是核心资产几何构建部分真正的核心不是画图而是维护一棵“特征树”。为什么强调特征树因为用户改需求是常态。今天外径80明天可能改成90。你要做的不是重新生成一个模型而是在原有特征树上把“外径”这个参数从80改成90然后让树上的依赖节点自动更新。CAD里叫“参数驱动”实现上就是维护一棵多叉树。我在模拟项目X里维护的特征树长这样根节点法兰盘特征节点A盘体外径80厚度12特征节点B中心孔内径40依赖盘体中心特征节点C螺栓孔阵列依赖盘体中心直径706个直径6.5特征节点D外圈倒角依赖盘体边缘每个特征节点存了“特征类型”、“参数表”、“父节点引用”。这样用户说“把外径改成90”我只需要找到节点A更新参数然后遍历所有依赖A的子节点重新计算它们的位置和几何。这就是为什么我坚持要用程序化建模而不是直接生成网格——程序化建模天然就是一棵特征树网格没有这个概念。给一个日常类比特征树就像是购物清单里的“总分结构”。你买一台电脑显示器是显示器键盘是键盘它们都隶属于“电脑”这个大项。显示器坏了你只换显示器不用把整台电脑扔了。CAD里的特征树也是这个道理改动某个特征不牵连整棵树重建只重算受影响的树枝。2.3 为什么CSG足够支撑大部分场景几何内核我用的是CSG构造实体几何方案就是靠圆柱、方块、球这些基本体做并、交、差组合出零件。很多人觉得CSG土但它真的够用。法兰盘这个例子就很有代表性。盘体是一个圆柱中心孔是一个细圆柱布尔差一减就出来螺栓孔是六个小圆柱循环摆到圆周上再逐个布尔差倒角麻烦点但也能用圆角扫掠或倒角命令处理。对机械零件里大量存在的轴、孔、槽、法兰、支架这类特征CSG完全可以覆盖而且实现简单、计算稳定。B-rep边界表示法当然更强大能处理自由曲面、复杂倒角和拓扑问题。但代价是要维护边、面、环之间的拓扑关系开发量大得多。如果你的目标是“跑通text-to-cad让它能处理80%的常见机械零件”CSG是性价比极高的选择。如果目标是工业级曲面造型那再考虑换内核但那是另一个量级的项目了。CSG也不是没有坑。最典型的是布尔运算后产生的退化面。两个圆柱端面完全齐平的时候布尔差运算很容易产生零厚度的“纸片面”后面导出STEP时会出报警。解决办法是让刀具体比目标体稍微长一点点比如中心孔圆柱的高度比盘体厚度多出1mm确保完全贯穿。这个小技巧是后面排查问题时的关键。3. 实操用“法兰盘”跑通整个流程3.1 定义目标案例和验收标准实践出真知我只讲自己验证过的流程。模拟项目X就拿法兰盘开刀因为它的特征足够丰富有一个主体、一个中心孔、一组圆周阵列孔、一个倒角刚好覆盖了text-to-cad里最常见的几类特征。目标输入文本就是这句做一个外径80mm、内径40mm、厚度12mm的法兰盘在直径70mm的螺栓圆上均匀分布6个直径6.5mm的通孔外圈倒角1mm。输出验收标准有四条模型是STEP格式能导入主流CAD软件。特征数量对得上盘体、中心孔、螺栓孔阵列、倒角四类特征都在。关键尺寸参数化可改修改外径后螺栓孔阵列和倒角自动跟随。几何验证通过体积误差不超过理论值的1%无干涉、无退化面。这个验收标准看起来基础但真正做到位后面所有模块都能复用。3.2 文本解析层的实现解析环节我先用正则做一轮“粗提取”。这一步的意义不是理解语义而是把数字和单位先抓出来给大模型减少幻觉空间。我大概写了这样一段逻辑import re def extract_numbers_with_units(text: str) - list[dict]: pattern r(\d\.?\d*)\s*(mm|cm|m|inch|英寸)? matches re.findall(pattern, text, re.IGNORECASE) return [{value: float(v), unit: u or mm} for v, u in matches if v]这一步会把“外径80mm、内径40mm、厚度12mm”里的80、40、12全都抓出来。抓完之后我并不会直接用这些数字去建模因为它们还不知道谁是谁。接下来是主角大模型结构化抽取。我在提示词里给了一个固定模板要求它输出上面提到的JSON。为了让输出稳定我在提示词里强调“只输出JSON不要解释不要代码块标记”并且给了两个少样本示例。一个示例是简单轴一个示例是带法兰盘正好覆盖目标场景。抽完之后的JSON我会做一个严格的Schema校验。校验点包括必填字段是否齐全比如main_body必须有outer_diameter、inner_diameter、thickness。数值是否为正数单位是否支持数组数量是否超过1。是否有明显矛盾比如inner_diameter大于outer_diameter。这一步通过后参数才真正进入几何构建层。我见过很多团队在“大模型解析”这一步放得很宽结果下游建模全乱。记住一点大模型输出永远是“建议”校验和兜底必须靠确定性代码。3.3 几何构建层的核心流程拿到JSON之后构建法兰盘的函数我按步骤拆开写。第一步创建盘体第二步打中心孔第三步圆周阵列螺栓孔第四步倒角。伪代码如下def build_flange(params: dict): outer_r params[main_body][outer_diameter] / 2 inner_r params[main_body][inner_diameter] / 2 thickness params[main_body][thickness] # 1. 盘体一个外径80mm、高12mm的圆柱 body backend.cylinder(radiusouter_r, heightthickness) # 2. 中心孔一个内径40mm的圆柱高度比盘体多2mm彻底贯穿 center_hole backend.cylinder(radiusinner_r, heightthickness 2) center_hole backend.translate(center_hole, z-1) body backend.boolean_cut(body, center_hole) # 3. 螺栓孔阵列6个直径6.5mm的圆柱绕圆心均匀分布 pattern_d params[circular_pattern_holes][pattern_diameter] / 2 hole_r params[circular_pattern_holes][hole_diameter] / 2 count params[circular_pattern_holes][count] for i in range(count): angle 2 * math.pi * i / count x pattern_d * math.cos(angle) y pattern_d * math.sin(angle) bolt_hole backend.cylinder(radiushole_r, heightthickness 2) bolt_hole backend.translate(bolt_hole, xx, yy, z-1) body backend.boolean_cut(body, bolt_hole) # 4. 外圈倒角对盘体顶面和底面的外边缘做1mm倒角 body backend.chamfer( body, edgesbackend.get_outer_edges(body), sizeparams[chamfer][size] ) return body这里有几个细节值得展开说。第一个细节是所有减运算用的圆柱体高度都是thickness 2而且整体向下平移了1mm。这是故意的。如果孔圆柱高度和盘体完全一样布尔运算时两端面与盘体端面共面非常容易出现退化面。多给2mm高度相当于“刀具”切穿了“工件”保证布尔差稳定。第二个细节是圆周阵列的位置计算。很多人以为均匀分布6个孔就是把360度除以6然后循环算坐标就行。实际写代码时要注意用的是弧度制而且起始角度位置可能影响后续装配。我习惯第一个孔放在X轴正方向也就是角度0这样在工程图上看起来规整。如果后续还要做防呆定位可以加一个start_angle参数控制。第三个细节是倒角的选择。不是所有边缘都倒我调用了get_outer_edges(body)专门筛选盘体外边缘避免把中心孔和螺栓孔的边缘也倒了。这个筛选逻辑看起来不起眼但决定了一个模型是“能做出来”还是“能交付”。很多人就是在细节上偷懒倒角全选结果螺栓孔口也被倒成喇叭口产品直接报废。3.4 验证与导出几何构建完不是万事大吉。我在模拟项目X里加了一条强制规则任何自动生成的模型必须通过三类验证才能输出。第一类是面积体积守恒验证。比如这个法兰盘的理论体积可以用大圆柱体积减去中心孔圆柱体积再减去6个螺栓孔圆柱体积来算。如果实际生成的模型体积和理论值偏差超过1%说明布尔运算出问题了要么是某个孔没切穿要么是产生了额外碎片。第二类是特征存在性验证。我会遍历特征树确认每个特征节点对应的几何体块还在。比如螺栓孔阵列数一下模型里的孔洞数量不是6个就报警。这一步可以用截面分析实现也可以靠CAD内核的拓扑查询接口。第三类是装配干涉验证。如果你生成的零件要跟轴、螺母配合就把配合件的简化模型也加载进来跑一次干涉检查。自动生成100个法兰盘有一个和轴干涉装配线上就会出问题。验证通过后导出STEP文件。STL只是备选给3D打印用。STEP才是给生产看的因为它保留了精确几何和拓扑信息。导出时还要设置好单位我统一用毫米否则到别的软件里打开可能变成米或者英寸。backend.export_step(body, flange.step) backend.export_stl(body, flange.stl, tolerance0.01)导出完再做一个最终检查把文件重新读进来看特征树还在不在。有些导出设置会把模型“压平”成无特征的网格那之前所有工作就白费了。4. 常见问题与排查技巧实录4.1 单位缺失和默认值怎么处理实际使用中用户不会乖乖说全“mm”。他可能说的是“做一个外径80的法兰盘”单位完全没提。这种情况我建议不要硬猜直接采用默认单位毫米。但“M6螺丝孔”这种专业描述属于例外它本身自带工程标准不需要单位。更麻烦的是“大一点的孔”、“稍微厚一点”这种模糊描述。对付这些我在系统里内置了一个规则库把常见说法映射到经验值。比如“M6螺丝孔”映射为通孔直径6.5mm螺纹底孔5.0mm“大一点的孔”如果是用在法兰盘场景就默认取主流标准法兰盘的孔径比如16mm左右。这个规则库不是一次建成的。我是把用户反馈里出现的模糊词条一条条加进去积累了大概百十条之后系统才真正“懂事”。如果你要自己做一个建议先找行业标准手册把常用紧固件、配合公差、标准孔径抄一遍编成字典。这本来就是建筑工程里的常规操作只不过从人工变成了配置。4.2 “右侧”到底是谁的右侧几何关系歧义是text-to-cad最头疼的问题之一。例如“右侧面开孔”这个“右侧”是站在模型前方看还是站在系统默认坐标系看如果不约定解析出的参数可以南辕北辙。我在项目里明确约定了一套规则坐标系固定为右手系Z轴向上X轴指向用户右边Y轴指向屏幕里。凡是文本中出现“左/右/前/后”全部基于这个固定坐标系解释。用户可以在对话里表达意图但写入特征树时必须转化成绝对的方向向量。除了方向还有一个关系歧义“螺栓孔在中心孔周围”。这里“周围”是什么范围默认我定为均匀分布在某个圆周上但如果用户没说直径我就提示缺少“螺栓圆直径”而不是自作主张填一个70mm。让用户确认比生成一个错误模型再返工要高效得多。4.3 布尔运算失败和退化面做CSG建模的人最怕两个问题布尔运算失败和退化面。布尔运算失败最常见的场景是刀具体和目标体有共面部分比如中心孔圆柱端面刚好和法兰盘底面齐平运算时内核算不出交线。我在第3节里提到的高度多2mm、向下平移1mm就是专门治这个病的。还有一个补救措施是给布尔运算加一个“容差参数”让内核允许微小的面偏移。不同内核的容差参数名称不一样但思路都一样别让任何两个面完全重合。退化面则是布尔差之后留下的一些近乎零厚度的碎面。它们不影响视觉但在导出STEP时会像幽灵一样附在模型上影响CAM路径计算。我的排查手段是“拓扑清洗”遍历所有面计算面积和边长度小于阈值的直接删除并修复邻近环。听起来玄乎其实就是一个循环加几个几何判断跑起来很快。4.4 过约束和欠约束的处理解析大模型输出JSON时最怕同时遇到过约束和欠约束。过约束的例子用户既说了“外径80mm”又说了“孔到边缘距离5mm”这两个约束在已知中心孔半径的情况下可能会冲突。我在参数校验阶段顺手写了个简易矛盾检测器如果两个参数同时出现就按照优先级决定谁生效或者直接抛出让用户确认。原则是“宁可停下来问不可蒙着眼跑”。欠约束则更常见用户只说了“做一个法兰盘”别的什么都没说。这时候我不会直接硬编一套默认参数而是把缺失参数列表列出外径、内径、厚度、螺栓孔数量、螺栓圆直径、倒角大小。系统可以给一份基于标准法兰盘的默认建议但是必须打上“待确认”的标记。自动生成不能等于自动拍板这是工程伦理问题。4.5 可制造性规则检查最后生成模型后要过一遍可制造性规则。这是我踩过很多坑才补上的模块。比如壁厚太小CNC加工会颤刀孔间距太近两块孔壁重叠导致破裂螺纹孔离边缘太近加工时崩边。我把这些规则写成一个简单的静态检查器跑起来像这样检查项判断逻辑动作最小壁厚两孔间最小实体距离 3mm报警并提示调整孔位通孔最小直径通孔直径 1mm报警可能是文本解析错误倒角与孔重叠倒角范围覆盖孔口报警建议改倒角区域模型整体包围盒最大尺寸超过指定阈值提示可能超出加工范围这些规则每一行都不复杂但组合起来就能把大部分低级错误拦在生产前去。我强烈建议你在自己的text-to-cad项目里从一开始就留好规则模块的位置别等模型生成量大了再补。5. 后续可以继续做的方向5.1 从单零件扩展到装配体单零件跑通之后自然会想扩展装配体。比如“一根直径20mm的轴和刚才那个法兰盘的中心孔配合轴端做成直径16mm的螺纹段”。这不再是生成一个零件而是要生成两个零件同时定义它们之间的配合关系轴的外圆柱面和中心孔的内圆柱面同轴轴肩紧贴法兰端面。实现方式还是在特征树的基础上加一层“装配约束树”。零件特征树只管几何装配约束树管零件间的配合。解析文本时除了特征参数还要抽“配合关系”同轴、贴合、距离、角度偏移。这一步做通了text-to-cad的价值就从“替代手画零件”升级到“替代手画装配体”。5.2 多轮对话式修改现在很多方案还是一次性生成用户觉得不满意只能重新描述一遍。更好的方式是支持增量修改“把外径改成90mm”这句话应该触发对已有特征树节点A的参数更新而不是重新跑一遍完整解析。实现要点是给每个特征节点一个稳定的“唯一标识”和“可修改参数清单”。用户说“外径”系统先定位到特征类型是main_body的节点再修改outer_diameter字段最后触发重算。这需要在前端对话里维护当前模型状态把历史特征树缓存起来。这个功能的体验提升非常明显但也比一次性生成复杂一个量级。5.3 把企业规范和知识库顶上来文本到CAD不只是技术问题还是知识工程问题。一个机械工程师描述零件时脑子里装的是企业标准、设计手册、加工规范。这些知识不能只靠大模型“记住”。我后来的做法是把企业规范外置成一个知识库。比如“所有倒角必须大于0.5mm”、“螺栓孔离边缘至少8mm”这些写进规则文件。大模型负责理解自然语言规则引擎负责卡工程底线。两者各管一摊出问题的时候边界也清楚。做这个方向给我最大的体会是text-to-cad的真正难点不是让大模型“懂人话”而是让人话变成参数后系统还守得住工程约束。建模和解析都只是手段能交付一个让加工师傅挑不出毛病的模型才算真的跑通。这条路还有很多可以挖的地方但目前这套流程已经能帮我每天省掉一两个小时重复劳动我觉得已经值回票价了。