diff --git a/Include/nmNum/nmData/nmDataNumericalAnalysisCase.h b/Include/nmNum/nmData/nmDataNumericalAnalysisCase.h index ede8b3d..930b85d 100644 --- a/Include/nmNum/nmData/nmDataNumericalAnalysisCase.h +++ b/Include/nmNum/nmData/nmDataNumericalAnalysisCase.h @@ -5,6 +5,7 @@ #include "nmDefines.h" #include +#include #include /** @@ -129,10 +130,17 @@ public: /** @brief 返回最后一次成功求解所使用的 DLL 井槽位顺序。 */ QVector getResultSolverWellOrder() const; + /** @brief 返回最后一次成功求解时冻结的结果下拉框井编码。 */ + QStringList getResultSelectableWellCodes() const; /** @brief 从成果文件恢复结果存在状态、当前性和独立井顺序。 */ void restoreResultState(const QVector& vecOrder, bool bAvailable, bool bCurrent); + /** @brief 从成果文件同时恢复冻结的结果下拉框井编码。 */ + void restoreResultState(const QVector& vecOrder, + const QStringList& listSelectableWellCodes, + bool bAvailable, + bool bCurrent); /** @brief 选井或几何输入改变后,使当前网格和结果失效。 */ void invalidateGrid(); @@ -173,6 +181,8 @@ private: void normalizeIncludedWells(); /** @brief 保证当前结果井属于最后结果顺序,否则回退到第一口真实井。 */ void normalizeCurrentResultWellCode(); + /** @brief 按本次主井和已启用主动井冻结结果下拉框井编码。 */ + void freezeResultSelectableWellCodes(); private: QString m_sPrimaryWellCode; ///< 主分析井的项目唯一 WellCode。 @@ -184,6 +194,7 @@ private: QString m_sCurrentResultWellCode; ///< 当前结果查看井,仅属于展示状态。 QVector m_vecSolverWellOrder; ///< 网格确定的 DLL 数组下标映射。 QVector m_vecResultSolverWellOrder; ///< 最后结果使用的独立槽位映射。 + QStringList m_listResultSelectableWellCodes; ///< 最后结果允许在下拉框中显示的井编码。 quint64 m_nGridInputRevision; ///< 几何、选井等网格输入的最新版本。 quint64 m_nBuiltGridRevision; ///< 当前缓存网格对应版本,0 表示无有效网格。 diff --git a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp index 38a7392..df6426b 100644 --- a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp +++ b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp @@ -3541,19 +3541,11 @@ bool nmDataAnalyzeManager::isWellAvailableForResult( return false; } - // 已有结果使用求解完成时冻结的井顺序。当前参数、选井或网格变化 - // 不能把最后一套结果中的井从结果界面移除。 + // 已有结果使用求解完成时冻结的可显示井列表。当前参数、选井或网格变化 + // 不能改变最后一套结果的下拉框内容,自动加入的无流量观察井也不会进入界面。 if(m_oNumericalAnalysisCase.hasResults()) { - const QVector vecResultOrder = - m_oNumericalAnalysisCase.getResultSolverWellOrder(); - for(int nIndex = 0; nIndex < vecResultOrder.size(); ++nIndex) { - const nmSolverWellRef& oWellRef = vecResultOrder[nIndex]; - if(oWellRef.m_eEntryKind == NM_SolverEntry_Well && - oWellRef.m_sWellCode == sWellCode) { - return true; - } - } - return false; + return m_oNumericalAnalysisCase + .getResultSelectableWellCodes().contains(sWellCode); } // 第一步:主分析井是结果界面的固定入口,即使当前处于关井段也必须保留。 @@ -3562,7 +3554,7 @@ bool nmDataAnalyzeManager::isWellAvailableForResult( } // 第二步:其他井只有同时具备有效产量并以主动井角色参与当前求解时, - // 才能出现在“结果井”列表。所有无流量观察井一律只在内部保存计算结果。 + // 才能出现在“结果井”列表。主井以外的无流量观察井只在内部保存计算结果。 if(pWellData->getFlowPoints().size() < 2) { return false; } @@ -4109,6 +4101,7 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) QVector vecSavedIncludedWells; QVector vecSavedSolverOrder; QVector vecSavedResultSolverOrder; + QStringList listSavedResultSelectableWellCodes; // 第一步:版本 2 必须完整保存分析方案,缺字段时不再按旧项目规则猜测。 if(!doc.HasMember("NumericalAnalysisCase") || @@ -4310,14 +4303,17 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) const bool bHasAnyResultStateField = oCaseJson.HasMember("ResultsAvailable") || oCaseJson.HasMember("ResultsCurrent") || - oCaseJson.HasMember("ResultSolverWellOrder"); + oCaseJson.HasMember("ResultSolverWellOrder") || + oCaseJson.HasMember("ResultSelectableWellCodes"); const bool bHasCompleteResultState = oCaseJson.HasMember("ResultsAvailable") && oCaseJson["ResultsAvailable"].IsBool() && oCaseJson.HasMember("ResultsCurrent") && oCaseJson["ResultsCurrent"].IsBool() && oCaseJson.HasMember("ResultSolverWellOrder") && - oCaseJson["ResultSolverWellOrder"].IsArray(); + oCaseJson["ResultSolverWellOrder"].IsArray() && + (!oCaseJson.HasMember("ResultSelectableWellCodes") || + oCaseJson["ResultSelectableWellCodes"].IsArray()); if(bHasAnyResultStateField && !bHasCompleteResultState) { qWarning() << "Numerical result state contains invalid fields:" << filePath; @@ -4393,6 +4389,34 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) sWellCode, eEntryKind)); } + const bool bHasSelectableWellCodes = + oCaseJson.HasMember("ResultSelectableWellCodes"); + if(bHasSelectableWellCodes) { + QSet setSelectableWellCodes; + const rapidjson::Value& vecSelectableWellCodesJson = + oCaseJson["ResultSelectableWellCodes"]; + for(rapidjson::SizeType nIndex = 0; + nIndex < vecSelectableWellCodesJson.Size(); + ++nIndex) { + if(!vecSelectableWellCodesJson[nIndex].IsString()) { + qWarning() << "ResultSelectableWellCodes contains an invalid entry:" + << filePath; + return false; + } + + const QString sWellCode = QString::fromUtf8( + vecSelectableWellCodesJson[nIndex].GetString()); + if(!setResultWellCodes.contains(sWellCode) || + setSelectableWellCodes.contains(sWellCode)) { + qWarning() << "ResultSelectableWellCodes contains an invalid WellCode:" + << sWellCode; + return false; + } + setSelectableWellCodes.insert(sWellCode); + listSavedResultSelectableWellCodes.append(sWellCode); + } + } + if(bSavedResultsAvailable && (vecSavedResultSolverOrder.isEmpty() || !setResultWellCodes.contains(sSavedCurrentResultWellCode))) { @@ -4400,6 +4424,17 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) << filePath; return false; } + if(bHasSelectableWellCodes && + ((bSavedResultsAvailable && + (listSavedResultSelectableWellCodes.isEmpty() || + !listSavedResultSelectableWellCodes.contains( + sSavedCurrentResultWellCode))) || + (!bSavedResultsAvailable && + !listSavedResultSelectableWellCodes.isEmpty()))) { + qWarning() << "ResultSelectableWellCodes is inconsistent with result state:" + << filePath; + return false; + } } else { vecSavedResultSolverOrder = vecSavedSolverOrder; } @@ -4798,13 +4833,14 @@ bool nmDataAnalyzeManager::ReadProjectData(const QString & filePath) m_oNumericalAnalysisCase.setIncludedWells(vecSavedIncludedWells); m_oNumericalAnalysisCase.setIncludeOtherWells(bSavedIncludeOtherWells); m_oNumericalAnalysisCase.setSolverWellOrder(vecSavedSolverOrder); + m_oNumericalAnalysisCase.setCurrentResultWellCode( + sSavedCurrentResultWellCode.isEmpty() + ? sSavedPrimaryWellCode : sSavedCurrentResultWellCode); m_oNumericalAnalysisCase.restoreResultState( vecSavedResultSolverOrder, + listSavedResultSelectableWellCodes, bSavedResultsAvailable, bSavedResultsCurrent); - m_oNumericalAnalysisCase.setCurrentResultWellCode( - sSavedCurrentResultWellCode.isEmpty() - ? sSavedPrimaryWellCode : sSavedCurrentResultWellCode); // 解析 时间步 对象 @@ -4944,9 +4980,30 @@ bool nmDataAnalyzeManager::WriteProjectData(const QString & filePath) return false; } } + + QStringList listValidatedResultSelectableWellCodes; + if(bHasResults) { + listValidatedResultSelectableWellCodes = + m_oNumericalAnalysisCase.getResultSelectableWellCodes(); + } + QSet setResultSelectableWellCodes; + for(int nIndex = 0; + nIndex < listValidatedResultSelectableWellCodes.size(); + ++nIndex) { + const QString sWellCode = + listValidatedResultSelectableWellCodes[nIndex]; + if(!setResultWellCodes.contains(sWellCode) || + setResultSelectableWellCodes.contains(sWellCode)) { + qWarning() << "Cannot save numerical project: invalid selectable result WellCode:" + << sWellCode; + return false; + } + setResultSelectableWellCodes.insert(sWellCode); + } if(bHasResults && (vecValidatedResultOrder.isEmpty() || - !setResultWellCodes.contains(sCurrentResultWellCode))) { + listValidatedResultSelectableWellCodes.isEmpty() || + !setResultSelectableWellCodes.contains(sCurrentResultWellCode))) { qWarning() << "Cannot save numerical project: result order is incomplete."; return false; } @@ -5237,6 +5294,20 @@ bool nmDataAnalyzeManager::WriteProjectData(const QString & filePath) } oCaseJson.AddMember("ResultSolverWellOrder", vecResultOrderJson, allocator); + + rapidjson::Value vecResultSelectableWellCodesJson( + rapidjson::kArrayType); + for(int nIndex = 0; + nIndex < listValidatedResultSelectableWellCodes.size(); + ++nIndex) { + QByteArray baWellCode = + listValidatedResultSelectableWellCodes[nIndex].toUtf8(); + vecResultSelectableWellCodesJson.PushBack( + rapidjson::Value(baWellCode.constData(), allocator).Move(), + allocator); + } + oCaseJson.AddMember("ResultSelectableWellCodes", + vecResultSelectableWellCodesJson, allocator); doc.AddMember("NumericalAnalysisCase", oCaseJson, allocator); // 序列化 时间步数据 @@ -5423,6 +5494,8 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir) m_oNumericalAnalysisCase.hasResults(); const QVector vecResultSolverOrder = getResultSolverWellOrder(); + const QStringList listResultSelectableWellCodes = + m_oNumericalAnalysisCase.getResultSelectableWellCodes(); // 第一步:先检查分析方案、网格文件和结果文件是否构成完整的一组。 QString sCurrentGridPath = sLoadAnalDir + "/Grid/CurrentGrid.vtu"; @@ -5597,6 +5670,8 @@ bool nmDataAnalyzeManager::loadNmResult(QString sLoadAnalDir) m_oNumericalAnalysisCase.restoreResultState( bHasCompleteResultFiles ? vecResultSolverOrder : QVector(), + bHasCompleteResultFiles + ? listResultSelectableWellCodes : QStringList(), bHasCompleteResultFiles, bHasCompleteResultFiles && bHasCurrentGridFile && bSavedResultsCurrent); diff --git a/Src/nmNum/nmData/nmDataNumericalAnalysisCase.cpp b/Src/nmNum/nmData/nmDataNumericalAnalysisCase.cpp index f3c144c..cf9acf2 100644 --- a/Src/nmNum/nmData/nmDataNumericalAnalysisCase.cpp +++ b/Src/nmNum/nmData/nmDataNumericalAnalysisCase.cpp @@ -304,22 +304,54 @@ nmDataNumericalAnalysisCase::getResultSolverWellOrder() const return m_vecResultSolverWellOrder; } +QStringList nmDataNumericalAnalysisCase::getResultSelectableWellCodes() const +{ + return m_listResultSelectableWellCodes; +} + +void nmDataNumericalAnalysisCase::restoreResultState( + const QVector& vecOrder, + bool bAvailable, + bool bCurrent) +{ + restoreResultState(vecOrder, QStringList(), bAvailable, bCurrent); +} + void nmDataNumericalAnalysisCase::restoreResultState( const QVector& vecOrder, + const QStringList& listSelectableWellCodes, bool bAvailable, bool bCurrent) { m_vecResultSolverWellOrder = vecOrder; + QSet setResultWellCodes; for(int nIndex = 0; nIndex < m_vecResultSolverWellOrder.size(); ++nIndex) { m_vecResultSolverWellOrder[nIndex].m_nSolverIndex = nIndex; + const nmSolverWellRef& oWellRef = m_vecResultSolverWellOrder[nIndex]; + if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) { + setResultWellCodes.insert(oWellRef.m_sWellCode); + } + } + + m_listResultSelectableWellCodes.clear(); + for(int nIndex = 0; nIndex < listSelectableWellCodes.size(); ++nIndex) { + const QString sWellCode = listSelectableWellCodes[nIndex]; + if(setResultWellCodes.contains(sWellCode) && + !m_listResultSelectableWellCodes.contains(sWellCode)) { + m_listResultSelectableWellCodes.append(sWellCode); + } } m_bResultsAvailable = bAvailable && !m_vecResultSolverWellOrder.isEmpty(); m_bResultsValid = m_bResultsAvailable && bCurrent; if(m_bResultsAvailable) { + // 旧成果没有独立可显示井字段时,按保存的主井和包含井保守恢复。 + if(m_listResultSelectableWellCodes.isEmpty()) { + freezeResultSelectableWellCodes(); + } normalizeCurrentResultWellCode(); } } @@ -375,6 +407,7 @@ void nmDataNumericalAnalysisCase::markResultsAvailable() } m_vecResultSolverWellOrder = m_vecSolverWellOrder; + freezeResultSelectableWellCodes(); m_bResultsAvailable = true; m_bResultsValid = true; normalizeCurrentResultWellCode(); @@ -396,6 +429,7 @@ bool nmDataNumericalAnalysisCase::markResultsAvailableIfCurrent( } m_vecResultSolverWellOrder = m_vecSolverWellOrder; + freezeResultSelectableWellCodes(); m_bResultsAvailable = true; m_bResultsValid = true; normalizeCurrentResultWellCode(); @@ -451,6 +485,7 @@ void nmDataNumericalAnalysisCase::clear() m_sCurrentResultWellCode.clear(); m_vecSolverWellOrder.clear(); m_vecResultSolverWellOrder.clear(); + m_listResultSelectableWellCodes.clear(); m_nGridInputRevision = 1; m_nBuiltGridRevision = 0; m_nResultInputRevision = 1; @@ -482,22 +517,38 @@ void nmDataNumericalAnalysisCase::normalizeIncludedWells() void nmDataNumericalAnalysisCase::normalizeCurrentResultWellCode() { - QString sFirstResultWellCode; + if(m_listResultSelectableWellCodes.contains( + m_sCurrentResultWellCode)) { + return; + } + + m_sCurrentResultWellCode = m_listResultSelectableWellCodes.isEmpty() + ? QString() : m_listResultSelectableWellCodes.first(); +} + +void nmDataNumericalAnalysisCase::freezeResultSelectableWellCodes() +{ + QSet setSelectableWellCodes; + if(!m_sPrimaryWellCode.isEmpty()) { + setSelectableWellCodes.insert(m_sPrimaryWellCode); + } + if(m_bIncludeOtherWells) { + for(int nIndex = 0; nIndex < m_vecIncludedWells.size(); ++nIndex) { + setSelectableWellCodes.insert( + m_vecIncludedWells[nIndex].m_sWellCode); + } + } + + m_listResultSelectableWellCodes.clear(); for(int nIndex = 0; nIndex < m_vecResultSolverWellOrder.size(); ++nIndex) { const nmSolverWellRef& oWellRef = m_vecResultSolverWellOrder[nIndex]; - if(oWellRef.m_eEntryKind != NM_SolverEntry_Well) { - continue; - } - if(sFirstResultWellCode.isEmpty()) { - sFirstResultWellCode = oWellRef.m_sWellCode; - } - if(oWellRef.m_sWellCode == m_sCurrentResultWellCode) { - return; + if(oWellRef.m_eEntryKind == NM_SolverEntry_Well && + setSelectableWellCodes.contains(oWellRef.m_sWellCode) && + !m_listResultSelectableWellCodes.contains(oWellRef.m_sWellCode)) { + m_listResultSelectableWellCodes.append(oWellRef.m_sWellCode); } } - - m_sCurrentResultWellCode = sFirstResultWellCode; } diff --git a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp index f9c9c73..f17eaf2 100644 --- a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp +++ b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp @@ -378,7 +378,7 @@ void nmWxNumericalDesign::fillResultWellCombo(const QString& sSelectedWellCode) : pManager->getSolverWellOrder(); // 第一步:有结果时优先按最后结果顺序,否则按当前网格顺序收集结果井。 - // 无流量观察井仍在求解器顺序中,但不能出现在本下拉框。 + // 自动加入的无流量观察井仍在求解器顺序中,但不能出现在本下拉框。 for(int nOrderIndex = 0; nOrderIndex < vecSolverOrder.size(); ++nOrderIndex) { const nmSolverWellRef& oWellRef = vecSolverOrder[nOrderIndex]; if(oWellRef.m_eEntryKind == NM_SolverEntry_Well &&