text-to-cad 实战:从自然语言到 STEP/STL/GLB 的几何建模管线

📅 发布时间:2026/10/8 22:21:04
text-to-cad 实战:从自然语言到 STEP/STL/GLB 的几何建模管线
1. 从一段文字到三维实体text-to-cad 到底在解决什么问题第一次听到 text-to-cad 这个词很多人会下意识觉得它是个噱头——输入一句话就能生成 CAD 模型这听起来像是把设计师十几年的经验压缩成一次回车键。但真正在机械设计、工业建模或者 3D 打印这条线上摸爬滚打过的人会明白这个方向要解决的根本不是替代设计师而是把重复性建模劳动从人手里拿走。传统 CAD 工作流的痛点非常具体。假设你要建一个法兰盘参数是外径 120mm、内径 60mm、厚度 15mm、均布 6 个直径 10mm 的螺栓孔。在 SolidWorks 或者中望 CAD 里你需要新建草图、画圆、标注尺寸、拉伸、再建草图、画孔、阵列、切除。这一套动作熟练工也要两三分钟而且一旦参数改了你得重新走一遍。如果一天要出几十个类似的变体件这种重复劳动会把人逼疯。text-to-cad 的核心思路是用自然语言描述几何意图由程序解析成参数化的建模指令最终输出标准格式的三维文件。这里的标准格式就是关键词里反复出现的 STEP、GLB、STL。它们各自承担不同角色——STEP 是精确的 B-rep 边界表示适合后续在 CAD 软件里继续编辑STL 是三角网格适合 3D 打印和切片GLB 是带材质的轻量级场景格式适合网页预览和可视化展示。所以这个项目的本质是一条从语义到几何的转换管线。它适合谁三类人最该关注一是需要批量生成参数化零件的工程师二是做 3D 打印前处理、经常要临时改模型的创客三是想把建模能力集成到自己产品里的开发者。哪怕你只是刚学 CAD 制图入门理解这条管线的逻辑也能帮你搞清楚参数和几何之间到底是怎么映射的。我下面要拆的不是某个具体开源库的 API 文档而是这条管线在真实落地时会遇到的技术选型、几何内核、格式转换、精度控制这几道坎。这些内容在官方文档里往往一笔带过但真正动手做的时候每一道都能卡你半天。2. 语义解析层把人话翻译成建模指令的三种路线2.1 规则模板匹配最笨但最稳的第一版方案如果你现在就要动手做一个 text-to-cad 的原型我强烈建议第一版不要碰大模型先用正则加模板匹配把流程跑通。原因很简单你需要先验证几何生成这一段是通的而不是一上来就被语义理解的不可控性拖死。规则模板的思路是预先定义一批句式模式。比如用户输入创建一个直径 80 毫米、高 50 毫米的圆柱你用正则提取出直径80、高50、形状圆柱然后调用几何内核的make_cylinder(radius40, height50)。这套东西听起来原始但它的确定性是巨大优势——同样的输入永远得到同样的输出调试起来一目了然。我实际做的时候会把模板设计成槽位填充结构。先定义形状关键词表圆柱、立方体、球、法兰、支架、齿轮毛坯。再定义参数关键词表直径、半径、长、宽、高、厚、孔数、孔径。然后用一个简单的状态机扫描句子遇到形状词就锁定几何类型遇到参数词就往后抓数字和单位。单位处理是个坑毫米和米混用会导致模型差一千倍所以我在解析层强制做单位归一化默认毫米遇到米cm立刻换算。提示规则模板阶段一定要把解析结果打印出来给人看不要直接进几何生成。我踩过的坑是解析错了但几何照样生成结果出来一个尺寸离谱的模型排查了半天才发现是正则贪婪匹配把两个数字吃成了一个。这套方案的边界也很清楚它只能处理你预定义过的句式。用户说来个胖一点的圆柱它就懵了。但对于内部工具、批量出图这种场景输入往往是结构化的规则模板的覆盖率能到 80% 以上性价比极高。2.2 大模型抽取结构化参数能力上限高但需要约束当你需要处理更自由的自然语言时大模型就派上用场了。但注意不是让大模型直接生成几何代码而是让它做信息抽取——把一段话变成结构化的 JSON。这个定位非常关键因为让模型直接写几何代码出错率极高且难以验证而让它填 JSON 字段你可以对每个字段做类型和范围校验。具体做法是设计一个严格的输出 schema比如{ shape: cylinder, params: { radius: 40, height: 50, unit: mm }, features: [ {type: hole, diameter: 10, count: 6, pattern: circular} ] }然后通过提示词约束模型只输出这个结构。实测下来模型对直径半径这类词的区分偶尔会出错所以我在 schema 里加了一层校验如果shape是圆柱但只给了diameter程序自动除以 2 转成radius。这种后处理纠错比指望模型一次做对要可靠得多。这里有个经验不要让模型输出数值计算的结果。比如用户说直径是高的两倍你别让模型算出具体数字而是让它输出{radius: height * 1}这样的表达式由你的程序去求值。模型做算术的可靠性远低于做抽取把计算交给确定性代码是保证精度的关键。2.3 混合架构规则兜底加模型增强真正上生产的时候我采用的是混合架构。先用规则模板快速匹配命中就直接走没命中的再丢给大模型抽取。这样既保证了常见输入的稳定性和速度又用模型覆盖了长尾表达。这个架构还有一个隐藏好处规则命中的样本可以反哺模型。你可以把规则解析成功的句子和对应 JSON 存下来作为微调数据或者少样本示例让模型在你这个垂直领域的表现越来越好。我做过统计混合架构下规则能覆盖约 65% 的请求模型处理剩下的 35%整体准确率比纯模型方案高出不少而且响应延迟因为大部分请求走了规则而明显下降。3. 几何内核选型为什么 OpenCASCADE 是绕不开的那一个3.1 B-rep 与网格表示的本质区别在讲内核之前必须先搞清楚一个概念B-rep 和网格是两种完全不同的几何表示。这直接决定了你的 text-to-cad 输出 STEP 还是 STL。B-repBoundary Representation用数学曲面平面、圆柱面、NURBS 曲面精确描述物体的边界。一个圆柱面在 B-rep 里就是一个方程无论你放大多少倍都是光滑的。STEP 格式就是基于 B-rep 的所以它适合后续在 CAD 软件里继续做布尔运算、倒角、抽壳这些操作。网格表示则用一堆三角形去逼近曲面。一个圆柱面会被切成几十上百个三角形放大看就是多边形。STL 就是纯三角网格它没有圆柱这个概念只有一堆顶点和面片。好处是简单、通用3D 打印切片软件只认这个坏处是精度受三角面片数量限制而且没法直接做精确的布尔运算。理解这个区别后你就明白为什么 text-to-cad 的管线通常是语义 → B-rep 建模 → 导出 STEP保留精度→ 网格化 → 导出 STL用于打印。GLB 则是把网格加上材质和场景信息用于可视化。3.2 OpenCASCADE 的实际使用体验开源世界里做 B-rep 建模OpenCASCADE简称 OCCT基本是唯一成熟的选择。它提供了完整的几何建模能力基本体素、布尔运算、倒角、抽壳、曲面构造。Python 侧可以通过pythonocc-core调用C 侧直接用原生库。我实际用下来的感受是功能强大但学习曲线陡峭文档质量参差不齐。它的 API 命名偏学术比如创建一个圆柱要用BRepPrimAPI_MakeCylinder布尔运算要用BRepAlgoAPI_Cut。第一次看这些名字会有点懵但用熟了会发现它的抽象其实很合理。一个典型的圆柱建模代码大概是这样from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeCylinder from OCC.Core.gp import gp_Ax2, gp_Pnt, gp_Dir from OCC.Core.STEPControl import STEPControl_Writer, STEPControl_AsIs # 定义圆柱的轴位置在原点方向沿 Z 轴 axis gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)) # 半径 40高 50 cylinder BRepPrimAPI_MakeCylinder(axis, 40, 50).Shape() # 导出 STEP writer STEPControl_Writer() writer.Transfer(cylinder, STEPControl_AsIs) writer.Write(cylinder.step)这段代码看起来简单但有几个坑我踩过。第一gp_Ax2的方向参数必须是单位向量传了非单位向量不会报错但结果诡异。第二OCCT 的坐标系默认右手系Z 轴朝上如果你习惯了某些软件的 Y 轴朝上导入后模型方向会不对。第三STEP 导出时的STEPControl_AsIs模式保留原始几何但如果你的模型有单位设置导出时要注意单位一致性。3.3 网格化与 STL 导出的精度控制从 B-rep 到 STL 需要做网格化meshing这一步的精度控制直接决定打印质量。OCCT 提供了BRepMesh_IncrementalMesh来做这件事核心参数是线性偏差linear deflection和角度偏差angular deflection。线性偏差控制三角形边离真实曲面的最大距离。设成 0.1mm意味着网格和真实曲面的误差不超过 0.1mm。角度偏差控制相邻三角形法向的最大夹角。这两个值越小网格越密文件越大。我的经验值是普通 3D 打印件线性偏差设 0.05~0.1mm 足够精细件或者小尺寸零件设 0.01~0.02mm。角度偏差一般设 0.5 弧度左右。设太小会导致三角形数量爆炸一个简单圆柱能生成几十万个面片切片软件直接卡死。from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh from OCC.Core.StlAPI import StlAPI_Writer # 网格化线性偏差 0.05角度偏差 0.5 BRepMesh_IncrementalMesh(cylinder, 0.05, False, 0.5, True) stl_writer StlAPI_Writer() stl_writer.SetASCIIMode(False) # 二进制模式文件更小 stl_writer.Write(cylinder, cylinder.stl)注意STL 有 ASCII 和二进制两种格式。ASCII 可读但文件体积是二进制的 5 倍以上。生产环境一律用二进制除非你需要人工检查顶点数据。4. 格式转换链路STEP、STL、GLB 各自的坑4.1 STEP 转 STL 时最容易丢的东西很多人以为 STEP 转 STL 就是网格化一下但实际操作中经常发现转出来的模型缺面、破面、法向翻转。这些问题的根源通常有三个。第一是公差设置不当。STEP 里的曲面拼接处有缝合公差如果网格化时的线性偏差比缝合公差还大接缝处就会裂开。解决办法是网格化前先做一次ShapeFix_Shape修复把缝合公差统一。第二是法向不一致。STL 本身不强制法向朝外但切片软件依赖法向判断内外。OCCT 网格化后有时会出现部分面片法向翻转导致切片时把实体当成空腔。我一般会在导出前用ShapeAnalysis_Shell检查一下 shell 的闭合性和法向一致性。第三是微小特征丢失。如果模型上有 0.01mm 的倒角而你的线性偏差设了 0.1mm这个倒角在网格化时会被直接抹平。这不是 bug是精度不够。要么调小偏差要么在建模阶段就把这种微小特征处理掉。4.2 GLB 导出可视化场景的特殊要求GLB 是 glTF 的二进制版本主要用于网页和移动端的 3D 预览。它和 STL 最大的区别是带材质、带层级、带场景信息。如果你只是把 STL 换个后缀名成 GLB那是没用的因为格式结构完全不同。从 OCCT 的 B-rep 导出 GLB通常要经过网格化 → 转成三角网格数据结构 → 写入 glTF这几步。Python 里可以用trimesh库来承接先把 OCCT 网格导出成 STL 或 OBJ再用 trimesh 读进来加上材质导出 GLB。import trimesh mesh trimesh.load(cylinder.stl) # 给模型一个基础材质 mesh.visual trimesh.visual.ColorVisuals(mesh, face_colors[180, 180, 180, 255]) mesh.export(cylinder.glb)这里有个细节GLB 的坐标系和 CAD 不同。glTF 规范里 Y 轴朝上而 CAD 通常 Z 轴朝上。直接导出会导致模型在网页里躺着。我一般会在导出前做一次旋转把 Z-up 转成 Y-up。这个转换矩阵是绕 X 轴旋转 -90 度。4.3 三种格式的选型对照格式几何表示精度可编辑性典型用途文件体积STEPB-rep精确高可继续布尔运算CAD 交换、后续加工中等STL三角网格受网格密度限制低只能整体缩放3D 打印、切片较大GLB三角网格材质受网格密度限制低网页预览、AR/VR中等可压缩选型逻辑很直接要后续编辑就出 STEP要打印就出 STL要展示就出 GLB。很多 text-to-cad 项目会同时输出三种让用户按需下载。我建议默认给 STEP因为它是信息最完整的其他两种都可以从 STEP 再转出来反过来则不行。5. 参数化建模的实战细节从描述到实体的完整链路5.1 特征分解把复杂零件拆成基本操作的序列一个真实的零件描述往往不是单一形状。比如一个 100x60x10 的底板四角各有一个直径 8 的通孔中心有一个直径 30、深 5 的沉孔。这句话里包含了三个特征长方体基体、四个角孔、一个中心沉孔。我的处理方式是把描述解析成特征树每个特征对应一次建模操作。基体是make_box角孔是make_cylinder加cut布尔减沉孔是make_cylinder加cut。特征之间有顺序依赖——必须先有基体才能切孔。from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut # 基体 base BRepPrimAPI_MakeBox(100, 60, 10).Shape() # 四角孔假设孔中心距边 10mm hole_positions [(10, 10), (90, 10), (10, 50), (90, 50)] for x, y in hole_positions: axis gp_Ax2(gp_Pnt(x, y, -1), gp_Dir(0, 0, 1)) hole BRepPrimAPI_MakeCylinder(axis, 4, 12).Shape() base BRepAlgoAPI_Cut(base, hole).Shape()这段代码里有个容易忽略的点孔的圆柱高度要比板厚大。板厚 10mm我建了 12mm 高的孔并且起点在 z-1这样保证布尔减能完全穿透不会在底面留一层薄膜。这是布尔运算的经典技巧新手经常因为圆柱和板面刚好齐平而切不穿。5.2 布尔运算的稳定性问题与规避OCCT 的布尔运算在大多数情况下是可靠的但遇到共面、相切、微小间隙这些情况时偶尔会失败或者产生退化面。我遇到过最典型的问题是两个圆柱做布尔并集如果它们的轴线刚好相交于一点结果可能是一个空 shape。规避方法有几个。第一尽量避免精确相切让相交部分有明确的重叠量。比如两个零件要贴合让它们重叠 0.01mm比刚好接触要稳。第二布尔运算前对输入 shape 做ShapeFix修复。第三如果一次布尔失败尝试调整运算顺序先做简单的再做复杂的。提示布尔运算失败时不要反复重试同样的参数那只会浪费时间。先检查两个 shape 是否有有效体积用GProp_GProps算体积接近零说明 shape 有问题再检查是否有共面接触。5.3 参数校验在建模之前拦住错误输入text-to-cad 的一个隐藏风险是用户输入了物理上不合理的参数。比如直径 10 的圆柱上开一个直径 20 的孔这在几何上无法实现。如果不做校验布尔运算会失败或者产生诡异结果。我在解析层和建模层之间加了一道参数校验。校验规则包括孔径必须小于基体尺寸、孔深不能超过板厚除非是通孔、阵列孔不能超出边界、壁厚不能小于某个最小值。这些规则用简单的数值比较就能实现但能拦掉大量无效请求。校验失败时返回的不是一个报错而是带建议的提示。比如孔径 20 超过了基体宽度 10建议将孔径改为小于 10。这种反馈对用户友好得多也减少了来回试错。6. 精度、性能与批量生成的工程化考量6.1 浮点精度与单位一致性CAD 建模对精度的要求比一般图形应用高得多。OCCT 内部用双精度浮点但即便如此单位不一致仍然是头号杀手。我见过最离谱的 bug 是解析层默认毫米但某个模板里写死了米结果生成的模型小了 1000 倍在视图里看就是一个点。我的做法是在系统入口处强制单位归一化所有内部计算统一用毫米只在最终导出时按需转换。同时在解析层对每个数值都记录单位如果用户没写单位默认毫米但给出提示。另一个精度问题是数值比较。判断两个点是否重合不能用要用距离小于某个容差。OCCT 默认容差是 1e-7 米也就是 0.0001mm。在毫米单位下这个容差要相应调整。我一般用 1e-4mm 作为几何容差比这个小的差异视为零。6.2 批量生成的并发与缓存策略当 text-to-cad 用于批量出图时性能就成了问题。OCCT 的建模是 CPU 密集型的单个复杂零件可能要几百毫秒。如果一次要生成几百个串行跑会让人等到怀疑人生。我的方案是多进程并发。注意不是多线程因为 OCCT 的很多操作不是线程安全的多线程会出随机崩溃。用 Python 的multiprocessing起多个进程每个进程独立处理一个建模任务互不干扰。进程数设成 CPU 核心数就行再多反而因为上下文切换变慢。缓存也很重要。如果同样的参数被请求多次没必要重复建模。我用参数的哈希值作为 key把生成的 STEP 文件路径缓存起来。命中缓存直接返回文件省掉整个建模流程。实测在批量场景下缓存命中率能到 40% 以上整体吞吐提升明显。6.3 常见报错与排查对照报错现象可能原因排查方向布尔运算返回空 shape输入 shape 无效或共面接触检查体积、增加重叠量STL 缺面网格化偏差过大或曲面未缝合调小线性偏差、先做 ShapeFix模型尺寸差 1000 倍单位不一致检查解析层单位归一化导出 STEP 后打不开写入未完成或路径含中文检查文件流关闭、用英文路径网格面片数爆炸偏差设太小调大线性偏差、检查是否有微小特征模型方向不对坐标系约定不同检查 Z-up 与 Y-up 转换这张表是我在实际项目中一点点攒出来的每一条背后都是至少半小时的排查。尤其是单位不一致和坐标系约定这两个新手几乎必踩。7. 我在实际搭建这条管线时踩过的几个坑第一个坑是过早引入大模型。我一开始就想用大模型做端到端的语义到代码生成结果发现模型生成的几何代码经常有语法错误而且同样的输入两次结果不一样根本没法调试。后来退回到规则模板加结构化抽取流程才稳定下来。这个教训是先把确定性部分做扎实再引入不确定性。第二个坑是忽视 STL 的二进制模式。早期我用 ASCII 模式导出一个中等复杂度的零件 STL 有几十兆传输和加载都慢。改成二进制后体积降到几兆切片软件打开速度明显变快。这个改动只需要一行代码但收益巨大。第三个坑是没有做参数校验。有一次用户输入了一个孔径大于基体的请求布尔运算没报错但生成了一个破碎的 shape导出的 STL 在切片软件里显示成一堆散片。从那以后我加了参数校验层宁可提前拒绝也不生成垃圾数据。第四个坑是并发用了多线程。OCCT 在多线程下会偶发崩溃而且崩溃位置随机极难复现。换成多进程后彻底解决。这个坑让我明白对于非线程安全的库多进程是更稳妥的选择代价只是进程间通信的开销。第五个坑是GLB 的坐标系。第一次导出 GLB 在网页里预览模型是躺着的。查了 glTF 规范才发现 Y-up 的约定。加了一个旋转矩阵后正常。这种规范层面的差异不看文档根本想不到。8. 这条管线还能往哪些方向延伸把基础的 text-to-cad 管线跑通之后能扩展的方向其实不少。我目前在做的一个方向是模板库加参数替换。把常见的标准件法兰、轴承座、齿轮毛坯做成参数化模板用户只需要描述关键参数程序自动套模板生成。这比从零解析要快得多也更稳定。另一个方向是反向能力从已有的 STEP 文件里提取参数生成文字描述。这在零件复用场景下很有用——你拿到一个别人的模型想知道它的关键尺寸程序帮你读出来。这个方向技术上就是特征识别加参数提取OCCT 提供了BRepAdaptor系列工具可以做曲面类型识别。还有一个方向是和切片流程打通。生成 STL 后直接调用切片引擎输出打印路径预览。这样用户从一句话到看到打印预览全程不用打开任何 CAD 软件。对于创客场景这个闭环体验很有吸引力。不过我得说无论往哪个方向走几何内核的稳定性始终是地基。语义解析可以换模型格式转换可以换库但 B-rep 建模这一层如果选错了或者用不好上面盖什么都是空中楼阁。所以如果你打算认真做这个方向花时间把 OCCT 的布尔运算、网格化、修复工具吃透比追任何新模型都值。