#include "nmVTKMeshLayer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { // Mapper、Actor 和输入数据都属于 VTK 状态,只允许 GUI 线程修改。 bool isMeshLayerGuiThread() { QCoreApplication* pApplication = QCoreApplication::instance(); return pApplication == nullptr || pApplication->thread() == QThread::currentThread(); } } nmVTKMeshLayer::nmVTKMeshLayer(const QString& sLayerId) : nmVTKRenderLayer(sLayerId), m_pDataSet(nullptr), m_pInputConnection(nullptr), m_pMapper(vtkSmartPointer::New()), m_pActor(vtkSmartPointer::New()), m_pLookupTable(vtkSmartPointer::New()), m_eDisplayMode(DisplayMode_Surface), m_eScalarAssociation(ScalarAssociation_CellData), m_bUsingExplicitScalarRange(false) { Q_ASSERT(isMeshLayerGuiThread()); // 图层独占 Mapper、Actor 和 LookupTable;数据集通过智能指针共享。 m_pLookupTable->SetNumberOfTableValues(256); m_pLookupTable->SetHueRange(0.67, 0.0); m_pLookupTable->Build(); m_pMapper->SetLookupTable(m_pLookupTable); m_pMapper->ScalarVisibilityOff(); m_pActor->SetMapper(m_pMapper); m_pActor->GetProperty()->SetRepresentationToSurface(); m_pActor->GetProperty()->EdgeVisibilityOff(); setRenderProp(m_pActor); } nmVTKMeshLayer::~nmVTKMeshLayer() { Q_ASSERT(isMeshLayerGuiThread()); // 派生成员释放前先解除 Renderer,基类析构中的 detach 仍保持幂等。 detach(); clearInput(); m_pDataSet = nullptr; m_pInputConnection = nullptr; } bool nmVTKMeshLayer::setDataSet(vtkDataSet* pDataSet) { Q_ASSERT(isMeshLayerGuiThread()); if(!isMeshLayerGuiThread()) { return false; } if(pDataSet == nullptr) { clearInput(); return false; } double dScalarRange[2] = { 0.0, 1.0 }; if(!validateInputDataSet(pDataSet, dScalarRange)) { return false; } // 所有可能失败的校验均在替换 Mapper 输入前完成。 m_pMapper->SetInputData(pDataSet); m_pDataSet = pDataSet; m_pInputConnection = nullptr; applyRetainedScalarState(dScalarRange); return true; } bool nmVTKMeshLayer::setInputConnection( vtkAlgorithmOutput* pInputConnection) { Q_ASSERT(isMeshLayerGuiThread()); if(!isMeshLayerGuiThread() || pInputConnection == nullptr || pInputConnection->GetProducer() == nullptr || pInputConnection->GetIndex() < 0) { return false; } vtkAlgorithm* pProducer = pInputConnection->GetProducer(); pProducer->Update(pInputConnection->GetIndex()); vtkDataSet* pCandidateDataSet = vtkDataSet::SafeDownCast( pProducer->GetOutputDataObject( pInputConnection->GetIndex())); double dScalarRange[2] = { 0.0, 1.0 }; if(!validateInputDataSet(pCandidateDataSet, dScalarRange)) { return false; } // 输出对象、保留数组和自动范围均已验证,此后才替换活动连接。 m_pMapper->SetInputConnection(pInputConnection); m_pInputConnection = pInputConnection; m_pDataSet = nullptr; applyRetainedScalarState(dScalarRange); return true; } bool nmVTKMeshLayer::setInputConnection( vtkAlgorithmOutput* pInputConnection, const QString& sArrayName, ScalarAssociation eAssociation, double dMinimum, double dMaximum) { Q_ASSERT(isMeshLayerGuiThread()); if(!isMeshLayerGuiThread() || pInputConnection == nullptr || pInputConnection->GetProducer() == nullptr || pInputConnection->GetIndex() < 0 || sArrayName.isEmpty() || !vtkMath::IsFinite(dMinimum) || !vtkMath::IsFinite(dMaximum) || dMinimum > dMaximum) { return false; } vtkAlgorithm* pProducer = pInputConnection->GetProducer(); const int nOutputPort = pInputConnection->GetIndex(); pProducer->Update(nOutputPort); vtkDataSet* pCandidateDataSet = vtkDataSet::SafeDownCast( pProducer->GetOutputDataObject(nOutputPort)); const QByteArray aArrayName = sArrayName.toUtf8(); double dDataRange[2] = { 0.0, 0.0 }; if(validateScalarArray(pCandidateDataSet, aArrayName, eAssociation, dDataRange) == nullptr) { return false; } // 到这里所有可能失败的条件均已排除。下面只执行 VTK 无返回值 setter, // 因而输入、数组和范围会作为一个不可分割的 Mapper 状态提交。 m_pMapper->SetInputConnection(pInputConnection); m_pInputConnection = pInputConnection; m_pDataSet = nullptr; applyExplicitScalarState(aArrayName, eAssociation, dMinimum, dMaximum); return true; } bool nmVTKMeshLayer::setInputConnection( vtkAlgorithmOutput* pInputConnection, const QString& sArrayName, ScalarAssociation eAssociation, vtkLookupTable* pCandidateLookupTable) { Q_ASSERT(isMeshLayerGuiThread()); if(!isMeshLayerGuiThread() || pInputConnection == nullptr || pInputConnection->GetProducer() == nullptr || pInputConnection->GetIndex() < 0 || sArrayName.isEmpty()) { return false; } vtkAlgorithm* pProducer = pInputConnection->GetProducer(); const int nOutputPort = pInputConnection->GetIndex(); pProducer->Update(nOutputPort); vtkDataSet* pCandidateDataSet = vtkDataSet::SafeDownCast( pProducer->GetOutputDataObject(nOutputPort)); const QByteArray aArrayName = sArrayName.toUtf8(); double dDataRange[2] = { 0.0, 0.0 }; double dLookupRange[2] = { 0.0, 0.0 }; if(validateScalarArray(pCandidateDataSet, aArrayName, eAssociation, dDataRange) == nullptr || !validateLookupTable(pCandidateLookupTable, dLookupRange)) { return false; } // 输入、数组和候选 LUT 已全部验证;以下 VTK setter 均无失败返回值。 m_pMapper->SetInputConnection(pInputConnection); m_pInputConnection = pInputConnection; m_pDataSet = nullptr; applyLookupTableScalarState(aArrayName, eAssociation, pCandidateLookupTable, dLookupRange); return true; } void nmVTKMeshLayer::clearInput() { Q_ASSERT(isMeshLayerGuiThread()); if(!isMeshLayerGuiThread() || m_pMapper == nullptr) { return; } m_pMapper->RemoveAllInputs(); m_pInputConnection = nullptr; m_pDataSet = nullptr; clearInvalidScalarState(); } vtkDataSet* nmVTKMeshLayer::getDataSet() const { return getInputDataSet(false); } void nmVTKMeshLayer::setDisplayMode(DisplayMode eMode) { Q_ASSERT(isMeshLayerGuiThread()); if(!isMeshLayerGuiThread() || m_pActor == nullptr) { return; } // 未识别枚举值按纯表面处理,避免把无效值带入 VTK Property。 m_eDisplayMode = eMode == DisplayMode_SurfaceWithEdges ? DisplayMode_SurfaceWithEdges : DisplayMode_Surface; vtkProperty* pProperty = m_pActor->GetProperty(); pProperty->SetRepresentationToSurface(); pProperty->SetEdgeVisibility( m_eDisplayMode == DisplayMode_SurfaceWithEdges ? 1 : 0); } nmVTKMeshLayer::DisplayMode nmVTKMeshLayer::getDisplayMode() const { return m_eDisplayMode; } void nmVTKMeshLayer::setEdgeVisibility(bool bVisible) { setDisplayMode(bVisible ? DisplayMode_SurfaceWithEdges : DisplayMode_Surface); } bool nmVTKMeshLayer::isEdgeVisible() const { return m_eDisplayMode == DisplayMode_SurfaceWithEdges; } bool nmVTKMeshLayer::setOpacity(double dOpacity) { Q_ASSERT(isMeshLayerGuiThread()); if(!isMeshLayerGuiThread() || !vtkMath::IsFinite(dOpacity) || m_pActor == nullptr) { return false; } m_pActor->GetProperty()->SetOpacity(qBound(0.0, dOpacity, 1.0)); return true; } double nmVTKMeshLayer::getOpacity() const { return m_pActor != nullptr ? m_pActor->GetProperty()->GetOpacity() : 0.0; } void nmVTKMeshLayer::setScalarVisibility(bool bVisible) { Q_ASSERT(isMeshLayerGuiThread()); if(!isMeshLayerGuiThread() || m_pMapper == nullptr) { return; } m_pMapper->SetScalarVisibility(bVisible ? 1 : 0); } bool nmVTKMeshLayer::isScalarVisible() const { return m_pMapper != nullptr && m_pMapper->GetScalarVisibility() != 0; } bool nmVTKMeshLayer::setScalarArray( const QString& sArrayName, ScalarAssociation eAssociation) { Q_ASSERT(isMeshLayerGuiThread()); vtkDataSet* pDataSet = getInputDataSet(true); if(!isMeshLayerGuiThread() || sArrayName.isEmpty() || pDataSet == nullptr || m_pMapper == nullptr) { return false; } const QByteArray aArrayName = sArrayName.toUtf8(); double dRange[2] = { 0.0, 0.0 }; if(validateScalarArray(pDataSet, aArrayName, eAssociation, dRange) == nullptr) { // 失败时保留原数组选择和原 Mapper 状态。 return false; } m_aScalarArrayName = aArrayName; m_eScalarAssociation = eAssociation; if(eAssociation == ScalarAssociation_CellData) { m_pMapper->SetScalarModeToUseCellFieldData(); } else { m_pMapper->SetScalarModeToUsePointFieldData(); } m_pMapper->SelectColorArray(m_aScalarArrayName.constData()); // 已设置显式范围时不覆盖,否则同步提交已经验证的数据范围。 if(!m_bUsingExplicitScalarRange) { m_pMapper->SetScalarRange(dRange); m_pLookupTable->SetRange(dRange); m_pLookupTable->Build(); } return true; } bool nmVTKMeshLayer::setScalarArray( const QString& sArrayName, ScalarAssociation eAssociation, vtkLookupTable* pCandidateLookupTable) { Q_ASSERT(isMeshLayerGuiThread()); vtkDataSet* pDataSet = getInputDataSet(true); if(!isMeshLayerGuiThread() || sArrayName.isEmpty() || pDataSet == nullptr || m_pMapper == nullptr) { return false; } const QByteArray aArrayName = sArrayName.toUtf8(); double dDataRange[2] = { 0.0, 0.0 }; double dLookupRange[2] = { 0.0, 0.0 }; if(validateScalarArray(pDataSet, aArrayName, eAssociation, dDataRange) == nullptr || !validateLookupTable(pCandidateLookupTable, dLookupRange)) { return false; } applyLookupTableScalarState(aArrayName, eAssociation, pCandidateLookupTable, dLookupRange); return true; } bool nmVTKMeshLayer::setScalarArray( const QString& sArrayName, ScalarAssociation eAssociation, double dMinimum, double dMaximum) { Q_ASSERT(isMeshLayerGuiThread()); vtkDataSet* pDataSet = getInputDataSet(true); if(!isMeshLayerGuiThread() || sArrayName.isEmpty() || pDataSet == nullptr || m_pMapper == nullptr || !vtkMath::IsFinite(dMinimum) || !vtkMath::IsFinite(dMaximum) || dMinimum > dMaximum) { return false; } const QByteArray aArrayName = sArrayName.toUtf8(); double dDataRange[2] = { 0.0, 0.0 }; if(validateScalarArray(pDataSet, aArrayName, eAssociation, dDataRange) == nullptr) { return false; } // 数组和范围都已验证,提交阶段不会再产生可观察的半更新状态。 applyExplicitScalarState(aArrayName, eAssociation, dMinimum, dMaximum); return true; } QString nmVTKMeshLayer::getScalarArrayName() const { return QString::fromUtf8(m_aScalarArrayName.constData()); } nmVTKMeshLayer::ScalarAssociation nmVTKMeshLayer::getScalarAssociation() const { return m_eScalarAssociation; } bool nmVTKMeshLayer::setScalarRange(double dMinimum, double dMaximum) { Q_ASSERT(isMeshLayerGuiThread()); if(!isMeshLayerGuiThread() || !vtkMath::IsFinite(dMinimum) || !vtkMath::IsFinite(dMaximum) || dMinimum > dMaximum || m_pMapper == nullptr || m_pLookupTable == nullptr) { return false; } // Mapper 与 LookupTable 使用同一范围,保证几何着色和外接色标一致。 m_pMapper->SetScalarRange(dMinimum, dMaximum); m_pLookupTable->SetRange(dMinimum, dMaximum); m_pLookupTable->Build(); m_bUsingExplicitScalarRange = true; return true; } bool nmVTKMeshLayer::restoreDataScalarRange() { Q_ASSERT(isMeshLayerGuiThread()); if(!isMeshLayerGuiThread() || !applyDataScalarRange()) { return false; } // 只有成功应用数据范围后才退出显式范围模式。 m_bUsingExplicitScalarRange = false; return true; } bool nmVTKMeshLayer::isUsingExplicitScalarRange() const { return m_bUsingExplicitScalarRange; } bool nmVTKMeshLayer::setLookupTable( vtkLookupTable* pCandidateLookupTable) { Q_ASSERT(isMeshLayerGuiThread()); double dLookupRange[2] = { 0.0, 0.0 }; if(!isMeshLayerGuiThread() || getSelectedScalarArray() == nullptr || !validateLookupTable(pCandidateLookupTable, dLookupRange)) { return false; } // 候选对象由智能指针共享,Mapper 与 MeshLayer 始终引用同一 LUT。 m_pLookupTable = pCandidateLookupTable; m_pMapper->SetLookupTable(m_pLookupTable); m_pMapper->SetScalarRange(dLookupRange); m_bUsingExplicitScalarRange = true; return true; } vtkDataSetMapper* nmVTKMeshLayer::getMapper() const { return m_pMapper; } vtkActor* nmVTKMeshLayer::getActor() const { return m_pActor; } vtkLookupTable* nmVTKMeshLayer::getLookupTable() const { return m_pLookupTable; } vtkDataSet* nmVTKMeshLayer::getInputDataSet(bool bUpdatePipeline) const { if(m_pInputConnection == nullptr) { return m_pDataSet; } vtkAlgorithm* pProducer = m_pInputConnection->GetProducer(); const int nOutputPort = m_pInputConnection->GetIndex(); if(pProducer == nullptr || nOutputPort < 0) { return nullptr; } if(bUpdatePipeline) { pProducer->Update(nOutputPort); } return vtkDataSet::SafeDownCast( pProducer->GetOutputDataObject(nOutputPort)); } bool nmVTKMeshLayer::validateInputDataSet( vtkDataSet* pDataSet, double dScalarRange[2]) const { if(pDataSet == nullptr || dScalarRange == nullptr) { return false; } if(m_aScalarArrayName.isEmpty()) { return true; } return validateScalarArray(pDataSet, m_aScalarArrayName, m_eScalarAssociation, dScalarRange) != nullptr; } vtkDataArray* nmVTKMeshLayer::validateScalarArray( vtkDataSet* pDataSet, const QByteArray& aArrayName, ScalarAssociation eAssociation, double dDataRange[2]) const { if(pDataSet == nullptr || aArrayName.isEmpty() || dDataRange == nullptr || (eAssociation != ScalarAssociation_CellData && eAssociation != ScalarAssociation_PointData)) { return nullptr; } vtkDataArray* pArray = eAssociation == ScalarAssociation_CellData ? pDataSet->GetCellData()->GetArray(aArrayName.constData()) : pDataSet->GetPointData()->GetArray(aArrayName.constData()); if(pArray == nullptr) { return nullptr; } pArray->GetRange(dDataRange); return vtkMath::IsFinite(dDataRange[0]) && vtkMath::IsFinite(dDataRange[1]) && dDataRange[0] <= dDataRange[1] ? pArray : nullptr; } bool nmVTKMeshLayer::validateLookupTable( vtkLookupTable* pLookupTable, double dRange[2]) const { if(pLookupTable == nullptr || dRange == nullptr || pLookupTable->GetNumberOfTableValues() <= 0) { return false; } const double* pRange = pLookupTable->GetTableRange(); if(pRange == nullptr || !vtkMath::IsFinite(pRange[0]) || !vtkMath::IsFinite(pRange[1]) || pRange[0] >= pRange[1] || (pLookupTable->GetScale() == VTK_SCALE_LOG10 && pRange[0] <= 0.0)) { return false; } dRange[0] = pRange[0]; dRange[1] = pRange[1]; return true; } void nmVTKMeshLayer::applyExplicitScalarState( const QByteArray& aArrayName, ScalarAssociation eAssociation, double dMinimum, double dMaximum) { m_aScalarArrayName = aArrayName; m_eScalarAssociation = eAssociation; if(eAssociation == ScalarAssociation_CellData) { m_pMapper->SetScalarModeToUseCellFieldData(); } else { m_pMapper->SetScalarModeToUsePointFieldData(); } m_pMapper->SelectColorArray(m_aScalarArrayName.constData()); m_pMapper->SetScalarRange(dMinimum, dMaximum); m_pLookupTable->SetRange(dMinimum, dMaximum); m_pLookupTable->Build(); m_bUsingExplicitScalarRange = true; } void nmVTKMeshLayer::applyLookupTableScalarState( const QByteArray& aArrayName, ScalarAssociation eAssociation, vtkLookupTable* pLookupTable, const double dRange[2]) { m_aScalarArrayName = aArrayName; m_eScalarAssociation = eAssociation; if(eAssociation == ScalarAssociation_CellData) { m_pMapper->SetScalarModeToUseCellFieldData(); } else { m_pMapper->SetScalarModeToUsePointFieldData(); } m_pMapper->SelectColorArray(m_aScalarArrayName.constData()); m_pLookupTable = pLookupTable; m_pMapper->SetLookupTable(m_pLookupTable); // VTK 7.1 的数组重载要求可写 double*,这里使用双参数重载保留 const 契约。 m_pMapper->SetScalarRange(dRange[0], dRange[1]); m_bUsingExplicitScalarRange = true; } void nmVTKMeshLayer::applyRetainedScalarState( const double dScalarRange[2]) { if(m_aScalarArrayName.isEmpty()) { return; } if(m_eScalarAssociation == ScalarAssociation_CellData) { m_pMapper->SetScalarModeToUseCellFieldData(); } else { m_pMapper->SetScalarModeToUsePointFieldData(); } m_pMapper->SelectColorArray(m_aScalarArrayName.constData()); if(!m_bUsingExplicitScalarRange && dScalarRange != nullptr) { m_pMapper->SetScalarRange( dScalarRange[0], dScalarRange[1]); m_pLookupTable->SetRange( dScalarRange[0], dScalarRange[1]); m_pLookupTable->Build(); } } vtkDataArray* nmVTKMeshLayer::getSelectedScalarArray() const { vtkDataSet* pDataSet = getInputDataSet(true); if(pDataSet == nullptr || m_aScalarArrayName.isEmpty()) { return nullptr; } return m_eScalarAssociation == ScalarAssociation_CellData ? pDataSet->GetCellData()->GetArray( m_aScalarArrayName.constData()) : pDataSet->GetPointData()->GetArray( m_aScalarArrayName.constData()); } bool nmVTKMeshLayer::applyDataScalarRange() { vtkDataArray* pArray = getSelectedScalarArray(); if(pArray == nullptr || m_pMapper == nullptr || m_pLookupTable == nullptr) { return false; } // GetRange 只扫描当前选中的数组;无效或非有限范围不会污染现有颜色表。 double dRange[2] = { 0.0, 0.0 }; pArray->GetRange(dRange); if(!vtkMath::IsFinite(dRange[0]) || !vtkMath::IsFinite(dRange[1]) || dRange[0] > dRange[1]) { return false; } m_pMapper->SetScalarRange(dRange); m_pLookupTable->SetRange(dRange); m_pLookupTable->Build(); return true; } void nmVTKMeshLayer::clearInvalidScalarState() { // 失效选择不能继续驱动几何着色,也不能让外接色标沿用上一数据集范围。 m_aScalarArrayName.clear(); m_eScalarAssociation = ScalarAssociation_CellData; m_bUsingExplicitScalarRange = false; m_pMapper->ScalarVisibilityOff(); m_pMapper->SetScalarModeToDefault(); m_pMapper->SetScalarRange(0.0, 1.0); m_pLookupTable->SetRange(0.0, 1.0); m_pLookupTable->Build(); }