从几何到显示还是比较麻烦的,需要将几何对象转换成渲染对象,涉及几何建模、面的三角化、图形渲染等学科,阅读本文需了解一些基本的OCC、VTK编程
一、几何体显示基本流程
FastCAE几何内核使用的是OCC,显示渲染用的VTK,那么就存在将OCC建模后的几何对象变成VTK支持类型的数据这个过程。
FastCAE在用OCC创建完模型之后,会生成一个Geometry::GeometrySet对象,这个对象包含了最原始的OCC中TopoDS_Shape实例对象。创建完成后都会发出信号emit showSet(set);
,这个信号会触发将TopoDS_Shape变成VTK显示对象。这个段代码是在void GeometryViewProvider::showGeoSet(Geometry::GeometrySet *set, bool render)
函数中实现。
二、几何对象变成显示对象过程
几何体是由点、线、面构成的,进行显示的时候是分别提取这些数据,进行单独显示的。也就是说我们看到的一个立方体等几何对象是由三个VTK显示对象Actor拼起来的。注意一个体是没有直接的数据对象与其对应的,体只是逻辑上的概念,所以很多软件中隐藏一个体的面会发现内部是空的。这个函数代码如下:
void GeometryViewProvider::showGeoSet(Geometry::GeometrySet *set, bool render)
{
QList<vtkPolyData *> viewPolys = _viewData->transferToPoly(set); // 对几何体进行点、线、面的拆分
vtkPolyData *facePoly = viewPolys.at(0); // 面的多边形数据
vtkPolyData *edgePoly = viewPolys.at(1); // 边的数据集
vtkPolyData *pointPoly = viewPolys.at(2); // 点数据集
GeoViewObj viewObj;
if (facePoly != nullptr) // 创建显示面的Actor
{
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputData(facePoly);
actor->SetMapper(mapper);
bool vis = set->isVisible();
bool show = Setting::BusAPI::instance()->getGraphOption()->isShowGeoSurface();
actor->SetVisibility(show && vis); // 设置是否显示面
actor->SetPickable(false); // 不可鼠标拾取?哪里打开呢?
actor->GetProperty()->SetRepresentationToSurface(); // 这个函数不调用也没看到啥影响
_preWindow->AppendActor(actor, ModuleBase::D3, false); // 将面actor添加到场景中
viewObj._faceObj = QPair<vtkActor *, vtkPolyData *>(actor, facePoly);
}
// 创建显示边的Actor
if (edgePoly != nullptr)
{
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputData(edgePoly);
actor->SetMapper(mapper);
bool vis = set->isVisible();
bool show = Setting::BusAPI::instance()->getGraphOption()->isShowGeoEdge();
actor->SetVisibility(show && vis);
actor->SetPickable(false);
actor->GetProperty()->SetRepresentationToWireframe();
// actor->GetProperty()->EdgeVisibilityOn();
float width = Setting::BusAPI::instance()->getGraphOption()->getGeoCurveWidth();
actor->GetProperty()->SetLineWidth(width);
_preWindow->AppendActor(actor, ModuleBase::D3, false);
viewObj._edgeObj = QPair<vtkActor *, vtkPolyData *>(actor, edgePoly);
}
// 创建显示点的Actor
if (pointPoly != nullptr)
{
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
float size = Setting::BusAPI::instance()->getGraphOption()->getGeoPointSize();
mapper->SetInputData(pointPoly);
actor->SetMapper(mapper);
bool vis = set->isVisible();
bool show = Setting::BusAPI::instance()->getGraphOption()->isShowGeoPoint();
actor->SetVisibility(show && vis);
actor->SetPickable(false);
actor->GetProperty()->SetRepresentationToPoints();
actor->GetProperty()->SetPointSize(size);
_preWindow->AppendActor(actor, ModuleBase::D3, false);
viewObj._pointObj = QPair<vtkActor *, vtkPolyData *>(actor, pointPoly);
}
_geoViewHash.insert(set, viewObj);
if (render)
_preWindow->resetCamera();
}
三、几何面数据的提取
几何体的面、边、点转换为vtkPolyData对象的方式类似,都在函数GeometryViewData::transferToPoly()
中,这里只分析面数据的提取。
/* 提取几何表面的多边形数据集 */
vtkPolyData *GeometryViewData::transferFace(Geometry::GeometrySet *gset)
{
TopoDS_Shape *shape = gset->getShape(); // 获取OCC的Shape
TopExp_Explorer faceExp(*shape, TopAbs_FACE); // 这个类可访问Shape的拓扑关系
QList<Handle(TopoDS_TShape)> tshapelist; // 放置已访问的Face对象,防止重复访问
vtkSmartPointer<vtkAppendPolyData> appendFilter = vtkSmartPointer<vtkAppendPolyData>::New();
vtkPolyData *polyData = vtkPolyData::New();
int beg = 0;
for (int index = 0; faceExp.More(); faceExp.Next(), ++index) // 开始遍历面
{
const TopoDS_Shape &s = faceExp.Current();
Handle(TopoDS_TShape) ts = s.TShape();
if (tshapelist.contains(ts)) // 防止重复访问,会存在这种情况吗?
continue;
tshapelist.append(ts);
IVtkOCC_Shape::Handle aShapeImpl = new IVtkOCC_Shape(s); // OCC提供IVtkOCC_Shape类
vtkSmartPointer<IVtkTools_ShapeDataSource> DS = vtkSmartPointer<IVtkTools_ShapeDataSource>::New(); // OCC提供的VTK数据源
DS->SetShape(aShapeImpl);
vtkSmartPointer<vtkCleanPolyData> cleanFilter = vtkSmartPointer<vtkCleanPolyData>::New();
cleanFilter->SetInputConnection(DS->GetOutputPort());
cleanFilter->Update();
vtkSmartPointer<vtkPolyData> tpolys = vtkSmartPointer<vtkPolyData>::New();
vtkPolyData *tpolydata = cleanFilter->GetOutput();
const int np = tpolydata->GetNumberOfPoints(); // 点的数量
const int nc = tpolydata->GetNumberOfCells(); // cell的数量
vtkPoints *points = vtkPoints::New();
for (int i = 0; i < np; i++) // 提取几何点数据
{
double *coor = tpolydata->GetPoint(i);
points->InsertNextPoint(coor);
}
tpolys->SetPoints(points); // 设置几何点数据
vtkCellArray *cells = vtkCellArray::New();
for (int i = 0; i < nc; ++i)
{
vtkCell *cell = tpolydata->GetCell(i);
vtkIdList *ceid = cell->GetPointIds();
if (ceid->GetNumberOfIds() == 3) // 只提取三角形,这里获取的cell包含点、线、三角形,一个立方体最后应该有12个三角形
{
vtkTriangle *triangle = vtkTriangle::New();
triangle->DeepCopy(cell); // 有DeepCopy接口
cells->InsertNextCell(triangle);
}
}
tpolys->SetPolys(cells); // 设置拓扑多边形数据集的cell
// 法线数据?打开与否貌似不影响显示效果
vtkSmartPointer<vtkPolyDataNormals> normals = vtkSmartPointer<vtkPolyDataNormals>::New();
normals->SetInputData(tpolys);
normals->FlipNormalsOn();
normals->Update();
vtkPolyData *facePoly = normals->GetOutput();
const int ncell = facePoly->GetNumberOfCells();
if (ncell < 1)
continue;
GeometryViewObject *obj = new GeometryViewObject(GeometryViewObject::Face, beg, beg + ncell - 1, ts);
beg += ncell;
appendFilter->AddInputData(facePoly); // 追加到appendFilter中
auto setViewObj = this->getGeosetObj(gset);
setViewObj->appendFaceViewObj(index, ts, obj);
}
appendFilter->Update();
polyData->DeepCopy(appendFilter->GetOutput());
auto setViewObj = this->getGeosetObj(gset);
setViewObj->setFacePoly(polyData); // 保存面多边形数据
const int npc = polyData->GetNumberOfCells();
if (npc < 1)
return nullptr;
return polyData;
}
这里要注意几个类:
- TopExp_Explorer: OCC提供的遍历几何体拓扑结构的类;
- IVtkOCC_Shape、IVtkTools_ShapeDataSource:这俩类可以将面三角化,并变成VTK的cell,注意cell包含面上的点、边、三角形,所以提取面的时候只提取了三角形,这俩类同样是OCC提供的;
- vtkPolyDataNormals: 看类名猜测是生成法线的,但是不用这类显示也没啥问题;
- vtkAppendPolyData:可以将各个面的数据追加在一起。
最后来张图吧: