
“text-to-cad”这个词我第一次认真接触是在一个加工报价系统的内部开发里。当时想着既然大模型能写代码那让它直接写一段CadQuery脚本生成三维模型应该不算难事。结果真跑起来才发现从“能生成代码”到“能稳定生成对的几何”中间隔着一整条工程化的深坑。如果你也想搭一套从自然语言到CAD模型的工具或者单纯想搞明白这个方向到底行不行我把自己跑通流程、踩坑排查、落地的经验都写在这里希望能帮你省掉几周瞎折腾的时间。1. text-to-cad到底在解决什么从“画图两小时改图一秒钟”的真相说起1.1 传统CAD建模的“时间黑洞”在哪做过机械设计的人都有这种体验新建一个零件选基准面、画草图、加约束、拉伸再打孔、倒角、阵列一套动作下来哪怕结构很简单也得花上十几分钟。真正痛苦的还不是第一次建模而是改模型。早期的建模步骤如果没有按“参数化特征”的思路组织好客户跟你说“把孔距从80改成85”你很可能要把整个特征树从头到尾顺一遍然后发现某个草图约束早就锁死了一改全崩。行业里那句“画图两小时改图一秒钟”其实是反讽——画的只是体力活改的才是技术活。text-to-cad瞄准的正是这个痛点让自然语言直接变成可编辑的CAD模型缩短从“脑子里想的”到“CAD里有的”这段距离。它不是把文字变成一张图片而是变成真正的三维几何体能导出STEP、STL能进CAM能继续在CAD里改特征。1.2 什么是text-to-cad先看一个最小例子一句话解释text-to-cad让模型读入一句自然语言描述输出一个可执行的CAD建模脚本或直接输出模型数据。比如你输入“一个直径40mm、高20mm的圆柱体中心带一个直径10mm的通孔”理想情况下系统会生成类似这样的CadQuery代码import cadquery as cq result ( cq.Workplane(XY) .cylinder(20, 40) # 高20直径40 .faces(Z) .hole(10) # 直径10通孔 )看起来只是简单的几个API调用但关键在于这段话本身是文本而模型要从文本里提取尺寸、几何类型、特征位置、布尔关系再映射成参数化API。跟你直接命令ChatGPT写一段“计算圆面积”的代码完全不同这里每一步都可能产生几何语义漂移。1.3 它真正带来的增量价值有人问这不就是“AI替代设计师”吗我觉得方向完全相反。text-to-cad真正改变的是三类场景的成本结构降低入门门槛不需要先学一个月的CAD软件只要会说尺寸和特征就能得到初版模型。批量参数化设计搭好提示词模板后同类型的法兰、支架、壳体改几个参数就能批量生成效率是手动建模没法比的。把“口头描述”变成“可追溯的模型”很多需求沟通发生在三维建模之前text-to-cad可以把需求描述直接转成初始几何让需求方和设计方在同一个模型上对齐而不是靠几张截图来回猜。说白了它把CAD工程师从“重复堆特征”里解放出来让人有精力去做更复杂的解释、判断和验证。2. 主流实现路线拆解LLM是在“写代码”而不是“画图”2.1 为什么中间表示选择了“代码”我最初以为text-to-cad会让大模型直接“画”一个三维模型出来后来发现完全不现实。CAD底层的B-rep边界表示和CSG树是高度结构化的几何数据动辄上百KB而且对精确度要求极高。让Transformer直接输出这种底层数据相当于让一个人背出整个STEP文件的二进制格式几乎不可能。更聪明的做法是让LLM输出“建模指令”也就是一段代码。代码是离散的、有语法规则的正好是语言模型最擅长生成的东西。然后由CAD内核去执行这段代码把几何生成交给可靠的底层引擎。所以本质上text-to-cad不是在“画图”而是在“写一份让CAD内核执行的设计说明书”。2.2 两条主流技术路线特征序列生成与程序合成目前业内做得比较多的有两类方法我用自己的话总结一下。第一类是特征序列生成。这类方法不生成代码而是直接输出一个特征操作序列比如“拉伸一个圆柱-在顶面打孔-在圆周阵列”。每个特征用固定的参数表示模型本质上是一个序列生成器。公开的DeepCAD、Text2CAD相关研究大致属于这个方向。优点是输出结构紧凑、相对容易训练缺点是特征类型和草图能力受限于数据集一旦遇到非规则断面模型就抓瞎。第二类是程序合成也就是让LLM输出OpenSCAD、CadQuery、FreeCAD的Python脚本。刚才那个圆柱打孔的例子就属于这类。优点很明显可以借用大模型已有的代码能力且输出理论上可以表达任意复杂几何缺点也很明显——代码本身可能跑不过API用错、坐标系搞错、特征顺序错样样都是坑。我自己落地时选的是程序合成路线。原因是工程上可维护性更好脚本是文本可以做diff、做缓存、做静态检查万一模型生成了怪东西人打开代码一眼就能看出哪里出了问题。2.3 两类代码生成方式的一个直观对比OpenSCAD和CadQuery是两种常见的程序合成目标。OpenSCAD的写法很像CSG$fn 50; difference() { cylinder(h 20, d 40); cylinder(h 20, d 10); }CadQuery的写法更贴近参数化特征建模import cadquery as cq result ( cq.Workplane(XY) .cylinder(20, 40) .faces(Z) .hole(10) )两者都能生成同一个圆柱体带通孔。区别在于CadQuery的特征语义更强后续可以继续在顶面加特征、倒角、镜像比较适合需要反复修改的零件。OpenSCAD更简洁但对复杂特征的支持弱生成出来以后也很难跟传统CAD数据交换。所以我后来主力选CadQuery。2.4 当前路线的天花板在哪程序合成路线最大的问题是“几何事实”和“自然语言”之间存在鸿沟。LLM知道“圆角”这个词但它不一定知道.fillet()选哪些边才符合用户脑子里的“四角倒圆角”。它也不知道一个轮廓是不是封闭、一个草图会不会自交、一个拉伸方向会不会导致布尔失败。这些CAD内核的隐性规则训练语料里很少以显性约束出现所以自然就成了错误重灾区。理解了这一点后面所有优化方向其实都是在补一个“几何常识层”。你可以把它理解成LLM负责把语言翻译成大致正确的指令但真正拍板的必须是CAD内核和校验器。这个思路贯穿后面的所有实操。3. 复现一个最小可用的text-to-cad流程选型、环境和代码骨架3.1 选型为什么我选CadQuery而不是更“AI原生”的方案前面提到DeepCAD那种直接生成特征序列的方案确实更“原生”但对工程化不友好。我选CadQuery主要是因为三点它基于OpenCASCADE内核导出的STEP文件可以直接给CAM、PLM系统用。建模思路是“在平面上画草图-拉伸/切除-继续选面操作”跟传统CAD心智模型一致。有活跃的社区API相对稳定出了问题容易搜到答案。如果你想快速验证效果OpenSCAD也可以但后续做参数化约束会绕路。我的建议是只要不是自己玩直接上CadQuery。3.2 环境准备与最小化验证先搭环境。我这里用的是Python 3.10和condaconda create -n text2cad python3.10 -y conda activate text2cad pip install cadquery装完以后先跑一个最小脚本验证内核能用import cadquery as cq from cadquery import exporters shape cq.Workplane(XY).box(10, 10, 10) exporters.export(shape, test.step, exportTypeSTEP) print(shape.val().Volume())能输出1000.0就说明环境没问题。这一步别跳过很多翻车其实是OpenCASCADE版本或conda依赖没装好导致的。3.3 核心代码LLM生成CadQuery脚本并自动纠错接下来是最核心的部分调用LLM生成CadQuery代码执行它如果报错就把错误信息回喂给LLM让它修复。下面这段骨架是我实际在跑的最小闭环你可以直接抄走改一改。import cadquery as cq from cadquery import exporters from openai import OpenAI client OpenAI( base_urlhttp://localhost:11434/v1, # 本地模型服务地址 api_keyollama, # 本地服务随便填 ) PROMPT 你是资深CadQuery工程师。请把用户需求转换成可执行的CadQuery脚本。 规则 - 单位一律用毫米 - 只能输出纯Python代码不要任何解释 - 在脚本里定义 result 变量 - 不要写 import直接使用 cq - 默认基准面是XY平面 需求{description} 代码 def execute_script(script: str, output_path: str) - cq.Workplane: namespace {} exec(script, {cq: cq}, namespace) if result not in namespace: raise ValueError(脚本里没有定义 result 变量) exporters.export(namespace[result], output_path, exportTypeSTEP) return namespace[result] def generate_cad(description: str, output_path: str output.step, max_retries: int 3) - str: messages [{role: user, content: PROMPT.format(descriptiondescription)}] for _ in range(max_retries): resp client.chat.completions.create( modelqwen2.5-coder:14b, messagesmessages, temperature0.1, ) code resp.choices[0].message.content.strip() # 去掉可能出现的markdown代码围栏 if code.startswith(): code code.split(, 2)[1] if code.startswith(python\n): code code[len(python\n):] try: shape execute_script(code, output_path) print(f体积: {shape.val().Volume()}) return code except Exception as e: messages.append({role: assistant, content: code}) messages.append({role: user, content: f执行报错{e}。请修复后重新输出完整代码。}) raise RuntimeError(重试多次仍然失败)这里有个关键点temperature设成0.1。写代码不是生成文案温度越低越稳定。执行失败后的反馈信息必须包含异常内容但不能让LLM看到整个文件系统的日志否则容易把不该有的错误拉进上下文。3.4 一个实测跑通的例子法兰盘拿一个典型的加工件试一下。用户描述“一个外径80mm、内径40mm、厚度10mm的法兰盘周围均匀分布4个直径8mm的安装孔孔中心圆直径60mm。”模型可能生成类似这样的代码result ( cq.Workplane(XY) .circle(40) # 外径80 .extrude(10) # 厚度10 .faces(Z) .circle(20) # 内孔半径20 .cutBlind(-10) # 通孔 .faces(Z) .workplane() .pushPoints([(30, 0), (0, 30), (-30, 0), (0, -30)]) .hole(8) # 4个安装孔 )跑完后得到STEP文件用查看器打开能看到一个法兰形状。这一步是整个流程的“味觉测试”如果这个例子都跑不通说明你的模型推理能力或者提示词模板有问题先别急着往下做功能。4. 实测中的常见翻车现场不是模型错了是“语义漂移”4.1 一次完整的翻车案例四角圆角板我拿一个更接近真实工作的需求来测试“一块长100、宽50、高5的板四角倒圆角R10中间一个直径25的沉头孔沉头直径40沉头深度3。”第一次生成的代码我简化一下result ( cq.Workplane(XY) .box(100, 50, 5) .edges().fillet(10) # 错把所有边都倒了圆角 .faces(Z) .workplane() .hole(25) # 错只做了通孔没有沉头 )表面看代码没语法错误但几何完全不对。.edges()把所有12条边都选了倒出来的不是“四角圆角”而是整个长方体变成一个圆角鼓包尤其顶面和底面的周边棱线全变成了弧形。沉头孔也丢了。4.2 定位问题的完整排查链路遇到这种问题我的排查顺序是固定的先打开生成的代码做静态检查。看它调用的API是不是符合语义。.edges()在这里几乎肯定是错的应该用.edges(|Z)只选竖直边。再执行代码导出STEP用CAD查看器旋转观察。这一步能最直观地发现“显示出来的形状和需求对不上”。用几何校验输出几个关键指标跟预期对比。比如打印包围盒bb shape.val().BoundingBox() print(bb.xlen, bb.ylen, bb.zlen)如果是四角圆角包围盒应该仍然是100×50×5但如果你想验证“只有四角被倒”包围盒不够用得检查边数。更实用的方法是把模型导出成STL再用可视化工具截图看。把“错误现象”而不是“错误代码”反馈给LLM。让模型重新生成时不要只说“你写错了”而是说“用户要的是四角倒圆角现在的模型顶面和底面四边也变成圆角了另外缺少沉头孔直径40深度3。”我实测下来把几何差异描述清楚修复成功率比只贴报错信息高很多。4.3 用约束“钉死”语义提示词里的术语表与后置校验安抚完翻车现场后会发现一个深层问题自然语言本身就是模糊的。“四角倒圆角”到底是哪个视角的四个角沉头孔是通孔还是盲孔这类歧义不能指望LLM每次都能猜对。我的做法是两层兜底。第一层在提示词里加固定术语表。比如“四角倒圆角”默认指垂直于厚度方向的所有棱边使用edges(|Z)选择。“沉头孔”默认使用hole(..., cbore..., cboreDepth...)表达除非用户明确说不需要。“通孔”默认完全穿透当前实体。第二层写几何校验器。生成代码执行完以后自动检查def validate_shape(shape, expected_volumeNone): solid shape.val() if not solid.isValid(): raise ValueError(几何无效) if expected_volume: actual solid.Volume() if abs(actual - expected_volume) / expected_volume 0.02: raise ValueError(f体积偏差过大期望{expected_volume}实际{actual})把校验结果作为错误信息重新喂给LLM。这一层比任何提示词都可靠因为几何内核不会说谎。5. 能落地和不能落地的场景text-to-cad不是替代设计师而是替代重复劳动5.1 适合text-to-cad的几类高价值场景我把跑过半年以后觉得真正有价值的场景分成四类。标准件生成M8六角螺栓、弹簧垫片、平垫圈这类东西描述清楚以后生成效果极稳因为结构规则、参数固定。参数化改型例如“把底座长度从100改到120安装孔跟随向Y方向移动”这种增量修改如果能维护好上下文比手动改动整个特征树快得多。教学演示学生用自然语言描述设计意图立刻得到三维模型对理解“特征思维”帮助很大。批量配置同一类型的零件把一组参数写进表格逐行调用text-to-cad生成不同规格的模型适合搭流水线。5.2 硬边界为什么复杂曲面和装配体很难硬边界要讲清楚免得你投入错了方向。第一复杂曲面。汽车A级曲面、鼠标外壳、人体工学手柄这些需要曲率连续、光顺性约束的自由曲面程序化建模脚本本身就很难描述LLM自然也无能为力。不是LLM不够聪明是几何表示方式不适合。第二装配体。哪怕只有两三个零件也需要定义零件间的接触面、配合关系、自由度。文本描述一句话说不清多轮对话又容易丢失上下文。我建议暂时不要把text-to-cad用在多零件装配上。第三制造信息。text-to-cad生成的是纯几何模型它不含公差、表面粗糙度、热处理要求。你不可能让它直接出一张能下发车间的工程图。想用它替代工艺设计目前不现实。5.3 落地形态从“一次生成”到“人机协作”在我自己的工具链里text-to-cad不是无人驾驶而是辅助驾驶。正确用法是LLM先生成一版模型工程师在CAD里做检查和修改然后把修改后的特征树保存成模板。下一次类似零件过来直接调用模板让LLM只需要填参数不做自由发挥。这个模型很关键。不要追求“一次生成最终模型”而是追求“生成一版能改、能看、能用来对齐需求的初稿”。当我把心态从“AI完全替代建模”切换到“AI帮我起稿”之后几乎所有失败案例都变得可接受了。6. 进阶优化从“能出模型”到“敢放进产线”的几个改造方向6.1 先给LLM一根拐杖封装标准函数库基础流程跑通以后你会发现让LLM直接写CadQuery原始API错误率还是偏高。这时候应该封装一套业务函数库让LLM只调用你定义好的高层函数。比如你可以定义def make_plate(length, width, height, corner_radius0): return ( cq.Workplane(XY) .box(length, width, height) .edges(|Z) .fillet(corner_radius) if corner_radius 0 else cq.Workplane(XY).box(length, width, height) ) def make_hole(plate, diameter, cboreNone, cbore_depthNone): ...然后在提示词里明确“你只能使用工具库里的函数不要直接调用cq.Workplane。”这么做有两个好处语法错误率大幅下降模型输出的代码更像配置文件人一眼能看懂。6.2 加一道“质量门禁”几何校验和参数提取从“能出模型”到“敢放进产线”中间必须加质量门禁。除了前面提到的isValid()和体积校验还可以提取参数表把模型里的尺寸自动抽出来import ast def extract_constants(script: str) - dict: tree ast.parse(script) constants {} for node in ast.walk(tree): if isinstance(node, ast.Assign): for target in node.targets: if isinstance(target, ast.Name) and isinstance(node.value, ast.Constant): constants[target.id] node.value.value return constants提取出来的参数可以跟用户输入做交叉验证比如用户说板厚5脚本里如果出现厚度不是5就该报警。这是最容易被忽略、但也最实用的一层。6.3 支持增量修改不必每次从头生成我一开始的接口设计是用户每次输入完描述模型就从头生成一个完整脚本。结果发现一个100行的脚本只要改一个圆角半径模型很可能把其他特征也改乱。更好的做法是把上一版脚本作为上下文让模型生成“从旧版到新版的差异补丁”或者明确告诉模型“只允许修改跟需求相关的行其余保持原样。”我自己的做法是维护一个对话栈用户一个长100宽50高5的板四角倒圆角R10 助手脚本A 用户圆角半径改成15其余不变 助手脚本B且A中的其他部分不变这比每次空白对话重新生成稳定得多因为上下文里有了实际几何作为锚点。6.4 控制成本与安全缓存、沙箱和审计最后两条是工程上线必须做的事。第一是缓存。文本到CAD脚本的很多请求是高度重复的特别是标准件。把“规范化描述生成成功的脚本”存进数据库下一次先做相似度检索命中就直接用不调用LLM。我实测命中率能做到40%以上token开销省一大截。第二是安全。LLM生成的代码本质上不可信exec执行前一定要放进沙箱。至少做到容器隔离、限制文件系统访问、禁止网络请求。这个工具如果给团队用还要保留审计日志记录谁在什么时间生成了哪个模型方便回溯。如果你只是自己在本地跑用普通Python环境也能接受但只要多一个人用沙箱就必须上别在这个问题上省事。一个我自己特别深的体会是text-to-cad的瓶颈从来不是“大模型能不能理解图纸”而是你有没有能力把“语言”变成“参数”、把“几何”变成“校验结果”并且让这些信息在模型和CAD内核之间循环起来。真正让这个工具从玩具变成生产力的不是某一个提示词写得好而是那套自动纠错、几何校验、缓存复用和人工介入的闭环。我建议你照着上面的最小骨架先跑通一个法兰或一个板件然后把校验和缓存加上再去考虑复杂场景。先让一小撮完全可控的流程稳定跑起来比一开始就想“什么都能生成”靠谱得多。