#include "nmVolumeSceneController.h" #include "nmVolumeDataRenderAdapter.h" #include "nmWxVTKRenderContainerWidget.h" #include "nmVTKBoundsGridLayer.h" #include "nmVTKMeshLayer.h" #include "nmVTKScalarBarLayer.h" #include "nmVTKScene.h" #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", " (Cell)"), QT_TRANSLATE_NOOP("nmVolumeSceneController", " (Point)"), QT_TRANSLATE_NOOP("nmVolumeSceneController", "Not enough memory to display the volume mesh."), QT_TRANSLATE_NOOP("nmVolumeSceneController", "Unable to bind the volume scalar property.") }; /** @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); } } class nmVolumeSceneControllerPrivate { public: /** @brief 初始化未挂载图层的空场景,构造本身不改动画面。 */ explicit nmVolumeSceneControllerPrivate(nmWxVTKRenderContainerWidget* pContainer) : m_pContainer(pContainer), m_pAdapter(nullptr), m_pMesh(nullptr), m_pScalarBar(nullptr), m_pBounds(nullptr), m_bVisible(false), m_bScalarBarVisible(true), m_bScaleGridVisible(false) {} /** @brief 仅移除仍具有相同对象身份的图层,防止误删外部同 ID 图层。 */ ~nmVolumeSceneControllerPrivate() { // Scene 拥有图层,先解除 Mapper 对适配器的引用,再释放数据。 nmVTKScene* pScene = m_pContainer == nullptr ? nullptr : m_pContainer->getScene(); if(pScene != nullptr) { 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); if(m_pBounds != nullptr && pScene->getLayer(m_sBoundsId) == m_pBounds) pScene->removeLayer(m_sBoundsId); } 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 && m_pBounds != nullptr && pScene->getLayer(m_sMeshId) == m_pMesh && pScene->getLayer(m_sScalarBarId) == m_pScalarBar && pScene->getLayer(m_sBoundsId) == m_pBounds; } /** @brief 将模型可见状态与各覆盖层偏好合成,不触发 Render。 */ void updateVisibility() { if(!isIntact()) return; m_pMesh->setVisible(m_bVisible); m_pScalarBar->setVisible(m_bVisible && m_bScalarBarVisible && !m_sPropertyId.isEmpty()); m_pBounds->setVisible(m_bVisible && m_bScaleGridVisible); } /** @brief 完整验证候选 LUT 和属性后提交 Mapper,失败时保持旧绑定。 */ bool bindProperty(const QString& sPropertyId) { const nmVolumePropertyDescriptor* pProperty = m_pData->findProperty(sPropertyId); if(pProperty == nullptr) return false; double dMinimum = pProperty->m_dMinimum; double dMaximum = pProperty->m_dMaximum; const bool bConstant = dMinimum == dMaximum; if(bConstant) { // LUT 需要严格递增区间;色标仍显示真实常量值,不能伪造业务范围。 const double dPadding = qMax(1.0, std::fabs(dMinimum)) * 1e-6; dMaximum += dPadding; if(!vtkMath::IsFinite(dMaximum)) { dMaximum = dMinimum; dMinimum -= dPadding; } } vtkSmartPointer pTable = vtkSmartPointer::New(); pTable->SetNumberOfTableValues(256); pTable->SetHueRange(0.67, 0.0); pTable->SetTableRange(dMinimum, dMaximum); pTable->SetNanColor(0.65, 0.65, 0.65, 1.0); pTable->Build(); 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); QString sTitle = pProperty->m_sArrayName; const QString sOtherId = (pProperty->m_eAssociation == NM_VolumeProperty_CellData ? QString::fromLatin1("point:") : QString::fromLatin1("cell:")) + sTitle; if(m_pData->findProperty(sOtherId) != nullptr) { sTitle += pProperty->m_eAssociation == NM_VolumeProperty_CellData ? sceneText(" (Cell)") : sceneText(" (Point)"); } m_pScalarBar->setTitle(sTitle); m_pScalarBar->setLookupTable(pTable); m_pScalarBar->setConstantValueLabel(bConstant, QString::number(pProperty->m_dMinimum, 'g', 8)); m_pScalarBar->setNumberOfLabels(bConstant ? 0 : m_pScalarBar->getResponsiveLabelCount()); m_sPropertyId = sPropertyId; return true; } nmWxVTKRenderContainerWidget* m_pContainer; ///< 非所有权,生命周期长于控制器。 QSharedPointer m_pData; ///< 当前完整只读三维数据强引用。 nmVolumeDataRenderAdapter* m_pAdapter; ///< 本状态独占的渲染管线适配器。 nmVTKMeshLayer* m_pMesh; ///< Scene 所有的主模型图层,仅作非所有权观察。 nmVTKScalarBarLayer* m_pScalarBar; ///< Scene 所有的色标图层。 nmVTKBoundsGridLayer* m_pBounds; ///< Scene 所有的三维坐标框图层。 QString m_sMeshId; ///< 主模型图层稳定 ID,带本次候选唯一前缀。 QString m_sScalarBarId; ///< 色标图层稳定 ID。 QString m_sBoundsId; ///< 三维坐标框图层稳定 ID。 QString m_sPropertyId; ///< 最后成功绑定的属性 ID,纯几何时为空。 bool m_bVisible; ///< 是否已由窗口事务激活,而非尚在准备的隐藏候选。 bool m_bScalarBarVisible; ///< 当前三维模型的独立色标偏好。 bool m_bScaleGridVisible; ///< 当前三维模型的独立坐标框偏好。 }; 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_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"); pCandidate->m_sBoundsId = sPrefix + QLatin1String(".bounds"); 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->setVisible(false); if(!pScene->addLayer(pScalarBar.data())) return false; pCandidate->m_pScalarBar = pScalarBar.take(); QScopedPointer pBounds(new nmVTKBoundsGridLayer(pCandidate->m_sBoundsId)); pBounds->setVisible(false); if(!pScene->addLayer(pBounds.data())) return false; pCandidate->m_pBounds = pBounds.take(); double dBounds[6]; pData->getBounds(dBounds); pCandidate->m_pBounds->setCamera(pCandidate->m_pContainer->getRenderer()->GetActiveCamera()); pCandidate->m_pBounds->setMode(nmVTKBoundsGridLayer::Mode_Box3D); if(!pCandidate->m_pBounds->setBounds(dBounds[0], dBounds[1], dBounds[2], dBounds[3], dBounds[4], dBounds[5])) return false; pCandidate->m_pMesh->setDisplayMode(nmVTKMeshLayer::DisplayMode_SurfaceWithEdges); 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->m_pContainer->setRotationCenterPickTarget(m_pPrivate->m_pMesh->getActor()); 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->bindProperty(sPropertyId)) { if(pError != nullptr) *pError = sceneText("Unable to bind the volume scalar property."); return false; } m_pPrivate->updateVisibility(); if(m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->renderView(); return true; } 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(); } void nmVolumeSceneController::setScaleGridVisible(bool bVisible) { if(!isVolumeGuiThread()) return; if(m_pPrivate->m_bScaleGridVisible == bVisible) return; m_pPrivate->m_bScaleGridVisible = 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::isScaleGridVisible() const { return m_pPrivate->m_bScaleGridVisible; } bool nmVolumeSceneController::hasScalarProperty() const { return hasData() && !m_pPrivate->m_sPropertyId.isEmpty(); }