刚开始接触参数化建模的时候我总觉得倒角倒圆是个“锦上添花”的收尾操作直到自己用PythonOCC做了几轮真实零件的批量改型才意识到这个功能才是决定模型能不能用的关键门槛。PythonOCC作为开源几何内核OpenCASCADE的Python绑定可以让我用纯Python完成从二维草图轮廓到三维实体的完整建模链路而倒角倒圆在这条链路里承担着把几何尖角变成可制造、可装配形态的核心作用。本文就围绕PythonOCC的倒角倒圆功能分别讲二维轮廓和三维实体两条路径的实现思路、常用API和实战中踩过的坑适合正在用Python做参数化建模、CAD自动化工具、或者是刚接触OCC想快速上手的开发者参考。1. PythonOCC在建模生态里的位置以及为什么倒角倒圆值得单独讲1.1 PythonOCC和OpenCASCADE的关系先交代一下背景。PythonOCC不是一套独立的建模引擎而是OpenCASCADE简称OCCT的Python封装。OCCT是一个工业级的几何建模内核很多你熟悉的开源软件比如FreeCAD、KiCad的3D部分底层用的都是这套内核。换句话说你用PythonOCC画出来的模型和FreeCAD里建的模型在几何内核层面是同源的数据可以互相倒腾这也是我当初选它的原因。那为什么不直接用其它商业CAD的Python接口我的体会是PythonOCC有两点无可替代第一它是真正开源的你可以把建模能力嵌入到自己的Web服务、批处理脚本、CAE前处理流程里不依赖商业软件的授权和运行环境第二它操作的是拓扑数据结构和UG/NX、SolidWorks这一类商业软件里的“参数化特征树”思路不太一样——更底层、更自由但也意味着你需要理解边、面、顶点这些概念。1.2 倒角和倒圆不是一回事中文语境里“倒角”这个词经常被泛用很多人把倒圆也叫做倒角但在几何建模里这俩是严格区分的术语英文几何结果典型用途倒角Chamfer用平面切掉棱边形成斜切面去毛刺、装配导向、焊接坡口倒圆Fillet / Blend用圆弧面过渡形成相切连续的圆角减少应力集中、铸造圆角、外观过渡二维场景下的倒角倒圆作用对象是线框轮廓里的顶点——也就是两条线段或圆弧的交界处三维场景下的倒角倒圆作用对象是实体的棱边——也就是两个曲面或平面的交界处。二维处理影响的是“轮廓形状”三维处理影响的是“实体外观和结构强度”。理解这个区分后面选API的时候就不会犯迷糊。在工程意义上倒圆的价值尤其重要。一个机械零件如果内部尖角处理不好受载时应力集中系数可以差出好几倍疲劳寿命断崖式下降而倒角则更多是为了装配方便和加工工艺。用PythonOCC做自动化建模时倒角倒圆往往是最后一步但也是出错率最高的一步所以值得单独拎出来系统讲一遍。1.3 环境准备先跑通一个最小例子在开始写倒角倒圆之前先把环境装好。PythonOCC的安装推荐用conda方式因为OCCT本身依赖一堆原生动态库pip虽然也有包但版本匹配和动态库路径的问题在conda环境下会省心很多。conda create -n occ python3.10 -y conda activate occ conda install -c conda-forge pythonocc-core -y装完以后在Python里验证一下from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox box BRepPrimAPI_MakeBox(100, 60, 30).Shape() print(box)能打印出一个TopoDS_Shape对象就说明环境正常。这里有个小提醒pythonocc-core对Python版本有要求太新的Python版本可能还没有对应的预编译包建议用3.9或3.10的虚拟环境如果你在无显示器的服务器上跑后面需要可视化时还要额外配置Xvfb之类的虚拟显示方案不过本文讲建模本身显示部分先不展开。2. 二维倒角倒圆线框轮廓的尖角处理2.1 先用BRepBuilderAPI构造一个闭合轮廓二维倒角倒圆在PythonOCC里对应的类叫BRepFilletAPI_MakeFillet2d但用之前得先有“二维轮廓”。这个轮廓在OCC里就是一堆边组成的Wire线框。我们以一个80mm x 50mm的矩形轮廓为例四条边都在Z0平面上这样它就是一个标准的平面线框。from OCC.Core.gp import gp_Pnt from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire p1 gp_Pnt(0, 0, 0) p2 gp_Pnt(80, 0, 0) p3 gp_Pnt(80, 50, 0) p4 gp_Pnt(0, 50, 0) wire BRepBuilderAPI_MakeWire( BRepBuilderAPI_MakeEdge(p1, p2).Edge(), BRepBuilderAPI_MakeEdge(p2, p3).Edge(), BRepBuilderAPI_MakeEdge(p3, p4).Edge(), BRepBuilderAPI_MakeEdge(p4, p1).Edge(), ).Wire()注意Wire必须闭合后续做二维圆角、或者把轮廓拉伸成实体时闭合是硬性前提。如果你是从其它CAD导入的线框最好先检查一下有没有缝隙或者重叠边不然会在倒角阶段莫名其妙地失败。2.2 BRepFilletAPI_MakeFillet2d圆角和倒角的入口有了闭合线框就可以对四个角做圆角了。核心思路是遍历Wire上的所有顶点对每个顶点调用AddFillet传入半径最后取Shape()得到新的线框。from OCC.Core.TopExp import TopExp_Explorer from OCC.Core.TopAbs import TopAbs_VERTEX from OCC.Core.BRepFilletAPI import BRepFilletAPI_MakeFillet2d fillet2d BRepFilletAPI_MakeFillet2d(wire) exp TopExp_Explorer(wire, TopAbs_VERTEX) while exp.More(): v exp.Current() fillet2d.AddFillet(v, 5.0) exp.Next() rounded_wire fillet2d.Shape()这里有一个特别容易踩的坑AddFillet的顶点参数必须是原始Wire里探索出来的那个顶点对象不能自己按坐标新建一个gp_Pnt再转成顶点传进去否则OCC无法把这个顶点和原有Wire的拓扑关联起来结果就是直接抛异常或者静默不生效。我刚开始用的时候就在这卡了半天后来才意识到OCC的拓扑对象不是靠坐标相等来识别的而是靠拓扑标识。也有同学会问那二维倒角呢BRepFilletAPI_MakeFillet2d里也有一个AddChamfer方法它的参数是一条边和一个距离值。下面的代码对矩形顶边做了距离为4的二维倒角from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeEdge edge_top BRepBuilderAPI_MakeEdge(p2, p3).Edge() chamfer2d BRepFilletAPI_MakeFillet2d(wire) chamfer2d.AddChamfer(edge_top, 4.0) chamfered_wire chamfer2d.Shape()注意AddChamfer的边参数同样必须来自原始Wire的拓扑而不是新建的边。从效果上说二维倒角会在边的两个端点附近生成切角把相邻的两条边各截掉一段再连出一条新的直线段最终让轮廓在顶点处由“尖角”变成“斜边”。如果你要的是四个角都有C4倒角那就要对每条边都调用一次AddChamfer。2.3 二维圆角轮廓的实际用途拉伸成实体二维倒角倒圆做完以后最常见的用途就是把这个轮廓拉伸成三维实体。比如一个需要圆角过渡的钣金件截面二维做完圆角一拉伸所有棱边上自然就带上了圆角效果。from OCC.Core.gp import gp_Vec from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakePrism face BRepBuilderAPI_MakeFace(rounded_wire).Face() solid BRepPrimAPI_MakePrism(face, gp_Vec(0, 0, 20)).Shape()这个思路在二维截面设计、激光下料路径、有限元二维网格截面这些场景里很常用。做二维圆角时半径的选择也要提前想好因为一旦拉伸二维圆角半径就直接等于拉伸体棱边的圆角半径改一处等于改整个零件的结构强度分布。3. 三维倒角倒圆实体棱边的圆角和斜切3.1 从基础体到棱边集合TopExp_Explorer怎么用到了三维这边作用对象从顶点变成了棱边。先创建一个长方体作为演示基础from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox box BRepPrimAPI_MakeBox(100, 60, 30).Shape()要对它倒角倒圆第一步是拿到棱边。OCC里遍历子形状的标准工具是TopExp_Explorer它能按类型过滤出形状里的所有边from OCC.Core.TopExp import TopExp_Explorer from OCC.Core.TopAbs import TopAbs_EDGE exp TopExp_Explorer(box, TopAbs_EDGE) edges [] while exp.More(): edges.append(exp.Current()) exp.Next() print(len(edges)) # 长方体是12条边拿到12条边后你可以一股脑全部倒角也可以筛选出特定方向的边做局部处理。比如我只想对长方体的四条竖边倒圆就需要判断边的方向。判断方法不复杂取出边的两个端点比较坐标即可from OCC.Core.BRep import BRep_Tool from OCC.Core.TopAbs import TopAbs_VERTEX def is_vertical_edge(edge, tolerance1e-6): v_exp TopExp_Explorer(edge, TopAbs_VERTEX) v1 v_exp.Current() v_exp.Next() v2 v_exp.Current() p1 BRep_Tool.Pnt(v1) p2 BRep_Tool.Pnt(v2) return abs(p1.X() - p2.X()) tolerance and abs(p1.Y() - p2.Y()) tolerance在自动化建模里这种“按几何特征筛选边”的能力比手动选边实用得多因为当你批量处理几百个模型时不可能每个都去UI里点一遍。3.2 MakeFillet边倒圆等半径与批量处理三维边倒圆的API是BRepFilletAPI_MakeFillet。最基础的用法是Add(radius, edge)对单条边倒圆。想一次对多条边倒圆循环调用Add即可。from OCC.Core.BRepFilletAPI import BRepFilletAPI_MakeFillet fillet BRepFilletAPI_MakeFillet(box) for e in edges: fillet.Add(5.0, e) rounded_box fillet.Shape()对长方体12条边统一倒R5出来的效果就接近一个“圆角方盒”。实际设计里很少所有边都倒一样大的圆角更多是只处理受力关键边或外观边。批量处理时还需要注意Add方法是有返回值的布尔值返回True代表这条边可以成功生成圆角返回False就代表几何上无法构建。代码里建议加上判断别闷头全加完再去看结果。fillet BRepFilletAPI_MakeFillet(box) for e in edges: if not fillet.Add(5.0, e): print(edge fillet failed:, e)在OCCT里Add方法还有一些重载版本比如传入两个半径值Add(radius1, radius2, edge)可以做变半径倒圆效果是在一条边的两端使用不同半径形成渐变的圆角面。这个功能在注塑件、铸造件上非常常用因为拔模方向和受力分布往往不一致。不过变半径倒圆对拓扑质量要求更高我建议先把等半径用熟再碰变半径。3.3 MakeChamfer边倒角等距与非对称用法三维边倒角对应的是BRepFilletAPI_MakeChamfer。基本用法和倒圆几乎对称from OCC.Core.BRepFilletAPI import BRepFilletAPI_MakeChamfer chamfer BRepFilletAPI_MakeChamfer(box) for e in edges: if not chamfer.Add(3.0, e): print(edge chamfer failed:, e) chamfered_box chamfer.Shape()这段代码对每条边添加等距离3mm的倒角效果是每条棱边两侧各切掉3mm再连成一个斜面也就是机械图纸上常见的“C3”。如果要做非对称倒角Add方法还有一个重载形式Add(d1, d2, edge, face)其中face参数指定参考面d1是沿该参考面方向测量的距离d2是沿相邻另一个面方向测量的距离。这里我给个建议非对称倒角的face参数一定要传对。在OCC里指定参考面的意义是定义“第一条边偏移发生在哪个面上”传反了倒角方向就会朝另一侧偏做出来的零件和你想要的完全不是一回事。判断一个TopoDS_Face是否和某条边相邻可以用TopExp_Explorer在面上再遍历一次边和你的目标边做对比匹配上就说明这个面包含该边。3.4 实战布尔融合之后的组合件倒角倒圆实际项目里你要处理的零件很少是单个基础体更多是圆柱、方体、孔洞布尔运算后的组合体。这种情况下倒角倒圆的难度会上一个台阶。下面是一个圆台方形底座的融合例子from OCC.Core.gp import gp_Pnt, gp_Dir, gp_Ax2 from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeCylinder, BRepPrimAPI_MakeBox from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Fuse cyl BRepPrimAPI_MakeCylinder( gp_Ax2(gp_Pnt(0, 0, 30), gp_Dir(0, 0, 1)), 40, 40 ).Shape() base BRepPrimAPI_MakeBox(gp_Pnt(-50, -30, 0), 100, 60, 10).Shape() merged BRepAlgoAPI_Fuse(base, cyl).Shape()融合完成后对merged所有边执行圆角exp2 TopExp_Explorer(merged, TopAbs_EDGE) fillet_merged BRepFilletAPI_MakeFillet(merged) while exp2.More(): e exp2.Current() if was_created_by_boolean_conclusion(e): # 判断是否需要倒角的边 fillet_merged.Add(4.0, e) exp2.Next() result fillet_merged.Shape()这里有个非常重要的经验布尔运算后生成的相交线是全新拓扑你在base或cyl上取到的边对象不能直接用于merged模型的倒角必须重新对merged做一次TopExp_Explorer遍历。另外Fuse结果里经常混入一些退化边、非流形边对这它们做倒圆角往往会失败或生成畸形面所以“逐个尝试失败跳过最后统一检查结果形状”是我现在最常用的策略。4. 倒角倒圆过程中的坑、边界条件与进阶玩法4.1 半径过大的失败模式与前置校验倒角倒圆失败的头号原因就一个半径或者距离值超出了几何允许的范围。比如一条边只有4mm长你却想对这条边倒R10的圆角那圆弧根本放不下OCC就会失败Add返回False。更麻烦的是某些情况下Add返回True但最终Shape()才抛异常。所以我现在的习惯是在调用倒角API之前先做一次几何预检遍历目标边算出边的长度如果半径超过边长的一半就直接报警。from OCC.Core.BRepAdaptor import BRepAdaptor_Curve from OCC.Core.GCPnts import GCPnts_AbscissaPoint def edge_length(edge): curve BRepAdaptor_Curve(edge) return GCPnts_AbscissaPoint.Length(curve)在实际批处理脚本里我还会在外层加try...except把单条倒角失败的信息记录下来跑完统一看日志而不是让整个流程中断。这在处理几十上百个零件时会救你很多次。4.2 先布尔还是先圆角建模顺序对结果的影响这个问题我纠结过很久后来总结出一条经验如果你的圆角是“设计特征”比如某个功能面的过渡那最好在基础体上先做圆角再做布尔这样布尔结果里这个特征保持不变如果圆角是“后处理特征”比如要去除棱边毛刺、让整体外观更柔和那就应该先布尔融合再统一倒角这样交线处也能得到连续过渡。但先布尔再倒角有个隐患布尔产生的交线往往是不规则曲线对它们做圆角更容易因为曲面曲率变化过大而失败。所以更稳妥的做法是先对每个基础体单独做圆角再Fuse最后只对布尔交线单独尝试一次过渡失败就降半径再试。这个“先局部、后整体”的流程是我在自动化参数建模里用下来成功率最高的方案。4.3 边的方向与重复边迭代倒圆角时的隐藏陷阱用TopExp_Explorer遍历到的边有一个隐藏问题边是有方向的。同一段几何如果原始形状里有重复边比如布尔运算没有完全清理干净遍历时可能会拿到两条方向相反的边对它们同时倒圆角轻则算两次浪费时间重则产生自相交的曲面。判断重复边有一个土办法解析每条边的几何曲线比较起点终点坐标和曲线类型如果两条边的几何完全一致就只保留一条。虽然这个办法效率一般但胜在直观、稳定。在OCC里更正规的做法是用BRep_Tool取边上的几何曲线再用BRepAdaptor_Curve比较参数范围不过对大多数场景坐标比较已经够用了。4.4 参数化封装与STEP导出把前面这些经验固化成一个可复用的函数是参数化建模的第一步。下面这个函数可以生成一个所有棱边都带圆角的长方体from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox from OCC.Core.BRepFilletAPI import BRepFilletAPI_MakeFillet from OCC.Core.TopExp import TopExp_Explorer from OCC.Core.TopAbs import TopAbs_EDGE def rounded_box(width, depth, height, radius): base BRepPrimAPI_MakeBox(width, depth, height).Shape() fillet BRepFilletAPI_MakeFillet(base) exp TopExp_Explorer(base, TopAbs_EDGE) while exp.More(): e exp.Current() if not fillet.Add(radius, e): print(fwarning: failed on edge, radius{radius}) exp.Next() return fillet.Shape()这个函数看起来简单但已经把“半径合法性由调用方负责”“单边失败不阻断全流程”这两个关键经验包含进去了。你再往外走一步加上STEP导入导出整个参数化设计闭环就跑通了from OCC.Core.STEPControl import STEPControl_Writer, STEPControl_AsIs writer STEPControl_Writer() writer.Transfer(result_shape, STEPControl_AsIs) writer.Write(rounded_part.step)这样生成的STEP文件可以直接丢进FreeCAD、UG/NX、SolidWorks等软件做后续编辑或加工仿真整个“Python参数化建模商业CAD后处理”的流程就完整了。我个人的习惯是在做自动化批处理时把半径参数全部放到函数开头做一次合法性检查哪怕多写几行代码也好过最后生成一个坏模型再回头查半天。倒角倒圆看着简单实际是参数化建模里最容易翻车、也最有优化空间的一环。同一个R值从5改成8整个模型的应力分布、装配间隙、加工成本都会跟着变这也是为什么PythonOCC这类能脚本化驱动的建模库特别适合用来做设计变体批量计算。希望这篇文章能帮你把二维、三维两条倒角倒圆路径都走通少踩几个我踩过的坑。