☰
text-to-cad实战:Python+STEP+URDF实现自然语言生成三维模型
2026/10/27 7:15:39 网站建设 项目流程

1. 从一句话到三维模型:text-to-cad 到底在解决什么问题

第一次听到 “text-to-cad” 这个词,很多人脑子里蹦出来的画面可能是:对着电脑说一句“给我画个齿轮”,然后 CAD 软件里就自动蹦出来一个三维模型。这个想象方向没错,但实际落地远没有这么简单。text-to-cad 本质上是一套把自然语言描述转换成结构化三维几何数据的技术链路,它的输出通常不是某个 CAD 软件的原生文件,而是像 STEP、URDF 这类通用格式,然后再由下游工具去消费。

我最早接触这个方向,是因为手头有一批机械臂的装配需求,需要频繁生成不同尺寸的连杆和关节件。传统做法是在 CAD 软件里手动建模,改一个参数就要重新画一遍,效率极低。后来尝试用 Python 脚本驱动参数化建模,再往后就自然走到了“能不能直接用文字描述来生成模型”这一步。实测下来,text-to-cad 目前最成熟的落地场景集中在三类:参数化零件批量生成、机器人 URDF 模型快速搭建、教学演示中的即时建模。它不适合替代精细的工业设计,但非常适合做原型验证和重复性建模。

这篇文章面向的是有一定 Python 基础、对 CAD 或机器人仿真有初步了解、想把这套流程跑通的从业者。我会从整体设计思路讲起,拆解核心环节,给出可直接复现的实操步骤,最后把我踩过的坑和排查经验整理出来。全文围绕 text-to-cad 这条主线,涉及 CAD、STEP、URDF、Python 四个关键词的实际用法。

2. 整体设计思路:为什么是 Python + STEP + URDF 这套组合

2.1 核心链路的拆解逻辑

text-to-cad 的完整链路可以拆成四段:文本解析 → 参数提取 → 几何构建 → 格式导出。每一段都有多种技术选型,我选择当前这套组合是基于实际约束反推出来的。

文本解析这一段,最直接的做法是调用大语言模型做意图识别和参数抽取。但如果你不想依赖外部服务,也可以用正则加关键词匹配做轻量解析。我在早期版本里用过纯规则匹配,对于“生成一个长 100mm、宽 50mm、高 30mm 的方块”这类结构化描述,正则完全够用。但一旦描述变成“做一个比刚才那个高一点、窄一点的支架”,规则就崩了。所以现在的做法是:规则解析兜底,模型解析处理模糊描述。

参数提取之后,进入几何构建。这里的选择很多,可以用 FreeCAD 的 Python API,可以用 CadQuery,也可以用 OpenSCAD 的脚本模式。我最终选 CadQuery,原因是它的 API 设计更接近“描述式建模”,代码可读性强,而且原生支持 STEP 导出。FreeCAD 的 API 功能更全,但学习曲线陡,脚本调试体验差。OpenSCAD 适合做纯几何,但导出 STEP 需要额外转换,多一道工序就多一个出错点。

格式导出这一段,STEP 和 URDF 是两条不同的路。STEP 是通用三维交换格式,适合给下游 CAD 软件或 3D 打印切片工具用。URDF 是机器人描述格式,适合导入 CoppeliaSim、Gazebo 这类仿真环境。两者的数据结构差异很大:STEP 描述的是几何体本身,URDF 描述的是连杆和关节的拓扑关系。所以 text-to-cad 在导出阶段需要根据目标格式做不同的后处理。

2.2 为什么不用现成的 CAD 插件方案

市面上有一些 CAD 插件号称支持自然语言建模,我试过几款,普遍问题是封闭性太强。你没法控制中间过程,生成的模型参数不可追溯,改一个尺寸就要重新描述一遍。而且很多插件绑定特定 CAD 版本,换台机器就废了。

自己用 Python 搭这套链路,最大的好处是全流程可控。文本解析的规则可以自己调,几何构建的参数可以自己定,导出格式可以自己选。更重要的是,整个流程可以版本化管理,今天生成的模型和三个月后生成的模型,只要输入描述一样,输出就一样。这种可复现性在工程场景里比什么都重要。

另一个考虑是批量处理能力。用插件你只能一个一个手动操作,用 Python 脚本你可以写一个循环,读一个 CSV 参数表,一次性生成几百个不同尺寸的零件。我之前做一个支架系列,12 个规格,用脚本跑完不到 30 秒,手动建模至少半天。

2.3 方案的优势与边界

这套方案的优势集中在三点:可复现、可批量、可扩展。可复现前面说了,可批量是 Python 脚本的天然优势,可扩展是指你可以在任意环节插入自定义逻辑,比如自动加圆角、自动检查干涉、自动生成装配体。

但它的边界也很明显。第一,不适合复杂曲面。CadQuery 和大多数脚本化建模工具一样,擅长规则几何体,遇到自由曲面就力不从心。第二,不适合精细装配。URDF 导出的是简化模型,碰撞体通常是包围盒或简单几何,不能直接用于公差分析。第三,文本解析的准确率有上限。再好的模型解析也有歧义,比如“大一点”到底是多大,没有上下文就没法确定。

所以我的定位很明确:text-to-cad 是原型工具和批量工具,不是设计工具。用它来快速验证想法、生成仿真模型、批量出图,效率极高;用它来做最终交付的工业设计,还差得远。

3. 核心细节解析:文本解析、几何构建与格式导出的关键要点

3.1 文本解析:从自然语言到参数字典

文本解析的目标是把一句人话变成机器能读的参数字典。比如输入“生成一个长 100mm、宽 50mm、高 30mm 的方块”,输出应该是{"shape": "box", "length": 100, "width": 50, "height": 30, "unit": "mm"}。

用规则解析的话,核心是关键词映射 + 数值提取。关键词映射负责识别形状类型,比如“方块”“立方体”“box”都映射到box,“圆柱”“圆管”“cylinder”都映射到cylinder。数值提取负责抓尺寸,用正则匹配数字加单位的组合。这里有个细节:单位统一。用户可能写 100mm,也可能写 10cm,还可能写 0.1m,解析阶段必须统一转成毫米,否则后面几何构建会乱。

用模型解析的话,核心是提示词设计。我用的提示词模板大致是这样的:先给几个示例,说明输入输出格式,然后要求模型只输出 JSON,不要加任何解释。实测下来,给示例比不给示例的准确率高很多,尤其是尺寸参数的提取。但模型解析有个坑:它可能会编造参数。比如用户没说高度,模型可能自己补一个默认值。所以我在解析之后加了一层校验,检查必填参数是否齐全,不齐全就报错让用户补充。

注意:文本解析阶段一定要做参数校验,不能直接信任解析结果。我遇到过模型把“长 100”解析成length: 100但单位丢了的情况,后面几何构建按默认单位跑,结果差了 10 倍。

3.2 几何构建:CadQuery 的实操要点

CadQuery 的建模逻辑是链式调用。比如画一个方块,代码是这样的:

import cadquery as cq result = cq.Workplane("XY").box(100, 50, 30)

这行代码的意思是:在 XY 平面上,画一个长 100、宽 50、高 30 的方块。Workplane("XY")指定基准面,box()是建模操作。如果要画圆柱:

result = cq.Workplane("XY").circle(25).extrude(100)

先画一个半径 25 的圆,然后拉伸 100 的高度。注意这里的circle(25)是画圆,extrude(100)是拉伸,两个操作链式组合。

CadQuery 的坐标系默认是 Z 轴向上,X 轴向右,Y 轴向前。这个和很多 CAD 软件的默认坐标系一致,但和 URDF 的坐标系有差异。URDF 里通常 Z 轴向上,X 轴向前,Y 轴向左。所以在导出 URDF 的时候需要做坐标变换,这个后面会细说。

几何构建阶段有几个容易踩的坑。第一,布尔运算的顺序。CadQuery 支持union()、cut()、intersect()等布尔操作,但顺序不同结果可能不同。比如先并集再差集,和先差集再并集,结果可能完全不一样。我的经验是:先做加法再做减法,先把所有要加的形状并起来,再统一减去要挖掉的部分。第二,圆角的位置。CadQuery 的fillet()操作需要指定边,边的选择用edges()加选择器。选择器写不好,圆角就加错地方。我通常用edges("|Z")选平行于 Z 轴的边,或者edges(">Z")选 Z 坐标最大的边。第三,单位问题。CadQuery 默认单位是毫米,如果你输入的参数是米,记得先转换。

3.3 STEP 导出:参数设置与常见问题

STEP 导出在 CadQuery 里就一行代码:

cq.exporters.export(result, "output.step")

但这行代码背后有几个参数值得注意。export函数支持tolerance参数,控制导出精度,默认是 0.1。对于大多数零件,0.1 够用;如果零件尺寸很小,比如微型齿轮,建议调到 0.01。精度越高,文件越大,导出越慢,需要权衡。

STEP 导出最常见的问题是面丢失。尤其是做了复杂布尔运算之后,导出的 STEP 文件在某些 CAD 软件里打开会发现缺面。这通常是几何体本身有微小缝隙或自相交导致的。解决办法是在导出前做一次几何修复,CadQuery 没有直接的修复函数,但可以用clean()方法清理冗余边和面。如果还不行,就只能回退到布尔运算那一步,检查是不是运算顺序有问题。

另一个问题是颜色和材质丢失。STEP 格式本身支持颜色信息,但 CadQuery 默认不导出颜色。如果需要带颜色的 STEP,得手动设置color属性,然后在导出时指定exportType="STEP"并确保颜色信息被包含。不过大多数下游工具对 STEP 颜色的支持也不一致,所以我的做法是:颜色信息单独用 JSON 记录,不依赖 STEP 文件本身。

3.4 URDF 导出:拓扑结构与坐标变换

URDF 导出比 STEP 复杂得多,因为 URDF 描述的不是单个几何体,而是连杆和关节的树状结构。一个典型的 URDF 文件包含<link>和<joint>两类元素。<link>定义连杆的几何形状和惯性参数,<joint>定义关节的类型、位置和父子关系。

从 text-to-cad 的角度,文本描述需要包含拓扑信息。比如“一个两连杆机械臂,第一段长 200mm,第二段长 150mm,中间是旋转关节”,解析出来应该是两个 link 和一个 joint。如果文本只描述了一个零件,那导出 URDF 就没意义,因为 URDF 的最小完整单元至少是一个 link。

坐标变换是 URDF 导出的核心难点。CadQuery 的坐标系和 URDF 的坐标系不一致,需要在导出时做转换。具体来说,CadQuery 的 Z 轴向上,URDF 通常也是 Z 轴向上,但 X 和 Y 的方向可能不同。我的做法是:在 CadQuery 里建模时就用 URDF 的坐标系约定,这样导出时不需要额外变换。具体操作是在Workplane()里指定"XZ"平面而不是默认的"XY"平面,然后调整拉伸方向。

惯性参数是另一个坑。URDF 的<inertial>元素需要质量和惯性矩阵。text-to-cad 生成的几何体没有质量信息,需要根据体积和密度估算。我通常假设材料是铝合金,密度 2700 kg/m³,然后根据体积算质量。惯性矩阵对于简单几何体有解析公式,比如方块和圆柱的惯性矩阵可以直接算。对于复杂形状,可以用trimesh库做数值积分。

提示:URDF 导出后一定要在仿真环境里验证。我遇到过惯性矩阵算错导致机械臂在仿真里疯狂抖动的情况,排查了半天才发现是惯性矩阵的非对角元素符号搞反了。

4. 实操过程:从零搭建 text-to-cad 流水线

4.1 环境准备与依赖安装

先搞定 Python 环境。我推荐用 Python 3.8 或以上版本,太老的版本有些库不支持。安装 CadQuery 最省事的方式是用 conda:

conda create -n text2cad python=3.10 conda activate text2cad conda install -c conda-forge cadquery

用 pip 也行,但 CadQuery 依赖 OCCT 库,pip 安装有时候会编译失败。conda 的预编译包更稳。除了 CadQuery,还需要装trimesh用于惯性计算,numpy用于数值处理,lxml用于生成 URDF 的 XML 结构。

pip install trimesh numpy lxml

如果你打算用模型做文本解析,还需要装对应的 SDK。不过这部分我建议先跳过,用规则解析把流程跑通,再考虑加模型。

4.2 文本解析模块的实现

先定义一个解析函数,输入是字符串,输出是参数字典。规则解析的核心是正则匹配:

import re def parse_text(text): text = text.lower() params = {} # 识别形状 if any(kw in text for kw in ["方块", "立方体", "box"]): params["shape"] = "box" elif any(kw in text for kw in ["圆柱", "圆管", "cylinder"]): params["shape"] = "cylinder" else: raise ValueError("无法识别形状类型") # 提取尺寸 dims = re.findall(r'(\d+\.?\d*)\s*(mm|cm|m)', text) # 统一转毫米 unit_map = {"mm": 1, "cm": 10, "m": 1000} dims_mm = [float(v) * unit_map[u] for v, u in dims] if params["shape"] == "box": if len(dims_mm) < 3: raise ValueError("方块需要三个尺寸") params["length"], params["width"], params["height"] = dims_mm[:3] elif params["shape"] == "cylinder": if len(dims_mm) < 2: raise ValueError("圆柱需要半径和高度") params["radius"], params["height"] = dims_mm[:2] return params

这个函数能处理“长 100mm、宽 50mm、高 30mm 的方块”和“半径 25mm、高 100mm 的圆柱”。实测下来,对于格式规整的描述,准确率接近 100%。对于模糊描述,比如“大一点的方块”,这个函数会报错,这时候就需要模型解析兜底。

4.3 几何构建模块的实现

拿到参数字典后,构建几何体:

import cadquery as cq def build_geometry(params): if params["shape"] == "box": result = cq.Workplane("XY").box( params["length"], params["width"], params["height"] ) elif params["shape"] == "cylinder": result = cq.Workplane("XY").circle( params["radius"] ).extrude(params["height"]) else: raise ValueError("不支持的形状") return result

这段代码很直白,但有个细节:box()的参数顺序是长、宽、高,对应 X、Y、Z 方向。circle().extrude()是先画圆再拉伸,圆在 XY 平面,拉伸方向是 Z 轴。如果你需要圆柱沿 X 轴方向,得先旋转工作平面:

result = cq.Workplane("YZ").circle(radius).extrude(height)

Workplane("YZ")把基准面换成 YZ 平面,拉伸方向就变成 X 轴了。

4.4 STEP 导出与验证

导出 STEP 并验证文件完整性:

def export_step(result, filepath): cq.exporters.export(result, filepath, tolerance=0.01) print(f"STEP 文件已导出:{filepath}")

导出后,我习惯用 FreeCAD 打开检查一遍。如果 FreeCAD 能正常打开且没有缺面,基本就没问题。另一个验证方法是重新导入 CadQuery:

imported = cq.importers.importStep("output.step") print(imported.val().Volume())

如果导入后的体积和原始体积一致,说明导出没有丢几何。体积不一致就说明有问题,需要回退检查。

4.5 URDF 导出的完整流程

URDF 导出需要先构建拓扑结构。假设我们要生成一个两连杆机械臂:

from lxml import etree def build_urdf(link_params_list, joint_params_list): robot = etree.Element("robot", name="arm") for i, link_params in enumerate(link_params_list): link = etree.SubElement(robot, "link", name=f"link_{i}") # 几何 visual = etree.SubElement(link, "visual") geometry = etree.SubElement(visual, "geometry") box = etree.SubElement(geometry, "box") box.set("size", f"{link_params['length']} {link_params['width']} {link_params['height']}") # 惯性 inertial = etree.SubElement(link, "inertial") mass = etree.SubElement(inertial, "mass") volume = link_params['length'] * link_params['width'] * link_params['height'] / 1e9 # mm³ 转 m³ mass.set("value", str(volume * 2700)) # 铝合金密度 for j, joint_params in enumerate(joint_params_list): joint = etree.SubElement(robot, "joint", name=f"joint_{j}", type="revolute") parent = etree.SubElement(joint, "parent") parent.set("link", f"link_{joint_params['parent']}") child = etree.SubElement(joint, "child") child.set("link", f"link_{joint_params['child']}") origin = etree.SubElement(joint, "origin") origin.set("xyz", f"{joint_params['x']} {joint_params['y']} {joint_params['z']}") axis = etree.SubElement(joint, "axis") axis.set("xyz", "0 0 1") return etree.tostring(robot, pretty_print=True, xml_declaration=True)

这段代码生成一个包含两个 link 和一个 joint 的 URDF。注意质量计算:体积单位是 mm³,转 m³ 要除以 1e9,然后乘以密度 2700 kg/m³。惯性矩阵这里省略了,实际使用需要补上,否则仿真可能不稳定。

4.6 完整流水线的串联

把上面几个模块串起来:

def text_to_cad(text, output_format="step", output_path="output"): params = parse_text(text) geometry = build_geometry(params) if output_format == "step": export_step(geometry, f"{output_path}.step") elif output_format == "urdf": urdf_str = build_urdf([params], []) with open(f"{output_path}.urdf", "w") as f: f.write(urdf_str) return f"{output_path}.{output_format}" # 测试 text_to_cad("生成一个长 100mm、宽 50mm、高 30mm 的方块", "step", "test_box")

跑完这段代码,当前目录下会生成test_box.step。用 FreeCAD 打开,应该能看到一个 100x50x30 的方块。

5. 常见问题与排查技巧实录

5.1 文本解析类问题

问题一:单位识别错误。用户写“长 0.1m”,解析成length: 0.1但单位没转,后面按毫米处理,结果小了 1000 倍。解决办法是在正则里强制捕获单位,没有单位的数值直接报错,不猜默认值。

问题二:同义词漏识别。用户写“长方体”,规则里只写了“方块”和“立方体”,就识别不了。解决办法是维护一个同义词表,把所有可能的表达都列进去。我现在的同义词表有 20 多个词条,覆盖了常见的中英文表达。

问题三:数值提取歧义。用户写“长 100 宽 50 高 30”,没有单位,正则匹配不到。解决办法是允许无单位数值,但统一按毫米处理,并在输出里标注“假设单位为毫米”。

5.2 几何构建类问题

问题一:布尔运算失败。两个几何体做cut()操作时报错,提示“布尔运算失败”。原因通常是两个几何体没有相交,或者相交面有微小缝隙。解决办法是先检查两个几何体的位置关系,确保它们确实有重叠区域。如果有缝隙,把其中一个几何体稍微放大一点再运算。

问题二:圆角导致几何体破碎。给一个薄壁零件加圆角,圆角半径大于壁厚,几何体直接碎掉。解决办法是加圆角前检查半径是否合理,圆角半径必须小于最小壁厚的一半。

问题三:导出 STEP 后体积不对。导出前体积是 1000 mm³,导出后重新导入变成 999.8 mm³。这是精度问题,tolerance设得太小导致的。把tolerance从 0.01 调到 0.1,体积就一致了。精度和文件大小需要权衡,不是越小越好。

5.3 URDF 导出类问题

问题一:导入 CoppeliaSim 后模型位置不对。这是坐标系差异导致的。CadQuery 默认 Z 轴向上,CoppeliaSim 也是 Z 轴向上,但 X 和 Y 的方向可能差 90 度。解决办法是在 URDF 的<origin>里加旋转,或者建模时就按 CoppeliaSim 的坐标系来。

问题二:仿真时机械臂抖动。惯性矩阵算错了。检查<inertial>里的质量值和惯性矩阵,确保单位是 kg 和 kg·m²。我遇到过质量写成克的情况,仿真里机械臂轻得像纸片,一碰就飞。

问题三:关节类型不匹配。URDF 里写了revolute,但仿真环境里需要continuous。解决办法是查仿真环境的文档,确认支持的关节类型。CoppeliaSim 对revolute和continuous都支持,但行为略有不同。

5.4 常见问题速查表

问题现象可能原因排查方法解决方案
解析结果缺参数正则没匹配到打印解析中间结果补充同义词或放宽正则
几何体构建报错参数类型不对检查参数字典强制类型转换
STEP 导出缺面几何体有缝隙FreeCAD 打开检查清理几何体或调整布尔顺序
URDF 导入失败XML 格式错误用 xmllint 校验检查标签闭合和属性
仿真抖动惯性参数错误检查质量和惯性矩阵重新计算惯性参数
单位不一致解析时没统一打印所有尺寸值强制转毫米

提示:排查问题时,最有效的方法是在每一步打印中间结果。文本解析后打印参数字典,几何构建后打印体积,导出后打印文件大小。这样一旦出错,能快速定位是哪一步的问题。

5.5 独家避坑经验

经验一:不要追求一步到位。我一开始想直接做“文本到完整装配体”,结果卡在拓扑解析上整整一周。后来退回到“文本到单个零件”,先把链路跑通,再逐步加拓扑。这个思路救了我很多时间。

经验二:参数校验比解析本身更重要。解析可以不准,但校验必须严格。我现在的校验规则有十几条:必填参数是否齐全、数值是否为正、尺寸是否在合理范围内、单位是否统一。校验不过就直接报错,不往下走。这样虽然有时候会误报,但避免了大量隐蔽错误。

经验三:STEP 和 URDF 分开处理。一开始我想用一套代码同时支持两种导出,结果代码复杂度爆炸。后来拆成两个独立模块,各自处理各自的格式转换,代码清晰多了。虽然有一些重复逻辑,但维护成本低很多。

经验四:版本锁定。CadQuery 的 API 在不同版本之间有变化,我遇到过升级后box()参数顺序变了的情况。所以我现在用requirements.txt锁定版本,不随便升级。生产环境稳定比新功能重要。

经验五:批量生成时加日志。批量跑 100 个零件,中间某个失败了,没有日志根本不知道是哪个。我现在的做法是每个零件生成时都写一行日志,记录输入参数、输出路径、耗时、是否成功。出问题直接查日志,不用重新跑一遍。

6. 扩展方向与个人体会

这套 text-to-cad 流水线跑通之后,我陆续加了一些扩展功能。自动加圆角是一个,在几何构建之后加一步,检测所有锐边,自动加 1mm 圆角。自动生成装配体是另一个,读一个 CSV 文件,每行是一个零件的描述,自动生成所有零件并组装。自动检查干涉也做过,用trimesh做碰撞检测,两个零件有重叠就报警。

这些扩展都不是必须的,但加上之后,整个流程的自动化程度提升很多。我现在做一个新零件系列,从写描述到出 STEP 文件,全程不超过 5 分钟。这在以前手动建模的时代是不可想象的。

不过我也得说,text-to-cad 目前的能力边界很清楚。它擅长规则几何、批量生成、快速原型,不擅长自由曲面、精细装配、公差分析。如果你的需求落在前三个,这套方案能帮你省大量时间;如果落在后三个,还是老老实实打开 CAD 软件手动建模。

最后分享一个小技巧:把常用形状做成模板。我在代码里预定义了十几种常用形状,比如“法兰盘”“支架”“连杆”,每种都有默认参数。用户描述的时候只需要说“生成一个法兰盘,外径 100”,其他参数用默认值。这样解析逻辑大大简化,用户体验也好很多。模板化是 text-to-cad 从“能用”到“好用”的关键一步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询