From 17ef272cbe7f9a057713b8a35d41de1cde0854f6 Mon Sep 17 00:00:00 2001 From: lh <2334563547@qq.com> Date: Tue, 3 Nov 2026 17:08:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8E=A5=E9=80=9A=E4=B8=89=E7=BB=B4?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E4=B8=8E=E5=88=87=E7=89=87=E7=9A=84=E9=A2=9C?= =?UTF-8?q?=E8=89=B2=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 复用底栏颜色设置,支持色带、手工范围及恢复全模型范围 - 按属性独立保存设置,同步模型、截面和色标,不重算切片 - 增加身份校验、非法范围校验及绑定失败回退 - 补充测试和验收说明,同步中文翻译及 qm --- Bin/Config/Lang/cn/nmNum_cn.qm | Bin 203807 -> 204551 bytes Bin/Config/Lang/cn/nmNum_cn.ts | 24 +++ .../nmNum/nmSubWxs/nmVolumeSceneController.h | 16 ++ .../nmWxPebiResultColorScaleSettingsDlg.h | 19 +- .../nmNum/nmSubWxs/nmWxPebiResultControlBar.h | 2 +- .../nmSubWxs/nmVolumeSceneController.cpp | 193 ++++++++++++++++-- .../nmWxPebiResultColorScaleSettingsDlg.cpp | 117 ++++++++++- .../nmSubWxs/nmWxPebiResultControlBar.cpp | 25 ++- 8 files changed, 355 insertions(+), 41 deletions(-) diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 40d091fa8c6185b586a711b553dcf60d51857df6..e6791e84b2b95dfa3c36824a2de0ae416da3f1cf 100644 GIT binary patch delta 13921 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 E59L7s6aWAK delta 13636 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 XL8N$1Tv!DOTklaxPI*+)3&Z~hC}5** diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index a53004fd..77a9b9f6 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -9102,6 +9102,30 @@ Average pressure in contour: %2 MPa Unable to bind the volume scalar property. 无法绑定三维模型的标量属性。 + + The color settings are invalid. + 颜色设置无效。 + + + The selected color map is unavailable. + 所选色带不可用。 + + + The manual range must contain two finite increasing values. + 手工范围必须为两个有限数值,且最小值小于最大值。 + + + Unable to construct the color mapping. + 无法构建颜色映射。 + + + The color settings no longer match the current volume property. + 颜色设置已不匹配当前三维模型属性,请重新打开设置窗口。 + + + Unable to apply the color settings. + 无法应用颜色设置。 + nmVolumeImportTask diff --git a/Include/nmNum/nmSubWxs/nmVolumeSceneController.h b/Include/nmNum/nmSubWxs/nmVolumeSceneController.h index 2fea3457..bd549f32 100644 --- a/Include/nmNum/nmSubWxs/nmVolumeSceneController.h +++ b/Include/nmNum/nmSubWxs/nmVolumeSceneController.h @@ -4,6 +4,7 @@ #include "nmSubWxs_global.h" #include "nmVolumeData.h" #include "nmVolumeSliceSettings.h" +#include "nmPebiResultDisplaySettings.h" #include "nmVTKMeshLayer.h" class nmWxVTKRenderContainerWidget; class nmVolumeSceneControllerPrivate; @@ -32,6 +33,21 @@ public: bool hasData() const; /** @brief 按稳定 ID 原子切换着色属性;失败保留原属性,成功至多 Render 一次。 */ bool applyProperty(const QString& sPropertyId, QString* pError = nullptr); + /** @brief 返回当前着色属性的稳定 ID;无合法属性时返回空字符串。 */ + QString getCurrentPropertyId() const; + /** @brief 读取当前属性的全模型有限值范围,常量属性允许两端相等。 */ + bool getCurrentPropertyRange(double dRange[2]) const; + /** @brief 读取当前手工或自动显示范围,不包含常量渲染所需的内部微量扩展。 */ + bool getCurrentDisplayRange(double dRange[2]) const; + /** @brief 读取当前属性独立记忆的颜色设置,未编辑时返回默认彩虹和自动范围。 */ + bool getCurrentDisplaySettings(nmPebiResultDisplaySettings& oSettings) const; + /** + * @brief 核对数据及属性身份后事务式更新颜色;失败保留全部旧绑定,不重算截面。 + * @note 当前仅接受线性映射及端点越界颜色,成功至多渲染一次。 + */ + bool applyCurrentDisplaySettings(const QString& sExpectedDataId, + const QString& sExpectedPropertyId, + const nmPebiResultDisplaySettings& oSettings, QString* pError = nullptr); /** @brief 复用结果窗口的网格显示模式,立即生效,不重算截面或移动相机。 */ void setMeshDisplayMode(nmVTKMeshLayer::DisplayMode eMode); /** @brief 返回完整模型与截面共用的显示模式,供底部工具栏同步。 */ diff --git a/Include/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.h b/Include/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.h index 4bd9922b..f0e57b9c 100644 --- a/Include/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.h +++ b/Include/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.h @@ -16,12 +16,13 @@ class QPushButton; class QToolButton; class iUnitGroup; class nmPebiResultSceneController; +class nmVolumeSceneController; /** - * @brief Saphir 风格的当前结果属性颜色设置对话框。 + * @brief 计算结果与外部三维模型共用的当前属性颜色设置对话框。 * - * 对话框保存打开时的绑定序号、Snapshot ID 和 Property ID,每次 Apply/OK - * 都交给 SceneController 原子复核。Cancel 只丢弃尚未 Apply 的编辑, + * 对话框保存计算结果的绑定序号及快照身份,或三维数据身份,并记录属性 ID。 + * 每次 Apply/OK 都交给对应控制器原子复核。Cancel 只丢弃尚未 Apply 的编辑, * 不回滚已经提交的状态。 */ class NM_SUB_WXS_EXPORT nmWxPebiResultColorScaleSettingsDlg @@ -37,6 +38,10 @@ public: explicit nmWxPebiResultColorScaleSettingsDlg( nmPebiResultSceneController* pSceneController, QWidget* pParent = nullptr); + /** @brief 为外部三维属性创建同一颜色设置窗口,仅开放色带及线性范围;控制器非所有权。 */ + explicit nmWxPebiResultColorScaleSettingsDlg( + nmVolumeSceneController* pVolumeController, + QWidget* pParent = nullptr); /** @brief 子控件由 Qt 对象树自动释放,不修改 Scene 生命周期。 */ virtual ~nmWxPebiResultColorScaleSettingsDlg(); @@ -51,7 +56,7 @@ private slots: void onApplyClicked(); /** @brief 提交成功后关闭对话框,失败时保持打开。 */ void onOkClicked(); - /** @brief 恢复快照属性完整范围,不改变色带、Log 或越界颜色。 */ + /** @brief 恢复当前属性的全模型范围,不改变色带、Log 或越界颜色。 */ void onRestorePropertyRangeClicked(); /** @brief 用户编辑任一上下限后显式进入手工范围模式。 */ void onManualRangeEdited(const QString& sText); @@ -75,8 +80,12 @@ private: /** @brief 创建全部编辑控件和 Apply/OK/Cancel 按钮。 */ void createUi(); + /** @brief 两种构造入口共用初始化流程,保证控件布局与按钮行为一致。 */ + void initializeDialog(); /** @brief 从 Scene 读取初值并记录不可变的预期身份。 */ bool initializeFromScene(); + /** @brief 从当前数据源读取已提交颜色设置及显示范围,不改变场景。 */ + bool readCurrentDisplayState(nmPebiResultDisplaySettings& oSettings, double dVisibleRange[2]) const; /** @brief 从属性目录和全局单位表初始化当前对话框的单位选择。 */ void initializeUnitEditor(); /** @brief 换算成功后将设置和值域写入控件,不触发手工编辑状态。 */ @@ -111,6 +120,8 @@ private: void setErrorText(const QString& sError); nmPebiResultSceneController* m_pSceneController; ///< 非所有权 Scene 真源。 + nmVolumeSceneController* m_pVolumeController; ///< 非所有权三维场景,与计算结果控制器二选一。 + QString m_sExpectedVolumeDataId; ///< 三维窗口打开时的数据身份,阻止过期窗口写入新模型。 quint64 m_nExpectedBindingRevision; ///< 打开时捕获的 Scene 成功绑定序号。 QString m_sExpectedSnapshotId; ///< 打开时捕获的快照稳定 ID。 QString m_sExpectedPropertyId; ///< 打开时捕获的属性稳定 ID。 diff --git a/Include/nmNum/nmSubWxs/nmWxPebiResultControlBar.h b/Include/nmNum/nmSubWxs/nmWxPebiResultControlBar.h index 5582fb9c..fdd97ba1 100644 --- a/Include/nmNum/nmSubWxs/nmWxPebiResultControlBar.h +++ b/Include/nmNum/nmSubWxs/nmWxPebiResultControlBar.h @@ -103,7 +103,7 @@ private: /** @brief 按固定顺序创建属性、导航、时间、播放和设置控件。 */ void createUi(); - /** @brief 镜像三维场景的显示模式和原始属性名,保留但禁用时间、动画及颜色设置。 */ + /** @brief 镜像三维显示模式、属性及颜色设置可用性,保留但禁用时间和动画操作。 */ void refreshFromVolumeController(); /** @brief 按快照属性顺序重建属性项,itemData 保存稳定 ID。 */ void rebuildPropertyItems(const QStringList& listPropertyIds); diff --git a/Src/nmNum/nmSubWxs/nmVolumeSceneController.cpp b/Src/nmNum/nmSubWxs/nmVolumeSceneController.cpp index d023625d..274696a1 100644 --- a/Src/nmNum/nmSubWxs/nmVolumeSceneController.cpp +++ b/Src/nmNum/nmSubWxs/nmVolumeSceneController.cpp @@ -3,10 +3,12 @@ #include "nmVolumeSlicePipeline.h" #include "nmWxVTKRenderContainerWidget.h" #include "nmVTKMeshLayer.h" +#include "nmVTKColorMapCatalog.h" #include "nmVTKScalarBarLayer.h" #include "nmVTKScene.h" #include +#include #include #include #include @@ -26,7 +28,13 @@ 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", "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 线程操作。 */ @@ -42,22 +50,53 @@ QString sceneText(const char* pText) return QCoreApplication::translate("nmVolumeSceneController", pText); } -/** @brief 完整模型与截面共用的全局线性颜色表;常量范围只为渲染作最小扩展。 */ -vtkSmartPointer makeVolumeLookupTable(const nmVolumePropertyDescriptor& oProperty) +/** @brief 校验后构造独立颜色表;只读属性目录,绝不修改已有 Mapper 或已发布数据。 */ +vtkSmartPointer makeVolumeLookupTable(const nmVolumePropertyDescriptor& oProperty, + const nmPebiResultDisplaySettings& oSettings, QString* pError = nullptr) { - double dMinimum = oProperty.m_dMinimum; - double dMaximum = oProperty.m_dMaximum; + 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 = vtkSmartPointer::New(); - pTable->SetNumberOfTableValues(256); - pTable->SetHueRange(0.67, 0.0); + 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); - pTable->Build(); + 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; } @@ -83,6 +122,44 @@ private: 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 @@ -145,23 +222,31 @@ public: } 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, QString::number(oProperty.m_dMinimum, 'g', 8)); + 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 bool bConstant = pProperty->m_dMinimum == pProperty->m_dMaximum; + const nmPebiResultDisplaySettings oSettings = m_mapDisplaySettings.value(sPropertyId); vtkSmartPointer pTable = pPreparedTable; - if(pTable == nullptr) pTable = makeVolumeLookupTable(*pProperty); + 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); - m_pScalarBar->setTitle(pProperty->m_sArrayName); - m_pScalarBar->setLookupTable(pTable); - m_pScalarBar->setConstantValueLabel(bConstant, QString::number(pProperty->m_dMinimum, 'g', 8)); - m_pScalarBar->setNumberOfLabels(bConstant ? 0 : m_pScalarBar->getResponsiveLabelCount()); + applyScalarBarState(*pProperty, oSettings, pTable); m_sPropertyId = sPropertyId; return true; } @@ -173,6 +258,7 @@ public: 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; ///< 是否正在显示截面,而非完整模型。 @@ -295,6 +381,77 @@ bool nmVolumeSceneController::applyProperty(const QString& sPropertyId, QString* 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) || @@ -391,10 +548,12 @@ bool nmVolumeSceneController::applySliceSettings(const nmVolumeSliceSettings& oS if(vtkMath::IsFinite(pValues->GetComponent(nValue, 0))) { bHasFiniteValues = true; break; } } } - // LUT 只使用数据发布时的全局范围,不用截面的局部范围覆盖它。 + // 每次生成截面均读取当前属性的颜色偏好,自动范围始终取完整模型范围。 vtkSmartPointer pTable; if(pProperty != nullptr) { - pTable = makeVolumeLookupTable(*pProperty); + 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) { diff --git a/Src/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.cpp b/Src/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.cpp index ef544a96..526c6db8 100644 --- a/Src/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.cpp +++ b/Src/nmNum/nmSubWxs/nmWxPebiResultColorScaleSettingsDlg.cpp @@ -4,6 +4,7 @@ #include "iUnitHelper.h" #include "nmPebiResultPropertyCatalog.h" #include "nmPebiResultSceneController.h" +#include "nmVolumeSceneController.h" #include "nmVTKColorMapCatalog.h" #include @@ -83,6 +84,7 @@ nmWxPebiResultColorScaleSettingsDlg( QWidget* pParent) : QDialog(pParent), m_pSceneController(pSceneController), + m_pVolumeController(nullptr), m_nExpectedBindingRevision(0), m_pUnitGroup(nullptr), m_bManualRangeEdited(false), @@ -104,6 +106,42 @@ nmWxPebiResultColorScaleSettingsDlg( m_pOkButton(nullptr) { Q_ASSERT(m_pSceneController != nullptr); + initializeDialog(); +} + +nmWxPebiResultColorScaleSettingsDlg:: +nmWxPebiResultColorScaleSettingsDlg( + nmVolumeSceneController* pVolumeController, + QWidget* pParent) + : QDialog(pParent), + m_pSceneController(nullptr), + m_pVolumeController(pVolumeController), + m_nExpectedBindingRevision(0), + m_pUnitGroup(nullptr), + m_bManualRangeEdited(false), + m_bRangeValuesEditedByUser(false), + m_bInitialized(false), + m_pColorMapCombo(nullptr), + m_pMinimumEdit(nullptr), + m_pMaximumEdit(nullptr), + m_pUnitCombo(nullptr), + m_pRestoreRangeButton(nullptr), + m_pLogarithmicCheck(nullptr), + m_pLogReasonLabel(nullptr), + m_pBelowRangeCheck(nullptr), + m_pBelowColorButton(nullptr), + m_pAboveRangeCheck(nullptr), + m_pAboveColorButton(nullptr), + m_pErrorLabel(nullptr), + m_pApplyButton(nullptr), + m_pOkButton(nullptr) +{ + Q_ASSERT(m_pVolumeController != nullptr); + initializeDialog(); +} + +void nmWxPebiResultColorScaleSettingsDlg::initializeDialog() +{ m_dPropertyRange[0] = 0.0; m_dPropertyRange[1] = 0.0; createUi(); @@ -251,6 +289,13 @@ void nmWxPebiResultColorScaleSettingsDlg::onChooseAboveRangeColor() void nmWxPebiResultColorScaleSettingsDlg::refreshLogAvailability() { + if(m_pVolumeController != nullptr) { + // 三维首版只提供线性色带;保持旧控件位置,不因范围变化重新启用 Log。 + m_pLogarithmicCheck->setChecked(false); + m_pLogarithmicCheck->setEnabled(false); + m_pLogReasonLabel->hide(); + return; + } double dDisplayMinimum = 0.0; double dBaseMinimum = 0.0; const bool bMinimumValid = @@ -284,11 +329,13 @@ void nmWxPebiResultColorScaleSettingsDlg::refreshLogAvailability() void nmWxPebiResultColorScaleSettingsDlg::createUi() { + setObjectName(QLatin1String("resultColorSettingsDialog")); setWindowTitle(tr("Color settings for current property")); setModal(true); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); m_pColorMapCombo = new QComboBox(this); + m_pColorMapCombo->setObjectName(QLatin1String("colorMapCombo")); m_pColorMapCombo->setIconSize(QSize(128, 16)); m_pColorMapCombo->setMinimumWidth(280); const QStringList listColorMapIds = @@ -303,17 +350,21 @@ void nmWxPebiResultColorScaleSettingsDlg::createUi() m_pMinimumEdit = new QLineEdit(this); m_pMaximumEdit = new QLineEdit(this); + m_pMinimumEdit->setObjectName(QLatin1String("colorMinimumEdit")); + m_pMaximumEdit->setObjectName(QLatin1String("colorMaximumEdit")); m_pMinimumEdit->setMinimumWidth(220); m_pMinimumEdit->setMaximumWidth(250); m_pMaximumEdit->setMinimumWidth(220); m_pMaximumEdit->setMaximumWidth(250); m_pUnitCombo = new QComboBox(this); + m_pUnitCombo->setObjectName(QLatin1String("colorUnitCombo")); m_pUnitCombo->setMinimumWidth(80); m_pUnitCombo->setMaximumWidth(140); m_pUnitCombo->hide(); m_pMinimumEdit->setAlignment(Qt::AlignRight); m_pMaximumEdit->setAlignment(Qt::AlignRight); m_pRestoreRangeButton = new QToolButton(this); + m_pRestoreRangeButton->setObjectName(QLatin1String("colorRestoreRangeButton")); m_pRestoreRangeButton->setAutoRaise(true); m_pRestoreRangeButton->setFixedSize(36, 36); m_pRestoreRangeButton->setIconSize(QSize(32, 32)); @@ -322,6 +373,7 @@ void nmWxPebiResultColorScaleSettingsDlg::createUi() m_pRestoreRangeButton->setToolTip(tr("Restore property range")); m_pLogarithmicCheck = new QCheckBox(tr("Logarithmic scale"), this); + m_pLogarithmicCheck->setObjectName(QLatin1String("colorLogarithmicCheck")); m_pLogReasonLabel = new QLabel(this); m_pLogReasonLabel->setWordWrap(true); m_pLogReasonLabel->setStyleSheet( @@ -329,12 +381,14 @@ void nmWxPebiResultColorScaleSettingsDlg::createUi() m_pLogReasonLabel->hide(); m_pBelowRangeCheck = new QCheckBox(tr("Below min"), this); + m_pBelowRangeCheck->setObjectName(QLatin1String("colorBelowRangeCheck")); m_pBelowColorButton = new QPushButton(this); m_pBelowColorButton->setFixedSize(44, 22); m_pBelowColorButton->setEnabled(false); m_pBelowColorButton->setToolTip(tr("Below minimum color")); m_pAboveRangeCheck = new QCheckBox(tr("Above max"), this); + m_pAboveRangeCheck->setObjectName(QLatin1String("colorAboveRangeCheck")); m_pAboveColorButton = new QPushButton(this); m_pAboveColorButton->setFixedSize(44, 22); m_pAboveColorButton->setEnabled(false); @@ -391,6 +445,7 @@ void nmWxPebiResultColorScaleSettingsDlg::createUi() pRangeColorLayout->setColumnStretch(3, 1); m_pErrorLabel = new QLabel(this); + m_pErrorLabel->setObjectName(QLatin1String("colorSettingsErrorLabel")); m_pErrorLabel->setWordWrap(true); m_pErrorLabel->setStyleSheet(QString::fromLatin1("color: #b42318;")); m_pErrorLabel->hide(); @@ -403,6 +458,9 @@ void nmWxPebiResultColorScaleSettingsDlg::createUi() m_pApplyButton = new QPushButton(tr("Apply"), this); m_pOkButton = new QPushButton(tr("OK"), this); QPushButton* pCancelButton = new QPushButton(tr("Cancel"), this); + m_pApplyButton->setObjectName(QLatin1String("colorApplyButton")); + m_pOkButton->setObjectName(QLatin1String("colorOkButton")); + pCancelButton->setObjectName(QLatin1String("colorCancelButton")); const QSize oFooterIconSize(16, 16); m_pOkButton->setIcon(QIcon(resultColorIconPath( QString::fromLatin1("OK.png")))); @@ -462,6 +520,21 @@ void nmWxPebiResultColorScaleSettingsDlg::createUi() bool nmWxPebiResultColorScaleSettingsDlg::initializeFromScene() { + if(m_pVolumeController != nullptr) { + const QSharedPointer pData = m_pVolumeController->getData(); + if(pData.isNull()) return false; + m_sExpectedVolumeDataId = pData->getDataId(); + m_sExpectedPropertyId = m_pVolumeController->getCurrentPropertyId(); + double dVisibleRange[2] = { 0.0, 0.0 }; + if(m_sExpectedPropertyId.isEmpty() || + !m_pVolumeController->getCurrentPropertyRange(m_dPropertyRange) || + !readCurrentDisplayState(m_oBaselineSettings, dVisibleRange)) return false; + // 外部属性原名不代表物理单位,按原始数值编辑,不查询 PEBI 属性目录。 + m_sBaseUnit.clear(); + m_sDisplayUnit.clear(); + initializeUnitEditor(); + return populateEditor(m_oBaselineSettings, dVisibleRange); + } if(m_pSceneController == nullptr || !m_pSceneController->hasSnapshot()) { return false; } @@ -488,6 +561,17 @@ bool nmWxPebiResultColorScaleSettingsDlg::initializeFromScene() return populateEditor(m_oBaselineSettings, dVisibleRange); } +bool nmWxPebiResultColorScaleSettingsDlg::readCurrentDisplayState( + nmPebiResultDisplaySettings& oSettings, double dVisibleRange[2]) const +{ + if(m_pVolumeController != nullptr) { + return m_pVolumeController->getCurrentDisplaySettings(oSettings) && + m_pVolumeController->getCurrentDisplayRange(dVisibleRange); + } + return m_pSceneController != nullptr && m_pSceneController->getCurrentDisplaySettings(oSettings) && + m_pSceneController->getCurrentDisplayRange(dVisibleRange); +} + void nmWxPebiResultColorScaleSettingsDlg::initializeUnitEditor() { const bool bSignalsBlocked = m_pUnitCombo->blockSignals(true); @@ -627,21 +711,32 @@ bool nmWxPebiResultColorScaleSettingsDlg::collectEditedSettings( oSettings.m_oAboveRangeColor = m_oEditedAboveRangeColor; oSettings.m_oBelowRangeColor.setAlpha(255); oSettings.m_oAboveRangeColor.setAlpha(255); + if(m_pVolumeController != nullptr) { + // 禁用项不产生隐藏提交值;三维范围外的数据固定使用色带端点颜色。 + oSettings.m_eScaleMode = NM_PebiColorScale_Linear; + oSettings.m_bUseBelowRangeColor = false; + oSettings.m_bUseAboveRangeColor = false; + } return true; } bool nmWxPebiResultColorScaleSettingsDlg::applyEditor() { - if(!m_bInitialized || m_pSceneController == nullptr) { + if(!m_bInitialized || (m_pSceneController == nullptr && m_pVolumeController == nullptr)) { return false; } nmPebiResultDisplaySettings oSettings; QString sError; - if(!collectEditedSettings(oSettings, sError) || - !m_pSceneController->applyCurrentDisplaySettings( - m_nExpectedBindingRevision, - m_sExpectedSnapshotId, m_sExpectedPropertyId, - oSettings, &sError)) { + bool bApplied = collectEditedSettings(oSettings, sError); + if(bApplied) { + bApplied = m_pVolumeController != nullptr + ? m_pVolumeController->applyCurrentDisplaySettings( + m_sExpectedVolumeDataId, m_sExpectedPropertyId, oSettings, &sError) + : m_pSceneController->applyCurrentDisplaySettings( + m_nExpectedBindingRevision, m_sExpectedSnapshotId, m_sExpectedPropertyId, + oSettings, &sError); + } + if(!bApplied) { setErrorText(sError); emit operationFailed(sError); return false; @@ -649,8 +744,7 @@ bool nmWxPebiResultColorScaleSettingsDlg::applyEditor() double dVisibleRange[2] = { 0.0, 0.0 }; nmPebiResultDisplaySettings oCommittedSettings; - if(!m_pSceneController->getCurrentDisplaySettings(oCommittedSettings) || - !m_pSceneController->getCurrentDisplayRange(dVisibleRange)) { + if(!readCurrentDisplayState(oCommittedSettings, dVisibleRange)) { sError = tr("Unable to read the committed color settings."); setErrorText(sError); emit operationFailed(sError); @@ -765,8 +859,11 @@ void nmWxPebiResultColorScaleSettingsDlg::refreshRangeColorButtons() oMaximumColor = oMinimumColor; } - const bool bUseBelowColor = m_pBelowRangeCheck->isChecked(); - const bool bUseAboveColor = m_pAboveRangeCheck->isChecked(); + const bool bAllowRangeColors = m_pVolumeController == nullptr; + m_pBelowRangeCheck->setEnabled(bAllowRangeColors); + m_pAboveRangeCheck->setEnabled(bAllowRangeColors); + const bool bUseBelowColor = bAllowRangeColors && m_pBelowRangeCheck->isChecked(); + const bool bUseAboveColor = bAllowRangeColors && m_pAboveRangeCheck->isChecked(); m_pBelowColorButton->setEnabled(bUseBelowColor); m_pAboveColorButton->setEnabled(bUseAboveColor); updateColorButton( diff --git a/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp b/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp index 09e0596a..54e8d4db 100644 --- a/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp +++ b/Src/nmNum/nmSubWxs/nmWxPebiResultControlBar.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -255,22 +256,27 @@ void nmWxPebiResultControlBar::onTimeIndexChanged(int nIndex) void nmWxPebiResultControlBar::onColorSettingsClicked() { - if(m_pVolumeSceneController != nullptr) return; if(m_pPlaybackController == nullptr || m_pSceneController == nullptr || m_bExportInProgress || m_bExternalInteractionLocked || - m_pPlaybackController->isPlaying() || - !m_pSceneController->hasSnapshot()) { + m_pPlaybackController->isPlaying()) { return; } - nmWxPebiResultColorScaleSettingsDlg oDialog( - m_pSceneController, this); - connect(&oDialog, SIGNAL(displaySettingsApplied()), + // 同一底栏入口按数据源创建同一对话框,三维场景无需计算结果快照即可设置颜色。 + QScopedPointer pDialog; + if(m_pVolumeSceneController != nullptr) { + if(!m_pVolumeSceneController->hasScalarProperty()) return; + pDialog.reset(new nmWxPebiResultColorScaleSettingsDlg(m_pVolumeSceneController, this)); + } else { + if(!m_pSceneController->hasSnapshot()) return; + pDialog.reset(new nmWxPebiResultColorScaleSettingsDlg(m_pSceneController, this)); + } + connect(pDialog.data(), SIGNAL(displaySettingsApplied()), this, SIGNAL(operationSucceeded())); - connect(&oDialog, SIGNAL(operationFailed(QString)), + connect(pDialog.data(), SIGNAL(operationFailed(QString)), this, SLOT(forwardOperationFailure(QString))); - oDialog.exec(); + pDialog->exec(); } void nmWxPebiResultControlBar::onFirstFrameClicked() @@ -549,7 +555,7 @@ void nmWxPebiResultControlBar::refreshFromVolumeController() m_pPropertyCombo->setEnabled(bAvailable && !pData->getProperties().isEmpty()); // 外部数据没有计算时间轴;所有旧按钮保留原位置,仅禁用不适用的操作。 clearTimeItems(); - m_pColorSettingsButton->setEnabled(false); + m_pColorSettingsButton->setEnabled(bAvailable && m_pVolumeSceneController->hasScalarProperty()); m_pFirstButton->setEnabled(false); m_pPreviousButton->setEnabled(false); m_pNextButton->setEnabled(false); @@ -630,6 +636,7 @@ void nmWxPebiResultControlBar::createUi() m_pColorSettingsButton = createControlButton( this, QString::fromLatin1("NmColorScaleSettings.png"), tr("Color settings")); + m_pColorSettingsButton->setObjectName(QLatin1String("resultColorSettingsButton")); m_pFirstButton = createControlButton( this, QString::fromLatin1("NmFirstStep.png"),