在 ParaView/VTK 二次开发中,实现对 MultiBlockDataSet 中各个 Block 的选择和高亮是一个常见需求。本文将详细介绍如何实现点选、框选、多选等功能,并提供完整的代码实现。
功能概述
我们要实现的功能包括:
- 点选:鼠标点击选中单个 Block
- 框选:拖拽矩形区域选中多个 Block
- Ctrl + 点击:多选/取消选择
- 高亮显示:选中的 Block 以特殊效果显示
- 同源检查:只能选择同一数据源下的 Block
核心技术点
1. 使用 vtkSMRenderViewProxy 进行硬件加速选择
ParaView 提供了 vtkSMRenderViewProxy 类来实现硬件加速的选择功能。相比逐点采样的软件选择,性能提升显著。
1 2 3 4 5 6 7 8 9 10 11 12 13
| vtkSMRenderViewProxy *renderViewProxy = m_renderView->getRenderViewProxy();
int rect[4] = {x1, y1, x2, y2};
vtkSmartPointer<vtkCollection> selectedRepresentations = vtkSmartPointer<vtkCollection>::New(); vtkSmartPointer<vtkCollection> selectionSources = vtkSmartPointer<vtkCollection>::New();
renderViewProxy->SelectSurfaceCells(rect, selectedRepresentations, selectionSources, false);
|
2. 将 Cell Selection 转换为 Block Selection
SelectSurfaceCells 返回的是 Cell 级别的选择,需要使用 vtkSMSelectionHelper::ConvertSelection 转换为 Block 选择:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <vtkSMSelectionHelper.h> #include <vtkSelectionNode.h>
pqOutputPort *opPort = pqRepr->getOutputPortFromInput(); vtkSMSourceProxy *selectedSource = vtkSMSourceProxy::SafeDownCast(opPort->getSource()->getProxy());
vtkSMSourceProxy *blockSelectionSource = vtkSMSourceProxy::SafeDownCast( vtkSMSelectionHelper::ConvertSelection( vtkSelectionNode::BLOCKS, selectionSource, selectedSource, opPort->getPortNumber()));
vtkSMPropertyHelper blocksHelper(blockSelectionSource->GetProperty("Blocks")); for (unsigned int j = 0; j < blocksHelper.GetNumberOfElements(); ++j) { int blockId = blocksHelper.GetAsInt(j); }
blockSelectionSource->Delete();
|
3. 点选实现
使用 vtkSMRenderViewProxy::PickBlock 实现单点选择:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| int performPick(int x, int y, pqPipelineSource **pickedSource) { vtkSMRenderViewProxy *renderViewProxy = m_renderView->getRenderViewProxy(); vtkSMProxy *reprProxy = nullptr; vtkIdType flatIndex = renderViewProxy->PickBlock(x, y, &reprProxy); if (flatIndex < 0 || !reprProxy) return -1; pqServerManagerModel *smmodel = pqApplicationCore::instance()->getServerManagerModel(); pqDataRepresentation *pqRepr = smmodel->findItem<pqDataRepresentation*>(reprProxy); if (pqRepr && pickedSource) { *pickedSource = pqRepr->getInput(); } return static_cast<int>(flatIndex); }
|
4. 同源检查
在多选或框选时,只允许选择同一数据源下的 Block:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| pqPipelineSource *currentSource = getCurrentSource();
if (!m_ctrlPressed) { if (pickedSource && pickedSource != currentSource) { emit otherSourcePicked(pickedSource); return; } }
if (m_ctrlPressed) { if (pickedSource && pickedSource != currentSource) { return; } }
for (int i = 0; i < selectedRepresentations->GetNumberOfItems(); ++i) { pqPipelineSource *pickedSource = pqRepr->getInput(); if (pickedSource != currentSource) continue; }
|
高亮效果实现
方案一:特征边高亮(Feature Edges)
使用 vtkFeatureEdges 提取选中 Block 的特征边进行高亮显示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| vtkSmartPointer<vtkDataSetSurfaceFilter> surfaceFilter = vtkSmartPointer<vtkDataSetSurfaceFilter>::New(); vtkSmartPointer<vtkFeatureEdges> featureEdges = vtkSmartPointer<vtkFeatureEdges>::New();
featureEdges->BoundaryEdgesOn(); featureEdges->FeatureEdgesOn(); featureEdges->ManifoldEdgesOff(); featureEdges->NonManifoldEdgesOff(); featureEdges->SetFeatureAngle(30.0);
surfaceFilter->SetInputData(dataSet); surfaceFilter->Update();
featureEdges->SetInputConnection(surfaceFilter->GetOutputPort()); featureEdges->Update();
|
方案二:表面覆盖高亮(Surface Overlay)
创建一个覆盖在选中 Block 表面的半透明/不透明 Actor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| m_overlayRenderer = vtkSmartPointer<vtkRenderer>::New(); m_overlayRenderer->SetLayer(1); m_overlayRenderer->SetPreserveDepthBuffer(true); m_overlayRenderer->SetPreserveColorBuffer(false);
renderWindow->SetNumberOfLayers(2); m_overlayRenderer->SetActiveCamera(mainRenderer->GetActiveCamera()); renderWindow->AddRenderer(m_overlayRenderer);
vtkProperty *prop = m_highlightActor->GetProperty(); prop->SetRepresentationToSurface(); prop->SetColor(1.0, 1.0, 0.0); prop->SetOpacity(1.0); prop->SetLighting(true); prop->SetAmbient(0.6); prop->SetDiffuse(0.4);
|
框选 UI 实现
自定义 RubberBand
由于 QRubberBand::setStyleSheet 通常不起作用,需要自定义绘制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class SelectionRubberBand : public QRubberBand { public: SelectionRubberBand(QWidget *parent = nullptr) : QRubberBand(QRubberBand::Rectangle, parent) {} void setColor(const QColor &color) { m_color = color; }
protected: void paintEvent(QPaintEvent *) override { QPainter painter(this); QColor fillColor = m_color; fillColor.setAlpha(50); painter.fillRect(rect(), fillColor); QPen pen(m_color); pen.setWidth(2); painter.setPen(pen); painter.drawRect(rect().adjusted(0, 0, -1, -1)); }
private: QColor m_color = QColor(0, 120, 215); };
|
与 MultiBlockInspector 同步
选中 Block 后,需要同步更新 TreeWidget 的选择状态,并自动展开父节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void syncInspectorSelection() { m_inspector->clearSelection(); for (QTreeWidgetItem *item : m_selectedItems) { if (item) { QTreeWidgetItem *parent = item->parent(); while (parent) { parent->setExpanded(true); parent = parent->parent(); } item->setSelected(true); } } if (!m_selectedItems.isEmpty() && m_selectedItems.first()) { m_inspector->scrollToItem(m_selectedItems.first(), QAbstractItemView::EnsureVisible); } }
|
关键头文件
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <pqApplicationCore.h> #include <pqDataRepresentation.h> #include <pqOutputPort.h> #include <pqPipelineSource.h> #include <pqRenderView.h> #include <pqServerManagerModel.h> #include <vtkCollection.h> #include <vtkSelectionNode.h> #include <vtkSMPropertyHelper.h> #include <vtkSMRenderViewProxy.h> #include <vtkSMRepresentationProxy.h> #include <vtkSMSelectionHelper.h> #include <vtkSMSourceProxy.h>
|
总结
实现 ParaView 中 MultiBlock 的选择功能,核心是利用 vtkSMRenderViewProxy 提供的硬件加速选择接口,配合 vtkSMSelectionHelper 进行选择类型转换。高亮效果可以根据需求选择特征边或表面覆盖方案。通过事件过滤器拦截鼠标事件,可以实现点选、框选、多选等丰富的交互功能。
参考资料