Text-to-CAD全解析:从自然语言到可制造模型的工程化路径
这个标题看着简单,但真要把“text-to-cad”做成一篇能落地、能参考的文章,牵扯的东西其实不少。我在这个方向上折腾了几个月,从最早好奇“输入一句话能不能出三维模型”,到后来自己搭流程、调参数、踩坑,中间积累了挺多真实经验。这篇内容我不打算写成产品宣传稿,而是把整个技术链条拆开——它到底是什么、有哪些路线可选、实操时怎么选工具、以及最常见的问题都出在哪,一条一条讲清楚。
1. text-to-cad 是什么:一条文本指令背后的完整技术链
1.1 从“描述”到“模型”的思维转变
text-to-cad,字面意思是“文本转CAD模型”,核心目标就是让用户输入一段自然语言描述,比如“一个直径50mm、高30mm的圆柱体,顶部带一个半径5mm的圆孔”,系统自动生成对应的CAD模型文件。这个需求听起来很直接,但背后涉及的技术链条一点也不短:自然语言理解、空间语义解析、几何参数化建模、特征识别与布尔运算,每一环都得打通,才能做到“描述准确、结果可用”。
过去我们用CAD软件画零件,本质上是人把脑子里的三维构想,逐步翻译成软件能理解的操作指令——拉伸、旋转、倒角、打孔。这一步翻译过程恰恰是设计效率的最大瓶颈。你想象一个零件只需要几秒,但把它完整画出来可能需要十几分钟甚至更久,尤其是特征多、配合关系复杂的模型。text-to-cad想做的事,就是把“脑内建模”和“软件建模”之间这层翻译成本大幅压缩,让自然语言直接成为建模的输入语言。
这种思维转变,不仅仅是交互方式的升级,更深层的是建模逻辑的重构。传统CAD是“过程式”建模,每一步操作都有先后顺序,一个孔打在拉伸体之前还是之后,结果可能完全不同。而text-to-cad更接近“声明式”建模,用户只描述最终要什么,不需要关心过程怎么组织。这个差异在实操中会直接影响到工具选型和流程设计,后面我会专门展开。
1.2 谁在用、用在哪:工程设计与快速原型场景
从我实际接触到的人群来看,text-to-cad最典型的用户有三类。
第一类是结构设计师和机械工程师。他们经常需要快速验证概念方案,比如“这个支架改成铝合金材质,壁厚减到2mm,加三个安装孔”这种需求,在传统流程里需要重新改图、再生特征、检查约束,一套下来半小时没了。如果文本能直接驱动模型参数更新,前期方案评审的效率会提升一个量级。
第二类是3D打印爱好者和小批量创客。他们未必有专业的CAD制图功底,但脑子里经常有很具体的想法。用文本直接描述“一个能放下两张SD卡的小盒子,带卡扣”就能得到一个基础模型,再导入切片软件微调,整个流程门槛低很多。
第三类是自动化设计流程的开发者。在一些定制化生产场景里,订单参数往往是表格或文本形式,比如“外径40mm、内径25mm、厚度8mm的垫圈”。过去需要程序参数化驱动模型,现在可以直接在文本描述和CAD生成之间建立管道,把人工建模环节省掉。
适用人群越广,越要求技术方案在“易用性”和“可控性”之间找到平衡。纯自由生成的方案虽然看起来炫,但工程场景里更看重的是结果能不能预测、参数能不能约束、模型能不能直接用于制造。所以我在后面的实操部分,会刻意区分“探索式生成”和“生产式生成”两类场景,它们的工具选择几乎完全不同。
2. 主流实现路线:四种思路的权衡与取舍
text-to-cad目前没有统一标准答案,市面上的方案大致可以分成四条路线。每一条都有它的适用边界,选错了后面会很难受。我按工程实用性排序,把每条路线的原理、优点、局限都说清楚。
2.1 程序化生成路线:规则驱动,稳定可控
程序化生成的核心思路,是预先定义一组参数化模板,每个模板绑定若干关键尺寸和特征参数,然后通过文本解析去填充这些参数。举个例子,模板“带孔圆柱体”包含直径、高度、孔径三个参数,文本解析模块从“直径50mm、高30mm、孔径5mm”这句话中抽出数值,直接实例化模型。
这种路线的优势非常明显:结果稳定、可预测、模型质量高,生成的CAD文件带完整特征树,后期可以继续编辑。非常适合参数明确、结构规整的机械零件。目前CadQuery和OpenSCAD的结合方案是这条路线里最成熟的实践路径,CadQuery用Python代码描述建模过程,OpenSCAD用声明式语言描述几何体,两者都能被程序动态调用。
局限在于,模板的覆盖面有限。你不可能把所有可能的零件结构都预制成模板,超出模板范围的描述就无法处理。实际项目中,一般会先搭建一个覆盖常见机械特征的基础模板库,再根据业务需求慢慢扩充。
2.2 深度学习生成路线:无需模板,想象力上限更高
深度学习路线的代表性工作包括OpenAI的Point-E、Shap-E,以及学术界一些基于扩散模型的三维生成方案。它的核心机制,是让模型从海量三维数据中学习“文本特征”和“几何特征”之间的映射关系,输入文本后直接推理输出体素、点云或隐式曲面表示,再通过后处理转换成网格或CAD格式。
这条路线最大的卖点是“无需预定义模板”,理论上模型见过足够多形状之后,能生成许多人类难以预先设计的复杂形态。我在测试Shap-E的时候,输入“一把扭曲的椅子”“一个像鸟一样的金属雕塑”,生成的粗模确实能抓住形态神韵,这是程序化路线难以做到的。
但工程落地上问题也不少。第一,生成结果目前主要是网格(Mesh)模型,不是实体模型(B-Rep),没有特征历史,不能像原生CAD文件那样继续编辑倒角、改孔径。第二,精度难以保障,几何尺寸不会严格符合描述,经常出现“大致像但具体尺寸不对”的情况。第三,需要较强的算力支持,本地推理一张显卡动辄跑几十秒,交互体验谈不上流畅。
2.3 检索式/模板组合路线:从库中匹配再微调
检索式方案的核心逻辑是“不从头生成,而是找到最接近的再改”。系统先维护一个三维模型库,每个模型附带文本描述标签,收到用户输入后先在库中做语义检索,找出最匹配的模型,再通过参数调整或局部特征替换的方式逼近用户需求。
它的优势在于,如果模型库覆盖度高、文本标签质量好,检索出来的模型往往比纯生成结果更精细、更可靠。工业界很多“选型+改型”的场景本质上就是这种思路,比如标准件库、紧固件库、型材库。用户说“需要一个M8的六角法兰螺栓”,系统直接检索出对应规格的模型,几乎零误差。
局限则是库的构建成本很高。每个模型都要手工标注文本描述,描述的语言表达方式稍微偏一点,检索准确率就会掉得很快。而且它的上限受库容限制,库里没有类似形态的模型,再怎么搜索也搜不出来。实践中我建议把它定位成“程序化生成路线的补充层”,先检索,检索不到再走模板参数化流程。
2.4 混合方案:工程落地的现实选择
实际工程场景里,很少有人只用单一路线,更多是混合架构。我目前在用的方案就是“深度学习做语义理解 + 程序化模板做几何生成”的组合。
具体做法是,用大语言模型(LLM)解析用户的自然语言输入,输出结构化的JSON描述,比如:
{ "shape": "cylinder", "diameter_mm": 50, "height_mm": 30, "features": [ {"type": "hole", "diameter_mm": 5, "position": "top_center"} ] }然后再把这个JSON交给CadQuery脚本执行,生成实体模型。深度学习负责处理千变万化的语言表达,程序化负责保证几何精度,两者各管一段,各取所长。这条路线目前是我最推荐给需要实际产出模型、而不是只做形态探索的朋友的。
3. 实操全过程:从文本描述到可制造CAD模型
3.1 环境准备与工具选型
实操之前先把工具链准备好。我的推荐组合是:Python 3.10+、CadQuery 2.x、OpenSCAD(备用)、大语言模型API(本地部署或云端均可)。CadQuery是目前Python生态里最成熟的程序化建模库,API设计接近机械工程师的思维习惯,支持导出STEP、STL、DXF等格式,能直接对接后续制造环节。
安装过程比较简单,创建虚拟环境后安装即可:
python -m venv cad_env source cad_env/bin/activate # Windows下执行 cad_env\Scripts\activate pip install cadquery pip install openai # 如果用云端LLM API的话这里有个小提示:CadQuery的版本迭代比较快,有些老教程的API写法在新版本里已经变了,建议直接参考官方文档确认当前版本的关键接口。我在一个旧项目里就遇到过Workplane类方法的参数签名变化,排查了半天才发现是版本问题。
3.2 文本解析到参数提取:让机器看懂描述
工具链准备好之后,最关键的一步是把自然语言转换成结构化参数。这一步做不好,后面建模逻辑再正确也白搭。
我实际使用中反复验证过的一套提示词模板,可以把LLM的解析准确率稳定在较高水平:
你是一个CAD参数提取助手。根据用户对机械零件的描述,提取以下字段: - shape_type: 基础形状类型(box/cylinder/cone/sphere等) - dimensions: 关键尺寸(长度、宽度、高度、直径等) - features: 附加特征(孔、倒角、凹槽、凸台等) - 单位统一为毫米,如果原文没有明确单位,按毫米处理 - 无法确定的字段填null,不要臆造 输入描述:{user_input} 输出格式:JSON举个例子,用户输入“一个直径40mm、厚度10mm的圆盘,中心有一个直径8mm的通孔”,模型输出的JSON应该类似:
{ "shape_type": "cylinder", "dimensions": { "diameter_mm": 40, "height_mm": 10 }, "features": [ {"type": "through_hole", "diameter_mm": 8, "position": "center"} ] }这个环节有几个容易踩的坑。第一个是单位问题,中文用户经常会说“直径40”“高10”,不习惯带单位,必须明确约定默认为毫米。第二个是模糊表述,比如“一个圆盘”“一个小支架”,没有具体尺寸时,需要我提前设定默认值策略——是给默认值,还是让用户补充?工程场景里我倾向于“缺省参数自动给默认值,同时明确记录哪些参数是推断的”,这样生成的模型至少能看,用户也能知道哪些需要二次确认。
另一个很实用的技巧,是把LLM的解析结果和正则表达式结构化提取做双保险。对于“直径XXmm”“孔径XXmm”这种强模式表达,正则提取非常可靠,速度又快。文本形式上,我会优先让正则提取,提取失败才请求LLM兜底,这样既能控制API调用成本,又能保证高确定性场景下的稳定性。
3.3 CadQuery模型构建:从结构化参数到实体模型
拿到结构化JSON之后,进入CadQuery建模环节。以刚才的例子来说明,核心代码如下:
import cadquery as cq def create_flange_with_hole(diameter_mm, height_mm, hole_diameter_mm): # 创建圆柱主体 result = ( cq.Workplane("XY") .cylinder(height_mm, diameter_mm / 2) .faces(">Z") .workplane() .hole(hole_diameter_mm) ) return result # 调用 model = create_flange_with_hole(40, 10, 8) cq.exporters.export(model, "flange.step") cq.exporters.export(model, "flange.stl")这段代码的逻辑是:在XY平面上创建一个高度为10mm、半径为20mm的圆柱体,然后选中顶面,在中心位置打一个直径8mm的通孔。导出的STEP格式适合机械加工和后续编辑,STL格式适合3D打印。
这里要注意CadQuery里hole()方法的使用细节。它默认垂直于当前工作平面打孔,方向由工作平面决定。如果我需要打侧向孔,就得先变换工作平面再调用,否则孔的方向会不对。这是我早期犯过最多的错误——总觉得孔的位置对,但方向偏了,检查半天才发现是平面选择的问题。
再展开一个复杂一点的场景:如果描述里同时包含多个特征,比如“中心孔旁边再开两个对称的M4螺纹孔”,就需要用到极坐标阵列:
result = ( result.faces(">Z") .workplane() .polarArray(radius=15, startAngle=0, angle=180, count=2) .hole(3.4) # M4螺纹底孔直径通常取3.3~3.4mm )这个案例里的polarArray很值得理解一下:以圆心为基准,在半径15mm位置、间隔180度生成两个打孔位置。工程中凡是“圆周均匀分布”的需求,比如法兰螺栓孔,都会用到这个功能。
3.4 针对不同描述类型的灵活建模策略
上面是规则较明确的零件,但用户描述有时会涉及“文本描述模糊但形状模式清晰”的情况,这时需要建模逻辑具备分支处理能力。我会在代码里做一个简单的“形状分发器”:
def build_from_json(spec: dict): shape_type = spec.get("shape_type") dims = spec.get("dimensions", {}) if shape_type == "box": base_model = ( cq.Workplane("XY") .box(dims.get("length_mm", 20), dims.get("width_mm", 20), dims.get("height_mm", 20)) ) elif shape_type == "cylinder": base_model = ( cq.Workplane("XY") .cylinder(dims.get("height_mm", 20), dims.get("diameter_mm", 20) / 2) ) elif shape_type == "cone": base_model = ( cq.Workplane("XY") .cone(radius_bottom=dims.get("bottom_diameter_mm", 30) / 2, radius_top=dims.get("top_diameter_mm", 10) / 2, height=dims.get("height_mm", 20)) ) # 其他类型继续扩展 return base_model这种分发器的最大好处是扩展性。每新增一种模板,只需要增加一个分支,不影响已有逻辑。配合前面LLM解析出的shape_type字段,就能形成一个覆盖大部分常见机械零件的生成体系。
3.5 参数化联动:文本驱动模型迭代更新
我额外想重点聊聊参数化联动,因为这是text-to-cad在工程应用中最有实用价值的场景。
有些需求是会反复迭代的,比如“法兰厚度能不能再薄一点?”“孔的直径改到10mm试试?”“倒角半径改成2mm”,每一轮修改如果都重新走一遍完整流程,效率提升会非常有限,只有建立了参数联动才能真正释放提效价值。
具体做法是把模型的每一个关键尺寸都绑定到一个字典变量上,而不是硬编码数值:
params = { "flange_diameter": 40, "flange_height": 10, "hole_diameter": 8, "bolt_hole_radius": 15, "bolt_hole_count": 4, } def build_flange(p: dict): model = ( cq.Workplane("XY") .cylinder(p["flange_height"], p["flange_diameter"] / 2) .faces(">Z") .workplane() .hole(p["hole_diameter"]) .faces(">Z") .workplane() .polarArray(radius=p["bolt_hole_radius"], startAngle=0, angle=360, count=p["bolt_hole_count"]) .hole(5) # 螺栓孔直径固定为5mm ) return model这样修改任何参数后重新执行脚本,模型自动更新,文件重新导出。这个流程和传统CAD“改尺寸重新生成”的逻辑非常像,但入口从图形界面变成了文本/代码,为后续自动化打开了空间。
我在实际应用里,把这一套流程封装成了一个命令行工具,输入是一段自然语言描述,输出是STEP/STL文件以及一份PDF格式的参数清单报告。团队里非设计背景的同事也能直接上手用,这在过去是不可想象的。
4. 常见问题与排查技巧实录
关于text-to-cad,网上能找到的示例代码不少,但真正在生产环境里长期运行后才会遇到的各种问题,很少有人在文章里讲透。以下是我实际踩过的坑,以及对应的解决思路。
4.1 LLM解析结果不稳定,参数偶尔丢失或臆造
LLM在解析文本时偶尔会出现字段丢失,或者在原文没提尺寸时“好心”帮你编一个尺寸。这种情况在参数化建模里是很危险的——模型生成了,但尺寸完全不是你想要的。
我的解决方案有两层。第一层,在提示词里明确加一条硬性规则:“无法确定的字段一律填null,禁止推测”,这能大幅减少凭空捏造的情况。第二层,在代码里做数据清洗,遇到null字段就切换到默认值,并且生成一份“参数来源清单”,标注哪些是原文明确提到的、哪些是默认填充的。交付模型的同时交付这份清单,很多协作误会就能提前避免。
4.2 生成STEP文件导入其他CAD软件后出现破面或拓扑错误
CadQuery生成的模型理论上拓扑是干净的,但经过某些CAD软件导入再导出后,偶尔会出现破面、法线反转等问题。我排查后发现,很多时候问题出在模型包含了复杂的布尔运算结果,尤其是一个实体被多次切割、合并之后,边界表示会变得非常复杂,导入导出过程中拓扑容差处理不当就会报错。
解决思路是尽量避免在CadQuery里做过于复杂的连续布尔运算。能在草图阶段用轮廓裁切解决的,就不要用后续布尔减运算;能拆分成多个独立实体再组合的,就不要在同一个实体上反复切割。另外,如果模型需要导入其他软件做后续处理,优先导出STEP格式,STL因为网格化处理更容易出现边界瑕疵。
4.3 孔洞位置和描述不一致,方向偏移或者位置跑偏
最常见的问题几乎都出在工作平面和坐标系理解上。CadQuery的hole()方法是在当前工作平面上打孔,方向垂直于该平面,工作平面的选择必须明确。我遇到过一个案例,想在圆柱侧面打一个水平孔,代码里从顶面工作平面出发直接调用hole(),结果孔垂直于顶面打进去了,成了一个竖直孔。
后来修正的方法是,先选中圆柱侧面,再建立新的工作平面:
result = ( cq.Workplane("XY") .cylinder(30, 15) .faces("<Y") # 选择Y轴负方向的侧面 .workplane() .hole(6) )这个选择逻辑不直观,如果不熟悉CadQuery的面向对象建模思维,很容易漏掉。建议在写这类代码时,每步操作后都先导出中间结果的STL文件预览一下,确认方向和位置没问题再往下走。
4.4 性能问题:批量生成大量模型时速度越来越慢
如果只是单次生成一个模型,性能基本不用考虑。但我在批量场景里吃过亏——一次要生成一百多个规格不同的垫片和法兰模型,逐个调用CadQuery单线程执行,跑了几分钟还没跑完。
排查思路分两块。首先确认建模逻辑本身有没有冗余计算,比如反复调用Workplane()、重复执行不依赖参数的常量操作,这些对性能影响不小。其次,如果单线程跑确实慢,可以用concurrent.futures做多进程并行,因为CadQuery建模函数大多是CPU密集型,多进程能明显提升吞吐量:
from concurrent.futures import ProcessPoolExecutor def generate_many(specs): with ProcessPoolExecutor(max_workers=8) as executor: results = list(executor.map(generate_one_model, specs)) return results实测下来,8进程并行相比单线程,在批量生成场景下能快五六倍,效果非常明显。
4.5 LLM API调用成本和延迟控制
工程化落地时,API调用成本是一个绕不开的话题。解析一条文本描述调用一次LLM,单价看起来不贵,但乘以每天几百上千次的调用量,一个月下来成本并不低,响应延迟也会影响实时交互体验。
我的优化策略是“分级调用”:对于文本描述中强模式语句,先用正则或者简单规则提取参数,命中率很高的情况下完全不需要走LLM;只有遇到复杂长句、模糊歧义描述时,才触发LLM解析。实测下来,一个典型机械零件库里有六成左右的文本可以通过正则直接解决,只有四成需要请求LLM,整体成本能降一半还多。需要强调的一点是,这个权衡完全可以按业务场景调节,并不是说LLM质量不行,而是“匹配工作由合适工具做”更划算。
5. 几条经验总结
整体走完这套流程,我最深的体会是,text-to-cad的落地难点并不在于哪一个单点技术特别神秘,而是在于如何把语言理解、参数抽取、几何建模、文件输出这些环节有机地串成一个稳定管道。任何一环不稳定,最后交付的模型就不可用。
还有一个很重要的认知:当前阶段,text-to-cad更适合解决“参数明确、特征清晰”的标准化建模任务,而不是完全开放式的创意设计。对于非标曲面、仿生结构、复杂装配体,现有方案的自动化程度仍然有限。但换个角度想,哪怕只把重复性最高的标准化零件生成交给文本管道,设计师腾出来的时间就已经能创造很大价值了。
如果你正准备在团队里尝试类似方案,我的建议是从一个最窄的场景切入,比如“法兰盘+螺栓孔”或者“垫片+中心孔”,把完整链路跑通,验证质量和稳定性,再逐步扩展形状类型。一口吃成胖子的规划,最后往往卡在难以周全的边界情况上。这个方向值得长期投入,但一步一个脚印才是工程化的正确姿势。