feat: 接通三维模型与切片的颜色设置

- 复用底栏颜色设置,支持色带、手工范围及恢复全模型范围
- 按属性独立保存设置,同步模型、截面和色标,不重算切片
- 增加身份校验、非法范围校验及绑定失败回退
- 补充测试和验收说明,同步中文翻译及 qm
feature/PEBI-Region-Average-Pressure-20261102
lh 2 weeks ago
parent 21745a3bb0
commit 17ef272cbe

Binary file not shown.

@ -9102,6 +9102,30 @@ Average pressure in contour: %2 MPa</source>
<source>Unable to bind the volume scalar property.</source>
<translation>无法绑定三维模型的标量属性。</translation>
</message>
<message>
<source>The color settings are invalid.</source>
<translation>颜色设置无效。</translation>
</message>
<message>
<source>The selected color map is unavailable.</source>
<translation>所选色带不可用。</translation>
</message>
<message>
<source>The manual range must contain two finite increasing values.</source>
<translation>手工范围必须为两个有限数值,且最小值小于最大值。</translation>
</message>
<message>
<source>Unable to construct the color mapping.</source>
<translation>无法构建颜色映射。</translation>
</message>
<message>
<source>The color settings no longer match the current volume property.</source>
<translation>颜色设置已不匹配当前三维模型属性,请重新打开设置窗口。</translation>
</message>
<message>
<source>Unable to apply the color settings.</source>
<translation>无法应用颜色设置。</translation>
</message>
</context>
<context>
<name>nmVolumeImportTask</name>

@ -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 返回完整模型与截面共用的显示模式,供底部工具栏同步。 */

@ -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。

@ -103,7 +103,7 @@ private:
/** @brief 按固定顺序创建属性、导航、时间、播放和设置控件。 */
void createUi();
/** @brief 镜像三维场景的显示模式和原始属性名,保留但禁用时间、动画及颜色设置。 */
/** @brief 镜像三维显示模式、属性及颜色设置可用性,保留但禁用时间和动画操作。 */
void refreshFromVolumeController();
/** @brief 按快照属性顺序重建属性项,itemData 保存稳定 ID。 */
void rebuildPropertyItems(const QStringList& listPropertyIds);

@ -3,10 +3,12 @@
#include "nmVolumeSlicePipeline.h"
#include "nmWxVTKRenderContainerWidget.h"
#include "nmVTKMeshLayer.h"
#include "nmVTKColorMapCatalog.h"
#include "nmVTKScalarBarLayer.h"
#include "nmVTKScene.h"
#include <QCoreApplication>
#include <QMap>
#include <QScopedPointer>
#include <QThread>
#include <QUuid>
@ -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<vtkLookupTable> makeVolumeLookupTable(const nmVolumePropertyDescriptor& oProperty)
/** @brief 校验后构造独立颜色表;只读属性目录,绝不修改已有 Mapper 或已发布数据。 */
vtkSmartPointer<vtkLookupTable> 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<vtkLookupTable>();
}
if(!nmVTKColorMapCatalog::contains(oSettings.m_sColorMapId)) {
if(pError != nullptr) *pError = sceneText("The selected color map is unavailable.");
return vtkSmartPointer<vtkLookupTable>();
}
double dMinimum = oSettings.m_bHasManualRange ? oSettings.m_dManualMinimum : oProperty.m_dMinimum;
double dMaximum = oSettings.m_bHasManualRange ? oSettings.m_dManualMaximum : oProperty.m_dMaximum;
if(!vtkMath::IsFinite(dMinimum) || !vtkMath::IsFinite(dMaximum) || dMinimum > dMaximum ||
(oSettings.m_bHasManualRange && dMinimum == dMaximum)) {
if(pError != nullptr) *pError = sceneText("The manual range must contain two finite increasing values.");
return vtkSmartPointer<vtkLookupTable>();
}
const bool bSingleValue = !oSettings.m_bHasManualRange && dMinimum == dMaximum;
if(dMinimum == dMaximum) {
// 常量对外仍显示真实数值;只扩展内部范围,并兼容接近 double 上限的值。
const double dPadding = qMax(1.0, std::fabs(dMinimum)) * 1e-6;
dMaximum += dPadding;
if(!vtkMath::IsFinite(dMaximum)) { dMaximum = dMinimum; dMinimum -= dPadding; }
}
vtkSmartPointer<vtkLookupTable> pTable = vtkSmartPointer<vtkLookupTable>::New();
pTable->SetNumberOfTableValues(256);
pTable->SetHueRange(0.67, 0.0);
vtkSmartPointer<vtkLookupTable> pTable = nmVTKColorMapCatalog::createLookupTable(oSettings.m_sColorMapId);
if(pTable == nullptr || !vtkMath::IsFinite(dMinimum) || !vtkMath::IsFinite(dMaximum) || dMinimum >= dMaximum) {
if(pError != nullptr) *pError = sceneText("Unable to construct the color mapping.");
return vtkSmartPointer<vtkLookupTable>();
}
pTable->SetTableRange(dMinimum, dMaximum);
pTable->SetScaleToLinear();
pTable->SetUseBelowRangeColor(0);
pTable->SetUseAboveRangeColor(0);
pTable->IndexedLookupOff();
pTable->SetNanColor(0.65, 0.65, 0.65, 1.0);
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<vtkLookupTable> m_pOldMeshTable; ///< 回退所需的旧模型颜色表强引用。
vtkSmartPointer<vtkLookupTable> 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<vtkLookupTable> 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<vtkPolyData> m_pSliceData; ///< 与 Cutter 断开的独立截面,关闭显示时保留。
nmVolumeSliceSettings m_oSliceSettings; ///< 当前数据最后一次成功应用的切片参数。
QMap<QString, nmPebiResultDisplaySettings> 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<vtkLookupTable> pTable = makeVolumeLookupTable(*pProperty, oSettings, pError);
if(pTable == nullptr) return false;
const nmPebiResultDisplaySettings oNormalized = oSettings.normalized();
if(oNormalized == m_pPrivate->m_mapDisplaySettings.value(sExpectedPropertyId)) return true;
QMap<QString, nmPebiResultDisplaySettings> mapCandidateSettings = m_pPrivate->m_mapDisplaySettings;
mapCandidateSettings.insert(sExpectedPropertyId, oNormalized);
// 缓存截面即使隐藏也同步;全缺失和空截面不绑定标量,只更新模型与全局色标。
nmVTKMeshLayer* pColoredSlice = m_pPrivate->m_pSliceMesh;
if(pColoredSlice != nullptr && !pColoredSlice->isScalarVisible()) pColoredSlice = nullptr;
VolumeColorBindingGuard oGuard(m_pPrivate->m_pMesh, pColoredSlice);
if(!oGuard.bind(pTable)) {
if(pError != nullptr) *pError = sceneText("Unable to apply the color settings.");
return false;
}
m_pPrivate->applyScalarBarState(*pProperty, oNormalized, pTable);
m_pPrivate->m_mapDisplaySettings.swap(mapCandidateSettings);
oGuard.release();
if(m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->renderView();
return true;
} catch(const std::bad_alloc&) {
if(pError != nullptr) *pError = sceneText("Not enough memory to display the volume mesh.");
return false;
}
}
void nmVolumeSceneController::setMeshDisplayMode(nmVTKMeshLayer::DisplayMode eMode)
{
if(!hasData() || !nmVTKMeshLayer::isValidDisplayMode(eMode) ||
@ -391,10 +548,12 @@ bool nmVolumeSceneController::applySliceSettings(const nmVolumeSliceSettings& oS
if(vtkMath::IsFinite(pValues->GetComponent(nValue, 0))) { bHasFiniteValues = true; break; }
}
}
// LUT 只使用数据发布时的全局范围,不用截面的局部范围覆盖它。
// 每次生成截面均读取当前属性的颜色偏好,自动范围始终取完整模型范围。
vtkSmartPointer<vtkLookupTable> 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) {

@ -4,6 +4,7 @@
#include "iUnitHelper.h"
#include "nmPebiResultPropertyCatalog.h"
#include "nmPebiResultSceneController.h"
#include "nmVolumeSceneController.h"
#include "nmVTKColorMapCatalog.h"
#include <QCheckBox>
@ -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<const nmVolumeData> 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(

@ -15,6 +15,7 @@
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QScopedPointer>
#include <QSize>
#include <QSizePolicy>
#include <QToolButton>
@ -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<nmWxPebiResultColorScaleSettingsDlg> 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"),

Loading…
Cancel
Save