FreeCAD中几何元素类型的适配误差

FreeCAD是基于opencascade的。对于一些几何元素的提取时,偶尔会产生一些误差。例如,把线段判断成更广义的BSplineCurve, 把圆锥面判断成更广义的旋转面等等。本文将对这些可能存在的偏差做一些记录。

  1. 线段识别为BSplineCurve, 此时虽然是BSplineCurve,但是该曲线所属边只有2个顶点。这种情况下,如果对线段类型要求严格,则需要对其进行纠正。

opencascade 的建模数据与算法的代码架构简述

大概10年前,一位业内知名的教授在给我们上数据结构的课时,提到过一个重要概念。他说:“大道极简,其实世界上所有的程序只有两个东西,其一,是数据结构,其二,是运算过程”。opencascade是cad几何引擎库,那么对于一个几何引擎库而言,在几何层面它也只创建了两个东西,其一是几何数据结构,其二是几何数据的构建与操作的算法过程。

基本数据结构简介

当我们打开opencascade源代码,发现在它的src目录下面有非常多的子目录。从目录名称可以看出,其命名规则是有讲究的。对于现代cad来说,其数据结构包含3个层面,其一,几何层面描述几何元素;其二,描述拓扑结构中单位元素的边界描述法;其三,描述3D几何体的拓扑结构;这3个层面对应这opencascade源代码中重要的几个子目录:

  1. Geom 目录中的代码定义opencascade会用到的所有几何元素
  2. Geom2d 目录中的代码定义了opencascade会用到的所有2维几何元素
  3. gp 目录中定义了组成Geom或Geom2d的更基本的几何元素
  4. BRep目录中的代码定义了opencascade的边界描述法的数据结构
  5. TopoDS目录中的代码定义了opencascade的3D几何体的拓扑结构

TopoDS中的Shape与TShape

TopoDS包中定义了两套Shape,一套是常规的Shape,而另外一套是TShape。一般用户是不需要直接操作TShape的,Shape其实是对TShape的一次封装,它给TShape提供了方向和位姿属性。也就是说,TShape只管几何体的外形,而Shape还要负责描述几何体在空间的位置和方向。TopoDS包中的代码为我们构建了一层抽象的拓扑几何关系。它定义了点,线,面和体的关系。

BRep对TShape的进一步具体化

前面提到的TopoDS包只描述了抽象的顶点,边和面之间的关系。却没有描述点,线,面与实际几何对象的关系。在BRep包中,就具体的建立了顶点和点,边与曲线,面与曲面之间的关系。例如,在TopoDS中只表达了顶点这个概念,但却没有给顶点定义坐标。而在BRep包中,描述了面是由什么曲面生成的,面的误差是多少,用三角面片表达的数据又是什么样的?

值得单独拿出来一提的是,BRep中对边与曲线建立了一对多的关系。opencascade中引入了曲线表达的概念。同一个边,在实际应用中需要用到的多种表达方式。例如圆柱面与平面的交线,为了很好的应用,我们需要用至少3条曲线方法来表达这条边的几何外形。其一,是在3D空间内的几何表达;其二,是在平面空间内的几何表达;其三,是在圆柱空间内的几何表达。后两者虽然是2维曲面,但却很重要。尤其是在曲面之间做切割,三角化运算等操作时,都需要用到。对与同一条边的不同几何表达,在算法应用上大多有一个基本要求,那就是不同的参数表达里,参数的取之范围和参数值对应的实际点位置应该一致。这两个条件分别对应着边的SameRange和SameParameter两个标识位。如果边的三条曲线不满足此要求,则很多算法都会无效。从某种意义上来说,如果这两个条件不满足,此边将被认为是无效边。

缝合边与退化边

在正常的封闭实体中只存在3种类型的边,一种是普通边。正如,上文所提到的,普通边都有3个几何曲线的表达,一条是在3D空间的曲线表达,一条是在曲面A空间的曲线表达,还有一条是在曲面B空间上的曲线表达。而缝合边,比较特殊,它除了3D空间的曲线表达外,其余的两条表达曲线是在同一个曲面内(例如圆柱面的圆柱上的那条封闭边)。退化边的更特殊了,它表示在2维曲面空间内虽然存在有意义的曲线,但它在3维空间里不存在有意义的曲线(例如球面的南北极顶点)。退化边在创建的时候,opencascade会刻意的移除其3D空间的表达曲线,并将Degenerated标识为置为true。

基本的构建和操作方法

构建一个3D实体,实际上就称为了构建一个有效的TopoDS_Shape。上文提到了BRep是TShape的具体化,而TopoDS是对TShape增加位姿和方向的封装。构建一个3D实体从大体上,可以分为两步:1.通过几何元素构建基本的点,边,面。2.通过组合点,边,面,构建拓扑逻辑体。第1步的操作对应着BRep/BRep_Builder.x 文件中的代码,第2步对应着TopoDS/TopoDS_Builder.x文件中的代码。理论上通过这两个文件提供的api,我们已经能够构建任何3D实体。

然而,实时上却没有这么简单。TopoDS_Builder和BRep_Builder定义的方法都太底层了。如果创建任何实体都要先研究它每个面的曲面以及边界曲线的参数方程,这会要了那群设计师的老命的。所以,为了让这个世界更加美好,opencascade定义一系列的工具和算法来帮助设计师更好的建模。

BRepBuilder与BRepLib

在opencascade中,BRepBuilder和BRepLib是上下级的关系。BRepLib定义了比较方便的构建BRep元素的方法,而BRepBuilder定义了调用BRepLib的方法。通过BRepBuilder调用BRepLib可以更好的访问和控制创建BRep元素的过程与结果。

BRepPrim与BRepPrimAPI

前面提到的BRepBuilder和BRepLib都只能比较单一的一个个创建或改变某个BRep元素。这是非常低效的建模行为。BRepPrim则定义了通过拉伸,旋转等逻辑来批量的创建BRep元素。到了这一步,设计师们终于看到了曙光。通过一些直观的逻辑来批量的创建点,线,面使的设计师快速设计3维实体成为现实。实时上,opencascade中不只BRepPrim包是用来批量建立BRep,还有一些其他的包也是做同样的事情,只不过是应用逻辑不一样而已。BRepPrimAPI与BRepPrim也是上下级的关系,BRepPrimAPI对BRepPrim进一步封装,让建模过程和结果更加可控。

使用BRepCheck来检查实体

BRepCheck是opencascade中用来测试的库,这个库能检查我们创建的3D实体是否有效。这个库在软件开发过程中是非常重要的存在。理论上,通过高级方法创建实体是需要保证一定有效的。但一方面opencascade的高级建模方法在一些特殊情况下,可能本身存在缺陷,另一方面,opencascade的用户也不全是设计师,也许有人需要用occ底层来建模。在第二种情况下,BRepCheck就是神器,它能快速帮助开发人员定位问题。

opencascade通过surface和wires构建face的注意事项

通过surface和wires构建face时,常规的一种直观方法是可能会发生问题的。此常规方法如下:

  1. 通过surface调用BRepLib_MakeFace直接构建face
  2. 通过BRepLib_MakeFace.Add往face上面添加wire

这个流程有一个潜在的风险,第一步在执行的时候,系统可能会根据surface的boundary来创建wire。这里自动创建的wire很有可能会是用户不希望看到的。

这里提出另外一种方法来构建,这种方法需要我们事先知道哪个wire是外部wire。

  1. 通过BRepLib_MakeFace(surface, outerWire)创建初始化aFace
  2. 通过for aWire in innerWires:
    BRepLib_MakeFace(aFace, aWire)来完善face

这一种方法,会避开常规直观方法的坑。

opencascade中顶点的精度对构建edge和wire的影响

顶点精度对edge构造的影响

在构建edge的各个函数中,有一个指定曲线,曲线参数和顶点值的构造方法。其函数原型如下:

BRepBuilderAPI_MakeEdge (const Handle< Geom_Curve > &L, const TopoDS_Vertex &V1, const TopoDS_Vertex &V2, const Standard_Real p1, const Standard_Real p2)

此函数在调用时,需要注意,各顶点的实际空间位置与该指定曲线实际首尾的位置需要满足如下条件:

P1.Distance(BRep_Tool::Pnt(V1)) > Max(preci,BRep_Tool::Tolerance(V1))

P2.Distance(BRep_Tool::Pnt(V2)) > Max(preci,BRep_Tool::Tolerance(V2))

其中,P1,P2分别为曲线的按p1,p2所指定起点和终点,V1,V2为传入的顶点参数。preci为BRepLib::Precision。这两个条件中任意一个不满足,都会导致创建edge失败。

顶点精度对wire构造的影响

然后是构建wire,opencascade提供构建wire函数,是通过一个个edge的传入而确定的。edge在wire中一定要首尾相连,否则就会构建失败。而判断是否首尾相连的关键是通过各个edge的vertex来确定的。判断两条边的vertex是否相关联分两个可能,第一,判断这两个边的顶点是否就是一条边。第二,判断这两个边的顶点在空间几何上是否满足精度要求以内的重合。第一条判断会被优先执行。

FreeCAD可视化生成数据的指引方案

在实际工程上,我们遇见一个问题,就是对于topo几何元素的唯一指引性的问题。假设有一个模型文件,这个模型文件可以生成一个topo几何模型。然后我有另外一个文件,这个文件是对topo几何模型中某个拓扑元素的附加数据。例如,这个文件的信息能够指示模型中的某几条边需要拉伸行程另外一个模型。那么问题就来了,我们怎么确定这几个边呢?

直观的,你的目的可能是想给模型最上方的某几条边来做拉伸。但通过几何信息来指引边是不可靠的。例如,存在空间重合边的可能性。通过opencadcade内核自带hash值也不行,hash值只能在一次应用运行时有效,且能保证唯一性。但当你下次运行同一个项目时同一条边的hash值就不一样了。

通过指引排序是目前比较可行的方案。一个模型在被打开时,程序都是按照同一算法去解析数据。因此topo几何元素对于整体而言的排序是不变的。我们只要能够确保对模型解析出来的可视化数据的顺序严格遵守模型自身对topo几何元素的排序即可。只要采用这种方案,同一个模型文件可以运行不同的解析信息程序,而这些解析出来的数据,都以整体性的topo顺序来指引几何元素,那么大家就能达到一致性。

opencascade对step文件“破边”零容忍

有的软件,例如solidwork2019导出的step文件会存在“破面”和“破边”的现象。opencascade的内核对这些异常是采取0容忍的态度,出现“破面”或者“破边”直接就把整个模型认为是错误的而排除。

为了更好的服务客户,我们公司修改opencascade的内核代码,让其能够接受破边,破面,并恰当的忽略掉那些破面,或者破边。当然,因此,一些solid也被迫降级为shell。也因此,我们公司创立了一个独立的opencascade分支。

opencascade解析step文件无法处理超过32位的id

opencascade解析step文件时对实体编号识别是有bug的。它无法识别超过32位的id编号。这些都源于它内部的Standard_Integer是32位,而它用Standard_Integer来读取step文件中的实体编号,这就导致实体编号数一旦大于或等于2147483648,就会被认为是负数。

opencascade解析step文件的编码bug

opencascade是一个很老旧的cad库,对于step的解析过程,尤其对于非ascii编码的step文件,在解析的时候会偶尔出现莫名的bug。

我经过了2天的调试,测试,发现这个些bug来源于零部件名称中的0x80编码。凡是step文件中的字符串类型的token如果出现了0x80编码就会解析出错。例如utf8编码的汉字“总”,“总”这个字的utf8编码为/xe6/x80/xbb 。含有0x80编码,因此含有这个字的utf8编码的step文件都会在opencascade中解析出错。为此我专门购买了step文件的2002标准文档。2002标准只解析到了latin-1编码,latin-1中的0x80是未定义的,可能这是问题的根源。step文件的2016标准才支持utf8,由此推测opencascade的step文件解析器只实现了2002标准。

针对此问题,为了正确的解析step文件中的非ascii码,我们提出了“字符替换”方案。在调用opencascade前,先对step文件做一次预处理,将ascii码全部替换掉。替换的数据保存起来,在需要的时候再调回来使用。此解决方案已经使用于cadparser项目中。