fix(nmNum): 完善v3成果保存加载及无Map恢复

- 接入跨窗口数值成果保存会话和代次发布
- 使用候选Manager加载,失败时保留原分析数据
- 完整恢复井UUID、数值面板、网格及结果窗口
- 支持未打开Map和网格时保存数值成果
- 修复成果加载后延迟打开Map时边界图元不可见
- 补充v3持久化回归测试
feature/pebi-result-snapshot-20260831
lh 1 month ago
parent e0577d1649
commit 50c2ab93f4

@ -828,6 +828,12 @@ public:
private:
/** @brief 按 UUID 给当前实时井统一绑定或解除最后结果快照弱引用。 */
void rebindPebiResultSnapshotToWells();
/** @brief 在候选 Manager 内完整读取并校验一套 v3 成果。 */
bool loadNmResultV3InPlace(const QString& sLoadAnalDir);
/** @brief 通过交换所有权一次发布已完整校验的候选项目状态。 */
void swapLoadedProjectState(nmDataAnalyzeManager& oLoadedManager);
/** @brief 项目状态替换后用新对象地址重建属性注册表。 */
void rebuildLoadedAttributeRegistry();
/** @brief 创建本数据管理器的成果窗口;只用于读取本成果上下文,不拥有对象。 */
iSubWndFitting* m_pOwnerFitting;
/** @brief 保护后台使用计数和析构等待条件。 */

@ -65,8 +65,9 @@ class NM_DATA_EXPORT nmDataOutline : public nmDataBase {
int getBackgrdAlpha() const;
void setBackgrdAlpha(int nAlpha);
// 视觉属性是否有效(仅从已保存文件加载后为true,新建时为false)
// 视觉属性是否有效;未创建过Map图元的边界保持false。
bool hasVisualProps() const;
// 设置视觉属性有效状态,图元完成样式同步后置为true。
void setVisualPropsValid(bool bValid);
signals:
@ -115,7 +116,7 @@ class NM_DATA_EXPORT nmDataOutline : public nmDataBase {
double m_dPenWidth; // 画笔宽度
QColor m_clrBackgrd; // 填充背景色
int m_nBackgrdAlpha; // 填充透明度 (0-255)
bool m_bHasVisualProps; // 视觉属性是否有效(从文件加载后为true)
bool m_bHasVisualProps; // 视觉属性是否已由Map图元初始化
};
#endif // NMDATARECTOUTLINE_H

@ -67,6 +67,15 @@ public:
QString& sMainJsonPath,
nmNumericalFileReference* pMainReference = NULL,
QString* pError = NULL);
/**
* @brief 校验已发布根清单,并查询清单是否明确引用指定窗口。
* @param bContainsWindow 返回窗口是否存在;不存在不是成果损坏。
*/
static bool containsPublishedWindow(
const QString& sResultRootDirectory,
const QString& sWindowId,
bool& bContainsWindow,
QString* pError = NULL);
/** @brief 只校验根清单结构、窗口唯一性和全部主 JSON 哈希。 */
static bool validatePublishedManifest(
const QString& sResultRootDirectory,

@ -154,6 +154,8 @@ public:
void slotHandleMdiClosing(bool& bAllowClose);
// 外层成果窗口析构后,移除保存的关闭状态
void slotHandleMdiDestroyed(QObject* pObject);
// 当前外层保存调用栈结束后,允许下一次保存重新发布完整代次
void slotClearCompletedNumericalSaveRoots();
protected:
@ -191,6 +193,8 @@ public:
/** @brief 当前显式跨窗口保存会话;析构时自动中止未提交代次。 */
nmNumericalSaveSession* m_pNumericalSaveSession;
/** @brief 本轮外层回调中已经完整发布的成果根目录,避免逐窗口重复写入。 */
QSet<QString> m_setCompletedNumericalSaveRoots;
};
/**

@ -77,6 +77,7 @@
#include <QFile>
#include <QFileInfo>
#include <QMessageBox>
#include <QUuid>
// 井基参数名 → 数据成员访问器的映射描述
struct WellParaDesc {
@ -4742,12 +4743,15 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
// 第二步:先从 JSON 井数组建立唯一 WellCode 到井型的索引,尚不修改当前内存数据。
QMap<QString, NM_WELL_MODEL> mapSavedWellTypes;
QSet<QString> setSavedWellInstanceIds;
const rapidjson::Value& vecWellsJson = doc["Wells"];
for(rapidjson::SizeType nIndex = 0; nIndex < vecWellsJson.Size(); ++nIndex) {
const rapidjson::Value& oWellJson = vecWellsJson[nIndex];
if(!oWellJson.IsObject() ||
!oWellJson.HasMember("WellCode") ||
!oWellJson["WellCode"].IsString() ||
!oWellJson.HasMember("WellInstanceId") ||
!oWellJson["WellInstanceId"].IsString() ||
!oWellJson.HasMember("WellType") ||
!oWellJson["WellType"].IsInt()) {
qWarning() << "Numerical project contains an invalid well entry:" << filePath;
@ -4756,6 +4760,11 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
const QString sWellCode = QString::fromUtf8(
oWellJson["WellCode"].GetString());
const QString sWellInstanceId = QString::fromUtf8(
oWellJson["WellInstanceId"].GetString());
const QUuid oWellInstanceUuid(sWellInstanceId);
const QString sNormalizedWellInstanceId =
oWellInstanceUuid.toString().remove('{').remove('}');
const NM_WELL_MODEL eWellType = static_cast<NM_WELL_MODEL>(
oWellJson["WellType"].GetInt());
const bool bSupportedWellType =
@ -4763,12 +4772,16 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
eWellType == NM_WELL_MODEL::Vertical_Fractured_Well ||
eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
if(sWellCode.isEmpty() || !bSupportedWellType ||
mapSavedWellTypes.contains(sWellCode)) {
qWarning() << "Numerical project contains an empty, duplicate or unsupported WellCode:"
<< sWellCode;
mapSavedWellTypes.contains(sWellCode) ||
oWellInstanceUuid.isNull() ||
sNormalizedWellInstanceId != sWellInstanceId ||
setSavedWellInstanceIds.contains(sWellInstanceId)) {
qWarning() << "Numerical project contains an invalid well identity:"
<< sWellCode << sWellInstanceId;
return false;
}
mapSavedWellTypes.insert(sWellCode, eWellType);
setSavedWellInstanceIds.insert(sWellInstanceId);
}
if(!mapSavedWellTypes.contains(sSavedPrimaryWellCode)) {
qWarning() << "Primary WellCode is absent from Wells:" << sSavedPrimaryWellCode;
@ -5391,17 +5404,24 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath)
// 反序列化函数没有返回值,因此必须再次核对实际创建的井目录。
// JSON 预校验目录与内存目录必须在 WellCode、井型和数量上完全一致。
QMap<QString, NM_WELL_MODEL> mapLoadedWellTypes;
QSet<QString> setLoadedWellInstanceIds;
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) {
nmDataWellBase* pWellData = m_vWellData[nIndex];
if(pWellData == nullptr || pWellData->getWellCode().isEmpty() ||
mapLoadedWellTypes.contains(pWellData->getWellCode())) {
mapLoadedWellTypes.contains(pWellData->getWellCode()) ||
!setSavedWellInstanceIds.contains(
pWellData->getWellInstanceId()) ||
setLoadedWellInstanceIds.contains(
pWellData->getWellInstanceId())) {
qWarning() << "Loaded numerical project contains an invalid WellCode:";
return false;
}
mapLoadedWellTypes.insert(pWellData->getWellCode(),
pWellData->getWellType());
setLoadedWellInstanceIds.insert(pWellData->getWellInstanceId());
}
if(mapLoadedWellTypes != mapSavedWellTypes) {
if(mapLoadedWellTypes != mapSavedWellTypes ||
setLoadedWellInstanceIds != setSavedWellInstanceIds) {
qWarning() << "Loaded well directory differs from validated JSON:"
<< filePath;
return false;
@ -5475,15 +5495,25 @@ bool nmDataAnalyzeManager::WriteProjectData(
}
// 第一步:保存前校验 Map 井目录,持久化身份只允许非空且唯一的 WellCode。
QSet<QString> setWellCodes;
QSet<QString> setWellInstanceIds;
QMap<QString, NM_WELL_MODEL> mapWellTypes;
for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) {
nmDataWellBase* pWellData = m_vWellData[nIndex];
const QString sWellInstanceId = pWellData == nullptr
? QString() : pWellData->getWellInstanceId();
const QUuid oWellInstanceUuid(sWellInstanceId);
const QString sNormalizedWellInstanceId =
oWellInstanceUuid.toString().remove('{').remove('}');
if(pWellData == nullptr || pWellData->getWellCode().isEmpty() ||
setWellCodes.contains(pWellData->getWellCode())) {
qWarning() << "Cannot save numerical project: empty or duplicate WellCode.";
setWellCodes.contains(pWellData->getWellCode()) ||
oWellInstanceUuid.isNull() ||
sNormalizedWellInstanceId != sWellInstanceId ||
setWellInstanceIds.contains(sWellInstanceId)) {
qWarning() << "Cannot save numerical project: invalid well identity.";
return false;
}
setWellCodes.insert(pWellData->getWellCode());
setWellInstanceIds.insert(sWellInstanceId);
mapWellTypes.insert(pWellData->getWellCode(), pWellData->getWellType());
}
@ -6319,6 +6349,39 @@ bool nmDataAnalyzeManager::saveNmResult(QString sRstCode, iSubWndFitting* pSubWn
}
bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
{
try {
// v3 先在独立候选对象上完成全部解析和业务校验。
// 任一步失败时只析构候选,当前窗口已经发布的数据保持不变。
nmDataAnalyzeManager oLoadedManager;
oLoadedManager.m_pOwnerFitting = m_pOwnerFitting;
oLoadedManager.initPvtParaFromSubFit();
if(!oLoadedManager.loadNmResultV3InPlace(sLoadAnalDir)) {
return false;
}
// 井构造时不能依赖尚未发布的候选 Manager,完整读取后统一绑定储层。
for(int nIndex = 0; nIndex < oLoadedManager.m_vWellData.size(); ++nIndex) {
nmDataWellBase* pWell = oLoadedManager.m_vWellData[nIndex];
if(pWell != NULL) {
pWell->m_pReservoir = oLoadedManager.m_reservoirData;
}
}
swapLoadedProjectState(oLoadedManager);
rebuildLoadedAttributeRegistry();
rebindPebiResultSnapshotToWells();
emit sigPebiResultSnapshotChanged();
return true;
}
catch(const std::bad_alloc&) {
qWarning() << "Not enough memory to stage numerical v3 result.";
return false;
}
}
bool nmDataAnalyzeManager::loadNmResultV3InPlace(
const QString& sLoadAnalDir)
{
try {
// 外部框架仍传入旧的“成果根/窗口标识”逻辑路径;v3 只从根清单解析真实代次。
@ -6714,6 +6777,78 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir)
#endif
}
void nmDataAnalyzeManager::swapLoadedProjectState(
nmDataAnalyzeManager& oLoadedManager)
{
// 这里只交换项目数据所有权。窗口、Plot、后台任务和同步原语仍属于当前 Manager。
qSwap(m_vWellData, oLoadedManager.m_vWellData);
qSwap(m_vFaultData, oLoadedManager.m_vFaultData);
qSwap(m_vFractureData, oLoadedManager.m_vFractureData);
qSwap(m_vRegionData, oLoadedManager.m_vRegionData);
qSwap(m_vRegionMarkData, oLoadedManager.m_vRegionMarkData);
qSwap(m_vecLayers, oLoadedManager.m_vecLayers);
qSwap(m_vecPropertyInterpolationDataSets,
oLoadedManager.m_vecPropertyInterpolationDataSets);
qSwap(m_outlineData, oLoadedManager.m_outlineData);
qSwap(m_axisData, oLoadedManager.m_axisData);
qSwap(m_pAutomaticFittingData,
oLoadedManager.m_pAutomaticFittingData);
qSwap(m_reservoirData, oLoadedManager.m_reservoirData);
qSwap(m_pGeoRefData, oLoadedManager.m_pGeoRefData);
qSwap(m_pSensitiveData, oLoadedManager.m_pSensitiveData);
qSwap(m_pebiPvtPara, oLoadedManager.m_pebiPvtPara);
qSwap(m_pMixedResults, oLoadedManager.m_pMixedResults);
qSwap(m_pTimeStep, oLoadedManager.m_pTimeStep);
qSwap(m_pCurDataWell, oLoadedManager.m_pCurDataWell);
qSwap(m_eSolverModelType, oLoadedManager.m_eSolverModelType);
qSwap(m_nPebiSolverType, oLoadedManager.m_nPebiSolverType);
qSwap(m_nPebiOmpThreads, oLoadedManager.m_nPebiOmpThreads);
qSwap(m_nPebiIluReuseSteps, oLoadedManager.m_nPebiIluReuseSteps);
qSwap(m_oNumericalAnalysisCase,
oLoadedManager.m_oNumericalAnalysisCase);
qSwap(m_pPebiResultSnapshot,
oLoadedManager.m_pPebiResultSnapshot);
qSwap(m_sCurrentResultWellInstanceId,
oLoadedManager.m_sCurrentResultWellInstanceId);
vtkSmartPointer<vtkUnstructuredGrid> pGrid =
m_pVtkUnstructuredGrid;
m_pVtkUnstructuredGrid = oLoadedManager.m_pVtkUnstructuredGrid;
oLoadedManager.m_pVtkUnstructuredGrid = pGrid;
pGrid = m_pResultBaseGrid;
m_pResultBaseGrid = oLoadedManager.m_pResultBaseGrid;
oLoadedManager.m_pResultBaseGrid = pGrid;
qSwap(m_mapTimeStepDataP, oLoadedManager.m_mapTimeStepDataP);
qSwap(m_mapWellLocations, oLoadedManager.m_mapWellLocations);
qSwap(m_dScalarRangeP[0], oLoadedManager.m_dScalarRangeP[0]);
qSwap(m_dScalarRangeP[1], oLoadedManager.m_dScalarRangeP[1]);
qSwap(m_bIsLoadData, oLoadedManager.m_bIsLoadData);
}
void nmDataAnalyzeManager::rebuildLoadedAttributeRegistry()
{
if(m_pAttrRegistry == NULL) {
return;
}
// 注册表对象本身不替换,避免破坏已经建立的 UI 信号连接。
m_pAttrRegistry->clear();
if(m_reservoirData != NULL) {
m_pAttrRegistry->regAttr("h", &m_reservoirData->getThickness());
m_pAttrRegistry->regAttr("Pi", &m_reservoirData->getInitialPressure());
m_pAttrRegistry->regAttr("K", &m_reservoirData->getPermeability());
m_pAttrRegistry->regAttr("phi", &m_reservoirData->getPorosity());
m_pAttrRegistry->regAttr("Cti", &m_reservoirData->getCt());
m_pAttrRegistry->regAttr("Cf", &m_reservoirData->getCf());
m_pAttrRegistry->regAttr("Soi", &m_reservoirData->getSoi());
m_pAttrRegistry->regAttr("Swi", &m_reservoirData->getSwi());
}
syncWellAttrs();
syncGeometryAttrs();
}
bool nmDataAnalyzeManager::ensureDirectoryExists(const QString & dirPath)
{
QDir dir(dirPath);

@ -98,6 +98,9 @@ rapidjson::Value nmDataOutline::ToJsonValue(rapidjson::Document::AllocatorType&
outlineObject.AddMember("PlotVisible", m_bPlotVisible, allocator); // 使用m_bPlotVisible
// 序列化视觉属性
// 未打开Map时,边界只有几何数据,视觉属性尚未由图元初始化。
// 必须显式保存该状态,避免加载时把默认的0线宽误当成有效样式。
outlineObject.AddMember("HasVisualProps", m_bHasVisualProps, allocator);
outlineObject.AddMember("PenWidth", m_dPenWidth, allocator);
outlineObject.AddMember("BackgrdR", m_clrBackgrd.red(), allocator);
outlineObject.AddMember("BackgrdG", m_clrBackgrd.green(), allocator);
@ -163,22 +166,43 @@ void nmDataOutline::FromJsonValue(const rapidjson::Value& jsonValue)
m_bPlotVisible = jsonValue["PlotVisible"].GetBool(); // 使用m_bPlotVisible
}
// 反序列化视觉属性
bool bHasVisual = false;
// 反序列化视觉属性。早期v3没有HasVisualProps,线宽大于0时才认为
// 样式有效;这样可直接修复“未打开Map就保存”产生的0线宽成果。
bool bVisualFieldsComplete = true;
if (jsonValue.HasMember("PenWidth") && jsonValue["PenWidth"].IsNumber()) {
m_dPenWidth = jsonValue["PenWidth"].GetDouble();
bHasVisual = true;
} else {
bVisualFieldsComplete = false;
}
int r = 0, g = 0, b = 255;
if (jsonValue.HasMember("BackgrdR") && jsonValue["BackgrdR"].IsInt()) r = jsonValue["BackgrdR"].GetInt();
if (jsonValue.HasMember("BackgrdG") && jsonValue["BackgrdG"].IsInt()) g = jsonValue["BackgrdG"].GetInt();
if (jsonValue.HasMember("BackgrdB") && jsonValue["BackgrdB"].IsInt()) b = jsonValue["BackgrdB"].GetInt();
if (jsonValue.HasMember("BackgrdR") && jsonValue["BackgrdR"].IsInt()) {
r = jsonValue["BackgrdR"].GetInt();
} else {
bVisualFieldsComplete = false;
}
if (jsonValue.HasMember("BackgrdG") && jsonValue["BackgrdG"].IsInt()) {
g = jsonValue["BackgrdG"].GetInt();
} else {
bVisualFieldsComplete = false;
}
if (jsonValue.HasMember("BackgrdB") && jsonValue["BackgrdB"].IsInt()) {
b = jsonValue["BackgrdB"].GetInt();
} else {
bVisualFieldsComplete = false;
}
m_clrBackgrd = QColor(r, g, b);
if (jsonValue.HasMember("BackgrdAlpha") && jsonValue["BackgrdAlpha"].IsInt()) {
m_nBackgrdAlpha = jsonValue["BackgrdAlpha"].GetInt();
bHasVisual = true;
} else {
bVisualFieldsComplete = false;
}
m_bHasVisualProps = bHasVisual;
const bool bSavedVisualProps =
jsonValue.HasMember("HasVisualProps") &&
jsonValue["HasVisualProps"].IsBool()
? jsonValue["HasVisualProps"].GetBool()
: true;
m_bHasVisualProps = bSavedVisualProps &&
bVisualFieldsComplete && m_dPenWidth > 0.0;
syncGeometryAttributes();
}

@ -4,6 +4,7 @@
#include "nmPebiResultSnapshot.h"
#include <QDir>
#include <QByteArray>
#include <QFile>
#include <QTextStream>
#include <QCoreApplication>
@ -19,14 +20,20 @@ nmDataWellBase::nmDataWellBase()
, m_pReservoir(nullptr)
{
m_sWellInstanceId = createWellInstanceId();
m_pReservoir = nmDataAnalyzeManager::getCurrentInstance()->getReservoirData();
nmDataAnalyzeManager* pDataManager =
nmDataAnalyzeManager::getCurrentInstance();
m_pReservoir = pDataManager == nullptr
? nullptr : pDataManager->getReservoirData();
m_wellName = "";
m_x = nmDataAttribute("X", 0.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_y = nmDataAttribute("Y", 0.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_radius = nmDataAttribute("Radius", 0.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_drillFloorElevation = nmDataAttribute("Drill floor elevation", 0.0, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_zw = nmDataAttribute("Zw", 22.5, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_wellLength = nmDataAttribute("Well length", m_pReservoir->getThickness().getValue(), "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
// 候选加载阶段尚未发布储层对象,井长随后会由 JSON 完整恢复。
const QVariant oInitialWellLength = m_pReservoir == nullptr
? QVariant(0.0) : m_pReservoir->getThickness().getValue();
m_wellLength = nmDataAttribute("Well length", oInitialWellLength, "m", UNIT_TYPE_LENGTH, QStringList(), QStringList() << "m" << "cm" << "mm" << "in" << "0.1 in" << "ft" << "mile" << "km");
m_rateDependentSkin = nmDataAttribute("Rate dependent skin", false, "");
m_dSdQ = nmDataAttribute("dS/dQ", 0.0, "1/B/D", UNIT_TYPE_FLOW_RATE_RECIPROCAL, QStringList(), QStringList() << "1/B/D" << "1/MMm^3/D" << "1/Mcf/D" << "1/Mm^3/D" << "1/Mm^3/hr"
@ -179,6 +186,13 @@ rapidjson::Value nmDataWellBase::ToJsonValue(rapidjson::Document::AllocatorType&
// 序列化井编码
wellObject.AddMember("WellCode", rapidjson::Value(m_wellCode.toStdString().c_str(), allocator).Move(), allocator);
// UUID 是实时井、井历史和结果快照之间唯一允许的关联键。
const QByteArray baWellInstanceId = m_sWellInstanceId.toUtf8();
wellObject.AddMember("WellInstanceId",
rapidjson::Value(baWellInstanceId.constData(),
static_cast<rapidjson::SizeType>(baWellInstanceId.size()),
allocator).Move(), allocator);
// 序列化井类型
wellObject.AddMember("WellType", rapidjson::Value(static_cast<int>(m_eWellType)), allocator);
@ -247,6 +261,13 @@ void nmDataWellBase::FromJsonValue(const rapidjson::Value& jsonValue)
m_wellCode = QString::fromUtf8(jsonValue["WellCode"].GetString());
}
// v3 已在 Manager 中完成格式校验,这里恢复求解时保存的稳定井身份。
if(jsonValue.HasMember("WellInstanceId") &&
jsonValue["WellInstanceId"].IsString()) {
restoreWellInstanceId(QString::fromUtf8(
jsonValue["WellInstanceId"].GetString()));
}
// 反序列化井类型 (m_eWellType)
if(jsonValue.HasMember("WellType") && jsonValue["WellType"].IsInt()) {
m_eWellType = static_cast<NM_WELL_MODEL>(jsonValue["WellType"].GetInt());

@ -209,7 +209,17 @@ bool loadManifest(const QString& sResultRootDirectory,
setReferencedPaths.insert(oWindow.m_oMainJson.m_sRelativePath);
mapWindows.insert(oWindow.m_sWindowId, oWindow);
}
return !mapWindows.isEmpty() && bReferencesCurrentGeneration
// 删除全部数值分析窗口时允许发布空清单;它明确表示本成果当前没有数值载荷。
if(mapWindows.isEmpty())
{
const QString sGenerationDirectory = QDir(
sResultRootDirectory).filePath(
"NumericalGenerations/" + sGenerationId);
return QFileInfo(sGenerationDirectory).isDir()
? true
: setError(pError, "Empty numerical manifest generation is missing.");
}
return bReferencesCurrentGeneration
? true
: setError(pError, "Numerical root manifest does not reference its current generation.");
}
@ -408,9 +418,9 @@ bool nmNumericalSaveSession::commit(QString* pError)
{
pError->clear();
}
if(!m_bActive || m_mapWindows.isEmpty())
if(!m_bActive)
{
return setError(pError, "Numerical save session has no window generation.");
return setError(pError, "Numerical save session is not active.");
}
rapidjson::Document oDocument;
@ -504,6 +514,8 @@ bool nmNumericalSaveSession::commit(QString* pError)
m_bActive = false;
// 只清理新清单未引用的代次;文件占用导致失败不影响已发布清单。
// 空窗口清单仍保留一个空代次目录,供根清单发布后完整性复核。
setReferencedGenerations.insert(m_sGenerationId);
oIt = m_mapWindows.constBegin();
for(; oIt != m_mapWindows.constEnd(); ++oIt)
{
@ -592,6 +604,33 @@ bool nmNumericalSaveSession::resolveWindowMainJson(
return true;
}
bool nmNumericalSaveSession::containsPublishedWindow(
const QString& sResultRootDirectory,
const QString& sWindowId,
bool& bContainsWindow,
QString* pError)
{
bContainsWindow = false;
QString sNormalizedWindowId;
if(!nmNumericalResultPersistence::normalizeRelativePath(
sWindowId, sNormalizedWindowId) ||
sNormalizedWindowId != sWindowId ||
sNormalizedWindowId.contains('/'))
{
return setError(pError, "Numerical window identifier is invalid.");
}
QMap<QString, nmNumericalSavedWindow> mapWindows;
if(!loadManifest(sResultRootDirectory, mapWindows, NULL, pError))
{
return false;
}
// 空清单或未引用当前窗口表示该分析没有数值成果,不属于加载错误。
bContainsWindow = mapWindows.contains(sWindowId);
return true;
}
bool nmNumericalSaveSession::validatePublishedManifest(
const QString& sResultRootDirectory,
QString* pError)

@ -688,6 +688,11 @@ void nmPlotGraphicBinder::bindOutlineToGraphic(nmObjBase* pGraphic, nmDataOutlin
pGraphic->setName(pData->getName());
_pushOutlineDataToGraphic(pGraphic, pData);
if (!pData->hasVisualProps()) {
// 未打开Map就保存的边界没有有效视觉属性。保留新图元的默认样式,
// 并立即回填数据,保证下一次保存得到可重复恢复的有效样式。
syncOutlineVisual(pGraphic);
}
_connectOutline(pGraphic, pData);
}

@ -5,6 +5,7 @@
#include <QFileInfo>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QTimer>
#include "zxSysUtils.h"
#include "ZxUiBase.h"
@ -792,24 +793,22 @@ bool nmSubWndUtils::beginNumericalSaveSession(
if(pError != NULL) {
pError->clear();
}
iSubWndFitting* pSubWndF = dynamic_cast<iSubWndFitting*>(pFirstSubWnd);
nmDataAnalyzeManager* pDataManager = pSubWndF == nullptr ? nullptr :
nmDataAnalyzeManager::findManagerByFitting(pSubWndF);
nmDataAnalyzeContextProvider* pProvider = nmDataAnalyzeContext::provider();
QString sLogicalWindowDirectory;
ZxDataWell* pDataWell = pFirstSubWnd == nullptr
? nullptr : pFirstSubWnd->getDataWell();
nmSubWndUtils* pUtils = getInstance();
if(pDataManager == nullptr || pProvider == nullptr || pUtils == nullptr ||
pUtils->m_pNumericalSaveSession == nullptr ||
!pProvider->getSaveResultDir(pSubWndF, sRstName,
sLogicalWindowDirectory)) {
if(pDataWell == nullptr || pUtils == nullptr ||
pUtils->m_pNumericalSaveSession == nullptr) {
if(pError != NULL) *pError = "Cannot resolve numerical save session root.";
return false;
}
const QFileInfo oWindowInfo(QDir::cleanPath(sLogicalWindowDirectory));
QString sResultRootDirectory = ZxBaseUtil::getWellDirOf(
pDataWell->getName(), "Nm");
sResultRootDirectory += sRstName;
// 显式会话从空窗口清单开始,只有本轮实际遍历到的窗口会被发布。
return pUtils->m_pNumericalSaveSession->begin(
oWindowInfo.dir().absolutePath(), sRstName, false, pError);
QDir::cleanPath(sResultRootDirectory),
sRstName, false, pError);
}
bool nmSubWndUtils::saveWindowGeneration(
@ -890,63 +889,78 @@ bool nmSubWndUtils::saveRsts(iSubWnd* pSubWnd, \
return false;
}
// 流动段分析窗体
if(pSubWnd->getWndID() == "3004") {
// 转换为Fitting窗口
iSubWndFitting* pSubWndF = dynamic_cast<iSubWndFitting*>(pSubWnd);
if(pSubWndF == nullptr) {
return false;
}
// 根据窗体指针,找到创建的对应的数据管理对象
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::findManagerByFitting(pSubWndF);
const QString sWindowId = pSubWnd->getWndID();
if(sWindowId != "3004" && sWindowId != "5118" &&
sWindowId != "5114") {
return true;
}
// 如果不存在,则说明当前分析窗体没有做数值解,不需要做保存处理
if(pDataManager == nullptr) {
return false;
}
ZxMainWindow* pMainWnd1 = const_cast<ZxMainWindow*>(pMainWnd);
ZxTabWidget* pTabWx = pSubWnd->getTabWx();
nmSubWndUtils* pUtils = nmSubWndUtils::getInstance();
if(pMainWnd1 == nullptr || pTabWx == nullptr || pUtils == nullptr) {
return false;
}
// 外层已经显式 begin 时只写当前窗口,由外层在遍历结束后 commit。
// 尚未接入外层钩子的旧调用走单窗口会话,并保留清单中的其他窗口。
nmSubWndUtils* pUtils = nmSubWndUtils::getInstance();
QString sError;
if(pUtils != nullptr && pUtils->m_pNumericalSaveSession != nullptr &&
pUtils->m_pNumericalSaveSession->isActive()) {
if(!saveWindowGeneration(pSubWnd, sRstName, &sError)) {
qWarning() << sError;
return false;
}
} else {
nmDataAnalyzeContextProvider* pProvider = nmDataAnalyzeContext::provider();
QString sLogicalWindowDirectory;
if(pUtils == nullptr || pUtils->m_pNumericalSaveSession == nullptr ||
pProvider == nullptr ||
!pProvider->getSaveResultDir(pSubWndF, sRstName,
sLogicalWindowDirectory)) {
return false;
}
const QFileInfo oWindowInfo(QDir::cleanPath(
sLogicalWindowDirectory));
if(!pUtils->m_pNumericalSaveSession->begin(
oWindowInfo.dir().absolutePath(), sRstName,
true, &sError) ||
!pUtils->m_pNumericalSaveSession->saveWindowGeneration(
oWindowInfo.fileName(), pDataManager, &sError) ||
!pUtils->m_pNumericalSaveSession->commit(&sError)) {
pUtils->m_pNumericalSaveSession->abort();
qWarning() << sError;
return false;
}
}
} else if(pSubWnd->getWndID() == "5118") {
return true;
} else if(pSubWnd->getWndID() == "5114") {
// 外层框架仍按 develop 的方式逐窗口回调。数值代次必须以实际分析窗口
// 作为保存锚点,不能让先到达的 Map 或网格回调改变成果井和根目录。
QVector<iSubWnd*> vecSubWnds = pMainWnd1->getAllSubWndsOf(
pTabWx, "3004", true);
iSubWnd* pSessionAnchor = vecSubWnds.isEmpty()
? pSubWnd : vecSubWnds.first();
ZxDataWell* pDataWell = pSessionAnchor == nullptr
? nullptr : pSessionAnchor->getDataWell();
if(pDataWell == nullptr) {
return false;
}
QString sResultRootDirectory = ZxBaseUtil::getWellDirOf(
pDataWell->getName(), "Nm");
sResultRootDirectory = QDir::cleanPath(
sResultRootDirectory + sRstName);
if(pUtils->m_setCompletedNumericalSaveRoots.contains(
sResultRootDirectory)) {
return true;
}
// TODO:Map窗口保存、网格窗口保存
// 第一次回调在本模块内收齐当前 Tab 的完整分析窗口集合并整体发布。
QString sError;
if(!beginNumericalSaveSession(pSessionAnchor, sRstName, &sError)) {
qWarning() << sError;
return false;
}
nmNumericalSaveSessionAbortGuard oAbortGuard;
for(int nIndex = 0; nIndex < vecSubWnds.size(); ++nIndex) {
iSubWndFitting* pSubWndF =
dynamic_cast<iSubWndFitting*>(vecSubWnds[nIndex]);
if(pSubWndF == nullptr ||
nmDataAnalyzeManager::findManagerByFitting(pSubWndF) == nullptr) {
continue;
}
if(!saveWindowGeneration(pSubWndF, sRstName, &sError)) {
qWarning() << sError;
return false;
}
}
if(!commitNumericalSaveSession(&sError)) {
qWarning() << sError;
return false;
}
oAbortGuard.release();
// 同一次外层遍历后续还会回调其他窗口,下一轮事件前不重复生成代次。
pUtils->m_setCompletedNumericalSaveRoots.insert(
sResultRootDirectory);
QTimer::singleShot(0, pUtils,
SLOT(slotClearCompletedNumericalSaveRoots()));
return true;
}
void nmSubWndUtils::slotClearCompletedNumericalSaveRoots()
{
m_setCompletedNumericalSaveRoots.clear();
}
bool nmSubWndUtils::loadRsts(iSubWnd* pSubWnd, \
QString sRstName, \
const ZxMainWindow* pMainWnd)
@ -1009,6 +1023,24 @@ bool nmSubWndUtils::loadRsts(iSubWnd* pSubWnd, \
return false;
}
bool bManifestContainsWindow = false;
QString sManifestError;
if(!nmNumericalSaveSession::containsPublishedWindow(
oLogicalWindowInfo.dir().absolutePath(),
sRstUtilWndCode, bManifestContainsWindow,
&sManifestError)) {
qWarning() << sManifestError;
QMessageBox::warning(nullptr,
tr("Numerical Result Load Failed"),
tr("The numerical result is incomplete or inconsistent and could not be loaded."));
return false;
}
if(!bManifestContainsWindow) {
// 与 develop 的“目录不存在”语义一致:当前窗口没有数值成果,
// 不创建空 Manager,也不尝试从其他窗口或目录扫描推断数据。
return true;
}
// 存在,说明保存了数值解的参数,需要解析文件里的数据到数据中心
// 动态创建数据中心类
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getInstanceByFitting(pSubWndF);
@ -1022,10 +1054,11 @@ bool nmSubWndUtils::loadRsts(iSubWnd* pSubWnd, \
// 设置当前操作的是哪一个分析
nmDataAnalyzeManager::setCurrentFitting(pSubWndF);
// 加载当前分析中的PVT数据
pDataManager->initPvtParaFromSubFit();
// 加载本地文件的数据到数据中心里的数据体里(含用户选择的模型类型,覆盖PVT推导结果)
// v3 在候选 Manager 中读取 PVT 和全部成果,成功后才替换当前状态。
if(!pDataManager->loadNmResult(sDir)) {
QMessageBox::warning(nullptr,
tr("Numerical Result Load Failed"),
tr("The numerical result is incomplete or inconsistent and could not be loaded."));
return false;
}
// 注册井参数到 nmAttrRegistry(必须在 loadNmResult 之后,面板构建之前)
@ -1042,8 +1075,9 @@ bool nmSubWndUtils::loadRsts(iSubWnd* pSubWnd, \
return false;
}
if(pWxDockNm1->widget() != nullptr && pWxDockNm2->widget() != nullptr) {
return true; //已经设定的话,则不再处理
if(pWxDockNm1->widget() != nullptr &&
pWxDockNm2->widget() != nullptr) {
return true; // 与 develop 一致,已有数值面板不重复创建。
}
// 创建Map,暂时

Loading…
Cancel
Save