diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 40d091fa..e6791e84 100644 Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ 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"),