首页 / 资讯中心 / 文章详情

text-to-cad实战:用自然语言生成STEP/URDF/G-code的完整方案

text-to-cad实战:用自然语言生成STEP/URDF/G-code的完整方案 ★ FEATURED ARTICLE
CAD 这行当有个特别拧巴的地方脑子里想清楚一个零件只要三秒手上把它画出来可能要三十分钟。拉伸、倒角、打孔、装配每一步都是重复劳动。所以当“用一句话生成 CAD 模型”这个念头冒出来的时候我第一反应不是“这能行吗”而是“这要是能行我能省下多少时间”。text-to-cad 就是冲着这个痛点去的——把自然语言描述直接翻译成可用的 CAD 文件输出 STEP、URDF、G-code 这些下游工具能直接吃进去的格式。它解决的不是“画得更好看”而是“从想法到可制造文件”这一段最枯燥的搬运工作。适合谁看做机械设计的、搞机器人仿真的、玩 3D 打印的以及任何被重复建模折磨过的工程师。下面我把自己折腾这套流程的完整思路和踩过的坑摊开讲。1. 整体设计思路与方案选型1.1 为什么是“文本到 CAD”而不是“文本到网格”很多人第一次听到 text-to-cad会下意识把它和文生 3D 模型混为一谈。这俩差别大了去了。文生 3D 模型比如生成一个花瓶摆件输出的是网格文件STL 或者 OBJ本质上是一堆三角面片拼出来的壳。这东西看着像但你没法改。想在那个花瓶上开个直径 20 毫米的孔对不起网格没有参数化信息你只能重新生成或者手动修。CAD 不一样。CAD 的核心是参数化特征——拉伸了多少、孔在哪个基准面上、倒角半径多大这些信息都以特征树的形式存着。STEP 文件之所以在工业界通用就是因为它保留了几何的精确数学描述NURBS 曲面而不是近似网格。所以 text-to-cad 的技术路线本质上是要让语言模型理解“特征建模”的逻辑而不是“捏形状”。我选定的方案是大语言模型负责语义解析和代码生成CAD 内核负责几何构建中间用脚本桥接。具体来说让模型把“一个 50x50x10 的底板四角各有一个直径 5 的通孔”翻译成 CadQuery 或 OpenSCAD 的代码然后调用内核执行最后导出 STEP。这条路的好处是每一步都可控——代码你能看、能改、能版本管理出了问题知道去哪找。1.2 工具链选型CadQuery 还是 OpenSCAD这是第一个要做的关键决策。两个都是代码驱动建模的利器但脾气完全不同。OpenSCAD 更老牌语法像写程序CSG构造实体几何的思路——用立方体、圆柱体这些基本体做布尔运算。上手快社区大但它的几何内核是网格化的导出的 STEP 精度有限而且不支持 NURBS 曲面。做简单零件没问题做复杂曲面就力不从心。CadQuery 基于 OCCTOpen CASCADE Technology内核这是工业级的几何内核和 SolidWorks、FreeCAD 用的是同一套底层。它支持真正的 B-rep边界表示建模导出的 STEP 是精确几何。语法上更接近“选择面、在上面做操作”的建模思维比如Workplane(XY).box(50,50,10).faces(Z).workplane().hole(5)读起来就是建模步骤本身。我最终选 CadQuery理由有三条。第一STEP 导出质量是硬指标下游要做仿真或者 CAM 加工网格化的几何根本没法用。第二CadQuery 的选择器语法selector和语言模型的输出习惯很契合——模型很容易学会“选顶面、选边、做操作”这种链式表达。第三URDF 导出需要精确的几何和坐标系定义CadQuery 的装配体功能能直接给出每个零件的位姿矩阵。提示如果你只是想做 3D 打印的摆件OpenSCAD 足够。但只要涉及装配、仿真、加工CadQuery 是更稳妥的选择。1.3 从文本到代码的映射策略语言模型怎么知道该生成什么代码这里有个关键设计我不让模型直接“想象”几何而是给它一套模板和约束。具体做法是准备一个提示词框架里面包含几个要素。第一是 CadQuery 的常用 API 清单比如box、cylinder、hole、fillet、chamfer这些高频操作的签名。第二是坐标系约定明确 Z 轴向上、XY 平面为基准面。第三是输出格式要求强制模型只输出可执行的 Python 代码块不要解释文字。这么做的原因是语言模型对几何空间的理解其实很弱。你让它“生成一个支架”它可能给你一个比例完全不对的东西。但如果你把任务拆解成“底板尺寸、孔位坐标、加强筋厚度”这些具体参数它就能准确映射到代码上。所以我的提示词里会引导用户把描述写具体比如不说“一个安装板”而说“一个 100x60x8 的矩形板长边两侧各有两个 M4 沉头孔孔中心距板边 10 毫米”。2. 核心细节解析与实操要点2.1 提示词工程把话说清楚比什么都重要我试过直接用一句话丢给模型“给我生成一个法兰盘”。结果出来的东西能用但尺寸全凭它猜内径外径螺栓孔数量都是随机的。后来我总结出一套描述模板效果稳定很多。描述里必须包含的信息有整体尺寸长宽高或者直径厚度、特征位置孔在哪、槽在哪用坐标或者相对位置描述、特征参数孔径、深度、圆角半径、基准约定哪个面是底面。比如“一个直径 80 毫米、厚度 10 毫米的圆盘中心有一个直径 30 毫米的通孔沿直径 60 毫米的圆周均布 6 个直径 6 毫米的通孔”这种描述模型几乎不会出错。还有个小技巧用相对位置而不是绝对坐标。说“距边缘 10 毫米”比说“在 x10 的位置”更不容易出错因为模型对绝对坐标的推算经常翻车。另外单位要明确我统一用毫米在提示词里写死避免模型在英寸和毫米之间反复横跳。2.2 代码生成后的校验环节模型生成的代码不能直接信。我踩过的最大的坑是代码能跑但几何是错的。比如孔打穿了不该穿的面或者两个实体没有正确合并导出 STEP 的时候变成两个分离的壳。所以我在流程里加了一道自动校验。用 CadQuery 执行完代码后检查几个指标实体的数量应该是一个如果是多个说明布尔运算没做对、包围盒尺寸和描述里的整体尺寸对比、体积排除掉空壳或者自相交的情况。这些检查用几行 Python 就能搞定但能拦下大部分低级错误。import cadquery as cq result cq.Workplane(XY).box(50, 50, 10).faces(Z).workplane().hole(5) solid result.val() bbox solid.BoundingBox() print(fX: {bbox.xlen}, Y: {bbox.ylen}, Z: {bbox.zlen}) print(fVolume: {solid.Volume()})如果包围盒的 Z 方向长度不是 10或者体积明显偏离预期就说明生成的代码有问题需要回退重试或者人工介入。2.3 STEP、URDF、G-code 三种输出的差异处理这三个格式面向的下游完全不同不能一套代码走天下。STEP是几何交换格式重点是几何精确和拓扑完整。导出时要注意单位——STEP 标准默认是毫米但有些内核会写成米导入到别的软件里尺寸就差一千倍。CadQuery 的exportStep默认单位是毫米这点比较省心。URDF是机器人描述格式它不只要几何还要运动学树——哪个零件是父连杆、哪个是子连杆、关节类型是什么、旋转轴在哪。所以生成 URDF 的时候文本描述里必须包含装配关系的信息。比如“一个底座和一个转台转台绕 Z 轴旋转”模型需要生成两个独立的几何体分别导出为 STL然后在 URDF 的 XML 里定义 link 和 joint。这里有个坑URDF 引用的网格文件路径是相对的如果路径写错导入 CoppeliaSim 或者 RViz 的时候会报找不到 mesh。G-code是给 CNC 或者 3D 打印机用的它不关心几何的数学表示只关心刀具轨迹。从 CAD 到 G-code 中间通常要经过 CAM 工序但简单的 2.5 轴加工可以跳过 CAM直接从几何生成路径。我的做法是提取零件的轮廓和孔位用简单的轮廓偏置算法生成刀具路径。这一步精度要求高因为 G-code 直接控制机床运动坐标错一点就撞刀了。输出格式核心信息主要坑点校验重点STEP精确几何、拓扑单位不一致、实体分离包围盒、实体数量URDF几何运动学树网格路径、关节轴定义link/joint 完整性G-code刀具轨迹坐标系原点、安全高度路径无碰撞、坐标范围3. 实操过程与核心环节实现3.1 环境搭建与依赖安装先把环境弄干净。我用的是 Python 3.10CadQuery 对 3.11 的支持当时还有点问题3.10 最稳。安装用 conda 比 pip 省心因为 OCCT 内核的二进制依赖比较多conda 能自动处理。conda create -n text2cad python3.10 conda activate text2cad conda install -c conda-forge cadquery pip install openai这里有个细节CadQuery 的 conda 包和 pip 包不兼容混装会出问题。我一开始用 pip 装跑起来报 OCCT 的 DLL 找不到折腾了半天换成 conda 就好了。另外如果你要用 URDF 导出还需要装urdfpy或者直接手写 XML我倾向手写因为依赖少、可控。3.2 从描述到 CadQuery 代码的完整链路整个链路分四步接收描述、生成代码、执行校验、导出文件。我用一个 Python 脚本串起来。第一步构造提示词。我把 CadQuery 的常用 API 和几个示例代码拼成 system prompt让模型知道该用什么语法。示例代码很关键模型会模仿示例的风格。我放了三个例子一个带孔的板、一个旋转体、一个简单的装配体。第二步调用模型生成代码。这里用流式输出还是等完整结果我选等完整结果因为代码需要整体校验流式没意义。生成的时候设置temperature0.2降低随机性让输出更稳定。第三步执行代码。用exec在受限的命名空间里跑只允许cadquery和基本数学库。跑完拿到result对象。第四步校验并导出。校验通过后根据目标格式调用对应的导出函数。import cadquery as cq def generate_and_export(description, output_format, output_path): code call_llm(description) namespace {cq: cq} exec(code, namespace) result namespace[result] if output_format step: cq.exporters.export(result, output_path) elif output_format stl: cq.exporters.export(result, output_path, tolerance0.01) return output_path3.3 参数计算以法兰盘为例拿一个具体例子走一遍。描述是“一个外径 100 毫米、内径 40 毫米、厚度 15 毫米的法兰盘沿直径 80 毫米的圆周均布 8 个直径 8 毫米的螺栓孔”。模型生成的代码大致是这样import cadquery as cq result ( cq.Workplane(XY) .circle(50) .circle(20) .extrude(15) .faces(Z) .workplane() .polarArray(40, 0, 360, 8) .hole(8) )这里有几个参数需要验证。外径 100 对应circle(50)内径 40 对应circle(20)都对。螺栓孔分布圆直径 80 对应polarArray的半径 40也对。8 个孔、360 度均布参数正确。孔径 8 对应hole(8)没问题。执行后检查包围盒X 和 Y 方向应该是 100Z 方向是 15。体积的话外圆柱体积减去内孔体积再减去 8 个螺栓孔的体积。外圆柱 π×50²×15 ≈ 117809内孔 π×20²×15 ≈ 18850每个螺栓孔 π×4²×15 ≈ 7548 个就是 6032。总体积约 117809 - 18850 - 6032 92927 立方毫米。实际跑出来如果在这个数附近说明几何是对的。3.4 URDF 导出的装配体处理URDF 这块单独说因为它比单零件导出复杂。假设描述是“一个两轮差速小车底盘是 200x150x50 的盒子左右两侧各有一个直径 60 毫米的轮子”。模型需要生成三个几何体底盘、左轮、右轮。底盘导出为base_link.stl轮子导出为left_wheel.stl和right_wheel.stl。然后生成 URDF 文件robot namediff_drive link namebase_link visual geometry mesh filenamebase_link.stl/ /geometry /visual /link link nameleft_wheel visual geometry mesh filenameleft_wheel.stl/ /geometry /visual /link joint nameleft_wheel_joint typecontinuous parent linkbase_link/ child linkleft_wheel/ origin xyz0 0.09 -0.02 rpy0 0 0/ axis xyz0 1 0/ /joint /robot这里的关键是origin的 xyz 值——轮子相对于底盘中心的位置。底盘宽 150轮子装在两侧所以 Y 方向偏移是 75 加上轮子厚度的一半。这些数值需要从几何计算里提取不能靠模型瞎猜。我的做法是在生成几何的代码里同时输出每个零件的位姿然后自动填入 URDF。注意URDF 里的 mesh 路径是相对于 URDF 文件所在目录的。如果你把 URDF 和 STL 放在不同文件夹路径要写对否则导入 CoppeliaSim 会报错。4. 常见问题与排查技巧实录4.1 代码能跑但几何不对怎么办这是最高频的问题。模型生成的代码语法没问题执行也不报错但出来的形状和你想的完全不一样。常见原因有三个。第一是布尔运算顺序错了。比如先打孔再拉伸结果孔被拉伸操作覆盖了。CadQuery 是链式操作顺序很重要。正确的顺序是先做主体再在主体上做减材操作。第二是工作平面选错了。workplane()不指定参数时默认在 XY 平面但如果你在 Z 方向的面操作需要先faces(Z).workplane()把工作平面移到顶面。模型经常忘记这一步导致孔打在了错误的位置。第三是选择器用错了。faces(Z)选的是 Z 方向最高的面faces(Z)选最低的。如果模型写反了操作就作用在了底面。排查方法是把中间结果导出看看或者用result.val().Faces()打印所有面的信息。4.2 STEP 导入其他软件后尺寸不对十有八九是单位问题。STEP 文件头里有个单位定义有些内核导出时写的是米导入方按毫米读尺寸就差一千倍。CadQuery 默认导出毫米但如果你在代码里用了英寸单位比如box(2, 2, 0.5)想表达英寸导出的 STEP 会按毫米解释变成 2 毫米的盒子。解决办法是在提示词里强制单位所有尺寸都用毫米并且在代码里加注释标明。导出后用文本编辑器打开 STEP 文件看头部有没有MILLI或者METRE的字样确认单位正确。4.3 URDF 导入 CoppeliaSim 报错找不到 mesh这个问题的根源通常是路径。URDF 里的mesh filename...如果是相对路径CoppeliaSim 会相对于 URDF 文件的位置去找。如果你把 URDF 放在robot/目录STL 放在robot/meshes/目录那 filename 应该写meshes/base_link.stl。另一个可能是 STL 文件本身有问题。CadQuery 导出的 STL 默认是二进制格式有些老版本的仿真软件只认 ASCII 格式。导出时加asciiTrue参数可以解决。cq.exporters.export(result, base_link.stl, asciiTrue)4.4 G-code 生成后机床报警G-code 直接控制机床出问题的后果比较严重。最常见的报警是“坐标超程”原因是 G-code 里的坐标超出了机床行程。排查方法是先看 G-code 里的坐标范围和机床的实际行程对比。另一个常见问题是安全高度不够。快速移动时刀具如果没抬到安全高度会撞到工件或者夹具。我的做法是在生成 G-code 时强制设置一个安全高度比如 Z 轴抬到工件最高点以上 10 毫米再移动。问题现象可能原因排查方法解决措施代码能跑但形状不对布尔顺序、工作平面、选择器导出中间结果查看调整操作顺序明确工作平面STEP 尺寸差千倍单位不一致查看 STEP 文件头统一用毫米检查导出参数URDF 找不到 mesh路径错误、格式不兼容检查相对路径和文件格式修正路径导出 ASCII STLG-code 机床报警坐标超程、安全高度不足对比坐标范围和行程设置安全高度检查原点4.5 模型生成的代码有安全风险这个必须单独提。语言模型生成的代码是直接exec执行的如果模型被诱导生成了恶意代码比如删除文件、发起网络请求后果很严重。我的做法是沙箱执行——在受限的命名空间里跑只暴露cadquery和math这些必要的模块禁用os、sys、subprocess这些危险模块。import cadquery as cq import math safe_globals { __builtins__: {}, cq: cq, math: math, } exec(code, safe_globals)这样即使模型生成了import os; os.system(rm -rf /)这样的代码也会因为os不在命名空间里而报错。另外执行前可以做个简单的静态检查扫描代码里有没有import os、subprocess、open(这些关键词有的话直接拒绝执行。4.6 批量生成时的效率优化如果你要批量生成几十个零件每次都调用模型会很慢。我的优化策略是缓存 模板。对于结构相似的零件比如一系列不同尺寸的法兰盘先生成一个模板代码然后只让模型输出参数用参数替换模板里的占位符。这样模型只需要生成几个数字速度快很多而且稳定性更高。另一个优化是并行执行。几何构建是 CPU 密集型的可以用多进程并行。但要注意 CadQuery 的 OCCT 内核不是线程安全的多线程会崩必须用多进程。from multiprocessing import Pool def process_one(description): code call_llm(description) return execute_and_export(code) with Pool(4) as p: results p.map(process_one, descriptions)5. 进阶玩法与扩展方向5.1 从单零件到装配体单零件生成跑通之后下一步自然是装配体。装配体的难点在于零件之间的约束关系。文本描述里说“轴穿过孔”模型需要理解轴的外径和孔的内径是配合关系轴的位置由孔的位置决定。我的做法是引入一个中间表示层——用 JSON 描述装配结构包含零件列表、每个零件的几何参数、以及零件之间的约束同轴、贴合、距离。模型先生成这个 JSON然后我用确定性的代码把 JSON 翻译成 CadQuery 的装配体代码。这样把“理解约束”和“生成几何”分开每步都更可控。5.2 参数化模板库的积累用久了会发现很多零件是重复的——法兰、支架、齿轮、轴承座。与其每次都让模型从头生成不如积累一个模板库。每个模板是一个参数化的 CadQuery 函数模型只需要识别出“这是法兰”然后填参数就行。模板库的好处是质量稳定。模板是人工验证过的不会出几何错误。模型只负责参数提取这部分它比较擅长。我现在已经攒了二十多个常用模板覆盖了大部分日常需求。5.3 和下游工具的联动生成的 STEP 可以直接导入 FreeCAD 做进一步编辑或者导入切片软件做 3D 打印。URDF 可以导入 CoppeliaSim 做机器人仿真。G-code 可以发给 CNC 或者打印机。整个链路打通之后从一句话到可制造文件最快只要几十秒。我最近在试的一个玩法是闭环迭代——生成几何后自动做仿真分析比如有限元如果应力超标自动调整参数重新生成。这个还在摸索阶段但思路是通的。6. 我踩过的那些坑第一个坑是过度信任模型的空间理解。早期我让模型直接生成复杂装配体的代码结果零件位置全乱套。后来改成先生成结构化的参数描述再用确定性代码构建几何问题就解决了。模型擅长语义解析和参数提取不擅长空间推理这个分工要明确。第二个坑是忽略了几何校验。有次生成了一批零件直接导出 STEP 发给加工厂结果对方反馈说文件打不开。查了半天发现是模型生成的代码里有个布尔运算没做成功导出的 STEP 里是两个分离的壳。从那以后我加了自动校验实体数量不对的直接拦截。第三个坑是URDF 的坐标系约定。不同仿真软件对坐标系的原点和朝向约定不一样。CoppeliaSim 默认 Z 轴向上但有些机器人框架用 Y 轴向上。生成 URDF 的时候如果不注意导入后机器人是躺着的。解决办法是在 URDF 里显式定义每个 link 的 origin不要依赖默认值。第四个坑是G-code 的安全高度。有次生成的 G-code 里快速移动没抬刀模拟的时候直接撞工件。虽然只是模拟没造成实际损失但吓出一身冷汗。现在我的 G-code 生成器里强制加安全高度移动前先抬到工件最高点以上。7. 一些实用的参数速查参数常用值说明STL 导出精度0.01-0.05 mm值越小越精细文件越大STEP 单位毫米导出前确认避免千倍误差URDF mesh 路径相对路径相对于 URDF 文件所在目录G-code 安全高度工件最高点10mm快速移动前必须抬到此高度模型 temperature0.1-0.3越低越稳定越高越有创意沙箱白名单cq, math只暴露必要模块这套流程我跑了小半年从最初的一句话生成一个歪歪扭扭的盒子到现在能稳定输出可用的 STEP 和 URDF中间踩的坑基本都在这了。核心体会就一条别指望模型一步到位把任务拆细每步都加校验比什么都强。模型负责它擅长的语义理解几何构建交给确定性的代码这个分工是整套方案能跑通的关键。
阅读完成 · 觉得有帮助?
咨询建站