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

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

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

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的开放api函数做重载

FreeCAD对opencascasde的开放程度有限,有时候我们需要occ函数并没有在FreeCAD中开放,于是我们可以采取修改FreeCAD源代码的方式来增强FreeCAD的某个python函数的功能。看如下示例:

PyObject* GeometryCurvePy::toShape(PyObject *args)
{
    Handle(Geom_Geometry) g = getGeometryPtr()->handle();
    Handle(Geom_Curve) c = Handle(Geom_Curve)::DownCast(g);
    try {
        if (!c.IsNull()) {
            double u,v;
            u=c->FirstParameter();
            v=c->LastParameter();
            PyObject* pcVertex0;
            PyObject* pcVertex1;
            if (!PyArg_ParseTuple(args, "|dd", &u,&v)){
                return 0;
              BRepBuilderAPI_MakeEdge mkBuilder(c, u, v);
              TopoDS_Shape sh = mkBuilder.Shape();
              return new TopoShapeEdgePy(new TopoShape(sh));
            }         
        }              
    }
    catch (Standard_Failure& e) {                                        
        PyErr_SetString(PartExceptionOCCError, e.GetMessageString());
        return 0;             
    }

    PyErr_SetString(PartExceptionOCCError, "Geometry is not a curve");
    return 0;                                               
}

这个函数是原FreeCAD中GeometryCurvePy.cpp的toShape函数。我们的目标是让其可以接收四个参数的传入:两个vertex和两个浮点数的函数。修改结果如下:

PyObject* GeometryCurvePy::toShape(PyObject *args)
{
    Handle(Geom_Geometry) g = getGeometryPtr()->handle();
    Handle(Geom_Curve) c = Handle(Geom_Curve)::DownCast(g);
    try {
        if (!c.IsNull()) {
            double u,v;
            u=c->FirstParameter();
            v=c->LastParameter();
            PyObject* pcVertex0;
            PyObject* pcVertex1;
            if (PyArg_ParseTuple(args, "|dd", &u,&v)){
                //return 0;
              BRepBuilderAPI_MakeEdge mkBuilder(c, u, v);
              TopoDS_Shape sh = mkBuilder.Shape();
              return new TopoShapeEdgePy(new TopoShape(sh));
            }
                       
            PyErr_Clear();
            if(PyArg_ParseTuple(args, "O!O!dd", &(Part::TopoShapeVertexPy::Type), &pcVertex0,
              &(Part::TopoShapeVertexPy::Type), &pcVertex1, &u, &v)){                                   
              TopoShape* shape1 = static_cast<TopoShapePy*>(pcVertex0)->getTopoShapePtr();
              TopoShape* shape2 = static_cast<TopoShapePy*>(pcVertex1)->getTopoShapePtr();
              const TopoDS_Vertex& v1 = TopoDS::Vertex(shape1->getShape());  
              const TopoDS_Vertex& v2 = TopoDS::Vertex(shape2->getShape());                 


              BRepBuilderAPI_MakeEdge mkBuilder(c, v1, v2, u, v);                         
              TopoDS_Shape sh = mkBuilder.Shape();
              return new TopoShapeEdgePy(new TopoShape(sh));
            }else
              return 0; 
        }              
    }
    catch (Standard_Failure& e) {                                        
        PyErr_SetString(PartExceptionOCCError, e.GetMessageString());
        return 0;             
    }

    PyErr_SetString(PartExceptionOCCError, "Geometry is not a curve");
    return 0;                                               
}

注意,修改后的代码中的PyErr_Clear()函数。这个函数的调用非常重要。否则,上一步的参数校验错误会遗留下来,导致后面的PyArg_ParseTuple函数不可能成功返回true。

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

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

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

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

FreeCAD新增visualize函数

我们对FreeCAD代码进行的修改,为TopoShape对象新添加了一个函数: visualize.此函数能够直接对TopoShape对象进行可视化解析,它会返回面,边数据。FreeCAD中任何TopoShape对象都可以调用此函数。

函数原型:

TopoShape.visualize(tolerance)

此函数返回一个向量:

(vets, nors, indices, vetsGroup, indicesGroup, edgeVets, edgeGroup)

队列中元素说明如下:

  • vets: 此TopoShape对象中面的顶点队列
  • nors: 此TopoShape对象中面的法线队列
  • indices: 此TopoShape对象中面顶点的指引队列
  • vetsGroup: 此TopoShape对象中顶点的分组情况
  • indicesGroup: 此TopoShape对象中顶点指引的分组情况队列
  • edgeVets: 此TopoShape对象中边的顶点队列
  • edgeGroup: 此TopoShape对象中边的顶点分组情况队列

返回值示例:

  • vets: [Vector (0.0, 0.0, 0.0), Vector (0.0, 0.0, 10.0), Vector (0.0, 10.0, 0.0), Vector (0.0, 10.0, 10.0), Vector (10.0, 0.0, 0.0), Vector (10.0, 0.0, 10.0), Vector (10.0, 10.0, 0.0), Vector (10.0, 10.0, 10.0), Vector (0.0, 0.0, 0.0), Vector (10.0, 0.0, 0.0), Vector (0.0, 0.0, 10.0), Vector (10.0, 0.0, 10.0), Vector (0.0, 10.0, 0.0), Vector (10.0, 10.0, 0.0), Vector (0.0, 10.0, 10.0), Vector (10.0, 10.0, 10.0), Vector (0.0, 0.0, 0.0), Vector (0.0, 10.0, 0.0), Vector (10.0, 0.0, 0.0), Vector (10.0, 10.0, 0.0), Vector (0.0, 0.0, 10.0), Vector (0.0, 10.0, 10.0), Vector (10.0, 0.0, 10.0), Vector (10.0, 10.0, 10.0)]
  • nors: [Vector (-1.0, -0.0, 0.0), Vector (-1.0, -0.0, 0.0), Vector (-1.0, -0.0, 0.0), Vector (-1.0, -0.0, 0.0), Vector (1.0, 0.0, -0.0), Vector (1.0, 0.0, -0.0), Vector (1.0, 0.0, -0.0), Vector (1.0, 0.0, -0.0), Vector (-0.0, -1.0, -0.0), Vector (-0.0, -1.0, -0.0), Vector (-0.0, -1.0, -0.0), Vector (-0.0, -1.0, -0.0), Vector (0.0, 1.0, 0.0), Vector (0.0, 1.0, 0.0), Vector (0.0, 1.0, 0.0), Vector (0.0, 1.0, 0.0), Vector (-0.0, -0.0, -1.0), Vector (-0.0, -0.0, -1.0), Vector (-0.0, -0.0, -1.0), Vector (-0.0, -0.0, -1.0), Vector (0.0, 0.0, 1.0), Vector (0.0, 0.0, 1.0), Vector (0.0, 0.0, 1.0), Vector (0.0, 0.0, 1.0)]
  • indices: [(0, 1, 2), (2, 1, 3), (5, 4, 6), (5, 6, 7), (9, 11, 8), (8, 11, 10), (15, 13, 12), (15, 12, 14), (17, 19, 16), (16, 19, 18), (23, 21, 20), (23, 20, 22)]
  • vetsGroup: (4, 4, 4, 4, 4, 4) //这里数据说明此对象的顶点队列中第1个面是最开始的4个顶点,第2个面是接下来的4个顶点,依次类推
  • indicesGroup: (6, 6, 6, 6, 6, 6) //这里同vetsGroup,只不过它指的是indices队列
  • edgeVets: [Vector (0.0, 0.0, 0.0), Vector (0.0, 0.0, 10.0), Vector (0.0, 0.0, 0.0), Vector (0.0, 10.0, 0.0), Vector (0.0, 10.0, 0.0), Vector (0.0, 10.0, 10.0), Vector (0.0, 0.0, 10.0), Vector (0.0, 10.0, 10.0), Vector (10.0, 0.0, 0.0), Vector (10.0, 0.0, 10.0), Vector (10.0, 0.0, 0.0), Vector (10.0, 10.0, 0.0), Vector (10.0, 10.0, 0.0), Vector (10.0, 10.0, 10.0), Vector (10.0, 0.0, 10.0), Vector (10.0, 10.0, 10.0), Vector (0.0, 0.0, 0.0), Vector (10.0, 0.0, 0.0), Vector (0.0, 0.0, 10.0), Vector (10.0, 0.0, 10.0), Vector (0.0, 10.0, 0.0), Vector (10.0, 10.0, 0.0), Vector (0.0, 10.0, 10.0), Vector (10.0, 10.0, 10.0)]
  • edgeGroup: (2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2) //这里同vetsGroup,只不过它指的是edgeVets队列