ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python+CADQuery实战:自然语言生成STEP与URDF模型

Python+CADQuery实战:自然语言生成STEP与URDF模型

1. 项目缘起与核心思路拆解

1.1 这个项目到底在解决什么问题

做机械设计或者机器人仿真的朋友大概率都经历过这样的场景:脑子里已经想清楚了一个零件的形状,甚至用文字都能描述得明明白白——“一个长宽高分别是80、60、40毫米的长方体底座,上面开四个M6的沉头孔,孔距60乘40”,但真要打开CAD软件把这个东西画出来,从启动软件到建草图到拉伸到打孔,没个十几分钟下不来。如果一天要生成几十个类似的变体零件,这种重复劳动能把人逼疯。

text-to-cad这个项目瞄准的就是这个痛点:用自然语言描述直接生成CAD模型文件。你输入一段文字,它输出一个标准的STEP文件或者URDF文件,可以直接丢进任意主流CAD软件里打开编辑,也可以直接导入到CoppeliaSim这类机器人仿真环境里做运动学验证。核心价值在于把“想法到模型”的路径从“人操作软件”变成“文字驱动生成”,省掉中间所有手工建模的环节。

这个项目适合谁呢?我梳理了一下,大概三类人最需要:第一类是做机器人仿真的工程师,经常需要快速生成大量测试用的连杆、底座、法兰盘模型,导入URDF做运动学仿真;第二类是做参数化设计的机械工程师,产品系列有大量结构相似但尺寸不同的零件,想用文字批量生成;第三类是对CAD二次开发感兴趣的程序员,想了解怎么用Python操作几何内核,把代码和CAD打通。

1.2 为什么选Python加STEP这条技术路线

技术选型这件事,我踩过的坑不少。最早我想的是直接用FreeCAD的Python API来建模,因为FreeCAD本身开源免费,API也算完善。但实际用下来发现两个问题:一是FreeCAD的API文档质量参差不齐,很多函数没有说明,得翻源码;二是FreeCAD的几何内核是OpenCASCADE,虽然功能强大但Python绑定层有时候会有内存泄漏,批量生成几百个模型之后进程就卡死了。

后来我转向了直接用Python的cadquery库。cadquery是对OpenCASCADE的Python封装,API设计比FreeCAD原生API干净得多,而且专门为脚本化建模优化过。它的核心思路是链式调用:先定义一个工作平面,然后在上面画草图,再拉伸、切割、倒角,每一步都返回一个新的Workplane对象,可以一直链下去。这种风格写起来很顺手,代码可读性也高。

输出格式选STEP而不是STL或者OBJ,原因很直接:STEP是B-rep(边界表示)格式,保留完整的几何拓扑信息,圆柱面就是圆柱面,孔就是孔,导入到SolidWorks或者中望CAD里还能继续编辑特征。STL是三角网格,导进去就是一坨三角面片,想改个孔径都得重新建模。URDF格式则是专门给机器人仿真用的,它本身不包含几何数据,而是引用外部网格文件,所以生成URDF的时候需要同时输出STL或者DAE格式的网格文件。

1.3 整体架构长什么样

整个项目的架构其实不复杂,核心就三层。最上面是自然语言解析层,负责把用户输入的中文或者英文描述拆解成结构化的参数。中间是几何生成层,用cadquery根据解析出来的参数构建3D模型。最下面是文件输出层,把生成的模型导出成STEP或者URDF格式。

自然语言解析这块,我一开始想用正则表达式硬匹配,比如检测到“长方体”就提取长宽高数字。但实际测试下来发现用户的表达方式太灵活了,“一个80x60x40的方块”和“长80宽60高40的矩形体”和“80毫米长、60毫米宽、40毫米厚的底板”说的是同一个东西,正则根本覆盖不过来。后来改用了一个轻量级的规则引擎加同义词映射的方案:先把输入文本分词,然后查同义词表把“方块”“矩形体”“底板”都映射到“box”这个标准类型,再从文本中提取所有数字和单位,按位置关系分配给长宽高。这个方案对常见表达方式的覆盖率能到90%以上,剩下的边缘情况再手动补规则就行。

几何生成层是项目的核心。cadquery的建模逻辑和传统CAD软件不太一样,它更像是“函数式建模”:你不是在画图,而是在描述一个形状的构造过程。比如要生成一个带孔的板子,代码是这样的:先创建一个长方体,然后在顶面选一个位置挖一个圆柱体出来。每一步操作都是不可变的,返回一个新的对象。这种模式的好处是代码即文档,看代码就知道模型长什么样。

文件输出层相对简单,cadquery原生支持导出STEP和STL,URDF需要自己拼XML。URDF的结构不复杂,就是定义link和joint,每个link引用一个网格文件,joint定义父子关系和运动类型。需要注意的是URDF里的单位是米,而CAD建模通常用毫米,导出的时候要记得除以1000。

2. 核心细节解析与实操要点

2.1 环境搭建:Python版本和依赖库的选择

Python版本我建议用3.8到3.10之间的,太老的版本cadquery不支持,太新的版本有些依赖库还没跟上。我实测下来3.9最稳,3.10也没问题,3.11在某些系统上装cadquery的依赖会报编译错误。

安装cadquery最省事的方式是用conda,因为cadquery依赖OpenCASCADE的二进制库,pip安装有时候会找不到预编译的wheel。命令是conda install -c conda-forge cadquery,conda-forge频道有维护好的包,装完直接能用。如果非要用pip,那就先装cadquery-ocp这个包,它是OpenCASCADE的Python绑定,然后再装cadquery本身。

除了cadquery,还需要装几个辅助库。numpy用来做数值计算,比如计算孔位坐标的时候会用到。lxml用来生成URDF的XML文件,比标准库的xml.etree好用。regex库处理中文分词的时侯比re模块更灵活,支持Unicode属性匹配。

注意:cadquery在Windows上安装的时候,如果之前装过FreeCAD,可能会因为OpenCASCADE的DLL冲突导致导入失败。解决办法是在虚拟环境里装,或者把FreeCAD的安装目录从PATH里临时移除。

2.2 自然语言解析的规则设计

解析层的核心是一张同义词映射表。我把常见的几何体类型和修饰词都整理了一遍,比如“长方体”“方块”“矩形体”“底板”“基座”都映射到box,“圆柱”“圆棒”“轴”“销”都映射到cylinder,“孔”“洞”“开口”都映射到hole。修饰词方面,“沉头”“ countersink”映射到沉头孔,“通孔”“through”映射到贯穿孔,“盲孔”“blind”映射到不贯穿的孔。

数字提取用的是正则表达式\d+(\.\d+)?,匹配所有整数和小数。单位识别单独处理,检测到“毫米”“mm”就按毫米算,检测到“厘米”“cm”就乘以10,检测到“米”“m”就乘以1000。默认单位是毫米,因为机械设计里毫米最常用。

参数分配的逻辑是这样的:先找到几何体类型关键词的位置,然后看这个关键词前面和后面各有哪些数字。对于长方体,通常前面三个数字分别是长宽高,或者用“长X宽Y高Z”的格式明确标注。对于圆柱体,通常两个数字是直径和高度,或者半径和高度。如果数字数量对不上,就按默认值填充,比如长方体缺一个维度就默认和第一个维度相同。

这里有个坑要注意:中文里“80乘60乘40”和“80x60x40”和“806040”是一个意思,但正则匹配的时候要分别处理。我的做法是先把所有乘号统一替换成x,然后再用x分割提取数字。另外“80毫米长”和“长80毫米”语序不同但意思一样,所以不能依赖位置,得靠单位词和维度词的位置关系来判断。

2.3 几何建模的关键参数与操作

用cadquery建一个带孔的长方体底座,代码大概长这样:

import cadquery as cq # 定义参数 length = 80.0 # 长 width = 60.0 # 宽 height = 40.0 # 高 hole_dia = 6.0 # 孔径 hole_positions = [(20, 15), (60, 15), (20, 45), (60, 45)] # 孔位坐标 # 创建底座 base = cq.Workplane("XY").box(length, width, height) # 在顶面打孔 base = base.faces(">Z").workplane().pushPoints(hole_positions).hole(hole_dia) # 导出STEP cq.exporters.export(base, "base.step")

这段代码的逻辑很清晰:先在XY平面上创建一个长方体,然后选中Z方向最高的那个面(也就是顶面),在这个面上创建一个工作平面,把孔位坐标推上去,然后调用hole方法打孔。hole方法默认是贯穿孔,如果要打盲孔,需要指定深度参数。

孔位坐标的计算有个细节:cadquery的坐标系原点在长方体的几何中心,所以孔位坐标是相对于中心的偏移量。如果你习惯从角落开始算坐标,需要先做一个平移变换。我一般会在代码里加一个辅助函数,把“从角落算”的坐标转换成“从中心算”的坐标,这样更符合机械设计的习惯。

倒角和圆角也是常用操作。edges("|Z").fillet(2.0)表示对所有平行于Z轴的边做半径2毫米的圆角。edges(">Z").chamfer(1.0)表示对顶面的边做1毫米的倒角。这些操作要放在打孔之前做,否则孔边缘的边也会被选中,结果就不是你想要的了。

2.4 URDF导出的格式要求与注意事项

URDF文件的结构是XML格式,根节点是<robot>,里面包含若干<link>和<joint>。每个link可以包含视觉几何(visual)和碰撞几何(collision),两者可以引用同一个网格文件,也可以分开。joint定义两个link之间的连接关系,包括类型(revolute、prismatic、fixed等)、父子link名称、旋转轴或平移轴、运动范围限制。

导出URDF的时候有几个容易出错的地方。第一是单位问题,前面说过URDF用米,所以所有尺寸要除以1000。第二是网格文件的路径,URDF里引用网格用的是相对路径或者package://格式的路径,如果路径写错了导入CoppeliaSim的时候会找不到模型。我一般把网格文件和URDF放在同一个目录下,然后用相对路径引用,这样最不容易出错。

第三是惯性矩阵的计算。URDF里每个link可以定义惯性属性,包括质量、质心位置和惯性张量。如果只是做运动学仿真,惯性属性可以省略或者随便填。但如果做动力学仿真,惯性矩阵必须准确,否则仿真结果没有意义。cadquery本身不提供惯性计算功能,需要自己根据几何形状和密度手动计算,或者用trimesh这类库从网格文件反推。

提示:导入CoppeliaSim的时候,如果URDF里的网格文件是STL格式,CoppeliaSim会自动做凸包分解用于碰撞检测。如果模型有凹面结构,碰撞检测会不准确。解决办法是单独指定碰撞几何,用简单的凸体代替复杂的视觉几何。

3. 实操过程与核心环节实现

3.1 从零搭建项目目录结构

我习惯的项目目录结构是这样的:

text-to-cad/ ├── src/ │ ├── parser/ │ │ ├── __init__.py │ │ ├── tokenizer.py # 分词和同义词映射 │ │ └── extractor.py # 参数提取和分配 │ ├── modeler/ │ │ ├── __init__.py │ │ ├── primitives.py # 基本几何体生成 │ │ └── features.py # 孔、倒角、圆角等特征 │ ├── exporter/ │ │ ├── __init__.py │ │ ├── step_exporter.py # STEP导出 │ │ └── urdf_exporter.py # URDF导出 │ └── main.py # 入口脚本 ├── tests/ │ ├── test_parser.py │ └── test_modeler.py ├── output/ # 生成的模型文件 ├── requirements.txt └── README.md

这个结构的好处是职责分离清晰。parser只负责文本到参数的转换,modeler只负责参数到几何的转换,exporter只负责几何到文件的转换。每个模块可以单独测试,改一个模块不会影响其他模块。

requirements.txt里至少要包含这些:

cadquery>=2.2 numpy>=1.21 lxml>=4.6 regex>=2021.0

如果要做单元测试,再加个pytest。如果要做命令行工具,加个click或者argparse。

3.2 解析器的完整实现流程

解析器的入口函数接收一个字符串,返回一个字典。字典的结构是这样的:

{ "shape_type": "box", "dimensions": {"length": 80.0, "width": 60.0, "height": 40.0}, "features": [ {"type": "hole", "diameter": 6.0, "positions": [(20,15), (60,15), (20,45), (60,45)], "through": True} ], "fillets": [{"edge_selector": "|Z", "radius": 2.0}], "chamfers": [] }

第一步是分词。中文分词用jieba或者regex的\p{Han}+匹配连续汉字,英文和数字用\w+匹配。分词之后得到一个token列表,比如“一个80x60x40的长方体底座上面开四个M6的沉头孔”会分成["一个", "80", "x", "60", "x", "40", "的", "长方体", "底座", "上面", "开", "四个", "M6", "的", "沉头孔"]。

第二步是类型识别。遍历token列表,查同义词表,找到第一个匹配的几何体类型。如果同时匹配到多个类型,比如“长方体底座”同时匹配到“长方体”和“底座”,优先取更具体的那个。同义词表用字典实现,key是各种说法,value是标准类型名。

第三步是参数提取。找到类型关键词的位置之后,看它前面和后面各有哪些数字token。对于长方体,取最近的三个数字作为长宽高。如果数字不够三个,缺的用默认值补。对于圆柱体,取最近的两个数字作为直径和高度。这里要注意“M6”这种带字母的数字,它表示螺纹规格,不是尺寸参数,提取的时候要排除掉。

第四步是特征识别。查找“孔”“洞”“开口”等关键词,然后看它前面有没有“沉头”“通孔”“盲孔”等修饰词,后面有没有数字表示孔径或数量。孔位坐标如果文本里没给,就按均匀分布自动计算。比如“四个孔”就默认在四个角各打一个,边距取板厚的1.5倍或者孔径的2倍,取较大的那个。

3.3 建模器的参数化实现

建模器的核心是一个build_model函数,接收解析器输出的参数字典,返回一个cadquery的Workplane对象。这个函数的实现逻辑是:

def build_model(params): shape_type = params["shape_type"] dims = params["dimensions"] if shape_type == "box": model = cq.Workplane("XY").box(dims["length"], dims["width"], dims["height"]) elif shape_type == "cylinder": model = cq.Workplane("XY").circle(dims["diameter"]/2).extrude(dims["height"]) else: raise ValueError(f"不支持的几何体类型: {shape_type}") # 应用特征 for feature in params.get("features", []): if feature["type"] == "hole": model = apply_holes(model, feature) # 应用倒角和圆角 for fillet in params.get("fillets", []): model = model.edges(fillet["edge_selector"]).fillet(fillet["radius"]) return model

apply_holes函数的实现要注意几个细节。第一是工作平面的选择,打孔要在正确的面上操作。对于长方体,如果孔在顶面,就选faces(">Z");如果在侧面,就选faces(">X")或者faces(">Y")。第二是孔位坐标的转换,如果用户给的是从角落算的坐标,要先减去半长半宽转换到中心坐标系。第三是孔的深度,贯穿孔用hole()方法,盲孔用hole(depth)方法。

倒角和圆角的顺序也有讲究。一般先做大的圆角再做小的倒角,因为倒角会改变边的拓扑结构,如果先倒角再选边做圆角,可能选不到想要的边。我的经验是:先做所有圆角,再做所有倒角,最后打孔。打孔放最后是因为孔会切断边,如果先打孔再选边做圆角,孔边缘的边也会被选中,结果就是孔口也被圆角了,这通常不是你想要的效果。

3.4 导出STEP和URDF的完整代码

STEP导出很简单,一行代码:

cq.exporters.export(model, "output/part.step")

如果要控制精度,可以加参数:

cq.exporters.export(model, "output/part.step", tolerance=0.001, angularTolerance=0.1)

tolerance是线性公差,单位是毫米,0.001表示1微米。angularTolerance是角度公差,单位是弧度,0.1表示大约5.7度。这两个值越小,导出的模型越精细,但文件也越大。一般机械零件用0.01和0.5就够了。

URDF导出要自己拼XML。先定义link和joint的结构:

from lxml import etree def export_urdf(model, name, mass=1.0): robot = etree.Element("robot", name=name) # 导出网格文件 mesh_path = f"{name}.stl" cq.exporters.export(model, f"output/{mesh_path}") # 创建link link = etree.SubElement(robot, "link", name=f"{name}_link") visual = etree.SubElement(link, "visual") geometry = etree.SubElement(visual, "geometry") mesh = etree.SubElement(geometry, "mesh", filename=mesh_path) # 创建joint(如果是单个零件,用fixed joint连接到base) joint = etree.SubElement(robot, "joint", name=f"{name}_joint", type="fixed") parent = etree.SubElement(joint, "parent", link="base_link") child = etree.SubElement(joint, "child", link=f"{name}_link") # 写入文件 tree = etree.ElementTree(robot) tree.write(f"output/{name}.urdf", pretty_print=True, xml_declaration=True)

这段代码生成的是一个最简单的URDF,只有一个link和一个fixed joint。如果要生成多连杆的机器人模型,需要循环创建多个link和joint,并且正确设置joint的origin和axis。

注意:CoppeliaSim导入URDF的时候,如果URDF里引用的网格文件路径是相对路径,CoppeliaSim会相对于URDF文件所在目录去找网格文件。所以网格文件和URDF必须放在同一个目录下,或者用绝对路径。我一般用相对路径,然后把所有文件打包到一个文件夹里,这样迁移的时候不会丢文件。

3.5 批量生成的性能优化

如果要批量生成几百个模型,性能是个问题。cadquery的建模操作是CPU密集型的,单个简单零件大概需要0.5到2秒。如果串行生成100个零件,就是一两分钟。优化思路有两个:一是用多进程并行,二是复用已经创建的对象。

多进程并行用multiprocessing.Pool就行,把参数列表分成几份,每个进程处理一份。注意cadquery的对象不能跨进程传递,所以每个进程要独立导入cadquery,独立创建模型,独立导出文件。进程数不要超过CPU核心数,一般设成核心数减一,留一个核心给系统调度。

复用对象这个思路更巧妙一些。如果一批零件的结构完全相同,只是尺寸不同,可以先创建一个模板模型,然后通过缩放或者参数修改来生成变体。但cadquery不支持直接修改已有对象的参数,所以这个思路实现起来比较麻烦。更实际的做法是把公共的建模步骤封装成函数,减少重复代码,但性能提升有限。

我实测下来,用8核的机器并行生成100个简单零件,总耗时大概15秒,平均每个0.15秒,比串行快了将近10倍。如果零件更复杂,比如带很多孔和倒角,并行加速比会更高,因为每个零件的计算量更大了。

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

4.1 安装和导入阶段的典型报错

问题一:ImportError: DLL load failed while importing OCP

这是Windows上最常见的问题,原因是OpenCASCADE的DLL文件没有被正确加载。解决办法有三个:一是用conda安装而不是pip,conda会自动处理DLL依赖;二是把OpenCASCADE的bin目录加到系统PATH里;三是用os.add_dll_directory()在Python代码里手动指定DLL目录。

问题二:ModuleNotFoundError: No module named 'cadquery'

这个通常是虚拟环境没激活,或者装到了错误的Python版本下。检查方法是pip list | grep cadquery看有没有装,which python看当前用的是哪个Python。如果确认装了但还是找不到,可能是PYTHONPATH的问题,试试python -c "import sys; print(sys.path)"看路径对不对。

问题三:ValueError: Cannot convert to a shape

这个错误通常出现在建模阶段,原因是传给cadquery的参数类型不对。比如把字符串传给了需要浮点数的参数,或者把None传给了必填参数。排查方法是打印出参数字典,逐个检查类型和值。我一般会在建模函数入口加一个参数校验,确保所有尺寸都是正数,所有坐标都是数值类型。

4.2 建模阶段的常见几何错误

问题一:打孔打到了错误的位置

这个问题的根源通常是坐标系理解错了。cadquery的box方法创建的长方体,原点在几何中心,不是角落。如果你按角落坐标给孔位,孔就会偏。解决办法是在打孔之前先做一个平移,把工作平面的原点移到角落。代码是model = model.translate((length/2, width/2, 0)),这样后续的坐标就都是从角落算了。

问题二:倒角之后模型变成空壳

这个通常是因为倒角半径太大了,超过了相邻边的长度。比如一个10毫米厚的板,你倒一个6毫米的角,两边加起来12毫米超过了板厚,几何内核就处理不了了。解决办法是减小倒角半径,或者先检查一下最小边长度,确保倒角半径不超过最小边的一半。

问题三:导出的STEP文件打开是空的

这个一般是导出路径的问题。cadquery的export函数如果路径不存在,不会自动创建目录,而是静默失败。解决办法是在导出之前用os.makedirs(os.path.dirname(path), exist_ok=True)确保目录存在。另外路径里不要有中文和空格,有些CAD软件对中文路径支持不好。

4.3 URDF导入CoppeliaSim的排查清单

URDF导入CoppeliaSim失败的原因很多,我整理了一个排查清单,按顺序检查:

检查项常见问题解决办法
文件路径网格文件路径错误确保URDF和网格文件在同一目录,用相对路径引用
单位尺寸单位不是米所有尺寸除以1000,URDF标准单位是米
XML格式标签未闭合或属性拼写错误用xmllint验证XML格式
惯性矩阵惯性张量为零或负定检查质量和惯性矩阵,确保正定
joint类型不支持的joint类型只使用revolute、prismatic、fixed、continuous
网格格式STL文件有非流形边用MeshLab修复网格,或者换DAE格式

提示:CoppeliaSim导入URDF之后,如果模型显示为白色没有纹理,这是正常的,因为URDF里没有定义材质。可以在CoppeliaSim里手动给模型赋材质,或者用DAE格式的网格文件,DAE可以包含材质信息。

4.4 批量生成时的内存管理技巧

批量生成模型的时候,内存泄漏是个隐蔽的问题。cadquery的Workplane对象在每次操作后都会创建新对象,旧对象如果没被垃圾回收就会一直占内存。生成几百个模型之后,内存可能涨到几个G。

解决办法是在每次循环结束后手动触发垃圾回收:

import gc for params in param_list: model = build_model(params) export_step(model, f"output/part_{i}.step") del model gc.collect()

del model删除引用,gc.collect()强制回收。实测下来,加了这两行之后,内存占用稳定在200M左右,不加的话会一直涨。

另一个技巧是不要在循环里累积结果。比如不要把所有模型对象存到一个列表里最后统一导出,而是一个一个生成、导出、释放。这样内存峰值最低。

4.5 自然语言解析的边界情况处理

解析器最怕的是用户输入模糊或者有歧义。比如“一个80x60x40的板,上面打几个孔”,这里“几个”没有具体数字,解析器就不知道打几个。我的处理策略是:如果数量词缺失,默认打4个孔,按矩形均匀分布。如果孔径缺失,默认取板厚的四分之一,但不小于3毫米。

还有一种情况是用户输入了矛盾的信息,比如“一个直径50高度100的圆柱,长80宽60”。这时候解析器会同时匹配到圆柱和长方体两个类型。我的处理策略是取第一个匹配到的类型,后面的忽略,同时在日志里输出一个警告,提示用户输入有歧义。

单位混用也是常见问题,比如“长80毫米宽6厘米”。解析器需要分别处理每个数字的单位,不能统一按一个单位算。我的做法是在提取数字的时候同时提取它后面的单位词,然后分别转换。如果数字后面没有单位词,就沿用前一个数字的单位,如果前面也没有,就用默认单位毫米。

4.6 模型精度与文件大小的平衡

STEP文件的精度由导出时的tolerance参数控制。tolerance越小,曲面越平滑,但文件越大。我做过一个测试,一个带圆角的圆柱体,tolerance从0.1降到0.001,文件大小从50KB涨到了2MB,涨了40倍,但视觉上看不出明显区别。

我的经验值是:做视觉展示用0.05,做3D打印用0.01,做精密装配用0.001。如果只是导入仿真软件做运动学验证,0.1都够用,因为仿真软件显示的时候本身就会做网格简化。

STL文件的精度由网格密度控制,cadquery导出STL的时候可以指定tolerance和angularTolerance。STL文件通常比STEP大,因为它是三角网格,曲面需要很多三角形来逼近。一个STEP文件50KB的零件,导出成STL可能变成5MB。所以如果不需要网格格式,尽量用STEP。

URDF里引用的网格文件建议用STL而不是DAE,因为STL更通用,所有仿真软件都支持。DAE虽然能带材质,但不同软件对DAE的支持程度不一样,有时候会出问题。

4.7 实际项目中的经验教训

做了几个实际项目之后,我总结了几个教训。第一个是不要过度依赖自然语言解析,对于复杂的零件,最好的方式还是提供一个结构化的参数接口,让用户填表格或者写JSON,自然语言只作为辅助输入方式。自然语言解析的准确率很难做到100%,对于关键尺寸,让用户确认一遍比事后返工划算得多。

第二个是版本控制很重要。生成的STEP文件是二进制格式,git没法做diff,所以每次生成的模型最好同时保存一份参数JSON,这样版本对比的时候看JSON就行了。我一般会在输出目录里同时保存part.step和part.json,JSON里记录生成这个模型用的所有参数。

第三个是测试用例要覆盖边界情况。我写了一个测试脚本,自动生成各种极端参数的模型,比如长宽高都是0.1毫米的微小零件,或者长宽高都是10000毫米的巨大零件,看会不会报错。这些边界情况在实际项目中真的会遇到,提前测出来比事后调试强。

第四个是文档要写清楚支持的自然语言表达方式。用户不知道你的解析器能识别哪些词,就会乱输入。我在README里列了一个支持的说法列表,比如“长方体”“方块”“矩形体”都支持,“圆柱”“圆棒”“轴”都支持。用户看了就知道该怎么描述,解析成功率也高了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进