ARTICLE DETAIL

资讯详情

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

text-to-cad 实战:从自然语言到 CAD 模型与 URDF 的完整链路

text-to-cad 实战:从自然语言到 CAD 模型与 URDF 的完整链路 1. 从一段话到可编辑模型text-to-cad 到底在解决什么问题如果你做过机械设计、建筑建模或者机器人仿真一定经历过这种场景脑子里已经想清楚了一个零件的形状甚至能用嘴描述得明明白白——“一个长宽高分别是 80、60、40 毫米的长方体四个角各倒一个半径 5 毫米的圆角正中间开一个直径 20 毫米的通孔”——但真到了 CAD 软件里还是得老老实实画草图、拉伸、倒角、打孔一步步点下来十分钟就没了。text-to-cad 想干的事情就是把这十分钟压缩成一句话。它的核心逻辑是用自然语言描述几何意图由程序自动生成符合规范的 CAD 文件。这里的“CAD 文件”不是随便一个三维模型而是能被下游工具链直接消费的格式比如 STEP、DXF、URDF 这些在工程界真正流通的格式。这件事为什么值得做因为 CAD 建模的本质是“把设计意图翻译成几何参数”而自然语言恰恰是设计意图最原始的载体。传统流程里这个翻译过程完全靠人手动完成效率低、易出错、不可批量。text-to-cad 把这个翻译过程自动化带来的直接收益有三个批量生成变体改一个参数就能生成一百个尺寸不同的零件、降低建模门槛不会用 CAD 的人也能出图、打通仿真链路生成的 URDF 可以直接丢进机器人仿真环境。适合读这篇的人有三类一是做机械设计或工业设计的工程师想看看能不能把重复建模的活儿交给脚本二是做机器人仿真的开发者需要批量生成 URDF 模型三是做 CAD 二次开发的程序员想理解自然语言到几何参数之间的映射该怎么设计。不管你属于哪一类接下来的内容都会从原理到实操把这条路走通。需要先说明一点text-to-cad 不是一个现成的商业软件它更像是一类技术方案的统称。市面上有开源的实现也有自己搭的方案核心思路大同小异。我下面讲的内容是基于常见工程实践总结出来的一套可落地路径你可以根据自己的技术栈做调整。2. 自然语言到几何参数text-to-cad 的核心映射逻辑2.1 为什么不能直接让大模型输出 STEP 文件很多人第一反应是既然大模型这么强直接让它生成 STEP 文件不就行了这个想法听起来合理但实际行不通。原因在于 STEP 文件本质上是一种**边界表示BRep**格式它描述的是几何体的精确数学定义——每个面的方程、每条边的曲线、顶点坐标全部是严格的数学表达。大模型擅长的是语言模式匹配它输出的文本在语法上可能像 STEP但里面的坐标、拓扑关系几乎必然是错的而且错得毫无规律。正确的做法是分两层第一层用大模型做“语义理解”把自然语言解析成结构化的几何参数第二层用几何内核做“精确建模”根据参数生成真正合法的 CAD 文件。这两层之间用 JSON 这样的中间格式衔接既方便调试也方便替换任意一层的实现。这个设计思路的好处是大模型只负责它擅长的事理解意图几何内核只负责它擅长的事精确计算各司其职出错时也能快速定位是哪一层的问题。2.2 几何参数的 JSON Schema 该怎么设计中间格式的设计是整个方案的地基。设计得太简单表达不了复杂形状设计得太复杂大模型容易输出格式错误。我的经验是从基本体出发用布尔运算组合。绝大多数工程零件都可以拆解成拉伸体、旋转体、扫掠体这几种基本体的组合再加上倒角、圆角、抽壳这些修饰操作。一个实用的 JSON Schema 大概长这样{ units: mm, operations: [ { type: box, params: {length: 80, width: 60, height: 40}, position: [0, 0, 0] }, { type: cylinder, params: {radius: 10, height: 40}, position: [40, 30, 0], boolean: subtract }, { type: fillet, params: {radius: 5}, target: vertical_edges } ] }这个 Schema 的关键设计点有三个。第一单位必须显式声明工程上毫米和米混用是灾难性的错误。第二每个操作带 position 和 boolean 字段position 决定放置位置boolean 决定这个体是新增、减去还是求交。第三修饰操作通过 target 引用前面的体或边而不是重新描述几何这样能保持参数化关系。提示Schema 里的字段名尽量用工程界通用的英文术语不要自己造词。大模型在训练数据里见过大量 CAD 相关的英文描述用通用术语能显著提高解析准确率。2.3 提示词工程在几何解析中的实际作用有了 Schema接下来要做的就是让大模型把自然语言翻译成符合 Schema 的 JSON。这里提示词的设计直接决定成败。我踩过的坑是一开始只给 Schema 定义让模型自由发挥结果模型经常漏字段、加字段、或者把数值写成字符串。后来改成给完整示例 明确约束准确率从大概六成提升到九成以上。一个有效的提示词结构是这样的你是一个 CAD 参数解析器。用户会用自然语言描述一个零件 你需要输出符合以下 JSON Schema 的结构化参数。 规则 1. 所有尺寸单位默认为毫米如果用户说了其他单位要转换。 2. 数值必须是数字类型不能是字符串。 3. 如果用户描述的形状无法用现有操作表达输出 {error: 原因}。 4. 不要输出任何解释文字只输出 JSON。 Schema: [这里放完整的 JSON Schema] 示例输入一个直径 50 毫米、高 30 毫米的圆柱中心开一个直径 10 毫米的通孔。 示例输出{units:mm,operations:[{type:cylinder,params:{radius:25,height:30},position:[0,0,0]},{type:cylinder,params:{radius:5,height:30},position:[0,0,0],boolean:subtract}]} 现在请解析{用户输入}这个提示词里最关键的其实是示例。大模型对示例的敏感度远高于对规则文字的敏感度。示例要覆盖你实际会遇到的典型场景比如带孔、带圆角、多体组合这些。示例给得越贴近真实需求解析效果越好。2.4 参数校验在生成几何之前拦住错误大模型输出的 JSON 不能直接拿去建模必须先过一遍校验。校验分三层格式校验是不是合法 JSON、字段类型对不对、语义校验数值是否为正、半径是否小于高度的一半这类几何约束、可行性校验布尔运算的目标是否存在、倒角半径是否超过相邻边长度。第三层最容易被忽略但恰恰是最容易出问题的。比如用户说“给这个 10 毫米厚的板倒一个半径 8 毫米的圆角”几何上根本做不到因为圆角半径超过了板厚。如果不在校验层拦住几何内核会直接抛异常整个流程就断了。我的做法是在校验层维护一张“几何约束表”把常见的约束条件都列进去校验不通过就返回明确的错误信息让用户知道哪里描述得不合理。3. 几何内核选型用什么把参数变成真正的 CAD 文件3.1 主流几何内核的能力边界对比参数校验通过之后就要调用几何内核来生成模型了。这一步的选型直接决定了你能生成什么格式、精度如何、能不能做复杂运算。市面上能用的几何内核不多我实际用过或深入研究过的有这几个内核开源情况支持格式布尔运算学习曲线适用场景OpenCASCADE开源STEP/IGES/DXF/STL强陡工业级零件、需要精确 BRepCadQuery开源基于 OCCTSTEP/DXF/STL强中Python 脚本建模、参数化零件FreeCAD开源STEP/DXF/URDF/STL强中需要 GUI 辅助、多格式导出trimesh开源STL/OBJ/PLY弱平缓网格模型、仿真用pythonocc开源OCCT 绑定STEP/IGES强陡需要底层控制选型的核心判断标准是你的下游要什么格式。如果下游是传统 CAD 软件比如中望 CAD、AutoCAD那必须输出 STEP 或 DXF只有 OpenCASCADE 系的内核能做到精确 BRep。如果下游是机器人仿真比如 CoppeliaSimURDF 是刚需FreeCAD 或 CadQuery 配合导出脚本更合适。如果只是做可视化展示trimesh 这种网格方案就够用还更轻量。3.2 CadQuery 为什么适合做 text-to-cad 的执行层在几个开源方案里我个人最推荐用 CadQuery 做执行层。原因有三点。第一它是 Python 库和上层的 JSON 解析、参数校验能无缝衔接不用跨语言调用。第二它的 API 设计非常贴近“描述几何”的思维比如cq.Workplane(XY).box(80,60,40).faces(Z).workplane().hole(20)这一行就描述了“在 XY 平面上建一个 80x60x40 的盒子在顶面打一个直径 20 的孔”可读性极强。第三它底层就是 OpenCASCADE导出的 STEP 是工业级精度能直接被中望 CAD、SolidWorks 这些软件打开。用 CadQuery 执行前面那个 JSON 的代码大概是这样import cadquery as cq import json def build_from_json(spec): result cq.Workplane(XY) for op in spec[operations]: if op[type] box: p op[params] result result.box(p[length], p[width], p[height]) elif op[type] cylinder: p op[params] if op.get(boolean) subtract: result result.faces(Z).workplane().hole(p[radius] * 2) else: result result.cylinder(p[height], p[radius]) return result spec json.loads(llm_output) model build_from_json(spec) cq.exporters.export(model, output.step)这段代码里有个细节值得说hole()方法的参数是直径不是半径而 JSON 里我习惯存半径所以调用时要乘 2。这种单位不一致的问题在几何编程里非常常见一定要在代码里显式处理不要靠脑子记。3.3 STEP、DXF、URDF 三种输出格式的生成路径text-to-cad 的输出格式不是单一的不同下游要不同格式。STEP 是最通用的三维格式CadQuery 一行exporters.export(model, x.step)就能出。DXF 是二维图纸格式适合钣金、激光切割这类场景CadQuery 可以通过投影生成但更稳的做法是用 ezdxf 库单独画二维图。URDF 是机器人描述格式本质是 XML描述的是连杆和关节的层级关系不是单纯的几何。URDF 的生成要单独说因为它和 STEP 的逻辑完全不同。STEP 描述的是一个静态的几何体URDF 描述的是一棵运动学树。如果你要生成 URDFJSON Schema 里就得包含 link 和 joint 的定义而不是单纯的几何操作。一个典型的 URDF 生成流程是先用 CadQuery 生成每个 link 的几何体并导出 STL再写一个脚本把 STL 路径、关节类型、关节轴向、父子关系组装成 URDF 的 XML 结构。这块内容比较多后面单独用一节讲。3.4 几何内核的常见报错与处理经验用几何内核最让人头疼的就是报错信息不友好。OpenCASCADE 系的报错经常是一串十六进制错误码根本看不懂。我总结了几类高频错误和处理方式。第一类是布尔运算失败通常是因为两个体刚好相切或者有微小重叠。解决办法是在做布尔运算前把参与运算的体稍微偏移一个极小量比如 0.001 毫米避开临界状态。第二类是倒角/圆角失败原因是半径超过了相邻边的长度或者相邻面之间的夹角太小。解决办法是在参数校验层就拦住或者在代码里做 try-except失败时自动减小半径重试。第三类是导出 STEP 时拓扑不完整通常是因为模型有自相交或者非流形边。解决办法是导出前调用model.val().isValid()检查不合法就先做修复。注意几何内核的报错一定要捕获并转成人类可读的信息返回给用户不要让原始错误码直接暴露。用户看到“BRep_API: command not done”只会一脸懵看到“圆角半径 8 毫米超过了板厚 10 毫米的一半请减小半径”才知道怎么改。4. 把生成的模型接进仿真URDF 导出的完整链路4.1 URDF 和 STEP 的本质区别很多人第一次接触 URDF 会以为它就是个三维模型格式其实不是。URDF 全称是 Unified Robot Description Format它描述的是机器人的运动学结构核心是 link连杆和 joint关节的树状关系。一个 URDF 文件里几何体只是 link 的一个属性visual 和 collision真正重要的是 joint 定义的父子关系和运动约束。这个区别决定了 text-to-cad 生成 URDF 时自然语言描述的重点不一样。描述 STEP 时你说的是“一个 80x60x40 的盒子”描述 URDF 时你说的是“一个两轮差速机器人底盘是 200x150x50 的盒子左右各有一个直径 60 的轮子轮子绕 Y 轴旋转”。后者包含了几何信息但更重要的是结构信息和运动信息。4.2 从自然语言提取运动学树的结构让大模型从自然语言里提取运动学树提示词的设计和提取几何参数类似但 Schema 要换成 link-joint 结构。一个实用的 Schema 是这样的{ robot_name: diff_drive, links: [ {name: base_link, geometry: {type: box, size: [200, 150, 50]}}, {name: left_wheel, geometry: {type: cylinder, radius: 30, length: 20}}, {name: right_wheel, geometry: {type: cylinder, radius: 30, length: 20}} ], joints: [ {name: left_wheel_joint, type: continuous, parent: base_link, child: left_wheel, axis: [0, 1, 0], origin: [0, 75, -25]}, {name: right_wheel_joint, type: continuous, parent: base_link, child: right_wheel, axis: [0, 1, 0], origin: [0, -75, -25]} ] }这个 Schema 里joint 的 type 字段是关键常见的有 continuous连续旋转比如轮子、revolute有限角度旋转比如机械臂关节、prismatic直线滑动、fixed固定连接。axis 定义旋转或滑动的轴向origin 定义子连杆相对于父连杆的安装位置。这几个字段描述清楚了URDF 的运动学就完整了。4.3 几何体导出 STL 并与 URDF 关联URDF 本身不包含几何体的网格数据它只引用 STL 或 DAE 文件的路径。所以生成流程是先用 CadQuery 把每个 link 的几何体单独导出成 STL再在 URDF 的 visual 和 collision 标签里引用这些 STL 的路径。这里有个容易踩的坑STL 的坐标系和 URDF 的坐标系要对齐。CadQuery 默认以几何体的某个角或中心为原点而 URDF 里 link 的原点是由 joint 的 origin 决定的。如果不对齐导入仿真环境后会发现轮子装偏了、机械臂关节错位。我的做法是在导出 STL 之前先把每个 link 的几何体平移到它自己的局部坐标系原点这样 URDF 里的 origin 就只需要描述 link 之间的相对位置逻辑清晰不容易错。import cadquery as cq # 底盘以几何中心为原点 base cq.Workplane(XY).box(200, 150, 50) cq.exporters.export(base, meshes/base_link.stl) # 左轮以轮子中心为原点 left_wheel cq.Workplane(XZ).cylinder(20, 30) cq.exporters.export(left_wheel, meshes/left_wheel.stl)4.4 导入 CoppeliaSim 后的常见问题排查URDF 生成好之后最常见的下游是 CoppeliaSim以前的 V-REP这类机器人仿真环境。导入之后经常遇到的问题有几个。第一个是模型位置不对整个机器人飘在空中或者陷进地面。这通常是 base_link 的原点位置问题URDF 里 base_link 的原点如果在几何中心导入后就会有一半陷进地面。解决办法是在 URDF 里加一个 base_footprint 的虚拟 link把它放在地面高度base_link 通过 fixed joint 连到它上面。第二个是关节转不动。检查 joint 的 type 是不是设成了 fixed或者 axis 设成了 [0,0,0]。continuous 类型的 joint 一定要有合法的 axis 向量。第三个是碰撞检测异常机器人自己和自己碰撞。这通常是因为 collision 标签引用的 STL 和 visual 用的是同一个而几何体之间有微小重叠。解决办法是给 collision 用一个简化版的几何体比如用包围盒代替复杂网格或者调整 joint 的 origin 让连杆之间留出间隙。5. 二维图纸场景DXF 生成与批量修改的实操细节5.1 什么情况下该输出 DXF 而不是 STEPSTEP 是三维格式DXF 是二维格式。什么时候该用 DXF答案是下游工艺只认二维图的时候。最典型的是激光切割、等离子切割、水刀切割这些钣金加工场景机器需要的是二维轮廓线不是三维模型。另外建筑平面图、电气接线图这些本身就是二维的图纸用 DXF 更合适。从 text-to-cad 的角度看生成 DXF 的自然语言描述通常是“一个 100x50 的矩形四角倒 R5 圆角中间开一个直径 10 的圆孔”这种二维描述。解析逻辑和三维类似但几何操作换成了二维的直线、圆弧、圆、多段线。5.2 用 ezdxf 生成二维轮廓的代码骨架Python 里生成 DXF 最顺手的库是 ezdxf。它的 API 比 CadQuery 更底层一些但控制力更强。一个生成带圆角矩形和圆孔的 DXF 的代码大概是这样import ezdxf doc ezdxf.new(R2010) msp doc.modelspace() # 画带圆角的矩形轮廓 points [(0, 0), (100, 0), (100, 50), (0, 50)] msp.add_lwpolyline(points, formatxy, closeTrue) # 加圆角ezdxf 本身不直接支持圆角需要用圆弧替代 # 这里用 fillet 库或手动计算切点 import math r 5 # 以左下角为例计算圆角圆弧 center (r, r) msp.add_arc(centercenter, radiusr, start_angle180, end_angle270) # 画中心圆孔 msp.add_circle(center(50, 25), radius5) doc.saveas(output.dxf)这段代码里有个现实问题ezdxf 不直接支持“给多段线加圆角”这种高级操作圆角需要手动计算切点和圆弧参数。如果圆角很多手算很痛苦。我的做法是写一个fillet_polyline的辅助函数输入多段线的顶点和圆角半径自动计算所有圆角并生成对应的圆弧。这个函数一旦写好后面所有 DXF 生成都能复用。5.3 批量修改已有 DXF 的脚本思路text-to-cad 不只是从零生成很多时候是批量修改已有的 DXF。比如你有一百张图纸需要把所有图纸里的某个图层颜色改掉或者把所有标注文字的字号统一。这种活儿手动做要命脚本做就是几行代码。用 ezdxf 批量修改的思路是遍历 doc 里的所有实体按条件筛选修改属性保存。比如把所有 TEXT 实体的高度改成 3.5import ezdxf doc ezdxf.readfile(input.dxf) msp doc.modelspace() for entity in msp: if entity.dxftype() TEXT: entity.dxf.height 3.5 doc.saveas(output.dxf)如果要批量处理多个文件外面再套一层os.listdir循环就行。这里的关键是先备份再修改因为 DXF 的实体类型很多改错了很难恢复。我的习惯是脚本里先shutil.copy一份原始文件到 backup 目录再在副本上操作。5.4 DXF 版本兼容性与图层管理的坑DXF 格式有多个版本从 R12 到 R2018 都有。不同版本的实体支持不一样比如 LWPOLYLINE 在 R12 里不支持得用 POLYLINE。如果你的下游软件比较老比如某些国产 CAD 的老版本生成 DXF 时要把版本设低一点。ezdxf 的new(R12)就能生成 R12 版本但要注意有些新实体用不了。图层管理是另一个高频坑。默认情况下ezdxf 生成的实体都在图层 0 上。如果下游有图层规范比如轮廓线在 CUT 层、标注在 DIM 层就得在生成时显式指定图层。更稳妥的做法是先在 doc 里创建好所有需要的图层设置好颜色和线型再把实体加到对应图层。这样生成的 DXF 打开就是规规矩矩的不用手动整理。6. 工程化落地从脚本到可用工具的最后一公里6.1 错误处理与用户反馈的设计一个能用的 text-to-cad 工具错误处理的重要性不亚于核心功能。用户输入“一个半径 -5 的圆”你得告诉他半径不能为负用户输入“给球体倒圆角”你得告诉他球体没有边可以倒角。这些错误信息要具体、可操作不能只说“输入无效”。我的做法是维护一个错误码表每个错误码对应一句人话解释和一条修改建议。比如错误码触发条件用户提示E001JSON 格式错误解析失败请检查描述是否包含无法识别的形状E002数值为负尺寸不能为负数请检查描述中的数值E003圆角半径过大圆角半径超过了相邻边长度请减小半径E004布尔运算目标不存在描述中引用的形状未定义请检查操作顺序E005导出格式不支持当前几何体无法导出为该格式请更换格式这张表在开发时就要建好每遇到一种新错误就加一条。时间长了这张表就是你这个工具最宝贵的资产因为它记录了所有真实用户踩过的坑。6.2 参数化模板让重复需求一键生成text-to-cad 最实用的场景不是每次从零描述而是基于模板改参数。比如你经常需要生成不同尺寸的法兰盘与其每次重新描述不如做一个模板只改几个关键参数。模板的本质是预定义的 JSON Schema 加参数占位符。用户只需要说“法兰盘外径 100内径 50螺栓孔 6 个”系统就自动填充模板生成完整参数。这种模式比纯自然语言解析更可靠因为模板的结构是固定的大模型只需要提取几个数值出错概率大大降低。实际做的时候模板可以存成 JSON 文件每个模板带一个参数列表和默认值。用户选择模板后系统只让大模型提取参数值然后套进模板。这种方式在工业场景下特别受欢迎因为工程师的需求往往就是“标准件改尺寸”不是每次都设计新东西。6.3 性能优化批量生成时的缓存与并行如果你要批量生成几百个模型性能就成了问题。几何内核的布尔运算和网格化都是计算密集型的单个模型可能要几秒到几十秒。优化手段有几个。第一是缓存中间结果。如果多个模型共享同一个基础几何体只在小特征上有差异可以把基础几何体缓存起来只对差异部分重新计算。第二是并行化。几何内核的运算大多是 CPU 密集型的用 Python 的 multiprocessing 开多进程能线性提升吞吐量。注意不要用多线程因为几何内核的 C 层不一定线程安全。第三是降低导出精度。如果下游只是做可视化STL 的精度可以调低导出速度快很多文件也小很多。6.4 版本管理与可复现性最后说一个容易被忽略但很重要的点可复现性。text-to-cad 的输入是自然语言而大模型的输出有随机性同样的输入两次可能得到略有不同的结果。这在工程场景下是致命的因为你需要的是确定性的输出。解决办法是固定随机种子 记录完整中间结果。调用大模型时设 temperature 为 0让它输出确定性的结果。同时把每次生成的 JSON 参数、使用的模板版本、几何内核版本都记录下来存成一个 manifest 文件。这样任何时候你都能根据 manifest 复现出完全一样的模型。这个习惯在团队协作时尤其重要别人拿到你的 manifest 就能复现你的结果不用猜你当时是怎么描述的。7. 我在实际项目里踩过的几个坑第一个坑是单位混乱。有一次生成的模型导入下游软件后尺寸差了一千倍排查半天发现是大模型把“米”当成了“毫米”而我的 Schema 默认单位是毫米没有做转换。从那以后我在提示词里强制要求如果用户描述里出现了非毫米单位必须显式转换并在 JSON 里标注原始单位。第二个坑是圆角顺序。CadQuery 里先倒圆角再打孔和先打孔再倒圆角结果可能完全不同。如果孔的位置靠近边角先打孔会导致倒圆角时找不到完整的边。我的经验是先做所有减材操作打孔、挖槽再做修饰操作倒角、圆角这样修饰操作面对的是完整的边不容易失败。第三个坑是URDF 的惯性矩阵。URDF 里每个 link 可以定义 inertial 标签包含质量和惯性矩阵。如果这个矩阵设得不对仿真时机器人会抖动甚至飞出去。我的做法是先用一个简单的质量值惯性矩阵用几何体的近似公式算比如长方体用m/12 * (h²d²)这类公式不要随便填零。填零的话仿真环境会报错或者行为异常。第四个坑是DXF 的闭合多段线。用 ezdxf 画轮廓时如果多段线没有正确闭合激光切割机可能不会把它识别为封闭轮廓导致切出来的零件是断开的。解决办法是画多段线时显式设置closeTrue或者在最后手动加一条回到起点的线段。这个细节很小但不注意的话下游加工会出大问题。8. 这条链路还能怎么扩展text-to-cad 目前能做到的是“描述形状生成模型”但工程上真正耗时的往往不是建模本身而是建模之后的验证和迭代。下一步可以扩展的方向有几个。一是自动生成工程图。模型生成后自动投影出三视图、标注尺寸、生成标题栏直接输出可打印的 PDF 图纸。CadQuery 和 FreeCAD 都有投影功能配合 ezdxf 做标注这条路是通的。二是参数优化。给定一个目标比如“在保证强度的前提下重量最轻”自动调整几何参数并调用有限元分析验证迭代出最优解。这需要把 text-to-cad 和 CAE 工具链打通复杂度高一个量级但价值也大得多。三是与 PLM/PDM 系统集成。生成的模型自动入库、自动版本管理、自动关联物料清单。这是企业级应用的方向需要对接现有的工程管理系统。我个人最看好的其实是第一个方向因为工程图是绝大多数制造场景的最终交付物把这一步自动化text-to-cad 的闭环才算真正完成。至于后面两个方向等基础链路跑稳了再考虑也不迟。
返回列表