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"),