You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmVolumeSceneController.cpp

594 lines
31 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#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 <QCoreApplication>
#include <QMap>
#include <QScopedPointer>
#include <QThread>
#include <QUuid>
#include <cmath>
#include <new>
#include <vtkActor.h>
#include <vtkCellData.h>
#include <vtkDataArray.h>
#include <vtkDataSetMapper.h>
#include <vtkLookupTable.h>
#include <vtkMath.h>
#include <vtkProperty.h>
#include <vtkPointData.h>
#include <vtkPolyData.h>
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<vtkLookupTable> 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<vtkLookupTable>();
}
if(!nmVTKColorMapCatalog::contains(oSettings.m_sColorMapId)) {
if(pError != nullptr) *pError = sceneText("The selected color map is unavailable.");
return vtkSmartPointer<vtkLookupTable>();
}
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<vtkLookupTable>();
}
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<vtkLookupTable> 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<vtkLookupTable>();
}
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<vtkLookupTable> m_pOldMeshTable; ///< 回退所需的旧模型颜色表强引用。
vtkSmartPointer<vtkLookupTable> 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<vtkLookupTable> 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<const nmVolumeData> m_pData; ///< 当前完整只读三维数据强引用。
nmVolumeDataRenderAdapter* m_pAdapter; ///< 本状态独占的渲染管线适配器。
nmVTKMeshLayer* m_pMesh; ///< Scene 所有的主模型图层,仅作非所有权观察。
nmVTKScalarBarLayer* m_pScalarBar; ///< Scene 所有的色标图层。
nmVTKMeshLayer* m_pSliceMesh; ///< Scene 所有的截面图层;每次成功计算后替换。
vtkSmartPointer<vtkPolyData> m_pSliceData; ///< 与 Cutter 断开的独立截面,关闭显示时保留。
nmVolumeSliceSettings m_oSliceSettings; ///< 当前数据最后一次成功应用的切片参数。
QMap<QString, nmPebiResultDisplaySettings> 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<const nmVolumeData>& pData, QString* pError)
{
Q_ASSERT(isVolumeGuiThread());
if(pError != nullptr) pError->clear();
if(!isVolumeGuiThread() || pData.isNull() || m_pPrivate->m_pContainer == nullptr) return false;
try {
QScopedPointer<nmVolumeSceneControllerPrivate> 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<nmVTKMeshLayer> pMesh(new nmVTKMeshLayer(pCandidate->m_sMeshId));
pMesh->setVisible(false);
if(!pScene->addLayer(pMesh.data())) return false;
pCandidate->m_pMesh = pMesh.take();
QScopedPointer<nmVTKScalarBarLayer> 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<nmVolumeSceneControllerPrivate> 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<vtkLookupTable> 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<QString, nmPebiResultDisplaySettings> 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<const nmVolumeData> nmVolumeSceneController::getData() const
{
return hasData() ? m_pPrivate->m_pData : QSharedPointer<const nmVolumeData>();
}
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<vtkPolyData> 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<nmVTKMeshLayer> 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<vtkLookupTable> 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;
}
}