ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

自然语言生成CAD模型:LLM+CadQuery搭建text-to-cad管道实战

自然语言生成CAD模型:LLM+CadQuery搭建text-to-cad管道实战 “text-to-cad”这个标题放在前几年听起来像是科幻小说里的概念。但如果你最近逛过技术社区或者在机械设计、3D打印的圈子里泡过一阵就会知道它已经从一个实验室里的想法变成了实实在在能跑通的工作流。我第一次看到有人用一段描述尺寸和形状的文字直接生成一个能拿去CNC加工的STEP文件时确实愣了一下。做这行十几年从最早的AutoCAD命令行一步步走过来这种反差带来的冲击感比当年从二维转到三维还要强烈。这篇文章要聊的就是我自己搭建一个text-to-cad管道的过程。说得更直白一点输入一句“设计一个M6内六角螺栓长度20mm”系统返回一个可编辑的STEP模型文件。我会把它拆成几个核心环节包括思路选型、关键参数设计、完整实操代码以及实际运行中那些文档里永远不会告诉你的坑。内容主要面向搞机械设计、3D打印的工程师也包括那些对AI辅助设计感兴趣、想自己动手跑一遍完整流程的玩家。1. 内容整体设计与思路拆解1.1 text-to-cad 到底解决什么问题在开始写代码之前先搞清楚它解决的真实需求。传统的CAD建模无论是SolidWorks还是FreeCAD本质上都是在跟“几何操作”打交道拉伸、旋转、放样、布尔运算。你的脑子里先要有模型的样子然后一步步在软件里实现。这个过程最大的痛点不是操作本身而是“思路到模型的转换成本”。一个稍微复杂点的零件可能要在软件里点几十次鼠标调十几个参数稍有不慎就报错。text-to-cad的核心价值就是把“思路”这一层直接变成“模型”。你用自然语言描述需求螺纹规格、长度、孔的直径、倒角大小系统自动完成几何构建。这在快速原型阶段特别有用——你不需要为每一个方案变体重新建模一遍改一句描述就行。比如你需要一个带四个沉头孔的安装板孔距是50mm还是60mm改个数字重跑一遍几秒钟就出新模型这在传统流程里是难以想象的效率。但要注意它并不是要取代参数化建模软件。跟真正的CAD高手比起来AI生成的模型在复杂装配和精密公差上还有不小差距。它的定位更像是“把想法迅速变成可以被验证的实体”是设计流程早期阶段的一个加速器而不是终点。1.2 两条技术路线端到端生成 VS 程序化代码生成text-to-cad的实现路线目前主流的有两条。第一条是端到端的生成模型比如一些基于深度学习的点云或体素生成方案。你输入文字模型直接输出一个网格模型。这条路看起来很性感原理上类似文本生成图片的扩散模型但在实际工程里问题很多生成的网格拓扑混乱、面片密度不均匀、尺寸完全不可控稍微复杂一点的结构就无法转换到可制造的CAD格式。我试过用这类工具生成一个简单的法兰盘输出的网格模型导入SolidWorks后根本无法进行下一步的草图编辑或布尔运算等于一堆废纸。第二条路线是“LLM 程序化建模库”这也是我最终选择的方案。核心思路很简单让大语言模型理解你的自然语言把它拆解成结构化的几何参数然后用CadQuery或OpenSCAD这类以代码为核心的建模库程序化地生成模型。CadQuery特别适合这个场景它的API基于“二维草图绘制-三维特征构建”的逻辑和人类工程师的使用习惯高度吻合。你可以定义一个草图轮廓拉伸成实体再在上面打孔每一个步骤都是可预测、可修改的代码。LLM在这里的作用相当于一个极懂机械设计的翻译官把人的模糊描述翻译成精确的几何操作序列。我选择这条路线的原因有三个。第一输出是真正的CAD模型文件STEP格式可以直接进CAM软件编制加工路径也可以直接导入切片软件做3D打印。第二生成过程是参数化的变量在代码里清晰可见随时可以调整尺寸重跑。第三可控性极强——即使LLM偶尔犯傻生成的代码报错你也可以直接在代码层面修改而不是面对一个已经“坏掉”的网格模型无从下手。1.3 这个项目适合谁来参考如果你属于以下三类人这篇文章会很有实操价值第一每天要在CAD软件里做大量重复性建模工作、却总在改尺寸的机械工程师第二喜欢折腾3D打印经常需要为手头的小装置设计外壳或支架的创客玩家第三对LLM的应用开发感兴趣想利用API做点除聊天机器人以外、有实际产物的应用的开发者。当然前提是你对Python有基本的了解能看懂简单的循环和函数调用。CadQuery本身不难大部分时间你只需要调用几个方法而已。2. 核心细节解析与实操要点2.1 构建“结构化描述协议”让语言模型的输出稳定可控如果你试过让LLM直接写CadQuery代码会发现一件很头疼的事同一个需求换个说法生成的代码风格和结构天差地别。上一轮还能跑通的代码下一轮可能就换了一套写法。原因很简单——LLM本质上在做概率预测不同的表述会触发不同的“记忆路径”。要让输出稳定关键不是提高模型温度或调参而是设计一套“结构化描述协议”Structured Description Protocol把你对模型的描述约束在一个固定的信息框架内。我的做法是定义了一个固定的输入模板强制用户包括我自己按“零件类型-关键尺寸-特征需求-公差要求”四个维度来描述。举个例子你不能只说“我要一个法兰盘”而是要说“这是一个圆形法兰盘外径80mm内径25mm厚度10mm均布4个直径6mm的通孔孔心距直径60mm”。然后把这个描述模板连同CadQuery的基础代码示例一起塞进系统提示词System Prompt里。为什么这样有效因为LLM的上下文窗口有限它需要在一段结构化的指令中“锚定”输出格式。模板和示例代码就像是一副骨架模型的任务变成了“填肉”而不是“造骨架”。这样做还有一个额外好处当你需要批量生成多个变体时只需要修改模板中的几个数字重跑管道即可完全不需要动代码逻辑。我自己实测下来用了这套协议之后生成的代码一次通过率从大概40%提升到了80%以上。2.2 CadQuery代码生成的三个关键几何操作CadQuery的建模思路跟传统参数化软件极其相似核心就三步画草图、建特征、加细节。在让LLM生成代码时我特别关注这三个操作的正确性和规范性因为它们的错误率最高。第一步是草图绘制。CadQuery的Workplane对象是所有几何操作的起点。你需要明确草图所在的平面和原点位置然后通过circle()、rect()、polyline()等函数绘制截面轮廓。这里最常见的问题是“草图未闭合”——你画了一条不封闭的线拉伸时就会报错。LLM在推算复杂轮廓的坐标时经常会出现最后一个点没回到起点的情况造成轮廓不自交但也不封闭的尴尬局面。第二步是特征构建。从草图生成三维体核心操作是extrude()拉伸、revolve()旋转和loft()放样。这一阶段要特别注意拉伸的方向和长度。CadQuery的默认拉伸方向是草图平面法向长度必须为正数。如果你想要对称拉伸比如凸台需要从平面两侧各长出5mm需要指定bothTrue参数否则只向一侧生长。第三步是细节操作包括打孔、倒角、圆角。在CadQuery里hole()是在已有实体上打孔的方法它会自动进行布尔减运算。chamfer()和fillet()分别处理倒角和圆角。这是最容易出问题的一步因为布尔运算对几何拓扑有严格的限制。如果孔的位置和实体边界过于接近或者倒角的半径超过了实体本身的尺寸就会直接导致计算失败。2.3 从文本到STL流程中每个环节的“校验点”设计一个完整的text-to-cad流程不只是“文字进去模型出来”那么简单。我在整个管道的每个关键节点都设置了“校验点”确保即使中间环节出错也能快速定位问题。第一个校验点在“结构化参数提取”之后。LLM会输出一个JSON格式的参数字典例如{outer_diameter: 80, inner_diameter: 25, thickness: 10, ...}。我会在这个环节做一次类型和范围检查所有参数必须是数字必须大于0尺寸上限不能超过一个合理值比如设定最大边长1000mm。这样做的好处是一旦LLM把80误读成800立刻就会被拦截而不是等到模型生成出一个“巨型法兰盘”才发现。这一步纯粹是几行Python代码的事但能救你无数次。第二个校验点在CadQuery代码生成之后。我会在沙箱环境里先尝试执行这段代码捕获所有异常。大多数情况下LLM生成的代码都能跑通但偶尔会因为没有导入正确的库、方法名写错等原因报错。我会把错误信息作为反馈重新喂给LLM让它修正代码。这个“生成-执行-报错-修正”的循环是我整个管道里最有价值的设计。第三个校验点在模型导出之后。生成的STEP或STL文件我会用Python读取实体信息的工具比如OCC检查实体的体积、表面积、包围盒尺寸跟输入的参数做交叉验证。比如你要求外径80mm生成模型的包围盒X方向就应该接近80mm如果明显对不上说明建模逻辑有误需要回到第二步检查。3. 实操过程与核心环节实现3.1 从一句自然语言到一个M6螺栓的完整实现为了让你有个直观的感受我来完整走一遍“M6内六角螺栓长度20mm”这个最简单需求的处理全过程。首先这句话会经过结构化描述协议转换成下面这个表单零件类型内六角圆柱头螺栓螺纹规格M6即公称直径6mm螺距1.0mm公称长度20mm头部直径10mm头部高度6mm内六角孔尺寸对边距4.55mm深度3.5mm倒角头部边缘倒角0.5mm这个表单是怎么来的一部分靠LLM从通用知识里提取比如“M6”对应的螺距是1.0mm、头部直径标准值约10mm这些数据在网上公开的机械手册里都有。也有一部分靠我在系统提示词里预设的规则兜底。如果LLM漏掉了内六角孔深度就自动套用默认值。接着LLM会根据这个参数表生成CadQuery代码核心部分大概是这样的import cadquery as cq # 参数提取 bolt_diameter 6.0 bolt_length 20.0 head_diameter 10.0 head_height 6.0 hex_socket_size 4.55 hex_socket_depth 3.5 # 创建一个六角形头部 head ( cq.Workplane(XY) .polygon(6, head_diameter / 2) # 六边形半径是直径的一半 .extrude(head_height) ) # 创建螺杆部分 shank ( cq.Workplane(XY) .circle(bolt_diameter / 2) .extrude(-bolt_length) # 向下拉伸 ) # 合并螺杆与头部 bolt head.union(shank) # 在头部钻内六角孔 bolt ( bolt.faces(Z) # 选择头部顶面 .workplane() .polygon(6, hex_socket_size / 2) .cutBlind(-hex_socket_depth) # 向下切除盲孔 ) # 可选添加倒角 bolt bolt.edges(%CIRCLE).chamfer(0.5)注意看extrude(-bolt_length)这个负号CadQuery的坐标轴方向决定了正值向上要让螺杆向下长就得取负数。这是新手最容易踩的坑之一。union()方法把头部和螺杆合成一个实体cutBlind()用多边形草图切除出一个内六角孔。这段代码里还有一个小细节.faces(Z)。这个选择器的意思是“沿着Z轴正方向最远的那个面”也就是顶部平面。如果你要做一个方向相反的零件Z就代表底部。这类方向选择的技巧是LLM比较容易搞错的点。如果生成的代码总是孔打到了错误的位置十有八九是这里出了问题。3.2 组合一个完整可复用的Python管道上面只是单独的一段代码但一个能反复使用的text-to-cad系统需要把这些片段拼成一个完整的自动化管道。我给这套管道起名叫txt2cad_pipeline.py核心流程分五个函数parse_requirements()、generate_code()、exec_and_build()、validate_model()、export_step()。import json import cadquery as cq import openai # 示例用的LLM客户端 from pathlib import Path # 伪代码展示核心调用逻辑 def build_model_from_text(text_prompt: str) - Path: # 第1步结构化参数提取 params parse_requirements(text_prompt) # 第2步LLM生成CadQuery代码 code generate_code(params) # 第3步在安全命名空间执行代码 result exec_and_build(code) # 第4步验证实体尺寸 validate_model(result, params) # 第5步导出STEP文件 output_path export_step(result, bolt.step) return output_path我实际用的版本在parse_requirements()里直接调用了LLM API并且把我自定义的JSON Schema一起发给模型强制它返回一个结构合法、字段齐全的JSON对象。exec_and_build()则采用了Python的exec()函数在动态创建的命名空间里执行代码。这里要特别提醒一句在生产环境里直接exec()LLM生成的代码有安全风险但在这个本地试验性的项目里是可接受的。如果你要部署到服务器上一定要跑在容器里并且设置严格的资源限制。执行完之后得到的result是一个cq.Workplane对象或cq.Compound这时候调用cq.exporters.export()指定输出路径和格式就能导出STEP文件。整个过程从输入文本到拿到文件在普通配置的电脑上大概只要5到8秒这在以前是不可想象的效率。3.3 如何在生成过程中“显式控制”关键参数很多人实验这个项目会踩一个共同的坑生成出来的模型大体结构对但尺寸差一点点。比如螺纹孔要求直径6.0mm生成的却是5.8mm。这是因为LLM在内部计算时可能使用了近似值或进行了不必要的四舍五入。解决方案是在系统提示词里明确强调“精确使用参数值不要近似不要重新计算”。另外一个实用技巧是在exec_and_build()之前把参数抽取值注入到代码模板中。不要只让LLM“使用outer_diameter变量”而是直接把具体的数字填进去。这样LLM生成代码时的自由度变小精度可控性反而大幅提升。比如上面的示例里你可以改写成这样再喂给模型# 这部分是预填的参数行 outer_diameter 80.0 # 从结构化协议提取 inner_diameter 25.0 thickness 10.0这就相当于给代码生成过程加了一个“护栏”LLM的任务只剩下组织几何操作逻辑而不需要操心具体数值。别小看这个小改动它把我在“一键生成”场景下的成功率直接推高到了95%以上。4. 常见问题与排查技巧实录4.1 提示词不准确导致的参数偏移我遇到过的最典型问题就是提示词里的描述不够具体。比如你对LLM说“设计一个支架”这个“支架”的范围可以宽到任何工程概念。它可能生成一个L型支架、U型支架或者一个完全认不出来的结构。这不是LLM的错而是“支架”这个词本身缺乏足够的约束信息。解决方式已经在前面说过用结构化描述协议把需求压缩成明确的尺寸和特征列表。第二个高发问题是单位不统一。人类描述习惯很随意“厚一点”“长5个毫米”“直径差不多6吧”这类模糊表达非常常见。虽然LLM能理解“毫米”这个概念但一旦出现“厚一点”这种相对描述它就会靠猜。我的补救办法是在描述协议里强制单位只有数字和单位都明确的信息才被允许传给生成阶段。用“厚度10mm”而不是“厚一点”用“直径6mm”而不是“直径差不多6吧”是让text-to-cad落地的第一课。4.2 布尔运算失败的几何拓扑问题CadQuery里的cut()和union()操作依赖底层OpenCASCADE几何内核。布尔运算失败是家常便饭最典型的情况是两个实体共享同一个面或者孔的位置过于靠近实体边界。我之前尝试生成一个带十字形加强筋的壳体加强筋和壳体壁面之间只有0.1mm的间隙结果布尔并集运算一直报错说“无法构建实体”。排查这个问题的思路是反向的不要直接查看布尔运算结果而是把参与运算的两个实体分别导出在CAD软件里查看它们有没有物理干涉或贴面。我发现问题根源是加强筋与壳体壁面实际上重叠了0.05mm产生了细微的“重合面”OpenCASCADE对这种拓扑很敏感。解决方法是把加强筋的尺寸稍微缩小一点点或者调成“刚好贴合但不重叠”。这类问题纯靠LLM自己很难发现因为LLM看不到几何内核的具体报错细节。我的建议是在exec_and_build()里捕获异常时把报错信息原样反馈给LLM让它尝试调整策略。实践下来大概有一半的拓扑错误可以通过这种方式被模型自行修复。剩下的就得靠你手动检查了。4.3 生成模型导入CAD或切片软件时的兼容性问题有时候CadQuery内部跑通了导出STEP也成功了但倒入SolidWorks或Cura后模型却显示异常。最常见的原因是显示精度和法向。STEP文件理论上应该无损但如果导出STL三角网格的精度设置不当可能导致曲面上出现明显的棱角或者法线方向错乱导致切片软件误判为“非法壳体”。解决STL网格问题的关键是设置合适的弦偏差chordal tolerance和角度公差。CadQuery的export()函数在导出STL时可以通过cq.exporters.ExportSettings控制这些参数。我把角度公差设为0.1弧度弦偏差设为0.01毫米这样生成的STL在保持文件体积合理的同时打印出来的曲面也比较光滑。另外一定要在导出后检查STL是否为“水密”的即有没有破面。我用三款免费工具检查模型的水密性微软的3D Builder、Autodesk的Meshmixer以及FreeCAD里的网格分析工具。只要有一个能正常分析大概率就能直接打印。4.4 几个隐藏很深的“工程习惯”问题还有几个跟技术无关、但影响巨大的工程习惯问题值得拿出来单独说说。第一版本锁定你用的LLM模型会迭代同样的提示词在GPT-3.5和GPT-4上生成的代码风格完全不同。即使是同一个模型官方也可能在后台默默更新参数。所以一旦你调试好一套稳定的生成流程一定要把模型名称和提示词版本用环境变量固定住不要每次都裸调最新的模型。第二输出文件命名生成的模型默认带一串随机哈希一点都不直观。我习惯让管道自动把零件的主要参数拼进文件名例如M6_bolt_L20_head10_hex.step。这样你在一堆文件里找模型的时候一眼就能看出区别。第三LLM输出的JSON一定要做二次校验。我遇到过LLM返回的JSON里混入了解释性的文字比如“这是您需要的参数”后面才是JSON。当时我的解析代码直接抛异常了。解决方式很简单用正则表达式提取第一个{到最后一个}之间的内容再交给json.loads()处理。这个小技巧在调用任何LLM API的时候都适用。结尾一些实际使用中的体会在做这个项目的过程中我最大的感触其实是text-to-cad真正的价值不在“零门槛建模”而在“把机械设计里最枯燥的一部分劳动自动化”。它不会替代你思考零件的受力、材料的选用、加工的工艺但它能让你把一天里大半的时间从“在CAD里反复拖拽草图”中解放出来去专注于设计里更本质的部分。目前这套工作流已经成了我个人项目里的常备工具尤其是做快速原型验证的时候。而站在更长远的角度看自然语言与三维模型的这种交互方式很可能就是未来设计软件的基础交互范式之一。如果你想试试水建议从CadQuery的官方示例开始先熟悉它的基本面再接入LLM一步步跑通属于自己的管道。
返回列表