#include "nmVolumeSceneController.h" #include "nmVolumeDataRenderAdapter.h" #include "nmVolumeSlicePipeline.h" #include "nmWxVTKRenderContainerWidget.h" #include "nmVTKMeshLayer.h" #include "nmVTKColorMapCatalog.h" #include "nmVTKScalarBarLayer.h" #include "nmNumericDisplayFormat.h" #include "nmVTKScene.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { // 固定文本标记供 Qt 4 lupdate 提取;运行期通过统一上下文翻译。 static const char* const s_pVolumeTranslationMarkers[] = { QT_TRANSLATE_NOOP("nmVolumeSceneController", "Not enough memory to display the volume mesh."), QT_TRANSLATE_NOOP("nmVolumeSceneController", "Unable to bind the volume scalar property."), QT_TRANSLATE_NOOP("nmVolumeSceneController", "The color settings are invalid."), QT_TRANSLATE_NOOP("nmVolumeSceneController", "The selected color map is unavailable."), QT_TRANSLATE_NOOP("nmVolumeSceneController", "The manual range must contain two finite increasing values."), QT_TRANSLATE_NOOP("nmVolumeSceneController", "Unable to construct the color mapping."), QT_TRANSLATE_NOOP("nmVolumeSceneController", "The color settings no longer match the current volume property."), QT_TRANSLATE_NOOP("nmVolumeSceneController", "Unable to apply the color settings.") }; /** @brief Renderer 和 Mapper 只允许所属 GUI 线程操作。 */ bool isVolumeGuiThread() { return QCoreApplication::instance() == nullptr || QCoreApplication::instance()->thread() == QThread::currentThread(); } /** @brief 返回与业务窗口解耦的三维场景翻译文本。 */ QString sceneText(const char* pText) { Q_UNUSED(s_pVolumeTranslationMarkers); return QCoreApplication::translate("nmVolumeSceneController", pText); } /** @brief 校验后构造独立颜色表;只读属性目录,绝不修改已有 Mapper 或已发布数据。 */ vtkSmartPointer makeVolumeLookupTable(const nmVolumePropertyDescriptor& oProperty, const nmPebiResultDisplaySettings& oSettings, QString* pError = nullptr) { if(!oSettings.isStructurallyValid() || oSettings.m_eScaleMode != NM_PebiColorScale_Linear || oSettings.m_bUseBelowRangeColor || oSettings.m_bUseAboveRangeColor) { if(pError != nullptr) *pError = sceneText("The color settings are invalid."); return vtkSmartPointer(); } if(!nmVTKColorMapCatalog::contains(oSettings.m_sColorMapId)) { if(pError != nullptr) *pError = sceneText("The selected color map is unavailable."); return vtkSmartPointer(); } double dMinimum = oSettings.m_bHasManualRange ? oSettings.m_dManualMinimum : oProperty.m_dMinimum; double dMaximum = oSettings.m_bHasManualRange ? oSettings.m_dManualMaximum : oProperty.m_dMaximum; if(!vtkMath::IsFinite(dMinimum) || !vtkMath::IsFinite(dMaximum) || dMinimum > dMaximum || (oSettings.m_bHasManualRange && dMinimum == dMaximum)) { if(pError != nullptr) *pError = sceneText("The manual range must contain two finite increasing values."); return vtkSmartPointer(); } const bool bSingleValue = !oSettings.m_bHasManualRange && dMinimum == dMaximum; if(dMinimum == dMaximum) { // 常量对外仍显示真实数值;只扩展内部范围,并兼容接近 double 上限的值。 const double dPadding = qMax(1.0, std::fabs(dMinimum)) * 1e-6; dMaximum += dPadding; if(!vtkMath::IsFinite(dMaximum)) { dMaximum = dMinimum; dMinimum -= dPadding; } } vtkSmartPointer pTable = nmVTKColorMapCatalog::createLookupTable(oSettings.m_sColorMapId); if(pTable == nullptr || !vtkMath::IsFinite(dMinimum) || !vtkMath::IsFinite(dMaximum) || dMinimum >= dMaximum) { if(pError != nullptr) *pError = sceneText("Unable to construct the color mapping."); return vtkSmartPointer(); } pTable->SetTableRange(dMinimum, dMaximum); pTable->SetScaleToLinear(); pTable->SetUseBelowRangeColor(0); pTable->SetUseAboveRangeColor(0); pTable->IndexedLookupOff(); pTable->SetNanColor(0.65, 0.65, 0.65, 1.0); if(bSingleValue) { // 与现有结果窗口一致,自动常量色标整体使用色带首色,避免暗示不存在的梯度。 double dColor[4]; pTable->GetTableValue(0, dColor); for(vtkIdType nIndex = 0; nIndex < pTable->GetNumberOfTableValues(); ++nIndex) { pTable->SetTableValue(nIndex, dColor); } } pTable->BuildSpecialColors(); return pTable; } /** @brief 候选挂入 Scene 后的回退保护;异常或提前返回时仅撤销该候选图层。 */ class VolumeSliceLayerGuard { public: /** @brief 在挂载之前准备 ID,避免挂载后字符串分配失败造成图层遗留。 */ VolumeSliceLayerGuard(nmVTKScene* pScene, const QString& sId) : m_pScene(pScene), m_sId(sId), m_pLayer(nullptr) {} /** @brief 候选未提交时解除 Scene 所有权并删除,已提交时不操作。 */ ~VolumeSliceLayerGuard() { if(m_pLayer != nullptr && m_pScene->getLayer(m_sId) == m_pLayer) m_pScene->removeLayer(m_sId); } /** @brief 记录成功挂载的图层,由 Scene 实际拥有对象。 */ void hold(nmVTKMeshLayer* pLayer) { m_pLayer = pLayer; } /** @brief 事务提交后解除回退保护。 */ void release() { m_pLayer = nullptr; } private: Q_DISABLE_COPY(VolumeSliceLayerGuard) nmVTKScene* m_pScene; ///< 非所有权 Scene,存活期覆盖本次同步事务。 QString m_sId; ///< 待提交候选的唯一身份。 nmVTKMeshLayer* m_pLayer; ///< Scene 所有的候选指针,仅用于身份检查。 }; /** @brief 两个网格图层的颜色绑定保护;任一绑定失败时恢复已经更换的颜色表。 */ class VolumeColorBindingGuard { public: /** @brief 保存完整模型及可着色截面的旧颜色表,输入图层必须在作用域内存活。 */ VolumeColorBindingGuard(nmVTKMeshLayer* pMesh, nmVTKMeshLayer* pSlice) : m_pMesh(pMesh), m_pSlice(pSlice), m_bMeshChanged(false), m_bSliceChanged(false), m_pOldMeshTable(pMesh->getLookupTable()), m_pOldSliceTable(pSlice == nullptr ? nullptr : pSlice->getLookupTable()) {} /** @brief 事务未提交时恢复已修改图层;恢复过程不主动渲染。 */ ~VolumeColorBindingGuard() { if(m_bMeshChanged) m_pMesh->setLookupTable(m_pOldMeshTable); if(m_bSliceChanged) m_pSlice->setLookupTable(m_pOldSliceTable); } /** @brief 先绑定截面、后绑定模型,返回失败时由析构保护统一撤销。 */ bool bind(vtkLookupTable* pTable) { if(m_pSlice != nullptr) { if(!m_pSlice->setLookupTable(pTable)) return false; m_bSliceChanged = true; } if(!m_pMesh->setLookupTable(pTable)) return false; m_bMeshChanged = true; return true; } /** @brief 设置、颜色表及色标全部提交后解除回退保护。 */ void release() { m_bMeshChanged = m_bSliceChanged = false; } private: Q_DISABLE_COPY(VolumeColorBindingGuard) nmVTKMeshLayer* m_pMesh; ///< 非所有权完整模型图层。 nmVTKMeshLayer* m_pSlice; ///< 非所有权截面图层;无交集、全缺失时为空。 bool m_bMeshChanged; ///< 完整模型已经切换颜色表,尚未提交。 bool m_bSliceChanged; ///< 截面已经切换颜色表,尚未提交。 vtkSmartPointer m_pOldMeshTable; ///< 回退所需的旧模型颜色表强引用。 vtkSmartPointer m_pOldSliceTable; ///< 回退所需的旧截面颜色表强引用。 }; } class nmVolumeSceneControllerPrivate { public: /** @brief 初始化未挂载图层的空场景,构造本身不改动画面。 */ explicit nmVolumeSceneControllerPrivate(nmWxVTKRenderContainerWidget* pContainer) : m_pContainer(pContainer), m_pAdapter(nullptr), m_pMesh(nullptr), m_pScalarBar(nullptr), m_pSliceMesh(nullptr), m_eMeshDisplayMode(nmVTKMeshLayer::DisplayMode_SurfaceWithEdges), m_bSliceEnabled(false), m_nSliceRevision(0), m_bVisible(false), m_bScalarBarVisible(true) {} /** @brief 仅移除仍具有相同对象身份的图层,防止误删外部同 ID 图层。 */ ~nmVolumeSceneControllerPrivate() { // Scene 拥有图层,先解除 Mapper 对适配器的引用,再释放数据。 nmVTKScene* pScene = m_pContainer == nullptr ? nullptr : m_pContainer->getScene(); if(pScene != nullptr) { if(m_pSliceMesh != nullptr && pScene->getLayer(m_sSliceId) == m_pSliceMesh) pScene->removeLayer(m_sSliceId); if(m_pMesh != nullptr && pScene->getLayer(m_sMeshId) == m_pMesh) pScene->removeLayer(m_sMeshId); if(m_pScalarBar != nullptr && pScene->getLayer(m_sScalarBarId) == m_pScalarBar) pScene->removeLayer(m_sScalarBarId); } delete m_pAdapter; } /** @brief 通过 Scene 登记验证观察指针,绝不解引用被外部清理的图层。 */ bool isIntact() const { nmVTKScene* pScene = m_pContainer == nullptr ? nullptr : m_pContainer->getScene(); return !m_pData.isNull() && pScene != nullptr && m_pMesh != nullptr && m_pScalarBar != nullptr && pScene->getLayer(m_sMeshId) == m_pMesh && pScene->getLayer(m_sScalarBarId) == m_pScalarBar && (m_pSliceMesh == nullptr || pScene->getLayer(m_sSliceId) == m_pSliceMesh); } /** @brief 将模型可见状态与各覆盖层偏好合成,不触发 Render。 */ void updateVisibility() { if(!isIntact()) return; const bool bHasSlice = m_pSliceData != nullptr && m_pSliceData->GetNumberOfCells() > 0; // 参照模型固定为灰色半透明表面;完整模型和截面沿用底栏选中的显示模式。 m_pMesh->setOpacity(m_bSliceEnabled ? 0.15 : 1.0); m_pMesh->setScalarVisibility(!m_bSliceEnabled && !m_sPropertyId.isEmpty()); m_pMesh->setDisplayMode(m_bSliceEnabled ? nmVTKMeshLayer::DisplayMode_Surface : m_eMeshDisplayMode); m_pMesh->setVisible(m_bVisible && (!m_bSliceEnabled || m_oSliceSettings.m_bShowSource)); if(m_pSliceMesh != nullptr) { m_pSliceMesh->setDisplayMode(m_eMeshDisplayMode); m_pSliceMesh->setVisible(m_bVisible && m_bSliceEnabled && bHasSlice); } m_pScalarBar->setVisible(m_bVisible && m_bScalarBarVisible && !m_sPropertyId.isEmpty() && (!m_bSliceEnabled || bHasSlice)); } /** @brief 仅在当前场景激活时更新拾取目标,隐藏候选不会抢占其他业务场景。 */ void updatePickTarget() { if(!m_bVisible || !isIntact()) return; vtkActor* pTarget = m_pMesh->getActor(); if(m_bSliceEnabled) { pTarget = m_pSliceMesh != nullptr && m_pSliceData != nullptr && m_pSliceData->GetNumberOfCells() > 0 ? m_pSliceMesh->getActor() : nullptr; } m_pContainer->setRotationCenterPickTarget(pTarget); } /** @brief 同步色标及常量标签;只更新显示对象,不执行 Render。 */ void applyScalarBarState(const nmVolumePropertyDescriptor& oProperty, const nmPebiResultDisplaySettings& oSettings, vtkLookupTable* pTable) { const bool bConstant = !oSettings.m_bHasManualRange && oProperty.m_dMinimum == oProperty.m_dMaximum; m_pScalarBar->setTitle(oProperty.m_sArrayName); m_pScalarBar->setLookupTable(pTable); m_pScalarBar->setConstantValueLabel(bConstant, nmNumericDisplayFormat::format(oProperty.m_dMinimum)); m_pScalarBar->setNumberOfLabels(bConstant ? 0 : m_pScalarBar->getResponsiveLabelCount()); } /** @brief 完整验证候选 LUT 和属性后提交 Mapper,失败时保持旧绑定。 */ bool bindProperty(const QString& sPropertyId, vtkLookupTable* pPreparedTable = nullptr) { const nmVolumePropertyDescriptor* pProperty = m_pData->findProperty(sPropertyId); if(pProperty == nullptr) return false; const nmPebiResultDisplaySettings oSettings = m_mapDisplaySettings.value(sPropertyId); vtkSmartPointer pTable = pPreparedTable; if(pTable == nullptr) pTable = makeVolumeLookupTable(*pProperty, oSettings); if(pTable == nullptr) return false; const nmVTKMeshLayer::ScalarAssociation eAssociation = pProperty->m_eAssociation == NM_VolumeProperty_CellData ? nmVTKMeshLayer::ScalarAssociation_CellData : nmVTKMeshLayer::ScalarAssociation_PointData; if(!m_pMesh->setInputConnection(m_pAdapter->getOutputPort(), pProperty->m_sArrayName, eAssociation, pTable)) return false; m_pMesh->setScalarVisibility(true); applyScalarBarState(*pProperty, oSettings, pTable); m_sPropertyId = sPropertyId; return true; } nmWxVTKRenderContainerWidget* m_pContainer; ///< 非所有权,生命周期长于控制器。 QSharedPointer m_pData; ///< 当前完整只读三维数据强引用。 nmVolumeDataRenderAdapter* m_pAdapter; ///< 本状态独占的渲染管线适配器。 nmVTKMeshLayer* m_pMesh; ///< Scene 所有的主模型图层,仅作非所有权观察。 nmVTKScalarBarLayer* m_pScalarBar; ///< Scene 所有的色标图层。 nmVTKMeshLayer* m_pSliceMesh; ///< Scene 所有的截面图层;每次成功计算后替换。 vtkSmartPointer m_pSliceData; ///< 与 Cutter 断开的独立截面,关闭显示时保留。 nmVolumeSliceSettings m_oSliceSettings; ///< 当前数据最后一次成功应用的切片参数。 QMap m_mapDisplaySettings; ///< 按单元/节点属性 ID 独立记忆的颜色设置,仅当前模型有效。 nmVTKMeshLayer::DisplayMode m_eMeshDisplayMode; ///< 完整模型及截面的共用显示偏好,不参与切片计算。 QString m_sSliceId; ///< 当前截面图层唯一 ID,不与完整模型图层复用。 bool m_bSliceEnabled; ///< 是否正在显示截面,而非完整模型。 unsigned int m_nSliceRevision; ///< 本次数据成功应用截面的次数,预览不增加。 QString m_sMeshId; ///< 主模型图层稳定 ID,带本次候选唯一前缀。 QString m_sScalarBarId; ///< 色标图层稳定 ID。 QString m_sPropertyId; ///< 最后成功绑定的属性 ID,纯几何时为空。 bool m_bVisible; ///< 是否已由窗口事务激活,而非尚在准备的隐藏候选。 bool m_bScalarBarVisible; ///< 当前三维模型的独立色标偏好。 }; nmVolumeSceneController::nmVolumeSceneController(nmWxVTKRenderContainerWidget* pContainer) : m_pPrivate(new nmVolumeSceneControllerPrivate(pContainer)) {} nmVolumeSceneController::~nmVolumeSceneController() { if(m_pPrivate->m_bVisible && m_pPrivate->m_pContainer != nullptr) { m_pPrivate->m_pContainer->setRotationCenterPickTarget(nullptr); } delete m_pPrivate; } bool nmVolumeSceneController::setData(const QSharedPointer& pData, QString* pError) { Q_ASSERT(isVolumeGuiThread()); if(pError != nullptr) pError->clear(); if(!isVolumeGuiThread() || pData.isNull() || m_pPrivate->m_pContainer == nullptr) return false; try { QScopedPointer pCandidate(new nmVolumeSceneControllerPrivate(m_pPrivate->m_pContainer)); pCandidate->m_pData = pData; pCandidate->m_oSliceSettings = nmVolumeSliceSettings::fromData(*pData); pCandidate->m_pAdapter = new nmVolumeDataRenderAdapter(pData); // 每次候选使用独立 ID,旧场景直到候选全部准备成功才释放。 const QString sPrefix = QString::fromLatin1("nm.volume.") + QUuid::createUuid().toString(); pCandidate->m_sMeshId = sPrefix + QLatin1String(".mesh"); pCandidate->m_sScalarBarId = sPrefix + QLatin1String(".scalar"); nmVTKScene* pScene = pCandidate->m_pContainer->getScene(); if(pScene == nullptr) return false; QScopedPointer pMesh(new nmVTKMeshLayer(pCandidate->m_sMeshId)); pMesh->setVisible(false); if(!pScene->addLayer(pMesh.data())) return false; pCandidate->m_pMesh = pMesh.take(); QScopedPointer pScalarBar(new nmVTKScalarBarLayer(pCandidate->m_sScalarBarId)); pScalarBar->setCompactNumericLabelsEnabled(true); pScalarBar->setVisible(false); if(!pScene->addLayer(pScalarBar.data())) return false; pCandidate->m_pScalarBar = pScalarBar.take(); pCandidate->m_pMesh->setDisplayMode(pCandidate->m_eMeshDisplayMode); pCandidate->m_pMesh->getActor()->GetProperty()->SetEdgeColor(0.0, 0.0, 0.0); pCandidate->m_pMesh->getActor()->GetProperty()->SetColor(0.65, 0.65, 0.65); if(pData->getInitialPropertyId().isEmpty()) { if(!pCandidate->m_pMesh->setInputConnection(pCandidate->m_pAdapter->getOutputPort())) return false; pCandidate->m_pMesh->setScalarVisibility(false); } else if(!pCandidate->bindProperty(pData->getInitialPropertyId())) { if(pError != nullptr) *pError = sceneText("Unable to bind the volume scalar property."); return false; } const QSize oSize = pCandidate->m_pContainer->getViewportSize(); pCandidate->m_pScalarBar->setViewportSize(oSize.width(), oSize.height()); // 从此不再存在失败分支。先解除旧拾取引用,再销毁旧 Actor。 if(m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->setRotationCenterPickTarget(nullptr); delete m_pPrivate; m_pPrivate = pCandidate.take(); return true; } catch(const std::bad_alloc&) { if(pError != nullptr) *pError = sceneText("Not enough memory to display the volume mesh."); return false; } } void nmVolumeSceneController::activate(bool bFitCamera) { if(!hasData()) return; double dBounds[6]; m_pPrivate->m_pData->getBounds(dBounds); m_pPrivate->m_bVisible = true; m_pPrivate->updateVisibility(); m_pPrivate->m_pContainer->setPrimaryViewBounds(dBounds); m_pPrivate->updatePickTarget(); if(bFitCamera) { m_pPrivate->m_pContainer->fitIsometricViewWithoutRender(); m_pPrivate->m_pContainer->resetRotationCenterWithoutRender(); } } void nmVolumeSceneController::hide() { if(!isVolumeGuiThread()) return; m_pPrivate->m_bVisible = false; m_pPrivate->updateVisibility(); } void nmVolumeSceneController::clear() { if(!isVolumeGuiThread()) return; nmWxVTKRenderContainerWidget* pContainer = m_pPrivate->m_pContainer; QScopedPointer pEmpty(new nmVolumeSceneControllerPrivate(pContainer)); if(m_pPrivate->m_bVisible) { pContainer->setRotationCenterPickTarget(nullptr); pContainer->clearPrimaryViewBounds(); } delete m_pPrivate; m_pPrivate = pEmpty.take(); } bool nmVolumeSceneController::hasData() const { return isVolumeGuiThread() && m_pPrivate->isIntact(); } bool nmVolumeSceneController::applyProperty(const QString& sPropertyId, QString* pError) { if(pError != nullptr) pError->clear(); if(!hasData()) return false; if(sPropertyId == m_pPrivate->m_sPropertyId) return true; if(m_pPrivate->m_pSliceData != nullptr) { nmVolumeSliceSettings oSettings = m_pPrivate->m_oSliceSettings; oSettings.m_sPropertyId = sPropertyId; const bool bWasEnabled = m_pPrivate->m_bSliceEnabled; if(!applySliceSettings(oSettings, pError)) return false; if(!bWasEnabled) setSliceEnabled(false); return true; } if(!m_pPrivate->bindProperty(sPropertyId)) { if(pError != nullptr) *pError = sceneText("Unable to bind the volume scalar property."); return false; } m_pPrivate->updateVisibility(); m_pPrivate->m_oSliceSettings.m_sPropertyId = sPropertyId; if(m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->renderView(); return true; } QString nmVolumeSceneController::getCurrentPropertyId() const { return hasData() ? m_pPrivate->m_sPropertyId : QString(); } bool nmVolumeSceneController::getCurrentPropertyRange(double dRange[2]) const { if(!hasScalarProperty() || dRange == nullptr) return false; const nmVolumePropertyDescriptor* pProperty = m_pPrivate->m_pData->findProperty(m_pPrivate->m_sPropertyId); if(pProperty == nullptr) return false; dRange[0] = pProperty->m_dMinimum; dRange[1] = pProperty->m_dMaximum; return true; } bool nmVolumeSceneController::getCurrentDisplaySettings(nmPebiResultDisplaySettings& oSettings) const { if(!hasScalarProperty()) return false; oSettings = m_pPrivate->m_mapDisplaySettings.value(m_pPrivate->m_sPropertyId); return true; } bool nmVolumeSceneController::getCurrentDisplayRange(double dRange[2]) const { if(!getCurrentPropertyRange(dRange)) return false; const nmPebiResultDisplaySettings oSettings = m_pPrivate->m_mapDisplaySettings.value(m_pPrivate->m_sPropertyId); if(oSettings.m_bHasManualRange) { dRange[0] = oSettings.m_dManualMinimum; dRange[1] = oSettings.m_dManualMaximum; } return true; } bool nmVolumeSceneController::applyCurrentDisplaySettings(const QString& sExpectedDataId, const QString& sExpectedPropertyId, const nmPebiResultDisplaySettings& oSettings, QString* pError) { if(pError != nullptr) pError->clear(); if(!hasScalarProperty() || sExpectedDataId != m_pPrivate->m_pData->getDataId() || sExpectedPropertyId != m_pPrivate->m_sPropertyId) { if(pError != nullptr) *pError = sceneText("The color settings no longer match the current volume property."); return false; } try { const nmVolumePropertyDescriptor* pProperty = m_pPrivate->m_pData->findProperty(sExpectedPropertyId); if(pProperty == nullptr) return false; // 先完整校验原始输入,再规范化;非法数值不能被自动范围的归零处理掩盖。 vtkSmartPointer pTable = makeVolumeLookupTable(*pProperty, oSettings, pError); if(pTable == nullptr) return false; const nmPebiResultDisplaySettings oNormalized = oSettings.normalized(); if(oNormalized == m_pPrivate->m_mapDisplaySettings.value(sExpectedPropertyId)) return true; QMap mapCandidateSettings = m_pPrivate->m_mapDisplaySettings; mapCandidateSettings.insert(sExpectedPropertyId, oNormalized); // 缓存截面即使隐藏也同步;全缺失和空截面不绑定标量,只更新模型与全局色标。 nmVTKMeshLayer* pColoredSlice = m_pPrivate->m_pSliceMesh; if(pColoredSlice != nullptr && !pColoredSlice->isScalarVisible()) pColoredSlice = nullptr; VolumeColorBindingGuard oGuard(m_pPrivate->m_pMesh, pColoredSlice); if(!oGuard.bind(pTable)) { if(pError != nullptr) *pError = sceneText("Unable to apply the color settings."); return false; } m_pPrivate->applyScalarBarState(*pProperty, oNormalized, pTable); m_pPrivate->m_mapDisplaySettings.swap(mapCandidateSettings); oGuard.release(); if(m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->renderView(); return true; } catch(const std::bad_alloc&) { if(pError != nullptr) *pError = sceneText("Not enough memory to display the volume mesh."); return false; } } void nmVolumeSceneController::setMeshDisplayMode(nmVTKMeshLayer::DisplayMode eMode) { if(!hasData() || !nmVTKMeshLayer::isValidDisplayMode(eMode) || m_pPrivate->m_eMeshDisplayMode == eMode) return; m_pPrivate->m_eMeshDisplayMode = eMode; m_pPrivate->updateVisibility(); if(m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->renderView(); } nmVTKMeshLayer::DisplayMode nmVolumeSceneController::getMeshDisplayMode() const { return m_pPrivate->m_eMeshDisplayMode; } void nmVolumeSceneController::setViewportSize(int nWidth, int nHeight) { if(hasData()) m_pPrivate->m_pScalarBar->setViewportSize(nWidth, nHeight); } void nmVolumeSceneController::setScalarBarVisible(bool bVisible) { if(!isVolumeGuiThread()) return; if(m_pPrivate->m_bScalarBarVisible == bVisible) return; m_pPrivate->m_bScalarBarVisible = bVisible; m_pPrivate->updateVisibility(); if(hasData() && m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->renderView(); } bool nmVolumeSceneController::isScalarBarVisible() const { return m_pPrivate->m_bScalarBarVisible; } bool nmVolumeSceneController::hasScalarProperty() const { return hasData() && !m_pPrivate->m_sPropertyId.isEmpty(); } QSharedPointer nmVolumeSceneController::getData() const { return hasData() ? m_pPrivate->m_pData : QSharedPointer(); } nmVolumeSliceSettings nmVolumeSceneController::getSliceSettings() const { return m_pPrivate->m_oSliceSettings; } bool nmVolumeSceneController::isSliceEnabled() const { return hasData() && m_pPrivate->m_bSliceEnabled; } bool nmVolumeSceneController::isSliceEmpty() const { return isSliceEnabled() && m_pPrivate->m_pSliceData != nullptr && m_pPrivate->m_pSliceData->GetNumberOfCells() == 0; } unsigned int nmVolumeSceneController::getSliceRevision() const { return m_pPrivate->m_nSliceRevision; } bool nmVolumeSceneController::setSliceEnabled(bool bEnabled, QString* pError) { if(pError != nullptr) pError->clear(); if(!hasData()) return false; if(bEnabled && m_pPrivate->m_pSliceData == nullptr) return applySliceSettings(m_pPrivate->m_oSliceSettings, pError); if(m_pPrivate->m_bSliceEnabled == bEnabled) return true; m_pPrivate->m_bSliceEnabled = bEnabled; m_pPrivate->updateVisibility(); m_pPrivate->updatePickTarget(); if(m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->renderView(); return true; } void nmVolumeSceneController::setSliceDisplayOptions(bool bShowPlane, bool bShowSource) { if(!hasData()) return; m_pPrivate->m_oSliceSettings.m_bShowPlane = bShowPlane; m_pPrivate->m_oSliceSettings.m_bShowSource = bShowSource; m_pPrivate->updateVisibility(); if(m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->renderView(); } bool nmVolumeSceneController::applySliceSettings(const nmVolumeSliceSettings& oSettings, QString* pError) { if(pError != nullptr) pError->clear(); if(!hasData()) return false; nmVolumeSliceSettings oCandidateSettings = oSettings; if(!oCandidateSettings.normalize(pError)) return false; const nmVolumePropertyDescriptor* pProperty = m_pPrivate->m_pData->findProperty(oCandidateSettings.m_sPropertyId); if(pProperty == nullptr && (!oCandidateSettings.m_sPropertyId.isEmpty() || !m_pPrivate->m_pData->getProperties().isEmpty())) { if(pError != nullptr) *pError = sceneText("Unable to bind the volume scalar property."); return false; } vtkSmartPointer pCandidateData; if(!nmVolumeSlicePipeline::build(m_pPrivate->m_pData, oCandidateSettings, pCandidateData, pError)) return false; try { const QString sCandidateId = QString::fromLatin1("nm.volume.slice.") + QUuid::createUuid().toString(); QScopedPointer pCandidateMesh(new nmVTKMeshLayer(sCandidateId)); pCandidateMesh->setVisible(false); pCandidateMesh->setDisplayMode(m_pPrivate->m_eMeshDisplayMode); pCandidateMesh->getActor()->GetProperty()->SetColor(0.65, 0.65, 0.65); pCandidateMesh->getActor()->GetProperty()->SetEdgeColor(0.0, 0.0, 0.0); // 关闭光照明暗,截面颜色直接对应色标,方便定量核对。 pCandidateMesh->getActor()->GetProperty()->LightingOff(); if(!pCandidateMesh->setDataSet(pCandidateData)) return false; bool bHasFiniteValues = false; if(pProperty != nullptr && pCandidateData->GetNumberOfCells() > 0) { vtkDataArray* pValues = pProperty->m_eAssociation == NM_VolumeProperty_CellData ? pCandidateData->GetCellData()->GetArray(pProperty->m_sArrayName.toUtf8().constData()) : pCandidateData->GetPointData()->GetArray(pProperty->m_sArrayName.toUtf8().constData()); for(vtkIdType nValue = 0; pValues != nullptr && nValue < pValues->GetNumberOfTuples(); ++nValue) { if(vtkMath::IsFinite(pValues->GetComponent(nValue, 0))) { bHasFiniteValues = true; break; } } } // 每次生成截面均读取当前属性的颜色偏好,自动范围始终取完整模型范围。 vtkSmartPointer pTable; if(pProperty != nullptr) { pTable = makeVolumeLookupTable(*pProperty, m_pPrivate->m_mapDisplaySettings.value(oCandidateSettings.m_sPropertyId), pError); if(pTable == nullptr) return false; const nmVTKMeshLayer::ScalarAssociation eAssociation = pProperty->m_eAssociation == NM_VolumeProperty_CellData ? nmVTKMeshLayer::ScalarAssociation_CellData : nmVTKMeshLayer::ScalarAssociation_PointData; if(bHasFiniteValues) { if(!pCandidateMesh->setScalarArray(pProperty->m_sArrayName, eAssociation, pTable)) return false; pCandidateMesh->setScalarVisibility(true); } } nmVTKScene* pScene = m_pPrivate->m_pContainer->getScene(); VolumeSliceLayerGuard oGuard(pScene, sCandidateId); if(!pScene->addLayer(pCandidateMesh.data())) return false; nmVTKMeshLayer* pNewMesh = pCandidateMesh.take(); oGuard.hold(pNewMesh); // 全部候选已验证。完整模型的属性绑定也按原接口原子提交,失败时撤销新图层。 if(pProperty != nullptr && !m_pPrivate->bindProperty(oCandidateSettings.m_sPropertyId, pTable)) { if(pError != nullptr) *pError = sceneText("Unable to bind the volume scalar property."); return false; } if(m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->setRotationCenterPickTarget(nullptr); if(m_pPrivate->m_pSliceMesh != nullptr) pScene->removeLayer(m_pPrivate->m_sSliceId); m_pPrivate->m_sSliceId = sCandidateId; m_pPrivate->m_pSliceMesh = pNewMesh; m_pPrivate->m_pSliceData = pCandidateData; m_pPrivate->m_oSliceSettings = oCandidateSettings; m_pPrivate->m_bSliceEnabled = true; oGuard.release(); ++m_pPrivate->m_nSliceRevision; m_pPrivate->updateVisibility(); m_pPrivate->updatePickTarget(); if(m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->renderView(); return true; } catch(const std::bad_alloc&) { if(pError != nullptr) *pError = sceneText("Not enough memory to display the volume mesh."); return false; } }