You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWnd/nmSubWndMain.cpp

3836 lines
136 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "zxLogInstance.h"
#include "ZxBaHelper.h"
#include <QDebug>
#include "ZxResolutionHelper.h"
#include "zxSysUtils.h"
#include "ZxPtyDock.h"
#include "iDockBaseWx.h"
#include "ZxMainWindow.h"
#include "ZxTabWidget.h"
#include "ZxSubAxisX.h"
#include "ZxSubAxisY.h"
#include "ZxPlot.h"
#include "ZxObjCurve.h"
#include "ZxObjCurveBar.h"
#include "ZxSubTitle.h"
#include "tCurvePlotScene.h"
#include "tCurvePlotView.h"
#include "ZxTableView.h"
#include "ZxTableModel.h"
#include "mModuleDefines.h"
#include "ZxDataWell.h"
#include "ZxDataProject.h"
#ifdef QT_DEBUG
#include "ZxDataGaugeP.h"
#endif
#ifdef QT_DEBUG
#include "nmXmlHelper.h"
#endif
#include "nmGuiPlot.h"
#include "nmPlotGraphicBinder.h"
#include "nmSubWndMain.h"
#include "nmPlotDialogContext.h"
#include "nmPlotDialogContextProvider.h"
#include "nmPebiResultPageInstaller.h"
#include "nmWxGridVTKContainerWidget.h"
#include "nmWxSelectWellsDlg.h"
#include "nmWxSelectWellsWidget.h"
#include "nmWxParameterProperty.h"
#include "nmCalculationDllPebiSolverTask.h"
#include "nmCalculationUtils.h"
#include "nmObjPointWell.h"
#include "nmDataAnalyzeManager.h"
#include "nmPebiResultSnapshot.h"
#include "nmDataDemo.h"
#include "nmDataLogFile.h"
#include "nmDataMeasure.h"
#include "nmWxParaProperty.h"
#include "nmWxRCDialog.h"
#include "nmWxWellLayerDlg.h"
#include "nmWxGeometryLayerDlg.h"
#include "nmReservoirPropertiesDataSource.h"
#include "nmWxReservoirPropertiesEditor.h"
#include "nmWxPropertyInterpolationDlg.h"
#include "nmWxGeoRefDlg.h"
#include "iSubWndFitting.h"
#include "iGuiPlotPF.h"
#include "iGuiPlotTableHelper.h"
#include "iGuiPlot.h"
#include "ZxObjCurveFlow.h"
#include "iAnalRun.h"
#include <QVector>
#include <QSet>
#include <QHash>
#include <QPushButton>
#include <QLabel>
#include <cmath>
#include <QMdiSubWindow>
#include "ZxMdiArea.h"
#include "nmSingalCenter.h"
#include "nmWxParaPropertyPebi.h"
#include "nmWxNumericalDesign.h"
#include "nmWxSensitive.h"
#include "nmWxForecast.h"
#include "nmWxReservoirProperties.h"
#include "nmCalculationPebiGrid.h"
#include "nmDataOutline.h"
#include "nmDataVerticalFracturedWell.h"
#include "nmDataHorizontalFracturedWell.h"
#include "nmDataVerticalWell.h"
#include "ZxDataGaugeP.h"
#include "ZxDataGaugeF.h"
#include "nmWxDFN.h"
#include "nmWxDisplaySettings.h"
#include "ZxRstWnd.h"
#include <QPointer>
#include <QtCore/qnumeric.h>
namespace
{
// 保留原来求解器单例的防重入语义:全局同一时间只允许一个DLL求解任务运行。
// QPointer会在QObject销毁后自动变空,避免保留已经释放的线程指针。
QPointer<nmCalculationDllPebiSolverTask> s_pRunningSolverTask;
QPointer<nmSubWndMain> s_pPendingSolverWindow;
void clearResultCurveDataTables(iGuiPlot* plot)
{
// 框架弹出的表格是独立顶层窗口;刷新前清除本图的旧表,仍由框架管理其生命周期。
foreach(QWidget* window, QApplication::topLevelWidgets()) {
iGuiPlotTableHelper* table = qobject_cast<iGuiPlotTableHelper*>(window);
if(!table) continue;
for(int i = 0; i < plot->m_pPlot->getObjCount(); ++i) {
ZxObjCurve* curve = qobject_cast<ZxObjCurve*>(plot->m_pPlot->getObjByIndex(i));
if(curve && table->getMapTableViews()->contains(curve)) {
table->resetTableHelpers();
table->hide();
break;
}
}
}
}
QSet<QString> hiddenSensitiveCurves(iGuiPlot* plot)
{
// 刷新同一批次或重新打开窗口时保留本图的显示选择。
QSet<QString> hidden;
for(int i = 0; i < plot->m_pPlot->getObjCount(); ++i) {
ZxObjCurve* curve = qobject_cast<ZxObjCurve*>(plot->m_pPlot->getObjByIndex(i));
if(curve && !curve->isVisible()) hidden.insert(curve->getName());
}
return hidden;
}
class nmSensitivityHistogramBars : public ZxObjCurveBar
{
public:
explicit nmSensitivityHistogramBars(double binWidth) : m_binWidth(binWidth) {}
protected:
void drawSubBars(QPainter* painter, QVector<QPointF>& points) override
{
// 框架柱宽使用绘图坐标,按分箱宽度换算,缩放和窗口调整后仍对应同一区间。
const ZxSubAxisX* axis = getAxisX();
const double left = axis->getRangeMin();
m_dBarWidth = 0.95 * qAbs(axis->getPosForValue(left + m_binWidth) - axis->getPosForValue(left));
ZxObjCurveBar::drawSubBars(painter, points);
}
private:
double m_binWidth;
};
// 历史窗口仍是单流量曲线结构,因此显示与双对数、半对数后处理一致的制度参考相。
const QVector<double>* resultReferenceRatesOf(
const nmPebiResultWellSnapshot* pWell)
{
if(pWell == NULL) {
return NULL;
}
switch(pWell->m_eFlowSchedulePhase) {
case PHASE_Oil:
return pWell->m_bUseOilRate ? &pWell->m_vecOilRates : NULL;
case PHASE_Gas:
return pWell->m_bUseGasRate ? &pWell->m_vecGasRates : NULL;
case PHASE_Water:
return pWell->m_bUseWaterRate ? &pWell->m_vecWaterRates : NULL;
default:
return NULL;
}
}
}
nmSubWndMain::nmSubWndMain(QWidget *parent, QString sExt) :
iSubWndBaseFit(parent, sExt)
{
m_pWxPlot = NULL;
m_pWxDockParas = NULL;
m_pWxParas = NULL;
m_pWxDockData1 = NULL;
m_pTableView1 = NULL;
m_pWxDockData2 = NULL;
m_pTableView2 = NULL;
#ifdef QT_DEBUG
m_pWxDockTemp = NULL;
m_pListWxTemp = NULL;
#endif
// 为了后续支持多井提供方便
if(m_pDataWell == NULL) {
m_pDataWell = zxCurWell;
}
//m_sMdiWndType = s_MdiType_DataNum;
m_sMdiWndType = s_MdiType_DataAnal;
setWindowTitle(tr("nmSubWndMain"));
m_lockState = false; // 默认为false
m_openDialogCmdIds.clear();
m_pProgressDlg = nullptr;
m_pSolverTask = nullptr;
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_pPendingResultDataManager = nullptr;
m_pPendingResultFitting = nullptr;
m_bPendingResultWidgetRefresh = false;
m_bPendingResultCompletionMessage = false;
m_bPendingResultWindowReady = false;
m_nPendingResultSolveTimeMs = -1;
m_bSolverResultCompletionPending = false;
m_bPendingSolverSucceeded = false;
m_bPendingSolverInputChanged = false;
m_nPendingSolverTimeMs = -1;
m_bSolverStartPending = false;
m_bSolverCancelRequested = false;
m_pResultRefreshDataManager = nullptr;
m_pResultRefreshFitting = nullptr;
m_sObservedPebiResultSnapshotId.clear();
m_bRefreshingPebiResultSnapshot = false;
m_pPlotToolBar = nullptr;
}
nmSubWndMain::~nmSubWndMain()
{
if(m_pWxPlot != nullptr) {
// Map窗口析构时,解除DataManager中保存的Plot引用,避免后续访问已释放画布。
foreach(nmDataAnalyzeManager* pManager, nmDataAnalyzeManager::s_mapDataAnalManager.values()) {
if(pManager != nullptr && pManager->getPlot() == m_pWxPlot) {
pManager->setPlot(nullptr);
}
}
}
#ifdef QT_DEBUG
// 坚持谁创建谁析构原则,删除(首先需要去除绑定)
if(NULL != m_pWxDockTemp) {
//QObject* pParent = m_pWxDockTemp->parent();
//ZxMainWindow* p = dynamic_cast<ZxMainWindow*>(pParent);
ZxMainWindow* p = m_pMainWindow;
if(NULL != p) {
p->detachOuterDockWx(m_pWxDockTemp);
}
delete m_pWxDockTemp;
m_pWxDockTemp = NULL;
}
#endif
this->setTopDocksVisible(true);
// TODO:删除已有在图元的数据对象
//if (m_pWxPlot != nullptr) {
// QVector<nmObjBase*> vecObjs = m_pWxPlot->getAllPlots();
// foreach(nmObjBase* obj, vecObjs) {
// obj->removeData();
// }
//}
m_bSolverStartPending = false;
disconnectPebiGridGenerationWait();
if(s_pPendingSolverWindow == this) {
s_pPendingSolverWindow = nullptr;
}
// 敏感性和预测任务由参数窗口释放;主窗口销毁前先解除回调并等待停止。
if(!m_pAuxiliarySolverTask.isNull()) {
disconnect(m_pAuxiliarySolverTask.data(), nullptr, this, nullptr);
m_pAuxiliarySolverTask->requestCancel();
m_pAuxiliarySolverTask->wait();
if(s_pRunningSolverTask == m_pAuxiliarySolverTask) s_pRunningSolverTask = nullptr;
m_pAuxiliarySolverTask = nullptr;
}
if (m_pProgressDlg) {
delete m_pProgressDlg;
m_pProgressDlg = NULL;
}
if (m_pSolverTask) {
// 第一步:停止向正在析构的窗口投递完成回调。
disconnect(m_pSolverTask, SIGNAL(sig_calculateDone(bool)), this, SLOT(on_solverTaskFinished(bool)));
m_pSolverTask->requestCancel();
// 第二步:DataManager 会在成果窗口销毁后释放,而任务仍读取该对象。
// Qt 4.8 下 DLL 执行中只能等待其正常返回,绝不能强杀持锁线程。
if(m_pSolverTask->isRunning()) {
m_pSolverTask->wait();
}
if (m_pSolverTask == s_pRunningSolverTask) {
s_pRunningSolverTask = nullptr;
}
// 第三步:线程已经停止,当前窗口仍是任务的明确所有者,可直接释放。
delete m_pSolverTask;
m_pSolverTask = nullptr;
}
/*if (m_parameterPropertyWindow != nullptr) {
delete m_parameterPropertyWindow;
m_parameterPropertyWindow = nullptr;
}*/
}
void nmSubWndMain::bindPebiResultRefreshContext(
nmDataAnalyzeManager* pDataManager,
iSubWndFitting* pSubWndFitting)
{
// 自动拟合完成时始终刷新创建该数值面板的分析窗口,避免页签切换后取到其他分析。
m_pResultRefreshDataManager = pDataManager;
m_pResultRefreshFitting = pSubWndFitting;
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
pDataManager != nullptr
? pDataManager->getPebiResultSnapshot()
: QSharedPointer<const nmPebiResultSnapshot>();
m_sObservedPebiResultSnapshotId = pSnapshot.isNull()
? QString() : pSnapshot->getSnapshotId();
}
void nmSubWndMain::onPebiResultSnapshotChanged()
{
nmDataAnalyzeManager* pDataManager = m_pResultRefreshDataManager.data();
iSubWndFitting* pSubWndFitting = m_pResultRefreshFitting.data();
if(pDataManager == nullptr || pSubWndFitting == nullptr ||
sender() != pDataManager || m_bRefreshingPebiResultSnapshot) {
return;
}
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
pDataManager->getPebiResultSnapshot();
if(pSnapshot.isNull()) {
m_sObservedPebiResultSnapshotId.clear();
return;
}
const QString sSnapshotId = pSnapshot->getSnapshotId();
if(sSnapshotId == m_sObservedPebiResultSnapshotId) {
// 切换结果井时关闭上一口井的敏感性窗口及附属表格,保留批次数据供再次查看。
ZxMdiArea* area = pSubWndFitting->m_pMdiArea;
if(area) {
iGuiPlot* loglog = pSubWndFitting->getPlotByTag(s_Tag_ChartSensitivity);
bool closed = false;
foreach(QMdiSubWindow* window, area->subWindowList()) {
if(window->objectName() != "nmSensitivityHistory" &&
window->objectName() != "nmSensitivityHistogram" &&
!(loglog && window->isAncestorOf(loglog))) continue;
foreach(iGuiPlot* plot, window->findChildren<iGuiPlot*>())
clearResultCurveDataTables(plot);
foreach(QDialog* dialog, window->findChildren<QDialog*>()) {
if(dialog->isWindow()) dialog->close();
}
window->close();
closed = true;
}
if(closed && area->isVisible()) area->relayoutSubWnds();
}
// 同一快照内切换结果井仍由 onWellSelected() 处理,不重复刷新全部结果页面。
return;
}
// 手工 Generate 有自己的完成和提示流程;这里只接管自动拟合提交的新快照。
if(m_pSolverTask != nullptr || m_bSolverStartPending ||
m_bSolverResultCompletionPending) {
m_sObservedPebiResultSnapshotId = sSnapshotId;
return;
}
m_bRefreshingPebiResultSnapshot = true;
QPointer<nmDataAnalyzeManager> pSavedSolverDataManager =
m_pSolverDataManager;
QPointer<iSubWndFitting> pSavedSolverFitting = m_pSolverFitting;
m_pSolverDataManager = pDataManager;
m_pSolverFitting = pSubWndFitting;
const bool bRefreshed = mergeAnaResultToFitting();
schedulePendingResultWidgets();
m_pSolverDataManager = pSavedSolverDataManager;
m_pSolverFitting = pSavedSolverFitting;
m_bRefreshingPebiResultSnapshot = false;
if(bRefreshed) {
m_sObservedPebiResultSnapshotId = sSnapshotId;
}
}
void nmSubWndMain::configWnd(QString sPrevInfo)
{
iSubWndBaseFit::configWnd(sPrevInfo);
if(NULL != m_pMainWindow) {
disconnect(this, SIGNAL(sigAppendDock2Main(iDockBaseWx*, Qt::DockWidgetArea)), \
m_pMainWindow, SLOT(slotAppendDock2Main(iDockBaseWx*, Qt::DockWidgetArea)));
connect(this, SIGNAL(sigAppendDock2Main(iDockBaseWx*, Qt::DockWidgetArea)), \
m_pMainWindow, SLOT(slotAppendDock2Main(iDockBaseWx*, Qt::DockWidgetArea)));
}
initUI();
}
void nmSubWndMain::initUI()
{
// iSubWndBaseAF::initUI();
#ifdef QT_DEBUG
// 示例:演示增加Dock并且追加至程序主窗口
initUiTop();
#endif
// 主布局:定义
initUiMain();
// 外围布局,DockWx
{
initUiDockWxs();
initUiDockPtys();
}
#ifdef QT_DEBUG
// 示例:如何加载xml文件
if(_nmXmlHelper->loadXmlDefines()) {
foreach(nmXmlData* pData, *(_nmXmlHelper->getVecNmXmlDataPtrs())) {
Q_ASSERT(NULL != pData);
qDebug() << QString("%1: %2").arg(pData->m_sName).arg(pData->m_sAlias);
}
}
#endif
}
QWidget* nmSubWndMain::initUiMainWx()
{
QSplitter* pSplitter = new QSplitter(Qt::Vertical);
Q_ASSERT(NULL != pSplitter);
nmGuiPlot* pWxPlot = new nmGuiPlot();
m_pWxPlot = pWxPlot;
// 将绘图对象添加到数据中心
nmDataAnalyzeManager::getCurrentInstance()->setPlot(m_pWxPlot);
{
// 建立信号,以便在此处增加新的工具
connect(pWxPlot, SIGNAL(sigToolbarBuilt(QToolBar*)),
this, SLOT(slotToolbarBuilt(QToolBar*)));
QSize sz = QSize(265, 203);
pWxPlot->initUI("TestGui", sz);
bindChartSignals(pWxPlot);
pSplitter->addWidget(pWxPlot);
}
return pSplitter;
}
void nmSubWndMain::initUiDockWxs()
{
// TODO,本Dock的操作
// 基础参数Dock
// {
// m_pWxDockParas = new iDockBaseWx(tr("Parameters"));
// Q_ASSERT (NULL != m_pWxDockParas);
// addDockWidget(Qt::LeftDockWidgetArea, m_pWxDockParas);
// m_pWxParas = new nmWxParaProperty();
// Q_ASSERT (NULL != m_pWxParas);
// m_pWxParas->initUI();
// m_pWxDockParas->setWidget(m_pWxParas);
// m_pWxDockParas->setMinimumWidth(_resoSizeW(200));
// }
// 表格数据Dock
// {
// QStringList listTitles;
// listTitles << tr("Name1");
// iDockBaseWx* pDockWx1 = new iDockBaseWx(tr("Data1"));
// Q_ASSERT (NULL != pDockWx1);
// {
// addDockWidget(Qt::RightDockWidgetArea, pDockWx1);
// m_pWxDockData1 = pDockWx1;
// ZxTableView* pTableView = new ZxTableView();
// Q_ASSERT (NULL != pTableView);
// {
// ZxTableModel* pTableModel = new ZxTableModel(1, 1, listTitles);
// Q_ASSERT (NULL != pTableModel);
// pTableView->setModel(pTableModel);
// pTableView->setMenuMode(ZxTableView::MTM_Edit_Without_Col);
// pTableView->setSelectionBehavior(ZxTableView::SelectRows);
// pTableView->fuzzyUiOfQt5();
// }
// pDockWx1->setWidget(pTableView);
// m_pTableView1 = pTableView;
// }
// iDockBaseWx* pDockWx2 = new iDockBaseWx(tr("Data2"));
// Q_ASSERT (NULL != pDockWx2);
// {
// addDockWidget(Qt::RightDockWidgetArea, pDockWx2);
// tabifyDockWidget(pDockWx1, pDockWx2);
// m_pWxDockData2 = pDockWx2;
// ZxTableView* pTableView = new ZxTableView();
// Q_ASSERT (NULL != pTableView);
// {
// ZxTableModel* pTableModel = new ZxTableModel(1, 1, listTitles);
// Q_ASSERT (NULL != pTableModel);
// pTableView->setModel(pTableModel);
// pTableView->setMenuMode(ZxTableView::MTM_Readonly);
// pTableView->fuzzyUiOfQt5();
// }
// pDockWx2->setWidget(pTableView);
// m_pTableView2 = pTableView;
// }
// }
}
void nmSubWndMain::initUiDockPtys()
{
iSubWndBaseAF::initUiDockPtys();
// 属性Dock
{
Q_ASSERT(NULL != m_pDockPty);
m_pDockPty->setMinimumWidth(_resoSizeW(200));
}
}
#ifdef QT_DEBUG
// 示例:演示增加Dock并且追加至程序主窗口
void nmSubWndMain::initUiTop()
{
#if 0
return;
// 隐藏所有Dock
this->setTopDocksVisible(false);
// 创建新的Dock
iDockBaseWx* pDockWx = new iDockBaseWx(tr("TODO:Demo"));
Q_ASSERT(NULL != pDockWx);
// QListWidget* pListWx = new QListWidget();
nmWxTreeWidget* treeWidget = new nmWxTreeWidget(pDockWx, m_pAnalyzeData);
Q_ASSERT(NULL != treeWidget);
pDockWx->setWidget(treeWidget);
// m_pListWxTemp = pListWx;
pDockWx->setMinimumWidth(_resoSizeW(200));
emit sigAppendDock2Main(pDockWx, Qt::LeftDockWidgetArea);
m_pWxDockTemp = pDockWx;
return;
// 基础参数Dock
// {
// iDockBaseWx* pDockWx = new iDockBaseWx(tr("TODO:Demo"));
// Q_ASSERT (NULL != pDockWx);
// QListWidget* pListWx = new QListWidget();
// Q_ASSERT (NULL != pListWx);
// pDockWx->setWidget(pListWx);
// m_pListWxTemp = pListWx;
// pDockWx->setMinimumWidth(_resoSizeW(200));
// emit sigAppendDock2Main(pDockWx, Qt::LeftDockWidgetArea);
// m_pWxDockTemp = pDockWx;
// }
#endif
}
#endif
void nmSubWndMain::bindChartSignals(iGuiPlot* pWxPlot)
{
nmGuiPlot* p = dynamic_cast<nmGuiPlot*>(pWxPlot);
if(NULL != p) {
connect(p, SIGNAL(sigObjSelChanged(ZxObjBase*, bool)), \
this, SLOT(slotObjSelChanged(ZxObjBase*, bool)));
connect(p, SIGNAL(sigObjPtsChanged(ZxObjBase*)),
this, SLOT(slotObjPtsChanged(ZxObjBase*)));
}
iSubWndBaseAF::bindChartSignals(pWxPlot);
}
bool nmSubWndMain::runActionOf(QString sAction)
{
if(iSubWnd::runActionOf(sAction)) {
return true;
}
// 数据加载
QString s = sAction;
if(_isSame(s, "LoadP")) {
return true;
}
return false;
}
bool nmSubWndMain::runCmdBy(QString sName, QString sID)
{
QString s = sName;
int nId = sID.toInt();
// 运行菜单之前,先将所有处于选中状态的图元状态恢复
if(m_pWxPlot != nullptr) {
m_pWxPlot->clearPlotsSelectStates();
}
switch(nId) {
case 5102:
this->geologicalMapImport();
return true;
/*case 5103:
this->geologicalMapHide();
return true;*/
case 5120:
this->drawMeasuringScale();
return true;
case 5103:
this->drawPolygonOutline();
return true;
case 5104:
this->drawSquareOutline();
return true;
case 5105:
this->drawRoundOutline();
return true;
case 5106:
this->drawFault();
return true;
case 5107:
this->selectWell();
return true;
// 新建井菜单暂时停用。
// case 5108:
// this->drawWell();
// return true;
case 5109:
this->drawCrack();
return true;
case 5110:
this->drawRegion();
return true;
case 5111:
this->drawRegionMark();
return true;
case 5112:
this->geoLayering();
return true;
case 5113:
this->reservoirCharacteristics();
return true;
/*case 5114:
this->generationMesh();
return true;
*/
case 5115:
this->solveAndAnalyze();
return true;
case 5117:
this->mergeAnaResultToFitting();
this->schedulePendingResultWidgets();
return true;
case 5121:
this->deleteOneObj();
return true;
case 5119:
this->setLocked();
return true;
case 5122:
this->generateDFN();
return true;
case 5123:
this->displaySetting();
return true;
case 5124:
return m_pWxPlot != nullptr &&
m_pWxPlot->runAction("print_Preview");
case 5125:
return m_pWxPlot != nullptr &&
m_pWxPlot->runAction("Print");
/*
case 5117:
this->initData();
return true;
*/
case 5129:
if(isDialogCmdOpened(5129)) {
return true;
}
this->drawMeasure();
return true;
case 5127:
this->drawGeoRef();
return true;
case 5128:
if(isDialogCmdOpened(5128)) {
return true;
}
this->drawPointerPos();
return true;
case 5126:
if(isDialogCmdOpened(5126)) {
return true;
}
this->showPropertyInterpolationDialog();
return true;
}
if(_isSame(s, "RunGrid")) {
if(NULL != m_pWxPlot) {
//m_pWxPlot->refreshGrid(true);
}
return true;
}
return false;
}
bool nmSubWndMain::checkCmdEnable(bool &b, \
QString sName, int nID, \
bool bLicensed)
{
bool b1 = (NULL != zxCurProject);
bool b2 = (NULL != m_pDataWell);
// 判断当前锁定状态
if(m_lockState) {
/*
if(nID >= 5102 && nID <= 5125) {
b = (b1 && b2 && bLicensed && !isReadonly() && NULL != m_pWxPlot);
return true;
}
*/
/*
if ((nID >= 5122 && nID <= 5125) || (nID >= 5112 && nID <= 5119 ))
{
b = true;
return true;
}
*/
/*
if(nID >= 5102 && nID <= 5124) {
b = true;
return true;
}
*/
// 可使用菜单栏
if(sName == "NmMap" || sName == "NmUnlocked" || sName == "NmGeometry" ||
sName == "NmProperties" || sName == "NmDFN" || sName == "NmCaculation" ||
sName == "NmBack" || sName == "NmResult" || sName == "NmGrid" ||
sName == "NmInterpolation" ||
sName == "NmShow" || sName == "NmPrintPreview" || sName == "NmPrint" ||
sName == "VisibleAnalBasic" || sName == "VisibleAnalPVT" || sName == "VisibleAnalDiff" ||
sName == "VisibleAnalPseu" || sName == "nmFitModel" || sName == "NmAnalChange") {
b = true;
return true;
} else {
b = false;
return false;
}
} else {
if((nID >= 5102 && nID <= 5130) /*|| (nID >= 9002 && nID <= 9003) */ || (nID >= 9101 && nID <= 9104) || nID == 3104) {
b = (b1 && b2 && bLicensed && !isReadonly() && NULL != m_pWxPlot);
return true;
}
}
return iSubWndBaseFit::checkCmdEnable(b, sName, nID, bLicensed);
}
void nmSubWndMain::reAdjustToolbar(QToolBar* pToolBar)
{
iSubWndBaseAF::reAdjustToolbar(pToolBar);
m_pPlotToolBar = pToolBar;
// 获取所有动作的列表
// QList<QAction *> actions = m_pPlotToolBar->actions();
// // 隐藏第1个和第2个动作
// if (actions.size() > 0) {
// actions[0]->setVisible(false); // 隐藏第1个动作
// }
// if (actions.size() > 1) {
// actions[1]->setVisible(false); // 隐藏第2个动作
// }
}
void nmSubWndMain::firstLoadAndUpdate()
{
iSubWndBaseAF::firstLoadAndUpdate();
}
void nmSubWndMain::finalDeals()
{
iSubWndBaseAF::finalDeals();
#ifdef QT_DEBUG
updatePlots();
updateDockWxs();
if(m_pWxDockData1 != NULL) {
Q_ASSERT(NULL != m_pWxDockData1);
m_pWxDockData1->raise();
}
#endif
}
void nmSubWndMain::onActivated()
{
#ifdef QT_DEBUG
// 当前窗体激活时,可以做些事情,比如把隐藏的Dock显示出来
if(NULL != m_pWxDockTemp) {
if(!m_pWxDockTemp->isVisible()) {
m_pWxDockTemp->setVisible(true);
}
}
#endif
}
#ifdef QT_DEBUG
void nmSubWndMain::updatePlots()
{
#if 0
// 里面根据曲线,重新刷了一下坐标轴范围。你屏蔽该函数,即可
// 坐标轴范围会变成背景图的大小
return ;
// 说明:以从当前井获取一条压力数据进行绘图为例
QString sType = iDataModelType::sTypeDataGaugeP;
QString sCodeP = ""; //如果已知压力数据Code,可以直接引用
ZxDataGaugeP* pDataObjP = dynamic_cast<ZxDataGaugeP*>(getDataObjOf(sType, sCodeP));
if(NULL == pDataObjP) {
qDebug() << tr("Failed to get data of type '%1'").arg(sType);
return;
}
VecDouble vecX, vecY;
QByteArray ba = pDataObjP->getGaugeDataOf(0);
if(!ZxBaHelper::convertBa2VecXY(vecX, vecY, ba)) {
qDebug() << tr("Failed to get x-y data of dataobj '%1'").arg(pDataObjP->getCode());
return;
}
nmGuiPlot* pWxPlot = m_pWxPlot;
Q_ASSERT(NULL != pWxPlot);
QString sName = tr("CurveName");
ZxObjBase* pObj = pWxPlot->updatePlotObjBy(sName, vecX, vecY, true);
if(NULL != pObj) {
pObj->setLockPos(true); //可以调整属性
// pObj->setReadOnly(true);
pWxPlot->freshAxisScales(true);//刷新坐标轴刻度
// 设置标题+坐标轴等
pWxPlot->setAxisX(tr("Time"), "hr");
pWxPlot->setAxisY(tr("Pressure"), "MPa");
pWxPlot->setAxisXYLog(false, 0);
pWxPlot->setAxisXYLog(false, 1);
pWxPlot->m_pPlot->setLegendVisible(false);
pWxPlot->m_pPlot->getTitle()->setName(tr("Demo"));
}
#endif
}
ZxDataObject* nmSubWndMain::getDataObjOf(QString sType, QString sCode /*= ""*/)
{
ZxDataWell* pDataWell = zxCurWell;
Q_ASSERT(NULL != pDataWell);
ZxDataObject* pDataObj = NULL;
if(!sCode.isEmpty()) {
pDataObj = pDataWell->getChild(sType, sCode);
} else {
ZxDataObjectList vecObjs = pDataWell->getChildren(sType);
if(!vecObjs.isEmpty()) {
pDataObj = vecObjs[0];
}
}
return pDataObj;
}
void nmSubWndMain::updateDockWxs()
{
#if 0
// TODO,刷新Dock内容
return;
// 左侧参数窗体
Q_ASSERT(NULL != m_pWxParas);
QStringList listParas;
listParas << "K" << "S" << "rw" << "Miuo" << "Bo" << "phi";
m_pWxParas->refreshUIs(listParas);
// 表格数据1
Q_ASSERT(NULL != m_pTableView1);
ZxTableModel* pTableModel = m_pTableView1->getModel();
Q_ASSERT(NULL != pTableModel);
QStringList listTitles;
listTitles << tr("Col1") << tr("Col2\n(MPa)");
VVecDouble vvec;
{
VecDouble vec;
vec << 0.2 << 3.4;
vvec << vec;
}
{
VecDouble vec;
vec << 5.6 << 0.456;
vvec << vec;
}
pTableModel->setTitles(listTitles);
pTableModel->setData(vvec);
if(NULL != m_pListWxTemp) {
m_pListWxTemp->clear();
for(int i = 0; i < 10; i++) {
QString sItem = tr("Demo Item %1").arg(i + 1);
QListWidgetItem* pItem = new QListWidgetItem(sItem);
Q_ASSERT(NULL != pItem);
pItem->setIcon(zxLoadIcon("Open"));
m_pListWxTemp->addItem(pItem);
}
}
#endif
}
void nmSubWndMain::updateTableByCurve(ZxObjCurve* pObjCurve, ZxTableView* pTableView)
{
Q_ASSERT(NULL != pObjCurve);
Q_ASSERT(NULL != pTableView);
// 刷新表格标题
{
QString sNameX = pObjCurve->getAxisX()->getName();
QString sUnit = pObjCurve->getAxisX()->getUnit();
if(!sUnit.isEmpty()) {
sNameX += QString("\n(%1)").arg(sUnit);
}
QString sNameY = pObjCurve->getAxisY()->getName();
sUnit = pObjCurve->getAxisY()->getUnit();
if(!sUnit.isEmpty()) {
sNameY += QString("\n(%1)").arg(sUnit);
}
QStringList listTitles;
listTitles << sNameX << sNameY;
pTableView->getModel()->setTitles(listTitles);
}
// 刷新表格数据
{
VVecDouble vvec;
QVector<QPointF> vecValues = pObjCurve->getAllValues();
for(int i = 0; i < vecValues.count(); i++) {
VecDouble vec;
vec << vecValues.at(i).x() * 1.0;
vec << vecValues.at(i).y() * 1.0;
vvec << vec;
}
pTableView->getModel()->setData(vvec);
}
}
#endif
void nmSubWndMain::slotObjSelChanged(ZxObjBase* p, bool b)
{
#ifdef QT_DEBUG
// 如果选中了一条曲线,则把其数据显示在数据表格2中
ZxObjCurve* pObjCurve = dynamic_cast<ZxObjCurve*>(p);
if(NULL != pObjCurve && b) {
Q_ASSERT(NULL != m_pWxDockData2);
m_pWxDockData2->raise();
updateTableByCurve(pObjCurve, m_pTableView2);
}
#endif
}
void nmSubWndMain::slotObjPtsChanged(ZxObjBase* p)
{
}
void nmSubWndMain::onSerialize(ZxSerializer* ser)
{
iSubWndBaseAF::onSerialize(ser);
if(NULL != m_pWxPlot) {
m_pWxPlot->setModified(false);
}
}
void nmSubWndMain::onDeserialize(ZxSerializer* ser)
{
iSubWndBaseAF::onDeserialize(ser);
if(NULL != m_pWxPlot) {
m_pWxPlot->setModified(false);
}
}
void nmSubWndMain::onDeserialized()
{
if(NULL != m_pWxPlot) {
m_pWxPlot->resetAfterDeserialized();
}
}
//bool nmSubWndMain::loadRss() {
// ZxRstJobWnd* pRstWnd = getRstJobWnd();
// Q_ASSERT(NULL != pRstWnd);
// // 基础信息
// {
// QByteArray v = pRstWnd->getDataInfo();
// IxSerDes* pSerObj = this;
// if(!loadRstInfoFromArr(v, pSerObj)) {
// return false;
// }
// }
// // 图形
// if(NULL != m_pWxPlot) {
// QByteArray v = pRstWnd->getDataInfo1();
// ZxPlot* pPlot = NULL;
// loadOnePlot(pPlot, v);
// if(NULL != pPlot) {
// m_pWxPlot->m_pPlotScene->freshToPlot(pPlot,
// m_pWxPlot->m_pPlotView);
// m_pWxPlot->m_pPlot = m_pWxPlot->m_pPlotScene->m_pPlot;
// pPlot->resetTools(m_pWxPlot->m_pPlotView);
// m_pWxPlot->resetAfterDeserialized();
// }
// m_pWxPlot->runUpdate();
// }
// return true;
//}
//bool nmSubWndMain::saveRss() {
// ZxRstJobWnd* pRstWnd = getRstJobWnd();
// if(NULL == pRstWnd) {
// return false;
// }
// // 基础信息
// {
// QByteArray v;
// IxSerDes* pSerObj = this;
// if(!saveRstInfoToArr(v, pSerObj)) {
// return false;
// }
// pRstWnd->setDataInfo(v);
// }
// // 图形
// if(NULL != m_pWxPlot) {
// ZxPlot* pPlot = m_pWxPlot->m_pPlot;
// {
// QByteArray v1;
// if(saveOnePlot(pPlot, v1)) {
// pRstWnd->setDataInfo1(v1);
// }
// }
// }
// return pRstWnd->save();
//}
bool nmSubWndMain::loadRsts()
{
return true;//
/*
ZxRstWnd* pRstWnd = getRstUtilWnd();
Q_ASSERT (nullptr != pRstWnd);
// 基础信息
if (!loadRstBase(pRstWnd))
{
return false;
}
// 图形
if (nullptr != m_pWxPlot)
{
QByteArray v = QByteArray();
pRstWnd->loadSubPlotContent(v);
ZxPlot* pPlot = nullptr;
loadOnePlotFromBa(pPlot, v);
if (nullptr != pPlot)
{
m_pWxPlot->m_pPlotScene->freshToPlot(pPlot,
m_pWxPlot->m_pPlotView);
m_pWxPlot->m_pPlot = m_pWxPlot->m_pPlotScene->m_pPlot;
pPlot->resetTools(m_pWxPlot->m_pPlotView);
m_pWxPlot->resetAfterDeserialized();
}
m_pWxPlot->runUpdate();
}//*/
}
bool nmSubWndMain::saveRsts()
{
ZxRstWnd* pRstWnd = this->getRstUtilWnd();
if(nullptr == pRstWnd) {
return false;
}
return pRstWnd->save();
// // 基础信息
// if (!saveRstBase(pRstWnd))
// {
// return false;
// }
// // 图形
// if (nullptr != m_pWxPlot)
// {
// ZxPlot* pPlot = m_pWxPlot->m_pPlot;
// {
// QByteArray v1;
// if (saveOnePlotToBa(pPlot, v1))
// {
// pRstWnd->saveSubPlotContent(v1);
// }
// }
// }
//return pRstWnd->save();
}
bool nmSubWndMain::slotSaveAll()
{
ZxRstWnd* pRstWnd = getRstUtilWnd();
Q_ASSERT(NULL != pRstWnd);
if(pRstWnd == NULL) {
return false;
}
return saveRsts();
}
void nmSubWndMain::updateSelectedWells(QList<ZxDataWell *> wellObjList)
{
// if(wellObjList.count() == 0) {
// return;
// }
// setp 1,获取现在画布上所有井的 ZxDataWell* 数据指针
QVector<nmObjPointWell*> pWellPlotList = m_pWxPlot->getWellPlots();
// QMap<ZxDataWell*, nmObjPointWell*> 用于快速查找和构建当前画布上的井数据列表
QMap<ZxDataWell*, nmObjPointWell*> pWellDataToPlotMap;
for(int i = 0; i < pWellPlotList.count(); ++i) {
nmObjPointWell* wellPlot = pWellPlotList[i];
ZxDataWell *pWellData = wellPlot->getWellData();
if (pWellData) { // 确保数据不为空
pWellDataToPlotMap.insert(pWellData, wellPlot);
}
}
// 用集合保存当前井和选中井,避免井数增加后反复调用 QList::contains 造成平方级查找。
QList<ZxDataWell*> pWellPlotDataList = pWellDataToPlotMap.keys();
QSet<ZxDataWell*> pWellPlotDataSet;
for(int i = 0; i < pWellPlotDataList.count(); ++i) {
pWellPlotDataSet.insert(pWellPlotDataList[i]);
}
QSet<ZxDataWell*> selectedWellDataSet;
for(int i = 0; i < wellObjList.count(); ++i) {
if(wellObjList[i] != nullptr) {
selectedWellDataSet.insert(wellObjList[i]);
}
}
// setp 2,找出需要 REMOVE (删除) 的图元
QVector<nmObjPointWell*> vDeleteWellPlotList;
// 遍历所有当前在画布上的井数据
for(int i = 0; i < pWellPlotDataList.count(); ++i) {
ZxDataWell *pWellDataOnPlot = pWellPlotDataList[i];
// 如果该井数据不在用户选中的列表 wellObjList 中,则需要删除
if (!selectedWellDataSet.contains(pWellDataOnPlot)) {
// 从 Map 中获取对应的图元对象
vDeleteWellPlotList.append(pWellDataToPlotMap.value(pWellDataOnPlot));
}
}
// setp 3,找出需要 ADD (添加) 的井数据
QVector<ZxDataWell*> vAddWellPlotList;
// 遍历所有用户选中的井数据 wellObjList
for(int i = 0; i < wellObjList.count(); ++i) {
ZxDataWell *pWellDataSelected = wellObjList[i];
// 确保井数据有效
if (pWellDataSelected && !pWellPlotDataSet.contains(pWellDataSelected)) {
// 如果该井数据不在当前画布上的数据列表中,则需要添加
vAddWellPlotList.append(pWellDataSelected);
}
}
// 单井增删过程中会多次请求重绘;批量阶段先暂停,全部处理完成后再统一刷新。
const bool updatesEnabled = m_pWxPlot->updatesEnabled();
m_pWxPlot->setUpdatesEnabled(false);
// setp 4,执行删除操作
for(int i = 0; i < vDeleteWellPlotList.count(); ++i) {
nmObjPointWell* pWellPlot = vDeleteWellPlotList[i];
if(pWellPlot == nullptr) {
continue;
}
// 选择井列表只能移除普通数值井;固定主井始终保留在本次分析中。
if(m_pWxPlot->graphicBinder() &&
m_pWxPlot->graphicBinder()->isPrimaryWellGraphic(pWellPlot))
continue;
// 数据删除失败时保留图元,避免画布和DataManager出现半套状态。
if(!pWellPlot->removeBoundData()) {
continue;
}
// 按准确对象删除,不能按井名删除,否则同名井可能移除错误图元。
const int nObjCount = m_pWxPlot->m_pPlot->getObjCount();
for(int nObjIndex = 0; nObjIndex < nObjCount; ++nObjIndex) {
if(m_pWxPlot->m_pPlot->getObjByIndex(nObjIndex) == pWellPlot) {
m_pWxPlot->m_pPlot->removeObjByIndex(nObjIndex);
break;
}
}
}
// setp 5,将vAddWellPlotList中的井数据,构建图元,添加到画布上
for(int i = 0; i < vAddWellPlotList.count(); i++) {
ZxDataWell* wellObj = vAddWellPlotList[i];
QString sName = wellObj->getName();
QVector<QPointF> points;
points.append(QPointF(wellObj->getLocationX(), wellObj->getLocationY()));
// 构建了图元
nmObjPointWell* pWellPlot = NULL;
pWellPlot = (nmObjPointWell*)m_pWxPlot->appendOneObj(NMOT_Point_Well, sName, points);
if(pWellPlot == NULL) {
continue;
}
// 选择已有井重新生成图元时,先恢复Binder,后续afterCreated才能建立井数据绑定
if(m_pWxPlot->graphicBinder()) {
pWellPlot->setGraphicBinder(m_pWxPlot->graphicBinder());
}
// 设置图元的井数据
pWellPlot->setWellData(wellObj);
// 移动到正确的位置
pWellPlot->moveToPos(pWellPlot->getPosOf(pWellPlot->getAllPos()));
pWellPlot->afterCreated(); //选择完后初始化参数
}
// 添加完毕后,重新渲染
m_pWxPlot->setUpdatesEnabled(updatesEnabled);
if(updatesEnabled) {
m_pWxPlot->update();
}
}
// 地质图导入
void nmSubWndMain::geologicalMapImport()
{
//打开图片选择弹框
//打开文件对话框选择图片文件
//QString filePath = QFileDialog::getOpenFileName(nullptr, "选择图片文件", "", "Images (*.png *.xpm *.jpg)");
//if(!filePath.isEmpty()) {
// // 画布 设置背景,并自适应宽高
// ZxPlot* pPlot = m_pWxPlot->m_pPlot;
// pPlot->setBkImgFile(filePath);
// // QImage* pBkImg = pPlot->getBkImg();
// // Q_ASSERT (NULL != pBkImg);
// // int w = pBkImg->width();
// // int h = pBkImg->height();
// // pPlot->getMainAxisX()->setRangeMinMax(0, w, true);
// // pPlot->getMainAxisY()->setRangeMinMax(0, h, true);
// nmDataLogFile::getInstance()->writeLog(filePath);
//}
QString sFilter =
"PNG Images (*.png);;"
"JPEG Images (*.jpg *.jpeg);;"
"Bitmap Images (*.bmp);;"
"All Files (*)";
QString sFile = QFileDialog::getOpenFileName(
nullptr,
tr("Open File"),
ZxBaseUtil::getLastDir(),
sFilter
);
if(sFile.isEmpty() || !QFile::exists(sFile)) {
return;
}
// 更新最后访问目录
QFileInfo fi(sFile);
ZxBaseUtil::setLastDir(fi.absolutePath());
// 输出文件失败原因
QImageReader reader(sFile);
if(!reader.canRead()) {
// 输出具体错误
qDebug() << "Error Message: " << reader.errorString();
return;
}
if(m_pWxPlot == nullptr || m_pWxPlot->m_pPlot == nullptr) {
return;
}
// 设置背景图片
ZxPlot* pPlot = m_pWxPlot->m_pPlot;
pPlot->setBkImgFile(sFile);
// 检查 ZxPlot 是否成功加载图片
QImage* pBkImg = pPlot->getBkImg();
Q_ASSERT(nullptr != pBkImg);
if(pBkImg == nullptr) {
return;
}
// 将图片信息存储到数据中心
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
if(pManager) {
QImage loadedImage = reader.read(); // 假设已经读取成功
BackgroundImageInfo bgInfo;
bgInfo.bIsVisible = true; // 默认设置为可见
bgInfo.sFilePath = sFile; // 当前背景文件路径
bgInfo.objImage = loadedImage; // 存储QImage对象
pManager->setBackgroundImageInfo(bgInfo); // 设置背景图片信息
}
// 手动调整坐标轴(如果 ZxPlot 未自动调整)
//int w = pBkImg->width();
//int h = pBkImg->height();
//if (w > 0 && h > 0) {
// pPlot->getMainAxisX()->setRangeMinMax(0, w, true);
// pPlot->getMainAxisY()->setRangeMinMax(0, h, true);
//}
}
void nmSubWndMain::drawMeasuringScale()
{
this->triggerToolBarAction(NMOT_Line_MeasuringScale);
}
void nmSubWndMain::geologicalMapHide()
{
}
void nmSubWndMain::setTopDocksVisible(bool visible)
{
// 获取主程序所有 Dock 视图
QList<QDockWidget*> dockWidgets = m_pMainWindow->findChildren<QDockWidget*>();
// 遍历所有 Dock 视图并移除位于左侧的
foreach(QDockWidget* dock, dockWidgets) {
if(visible) {
dock->show();
} else {
dock->hide();
}
}
}
void nmSubWndMain::drawPolygonOutline()
{
// 获取多边形边界的绘制,并模拟触发点击操作
// 目前多边形边界绘制是工具栏的第4个,所以用 3
// QList<QAction *> actions = m_pPlotToolBar->actions();
// if (!actions.isEmpty()) {
// QAction *action = actions[3];
// action->trigger();
// }
this->triggerToolBarAction(NMOT_PolygonOutline);
}
void nmSubWndMain::drawSquareOutline()
{
// 获取矩形边界的绘制,并模拟触发点击操作
this->triggerToolBarAction(NMOT_RectOutline);
}
void nmSubWndMain::drawRoundOutline()
{
// 获取矩形边界的绘制,并模拟触发点击操作
this->triggerToolBarAction(NMOT_RoundOutline);
}
void nmSubWndMain::drawFault()
{
this->triggerToolBarAction(NMOT_Line_Fault);
}
void nmSubWndMain::selectWell()
{
if(m_pWxPlot == nullptr || zxCurProject == nullptr) {
return;
}
// 第一步:Map 当前选择状态使用 WellCode 保存,井名只用于界面显示。
QList<QVariant> selectedWellCodes;
QVector<ZxDataWell*> vecMapWells = m_pWxPlot->getWellDatas();
for(int nIndex = 0; nIndex < vecMapWells.size(); ++nIndex) {
ZxDataWell* pMapWell = vecMapWells[nIndex];
if(pMapWell != nullptr && !pMapWell->getCode().isEmpty()) {
selectedWellCodes.append(pMapWell->getCode());
}
}
// 第二步:读取项目井库,并建立 WellCode 到项目井对象的稳定映射。
int wellCount = zxCurProject->getChildrenCount(iDataModelType::sTypeWell);
ZxDataObjectList wellList = zxCurProject->getChildren(iDataModelType::sTypeWell);
nmDataLogFile::getInstance()->writeLog(QString(" %1 %2").arg(wellCount).arg(wellList.size()));
QStringList listWellNames;
QList<QVariant> wellCodes;
QMap<QString, ZxDataWell*> wellCodeToObjMap;
for(int i = 0; i < wellList.size(); i++) {
ZxDataWell* wellObj = dynamic_cast<ZxDataWell*>(wellList[i]);
if(wellObj == nullptr || wellObj->getCode().isEmpty()) {
continue;
}
nmDataLogFile::getInstance()->writeLog(wellObj->getName() + " " + wellObj->getCode());
listWellNames.append(wellObj->getName());
wellCodes.append(wellObj->getCode());
wellCodeToObjMap.insert(wellObj->getCode(), wellObj);
}
// 第三步:恢复 Map 中已有井的勾选状态,允许用户增删其他项目井。
nmWxSelectWellsDlg dlg;
nmWxSelectWellsWidget wellListWidget;
wellListWidget.setSelectedValues(selectedWellCodes);
wellListWidget.addItems(listWellNames, wellCodes);
dlg.setWidget(&wellListWidget);
if(dlg.exec() == QDialog::Accepted) {
nmDataLogFile::getInstance()->writeLog(" OK ");
QList<QVariant> selectedValues = wellListWidget.getSelectedValues();
QList<ZxDataWell*> selectWellList;
// 第四步:按 WellCode 解析选择结果,避免同名井或井名修改导致关联错误。
QStringList selectedCodeTexts;
for(int i = 0; i < selectedValues.count(); i++) {
const QString sWellCode = selectedValues[i].toString();
ZxDataWell* pSelectedWell = wellCodeToObjMap.value(sWellCode, nullptr);
if(pSelectedWell != nullptr) {
selectWellList.append(pSelectedWell);
selectedCodeTexts.append(sWellCode);
}
}
this->updateSelectedWells(selectWellList);
nmDataLogFile::getInstance()->writeLog(
"===" + selectedCodeTexts.join(";") + "===");
} else {
nmDataLogFile::getInstance()->writeLog(" Cancel ");
}
}
// 新建井菜单暂时停用。
// void nmSubWndMain::drawWell()
// {
// this->triggerToolBarAction(NMOT_Point_Well);
// nmDataLogFile::getInstance()->writeLog(" in drawWell ");
// }
void nmSubWndMain::drawCrack()
{
this->triggerToolBarAction(NMOT_Line_Fracture);
}
void nmSubWndMain::drawRegion()
{
this->triggerToolBarAction(NMOT_Region);
}
void nmSubWndMain::drawRegionMark()
{
this->triggerToolBarAction(NMOT_RegionMark);
}
void nmSubWndMain::drawMeasure()
{
nmGuiPlot* plot = dynamic_cast<nmGuiPlot*>(m_pWxPlot);
QWidget* measureDlg = showMeasureDialog();
if(plot == nullptr || measureDlg == nullptr) {
return;
}
bindMeasureDialog(measureDlg);
plot->setAllPlotObjectsEditable(false);
this->triggerToolBarAction(NMOT_Line_Measure);
}
void nmSubWndMain::drawGeoRef()
{
nmWxGeoRefDlg dlg(nullptr);
dlg.exec();
}
void nmSubWndMain::drawPointerPos()
{
nmGuiPlot* plot = dynamic_cast<nmGuiPlot*>(m_pWxPlot);
if(plot) {
showPointerPosDialog();
}
}
bool nmSubWndMain::isDialogCmdOpened(int nId) const
{
return m_openDialogCmdIds.contains(nId);
}
void nmSubWndMain::markDialogCmdOpened(int nId)
{
if(!m_openDialogCmdIds.contains(nId)) {
m_openDialogCmdIds.append(nId);
}
}
void nmSubWndMain::markDialogCmdClosed(int nId)
{
// 从后向前遍历,避免索引错乱
for (int i = m_openDialogCmdIds.size() - 1; i >= 0; --i) {
if (m_openDialogCmdIds[i] == nId) {
m_openDialogCmdIds.remove(i);
}
}
}
void nmSubWndMain::onDialogCmdDestroyed(QObject* obj)
{
if(obj == nullptr) {
return;
}
bool bOk = false;
int nId = obj->property("dialogCmdId").toInt(&bOk);
if(bOk) {
markDialogCmdClosed(nId);
}
}
void nmSubWndMain::bindDialogCmdDestroyed(QWidget* dialog, int nId)
{
if(dialog == nullptr) {
return;
}
dialog->setProperty("dialogCmdId", nId);
connect(dialog, SIGNAL(destroyed(QObject*)), this, SLOT(onDialogCmdDestroyed(QObject*)));
}
void nmSubWndMain::bindMeasureDialog(QWidget* measureDlg)
{
nmGuiPlot* plot = dynamic_cast<nmGuiPlot*>(m_pWxPlot);
if(plot == nullptr || measureDlg == nullptr) {
return;
}
disconnect(plot, SIGNAL(sigMeasureStarted()), measureDlg, SLOT(resetButtons()));
connect(plot, SIGNAL(sigMeasureStarted()), measureDlg, SLOT(resetButtons()));
disconnect(measureDlg, SIGNAL(needDeleteMeasureObj()), plot, SLOT(finishMeasure()));
connect(measureDlg, SIGNAL(needDeleteMeasureObj()), plot, SLOT(finishMeasure()));
}
QWidget* nmSubWndMain::showMeasureDialog()
{
nmGuiPlot* plot = dynamic_cast<nmGuiPlot*>(m_pWxPlot);
if(plot == nullptr) {
return nullptr;
}
nmPlotDialogContextProvider* pDialogProvider = nmPlotDialogContext::provider();
if(pDialogProvider == nullptr) {
return nullptr;
}
QWidget* measureDlg = pDialogProvider->createMeasureDialog(plot);
if(measureDlg == nullptr) {
return nullptr;
}
markDialogCmdOpened(5129);
measureDlg->setObjectName("nmMeasureDialog");
measureDlg->setAttribute(Qt::WA_DeleteOnClose, true);
bindDialogCmdDestroyed(measureDlg, 5129);
nmDataMeasure* measureData = nmDataAnalyzeManager::getCurrentInstance()->getMeasureData();
if(measureData != nullptr) {
connect(measureData, SIGNAL(sigMeasureDataChanged()), measureDlg, SLOT(updateDisplay()));
}
measureDlg->show();
return measureDlg;
}
QWidget* nmSubWndMain::showPointerPosDialog()
{
nmGuiPlot* plot = dynamic_cast<nmGuiPlot*>(m_pWxPlot);
if(plot == nullptr) {
return nullptr;
}
nmPlotDialogContextProvider* pDialogProvider = nmPlotDialogContext::provider();
if(pDialogProvider == nullptr) {
return nullptr;
}
QWidget* pointerPosDlg = pDialogProvider->createPointerPosDialog(plot);
if(pointerPosDlg == nullptr) {
return nullptr;
}
markDialogCmdOpened(5128);
pointerPosDlg->setObjectName("nmPointerPosDialog");
pointerPosDlg->setAttribute(Qt::WA_DeleteOnClose, true);
bindDialogCmdDestroyed(pointerPosDlg, 5128);
plot->bindPointerPosDialog(pointerPosDlg);
pointerPosDlg->show();
return pointerPosDlg;
}
QWidget* nmSubWndMain::showPropertyInterpolationDialog()
{
nmWxPropertyInterpolationDlg* interpolationDlg = new nmWxPropertyInterpolationDlg(
m_pWxPlot, nmDataAnalyzeManager::getCurrentInstance(), this);
markDialogCmdOpened(5126);
interpolationDlg->setObjectName("nmPropertyInterpolationDialog");
// 关闭窗口时自动释放,并恢复菜单命令的可用状态
interpolationDlg->setAttribute(Qt::WA_DeleteOnClose, true);
bindDialogCmdDestroyed(interpolationDlg, 5126);
interpolationDlg->show();
interpolationDlg->raise();
interpolationDlg->activateWindow();
return interpolationDlg;
}
void nmSubWndMain::generationMesh()
{
// // TODO,还不支持原型边界
// // 拿到多边形和点的位置信息
// QVector<ZxObjBase*> pObjVec = m_pWxPlot->getObjsByTag("nObjPolygonOutline");
// nmDataLogFile::getInstance()->writeLog(" ---------- " + QString::number(pObjVec.count()));
// QVector<ZxObjBase*> pWellObjVec = m_pWxPlot->getObjsByTag("nObjPointWell");
// nmDataLogFile::getInstance()->writeLog(" ---------- " + QString::number(pWellObjVec.count()));
// // 生成geo文件
// // 调用gmsh生成vtk文件
// // 弹出网格的dialog,渲染文件
// if(true) {
// QDialog* dlg = new QDialog;
// QVBoxLayout* layout = new QVBoxLayout;
// dlg->setLayout(layout);
// nmWxGridVTKContainerWidget* gridVTKContainerWidget = new nmWxGridVTKContainerWidget(dlg);
// layout->addWidget(gridVTKContainerWidget);
// dlg->resize(800, 600);
// dlg->show();
// }
}
void nmSubWndMain::solveAndAnalyze()
{
if(!s_pPendingSolverWindow.isNull() ||
!s_pRunningSolverTask.isNull() ||
m_bSolverResultCompletionPending) {
QMessageBox::information(this, tr("solver error"), tr("task is running!"));
return;
}
// 新一次启动前清空旧上下文;后续所有检查通过后再成对写入。
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
// 根据当前数据中心的分析切换到对应的流动段分析窗口
ZxMainWindow* pMainWnd1 = const_cast<ZxMainWindow*>(m_pMainWindow);
if(nullptr == pMainWnd1) {
return;
}
ZxTabWidget* pTabWx = pMainWnd1->getCurTabWx();
iSubWndFitting* pSubWndFit = nmDataAnalyzeManager::getCurrentFitting();
Q_ASSERT(pSubWndFit != nullptr);
if(pSubWndFit == nullptr) {
return;
}
if(pTabWx == nullptr) {
return;
}
pTabWx->setCurrentWidget(pSubWndFit);
nmDataAnalyzeManager* pDataManager =
nmDataAnalyzeManager::findManagerByFitting(pSubWndFit);
if(pDataManager == nullptr) {
return;
}
// 第一步:求解时间轴必须由至少一口主动井的产注制度驱动。
// 当前数值接入只开放油、气、水单相和油水两相;其余相态保留数据,
// 等求解器支持后再开放,不在这里改写或丢弃框架流量。
if(!nmIsSupportedPebiSolverModel(
pDataManager->getSolverModelType())) {
QMessageBox::warning(
this,
tr("solver error"),
tr("The solver currently supports oil, gas, and water single-phase models and the oil-water two-phase model only."));
return;
}
// 观察井可以完全不传产量,但不能在所有井都是观察井时启动无源汇计算。
QVector<nmCalculationWellRef> vecEffectiveWells =
pDataManager->getEffectiveCalculationWells();
QHash<QString, nmDataWellBase*> mapWellsByCode;
const QVector<nmDataWellBase*> vecAllWells =
pDataManager->getWellDataList();
for(int nIndex = 0; nIndex < vecAllWells.size(); ++nIndex) {
nmDataWellBase* pWellData = vecAllWells[nIndex];
if(pWellData != nullptr) {
mapWellsByCode.insert(pWellData->getWellCode(), pWellData);
}
}
bool bHasRateControlledWell = false;
for(int nIndex = 0; nIndex < vecEffectiveWells.size(); ++nIndex) {
const nmCalculationWellRef& oWellRef = vecEffectiveWells[nIndex];
nmDataWellBase* pWellData =
mapWellsByCode.value(oWellRef.m_sWellCode, nullptr);
if(pWellData == nullptr) {
QMessageBox::warning(this, tr("solver error"),
tr("A selected well is missing from the map."));
return;
}
if(oWellRef.m_eMode != NM_CaseWell_RateControlled) {
continue;
}
bHasRateControlledWell = true;
if(!nmIsValidWellCategory(pWellData->getWellCategory()) ||
pWellData->getFlowSegmentCount() <= 0 ||
!pWellData->hasValidFlowSectionIndex()) {
QMessageBox::warning(
this,
tr("solver error"),
tr("Rate-controlled well '%1' has no valid rate schedule.")
.arg(pWellData->getWellName()));
return;
}
}
if(!bHasRateControlledWell) {
QMessageBox::warning(
this,
tr("solver error"),
tr("At least one active production or injection well is required."));
return;
}
// 手动求解可能直接启动, 因此必须在创建求解线程前初始化拟压力转换器.
// pSubWndFit 保存的是当前拟合窗口克隆后的 PVT、拟压力及模型选项数据.
// configPsAbouts() 会据此初始化常规、三区或组分转换器并设置当前模式.
if(pDataManager->getSolverModelType() == SMT_Gas_VariablePvt) {
iAnalRun* pAnalRun = pSubWndFit->getAnalRun();
if(pAnalRun == nullptr
|| !pAnalRun->configPsAbouts(true,
pSubWndFit->getModelOption(),
false,
pSubWndFit->getAllWxPtr())) {
QMessageBox::warning(this, tr("solver error"),
tr("Gas pseudo-pressure data is unavailable or invalid."));
return;
}
}
if(m_pSolverTask != nullptr) {
// 上一次线程已结束但指针还未清空时,先交给Qt事件循环安全释放。
m_pSolverTask->deleteLater();
m_pSolverTask = nullptr;
}
// 第一步:捕获本次求解的完整窗口上下文,完成回调不得再读取全局当前窗口。
m_pSolverDataManager = pDataManager;
m_pSolverFitting = pSubWndFit;
m_bSolverCancelRequested = false;
// 第二步:先显示真实进度框,再在下一轮事件中捕获输入并启动线程。
showSolverProgress();
m_bSolverStartPending = true;
s_pPendingSolverWindow = this;
QTimer::singleShot(0, this, SLOT(startSolverTask()));
}
void nmSubWndMain::showSolverProgress()
{
// Qt 4.8 下保留已经隐藏的实例,普通求解与批量任务都从这里初始化。
if (m_pProgressDlg == nullptr)
{
m_pProgressDlg = new QProgressDialog(
tr("Preparing input snapshot..."),
tr("Stop"),
0,
0,
getMainWindow());
m_pProgressDlg->setWindowTitle(tr("Solver Progress"));
// 保持 8 月 25 日前的稳定行为。任务和输入快照负责数据安全,进度框不参与
// 全局窗口阻塞,避免首次创建结果窗口时进入 Qt 4.8 模态布局重入。
m_pProgressDlg->setWindowModality(Qt::NonModal);
// 某些系统下,对话框右上角的关闭按钮可能仍存
// 使用 WindowFlags 彻底禁用关闭按钮
m_pProgressDlg->setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
m_pProgressDlg->setMinimumDuration(0);
m_pProgressDlg->setAutoClose(false);
m_pProgressDlg->setAutoReset(false);
connect(m_pProgressDlg, SIGNAL(canceled()),
this, SLOT(onSolverCancelRequested()));
}
m_pProgressDlg->reset();
QPushButton* pCancelButton = m_pProgressDlg->findChild<QPushButton*>();
if(pCancelButton != nullptr) pCancelButton->setEnabled(true);
m_pProgressDlg->setLabelText(tr("Preparing input snapshot..."));
m_pProgressDlg->setRange(0, 0);
m_pProgressDlg->show();
m_pProgressDlg->raise(); // 提升层级到最前
m_pProgressDlg->activateWindow(); // 激活窗口焦点
}
bool nmSubWndMain::prepareAuxiliarySolverTask(
QWidget* dialog, nmCalculationDllPebiSolverTask* task)
{
// 与普通求解共用防重入状态,避免两个任务同时写同一进度框。
if(!s_pPendingSolverWindow.isNull() || !s_pRunningSolverTask.isNull() ||
m_bSolverResultCompletionPending) {
QMessageBox::information(dialog, tr("solver error"), tr("task is running!"));
return false;
}
m_pAuxiliarySolverTask = task;
s_pRunningSolverTask = task;
m_bSolverCancelRequested = false;
// 先隐藏共享进度框,再让参数窗口的完成槽读取结果。
connect(task, SIGNAL(finished()), this, SLOT(onAuxiliarySolverTaskFinished()), Qt::QueuedConnection);
connect(task, SIGNAL(destroyed(QObject*)), this, SLOT(onAuxiliarySolverTaskDestroyed()));
showSolverProgress();
m_pProgressDlg->setLabelText(nmCalculationDllPebiSolverTask::tr("Solving model..."));
return true;
}
void nmSubWndMain::onForecastTaskReady(
nmWxForecast* dialog, nmCalculationDllPebiSolverTask* task)
{
// 预测沿用共享进度框和停止路径,计算结果不进入正式成果提交。
if(!prepareAuxiliarySolverTask(dialog, task)) return;
connect(dialog, SIGNAL(sigForecastCancelRequested()),
this, SLOT(onSolverCancelRequested()), Qt::UniqueConnection);
dialog->startForecastCalculation();
}
void nmSubWndMain::onSensitivityTaskReady(
nmWxSensitive* dialog, nmCalculationDllPebiSolverTask* task)
{
// 整个批次保持同一个滚动进度,不订阅每组求解的阶段刷新信号。
if(!prepareAuxiliarySolverTask(dialog, task)) return;
connect(dialog, SIGNAL(sigSensitivityCancelRequested()),
this, SLOT(onSolverCancelRequested()), Qt::UniqueConnection);
// 坐标标题与单位跟随求解时的双对数窗口,后来切换模型不改变已有批次。
QSharedPointer<nmDataSensitive::BatchResult> result = task->getSensitivityBatchResult();
if(!m_pResultRefreshFitting.isNull()) {
iGuiPlot* source = qobject_cast<iGuiPlot*>(
m_pResultRefreshFitting->getFitSubRstWxOf(FSRT_DoubleLog, false));
if(source && source->getAxisOf(0) && source->getAxisOf(1)) {
result->m_sAxisXName = source->getAxisOf(0)->getName();
result->m_sAxisXUnit = source->getAxisOf(0)->getUnit();
result->m_sAxisYName = source->getAxisOf(1)->getName();
result->m_sAxisYUnit = source->getAxisOf(1)->getUnit();
}
}
dialog->startSensitivityCalculation();
}
void nmSubWndMain::onAuxiliarySolverTaskFinished()
{
// 完整退出线程后再释放进度框的占用,不进入普通求解的成果提交路径。
if(m_pAuxiliarySolverTask.isNull() || sender() != m_pAuxiliarySolverTask.data()) return;
m_pAuxiliarySolverTask->wait();
if(s_pRunningSolverTask == m_pAuxiliarySolverTask) s_pRunningSolverTask = nullptr;
disconnect(m_pAuxiliarySolverTask.data(), nullptr, this, nullptr);
m_pAuxiliarySolverTask = nullptr;
m_bSolverCancelRequested = false;
m_pProgressDlg->hide();
}
void nmSubWndMain::onAuxiliarySolverTaskDestroyed()
{
// 参数面板提前销毁时,其析构函数已等待任务结束,解除共享进度框占用。
m_pAuxiliarySolverTask = nullptr;
m_bSolverCancelRequested = false;
m_pProgressDlg->hide();
}
void nmSubWndMain::onShowForecastResult(const QString& wellName, const QVector<double>& times,
const QVector<double>& pressures, const QVector<double>& durations,
const QVector<double>& rates)
{
// 使用面板绑定的分析页面,切换活动分析不会改变预测结果的归属。
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
nmDataAnalyzeManager* manager = m_pResultRefreshDataManager.data();
if(!fitting || !fitting->m_pMdiArea || !manager ||
nmDataAnalyzeManager::findManagerByFitting(fitting) != manager ||
!manager->isPebiResultSnapshotCurrent()) return;
ZxMdiArea* area = fitting->m_pMdiArea;
QMdiSubWindow* window = area->findChild<QMdiSubWindow*>("nmForecastResult");
const bool created = window == nullptr;
if(created) {
// 结果页面由 MDI 承载,关闭预测输入对话框不销毁结果。
QWidget* page = new QWidget;
QVBoxLayout* layout = new QVBoxLayout(page);
layout->setContentsMargins(0, 0, 0, 0);
QSplitter* charts = new QSplitter(Qt::Vertical, page);
charts->setObjectName("nmForecastCharts");
charts->setChildrenCollapsible(false);
layout->addWidget(charts);
// 布局完成后统一适配画布,避免小窗口下两个绘图区缩放比例不同。
charts->installEventFilter(this);
connect(charts, SIGNAL(splitterMoved(int,int)), this, SLOT(fitForecastViews()), Qt::QueuedConnection);
for(int i = 0; i < 2; ++i) {
iGuiPlot* plot = new iGuiPlot(true, charts);
plot->setObjectName(i == 0 ? "nmForecastRate" : "nmForecastPressure");
plot->initUI(i == 0 ? tr("Production rate") : tr("Bottomhole pressure"));
connect(plot, SIGNAL(sigDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)),
this, SLOT(onReadonlyCurveDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)));
plot->m_pPlotView->setSimpleMode(true);
plot->setDropEnabled(false);
plot->setAxisX(tr("Time"), "hr");
plot->setAxisY(i == 0 ? tr("Flow") : tr("Pressure"), i == 0 ? "m^3/D" : "MPa");
plot->setAxisY(i == 0 ? tr("Cumulative production") : QString(), i == 0 ? "m^3" : "", true);
// 两侧纵轴适当加宽,留出数字与名称的间距,上下图采用相同宽度保持对齐。
plot->getAxisOf(1)->setSpan(16.0);
plot->getAxisOf(2)->setSpan(16.0);
for(int axis = 0; axis < 3; ++axis) plot->setAxisXYLog(false, axis);
plot->getAxisOf(2)->setVisible(true);
// 下图右侧保留空轴位,确保两张图的时间刻度在水平方向对齐。
if(i == 1) {
plot->getAxisOf(2)->setColor(Qt::transparent);
}
plot->m_pPlot->setBKColor(Qt::white);
plot->m_pPlot->setLegendVisible(false);
charts->addWidget(plot);
charts->setStretchFactor(i, 1);
connect(plot->getAxisOf(0), SIGNAL(sigRangeChanged(double,double,bool,bool)),
this, SLOT(syncForecastTimeAxis(double,double,bool,bool)));
}
window = area->addSubWindow(page);
window->setObjectName("nmForecastResult");
window->setAttribute(Qt::WA_DeleteOnClose, false);
}
window->setWindowTitle(tr("Forecast - %1").arg(wellName));
// 压力直接取求解器结果;阶梯产量及累计量按本次输入制度精确构造。
QVector<double> rateTimes, rateValues, cumulativeTimes, cumulativeValues;
double time = 0.0, cumulative = 0.0, maximumRate = 0.0;
// 零时刻先画关井基线,再接首段产量,显示开始生产时的竖直跳变。
rateTimes.append(0.0);
rateValues.append(0.0);
cumulativeTimes.append(0.0);
cumulativeValues.append(0.0);
for(int i = 0; i < durations.size(); ++i) {
rateTimes << time << time + durations[i];
rateValues << rates[i] << rates[i];
maximumRate = qMax(maximumRate, rates[i]);
time += durations[i];
cumulative += rates[i] * durations[i] / 24.0;
cumulativeTimes.append(time);
cumulativeValues.append(cumulative);
}
const QStringList names = QStringList() << tr("Bottomhole pressure") << tr("Production rate") << tr("Cumulative production");
QVector<iCurveDesc> curves;
for(int i = 0; i < 3; ++i) {
iCurveDesc curve;
curve.m_sCurveName = names[i];
curve.m_oCurveType = POT_CurveDiscrete;
curve.m_bLineVisible = true;
curve.m_bPointVisible = false;
curve.m_bUseY2 = i == 2;
curve.m_oPen = QPen(i == 0 ? Qt::red : i == 1 ? Qt::darkGreen : Qt::gray, 0.35);
curve.m_vecX = i == 0 ? times : i == 1 ? rateTimes : cumulativeTimes;
curve.m_vecY = i == 0 ? pressures : i == 1 ? rateValues : cumulativeValues;
curves.append(curve);
}
iGuiPlot* plots[] = {window->findChild<iGuiPlot*>("nmForecastRate"),
window->findChild<iGuiPlot*>("nmForecastPressure")};
for(int i = 0; i < 2; ++i) {
iGuiPlot* plot = plots[i];
// 表格引用本批曲线,替换图元前关闭旧表,避免显示过期数据。
clearResultCurveDataTables(plot);
// 重复生成只替换曲线,清理旧图元和撤销记录,保留上下图窗口。
for(int j = plot->m_pPlot->getObjCount() - 1; j >= 0; --j)
if(qobject_cast<ZxObjCurve*>(plot->m_pPlot->getObjByIndex(j))) plot->m_pPlot->removeObjByIndex(j);
plot->clearUndoStack();
plot->setCurveData(i == 0 ? curves.mid(1, 2) : curves.mid(0, 1));
plot->runUpdate();
plot->setReadOnly(true);
plot->freshAxisScales(true, true);
if(i == 0) {
// 产量和累计量均从零起算,顶部留少量空白;全程关井保留有效轴范围。
const double rateTop = maximumRate > 0.0 ? maximumRate : 1.0;
const double cumulativeTop = cumulative > 0.0 ? cumulative : 1.0;
VecDouble rateTicks, cumulativeTicks;
for(int tick = 0; tick <= 5; ++tick) {
rateTicks.append(rateTop * tick / 5.0);
cumulativeTicks.append(cumulativeTop * tick / 5.0);
}
plot->setAxisScaleY(0.0, rateTop * 1.05, rateTicks);
plot->setAxisScaleY2(0.0, cumulativeTop * 1.05, cumulativeTicks);
}
// 自动定标会压缩没有曲线的右轴,下图需要保留这块空白以对齐上图。
plot->getAxisOf(2)->setMinimizeAxis(false);
// 两张图共用包含完整 DLL 结果的时间范围,多井计算可能长于当前井输入制度。
const double endTime = qMax(time, times.last());
VecDouble ticks;
for(int tick = 0; tick <= 5; ++tick) ticks.append(endTime * tick / 5.0);
plot->setAxisScaleX(-endTime * 0.05, endTime, ticks);
// 时间刻度及标题只在下图显示,上图仍保留横轴数据与同步关系。
plot->getAxisOf(0)->setMinimizeAxis(i == 0);
}
const bool restore = window->isHidden();
window->widget()->show();
window->show();
// 隐藏的分析页交给框架显示事件布局,避免此时重排把结果重新隐藏。
if((created || restore) && area->isVisible()) area->relayoutSubWnds();
area->setActiveSubWindow(window);
QTimer::singleShot(0, this, SLOT(fitForecastViews()));
}
void nmSubWndMain::onReadonlyCurveDoubleClicked(
ZxPlot* plot, ZxObjBase*, const QPointF& point, bool& processed)
{
iGuiPlot* widget = qobject_cast<iGuiPlot*>(sender());
if(!widget || !plot || processed) return;
for(int i = plot->getObjCount() - 1; i >= 0; --i) {
ZxObjCurve* curve = qobject_cast<ZxObjCurve*>(plot->getObjByIndex(i));
if(!curve || !curve->isVisible()) continue;
// 框架忽略只读曲线的命中,仅检查双击位置时临时解除,打开表格前恢复保护。
const bool readonly = curve->isReadOnly();
curve->setReadOnly(false);
int option = -1, index = -1;
const bool hit = curve->_runHitTest(point, option, index);
curve->setReadOnly(readonly);
if(!hit) continue;
processed = true;
widget->slotShowCurveData(curve);
return;
}
}
bool nmSubWndMain::eventFilter(QObject* watched, QEvent* event)
{
// 子窗口缩放、重新显示均在内部控件完成尺寸更新后再适配。
if(watched->objectName() == "nmForecastCharts" &&
(event->type() == QEvent::Resize || event->type() == QEvent::Show))
QTimer::singleShot(0, this, SLOT(fitForecastViews()));
return iSubWndBaseFit::eventFilter(watched, event);
}
void nmSubWndMain::fitForecastViews()
{
// 只适配图形画布的显示尺寸,不改变用户选定的时间和压力范围。
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
if(!fitting || !fitting->m_pMdiArea) return;
QMdiSubWindow* window = fitting->m_pMdiArea->findChild<QMdiSubWindow*>("nmForecastResult");
if(!window) return;
foreach(iGuiPlot* plot, window->findChildren<iGuiPlot*>())
plot->m_pPlotView->setZoomMode(ZxRenderView::eZoomFitWindow, true);
}
void nmSubWndMain::syncForecastTimeAxis(double minimum, double maximum, bool recalcTicks, bool adjustZoom)
{
// 只同步本分析预测图的横轴;阻断回传,避免两张图相互触发范围更新。
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
if(!fitting || !fitting->m_pMdiArea) return;
QMdiSubWindow* window = fitting->m_pMdiArea->findChild<QMdiSubWindow*>("nmForecastResult");
if(!window) return;
iGuiPlot* rate = window->findChild<iGuiPlot*>("nmForecastRate");
iGuiPlot* pressure = window->findChild<iGuiPlot*>("nmForecastPressure");
if(!rate || !pressure) return;
iGuiPlot* target = sender() == rate->getAxisOf(0) ? pressure : rate;
ZxSubAxisBase* axis = target->getAxisOf(0);
const bool blocked = axis->blockSignals(true);
axis->setRangeMinMax(minimum, maximum, recalcTicks, adjustZoom);
axis->blockSignals(blocked);
target->m_pPlot->update();
}
void nmSubWndMain::clearForecastResult()
{
// 由数据管理器通知主界面清场,不依赖预测输入对话框是否仍然打开。
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
if(sender() != m_pResultRefreshDataManager.data() || !fitting || !fitting->m_pMdiArea) return;
QMdiSubWindow* window = fitting->m_pMdiArea->findChild<QMdiSubWindow*>("nmForecastResult");
if(window) {
fitting->m_pMdiArea->removeSubWindow(window);
delete window;
// 直接移除窗口不会经过框架的关闭流程,清场后按当前布局重排其余窗口。
if(fitting->m_pMdiArea->isVisible()) fitting->m_pMdiArea->relayoutSubWnds();
}
}
void nmSubWndMain::onShowSensitiveResults()
{
// 使用固定所属分析的窗口与数据,不能把结果画到当前活动的其他分析中。
nmDataAnalyzeManager* manager = m_pResultRefreshDataManager.data();
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
if(!manager || !fitting || !manager->getSensitiveData()) return;
// 新批次或清空结果时同步移除上一批的上下限分组。
onSensitiveFilterChanged();
updateSensitiveHistoryPlot();
updateSensitiveHistogramPlot();
QSharedPointer<nmDataSensitive::BatchResult> result = manager->getSensitiveData()->getResults();
iGuiPlot* plot = fitting->getPlotByTag(s_Tag_ChartSensitivity);
if(plot) clearResultCurveDataTables(plot);
if(result.isNull()) {
// 新模型已丢弃旧批次,同时清除图元与撤销记录,避免窗口恢复或保存后残留旧曲线。
if(plot) {
for(int i = plot->m_pPlot->getObjCount() - 1; i >= 0; --i) {
if(qobject_cast<ZxObjCurve*>(plot->m_pPlot->getObjByIndex(i)))
plot->m_pPlot->removeObjByIndex(i);
}
plot->clearUndoStack();
plot->setCurveData(QVector<iCurveDesc>());
plot->runUpdate();
QMdiSubWindow* subWindow = nullptr;
for(QWidget* parent = plot->parentWidget(); parent && !subWindow; parent = parent->parentWidget())
subWindow = qobject_cast<QMdiSubWindow*>(parent);
if(subWindow) subWindow->close();
else plot->hide();
}
if(sender() == manager) fitting->setModified(true);
return;
}
const bool created = plot == nullptr;
// 通用取窗接口未实现敏感性类型,直接复用框架已有的内部 MDI 创建函数。
if(created) plot = fitting->initSensitivityPlot();
if(!plot) {
QMessageBox::warning(fitting, nmWxSensitive::tr("Sensitivity"),
tr("Failed to create sensitivity plot."));
return;
}
connect(plot, SIGNAL(sigDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)),
this, SLOT(onReadonlyCurveDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)), Qt::UniqueConnection);
installSensitiveShow(plot);
const QSet<QString> hidden = sender() == manager ? QSet<QString>() : hiddenSensitiveCurves(plot);
QVector<iCurveDesc> curves;
QVector<QVector<double> > history = result->m_vecHistoryLogLog;
if(history.size() == 3 && history[0].size() == history[1].size() &&
history[0].size() == history[2].size() && !history[0].isEmpty())
initloglog(history, curves, true);
for(size_t i = 0; i < result->m_runs.size(); ++i) {
const nmDataSensitive::RunResult& run = result->m_runs[i];
if(!run.m_bSucceeded || run.m_bCancelled) continue;
QVector<QVector<double> > data = run.m_vecLogLog;
if(data.size() != 3 || data[0].size() != data[1].size() ||
data[0].size() != data[2].size() || data[0].isEmpty()) continue;
QVector<iCurveDesc> runCurves;
if(!initloglog(data, runCurves, false)) continue;
// 每组压力与导数均使用实线,稳定名称和颜色避免组间曲线覆盖。
const QColor color = QColor::fromHsv(int((run.m_nRunIndex % 360) * 137 % 360), 190, 190);
runCurves[0].m_sCurveName = nmWxSensitive::tr("Run %1 - Pressure").arg(run.m_nRunIndex + 1);
runCurves[1].m_sCurveName = nmWxSensitive::tr("Run %1 - Derivative").arg(run.m_nRunIndex + 1);
runCurves[0].m_oPen.setColor(color);
runCurves[1].m_oPen.setColor(color);
runCurves[0].m_oPen.setStyle(Qt::SolidLine);
runCurves[1].m_oPen.setStyle(Qt::SolidLine);
curves += runCurves;
}
// 原模型数值解沿用普通双对数图的压力、导数样式,仅区分曲线名称。
QVector<QVector<double> > base = result->m_vecBaseLogLog;
if(base.size() == 3 && base[0].size() == base[1].size() &&
base[0].size() == base[2].size() && !base[0].isEmpty()) {
QVector<iCurveDesc> baseCurves;
if(initloglog(base, baseCurves, false)) {
baseCurves[0].m_sCurveName = nmWxSensitive::tr("Base model - Pressure");
baseCurves[1].m_sCurveName = nmWxSensitive::tr("Base model - Derivative");
curves += baseCurves;
}
}
// 对数坐标只接受有限正数,分别过滤压力与导数,保持各自时间和值对应。
for(int i = 0; i < curves.size(); ++i) {
QVector<double> x, y;
for(int point = 0; point < curves[i].m_vecX.size(); ++point) {
const double time = curves[i].m_vecX[point];
const double value = curves[i].m_vecY[point];
if(qIsFinite(time) && qIsFinite(value) && time > 0.0 && value > 0.0) {
x.append(time);
y.append(value);
}
}
curves[i].m_vecX = x;
curves[i].m_vecY = y;
}
plot->setDropEnabled(false);
plot->setAxisXYLog(true, 0);
plot->setAxisXYLog(true, 1);
if(!result->m_sAxisXName.isEmpty()) plot->setAxisX(result->m_sAxisXName, result->m_sAxisXUnit);
if(!result->m_sAxisYName.isEmpty()) plot->setAxisY(result->m_sAxisYName, result->m_sAxisYUnit);
// 框架刷新只创建或更新传入曲线,不删除缺席曲线,先移除已隐藏或上一批残留的图元。
QSet<QString> visibleNames;
for(int i = 0; i < curves.size(); ++i) visibleNames.insert(curves[i].m_sCurveName);
bool removed = false;
for(int i = plot->m_pPlot->getObjCount() - 1; i >= 0; --i) {
ZxObjCurve* curve = qobject_cast<ZxObjCurve*>(plot->m_pPlot->getObjByIndex(i));
if(curve && !visibleNames.contains(curve->getName())) {
plot->m_pPlot->removeObjByIndex(i);
removed = true;
}
}
// 清理删除曲线相关的撤销记录,避免后续操作恢复已经隐藏的图元。
if(removed) plot->clearUndoStack();
plot->setCurveData(curves);
plot->runUpdate();
for(int i = 0; i < curves.size(); ++i)
if(ZxObjCurve* curve = plot->getCurve(curves[i].m_sCurveName))
curve->setVisible(!hidden.contains(curve->getName()));
plot->setReadOnly(true);
plot->freshAxisScales(true, false);
// 只有新建或恢复窗口时才重新调整内部布局。
QMdiSubWindow* subWindow = nullptr;
for(QWidget* parent = plot->parentWidget(); parent && !subWindow; parent = parent->parentWidget())
subWindow = qobject_cast<QMdiSubWindow*>(parent);
const bool restore = subWindow && subWindow->isHidden();
plot->show();
if(subWindow) {
// 敏感性子窗口标题使用数值模块译文,与历史图、直方图保持一致。
subWindow->setWindowTitle(tr("Sensitivity loglog plot"));
subWindow->show();
if(created || restore) {
ZxMdiArea* area = qobject_cast<ZxMdiArea*>(subWindow->mdiArea());
fitting->layoutSubWnds(area ? area->getLayoutMode() : WLM_Cell);
}
}
if(sender() == manager) fitting->setModified(true);
}
void nmSubWndMain::updateSensitiveHistoryPlot()
{
// 复用框架的上下压力/流量控件,作为同一分析页面中的独立 MDI 子窗口。
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
nmDataAnalyzeManager* manager = m_pResultRefreshDataManager.data();
if(!fitting || !fitting->m_pMdiArea || !manager || !manager->getSensitiveData()) return;
ZxMdiArea* area = fitting->m_pMdiArea;
QMdiSubWindow* window = area->findChild<QMdiSubWindow*>("nmSensitivityHistory");
const QSharedPointer<nmDataSensitive::BatchResult> batch = manager->getSensitiveData()->getResults();
if(batch.isNull()) {
// 新模型丢弃旧批次时一并销毁窗口,曲线和撤销记录不再残留。
if(window) {
area->removeSubWindow(window);
delete window;
}
return;
}
const bool created = window == nullptr;
if(created) {
// PF 继承对话框,用普通页面承载,关闭子窗口时不进入其对话框关闭流程。
QWidget* page = new QWidget;
QVBoxLayout* layout = new QVBoxLayout(page);
layout->setContentsMargins(0, 0, 0, 0);
iGuiPlotPF* history = new iGuiPlotPF(s_Tag_HIST_PF, true, true, page);
history->setWindowFlags(Qt::Widget);
history->initUI(tr("Sensitivity history plot"));
history->setTableHelperVisible(false);
layout->addWidget(history);
window = area->addSubWindow(page);
window->setObjectName("nmSensitivityHistory");
window->setWindowTitle(tr("Sensitivity history plot"));
window->setAttribute(Qt::WA_DeleteOnClose, false);
}
installSensitiveShow(window->widget());
iGuiPlotPF* history = window->findChild<iGuiPlotPF*>();
QVector<iCurveDesc> curves;
// 基准压力、实测压力和流量均取成功求解快照,未求解的参数或制度编辑不参与绘图。
const QSharedPointer<const nmPebiResultSnapshot> snapshot = manager->getPebiResultSnapshot();
for(int i = 0; !snapshot.isNull() && i < snapshot->getWellCount(); ++i) {
const nmPebiResultWellSnapshot* well = snapshot->getWellAt(i);
if(well->m_sWellCode != batch->m_sWellCode) continue;
QVector<QVector<double> > measured = well->m_oCurves.m_vecHistoryPressure;
QVector<QVector<double> > base = well->m_oCurves.m_vecResultPressure;
if(measured.size() == 2) initPreHistory(measured, curves, true);
if(base.size() == 2) {
initPreHistory(base, curves, false);
curves.last().m_sCurveName = nmWxSensitive::tr("Base model - Pressure");
}
const QVector<double>* rates = resultReferenceRatesOf(well);
if(rates && rates->size() == well->m_vecFlowDurations.size()) {
iCurveDesc flow;
flow.m_oCurveType = POT_CurveFlow;
flow.m_sCurveName = s_HistorySouce_CurveF;
flow.m_bLineVisible = true;
flow.m_bPointVisible = false;
flow.m_oPen.setColor(Qt::red);
// 与普通历史图一致,流量使用段时长并保留框架的首行占位点。
flow.m_vecX = well->m_vecFlowDurations;
flow.m_vecY = *rates;
flow.m_vecX.prepend(0.0);
flow.m_vecY.prepend(0.0);
curves.append(flow);
}
break;
}
for(size_t i = 0; i < batch->m_runs.size(); ++i) {
const nmDataSensitive::RunResult& run = batch->m_runs[i];
if(!run.m_bSucceeded || run.m_bCancelled || run.m_vecPressure.size() != 2) continue;
QVector<QVector<double> > pressure = run.m_vecPressure;
initPreHistory(pressure, curves, false);
curves.last().m_sCurveName = nmWxSensitive::tr("Run %1 - Pressure").arg(run.m_nRunIndex + 1);
curves.last().m_oPen.setColor(QColor::fromHsv(int((run.m_nRunIndex % 360) * 137 % 360), 190, 190));
}
// 刷新前清除旧图元;同一批次保留本图的 Show 选择,新批次恢复全部显示。
iGuiPlot* plots[] = {history->m_pWxPlotP, history->m_pWxPlotF};
for(int i = 0; i < 2; ++i) {
iGuiPlot* plot = plots[i];
// 只读历史结果不进入 PF 的流动段编辑处理,双击统一打开框架数据表。
disconnect(plot, SIGNAL(sigDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)),
history, SLOT(slotSubDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)));
connect(plot, SIGNAL(sigDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)),
this, SLOT(onReadonlyCurveDoubleClicked(ZxPlot*,ZxObjBase*,const QPointF&,bool&)), Qt::UniqueConnection);
clearResultCurveDataTables(plot);
const QSet<QString> hidden = sender() == manager ? QSet<QString>() : hiddenSensitiveCurves(plot);
for(int j = plot->m_pPlot->getObjCount() - 1; j >= 0; --j)
if(qobject_cast<ZxObjCurve*>(plot->m_pPlot->getObjByIndex(j))) plot->m_pPlot->removeObjByIndex(j);
plot->clearUndoStack();
plot->setDropEnabled(false);
plot->setAxisXYLog(false, 0);
plot->setAxisXYLog(false, 1);
// PF 的统一分配面向普通历史曲线;多组结果直接交给对应的上下绘图控件。
QVector<iCurveDesc> data;
for(int j = 0; j < curves.size(); ++j)
if((curves[j].m_oCurveType == POT_CurveFlow) == (i == 1)) data.append(curves[j]);
plot->setCurveData(data);
plot->runUpdate();
for(int j = 0; j < data.size(); ++j)
if(ZxObjCurve* curve = plot->getCurve(data[j].m_sCurveName))
curve->setVisible(!hidden.contains(curve->getName()));
plot->setReadOnly(true);
plot->freshAxisScales(true, true);
}
const bool restore = window->isHidden();
history->show();
window->widget()->show();
window->show();
// 历史图只显示曲线,隐藏框架默认的空数据表侧栏。
history->m_pTableHelper->hide();
if(created || restore) area->relayoutSubWnds();
}
void nmSubWndMain::installSensitiveShow(QWidget* host)
{
// 使用布局的顶部栏,不改变框架绘图控件及子窗口的创建方式。
if(host->findChild<QPushButton*>("nmSensitivityShowButton")) return;
QWidget* bar = new QWidget(host);
QHBoxLayout* layout = new QHBoxLayout(bar);
layout->setContentsMargins(4, 2, 4, 2);
QPushButton* show = new QPushButton(tr("Show"), bar);
show->setObjectName("nmSensitivityShowButton");
layout->addWidget(show);
layout->addStretch();
host->layout()->setMenuBar(bar);
connect(show, SIGNAL(clicked()), this, SLOT(onSensitiveShowClicked()));
}
void nmSubWndMain::onSensitiveShowClicked()
{
// Show 是普通按钮,每张图复用一个独立的表格窗口。
QPushButton* button = qobject_cast<QPushButton*>(sender());
nmDataAnalyzeManager* manager = m_pResultRefreshDataManager.data();
if(!button || !manager || !manager->getSensitiveData()) return;
QWidget* host = button->parentWidget()->parentWidget();
const QSharedPointer<nmDataSensitive::BatchResult> batch = manager->getSensitiveData()->getResults();
if(batch.isNull()) return;
nmWxSensitivityShow* dialog = host->findChild<nmWxSensitivityShow*>("nmSensitivityShowDialog");
if(!dialog) {
iGuiPlot* plot = qobject_cast<iGuiPlot*>(host);
const bool loglog = plot != nullptr;
if(!plot) {
iGuiPlotPF* history = host->findChild<iGuiPlotPF*>();
if(!history) return;
plot = history->m_pWxPlotP;
}
dialog = new nmWxSensitivityShow(batch, plot, host);
dialog->setWindowTitle(loglog ? tr("Show - Sensitivity loglog plot") : tr("Show - Sensitivity history plot"));
// 发布新批次或清除旧模型后关闭旧表格,不能再操作上一批曲线。
connect(manager, SIGNAL(sigSensitiveResultsChanged()), dialog, SLOT(reject()));
}
dialog->show();
dialog->raise();
dialog->activateWindow();
}
void nmSubWndMain::updateSensitiveHistogramPlot()
{
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
nmDataAnalyzeManager* manager = m_pResultRefreshDataManager.data();
if(!fitting || !fitting->m_pMdiArea || !manager || !manager->getSensitiveData()) return;
ZxMdiArea* area = fitting->m_pMdiArea;
QMdiSubWindow* window = area->findChild<QMdiSubWindow*>("nmSensitivityHistogram");
const QSharedPointer<nmDataSensitive::BatchResult> batch = manager->getSensitiveData()->getResults();
if(batch.isNull() || batch->m_calcType != nmDataSensitive::CALC_MONTE_CARLO) {
// 新模型或确定性批次不保留上一批 Monte-Carlo 的统计图。
if(window) {
area->removeSubWindow(window);
delete window;
}
return;
}
const bool created = window == nullptr;
if(created) {
QWidget* page = new QWidget;
QVBoxLayout* layout = new QVBoxLayout(page);
layout->setContentsMargins(0, 0, 0, 0);
QHBoxLayout* options = new QHBoxLayout;
QPushButton* scatter = new QPushButton(tr("Scatter plot"), page);
scatter->setObjectName("nmScatterButton");
QPushButton* settings = new QPushButton(tr("Histogram options"), page);
settings->setObjectName("nmHistogramOptionsButton");
options->addWidget(scatter);
options->addWidget(settings);
options->addStretch();
layout->addLayout(options);
iGuiPlot* plot = new iGuiPlot(true, page);
plot->initUI(tr("Sensitivity histogram plot"));
plot->setDropEnabled(false);
plot->setAxisX(tr("Goodness of fit"), "MPa");
plot->setAxisY(tr("Count"));
plot->setAxisY(tr("Cumulative probability"), "%", true);
plot->m_pPlot->setBKColor(Qt::white);
for(int i = 0; i < 3; ++i) {
plot->setAxisXYLog(false, i);
plot->getAxisOf(i)->setVisible(true);
}
layout->addWidget(plot);
window = area->addSubWindow(page);
window->setObjectName("nmSensitivityHistogram");
window->setWindowTitle(tr("Sensitivity histogram plot"));
window->setAttribute(Qt::WA_DeleteOnClose, false);
connect(scatter, SIGNAL(clicked()), this, SLOT(onSensitiveScatter()));
connect(settings, SIGNAL(clicked()), this, SLOT(onSensitiveHistogramOptions()));
}
iGuiPlot* plot = window->widget()->findChild<iGuiPlot*>();
const int binCount = batch->m_nHistogramBins;
// 拟合参数与误差共用分箱绘制,轴名称和单位随本批选择同步切换。
const int variable = batch->m_nHistogramVariable;
plot->setAxisX(variable < 0 ? tr("Goodness of fit") : batch->m_fitParameters[variable].getName(),
variable < 0 ? "MPa" : batch->m_fitParameters[variable].getUnit());
QVector<double> values;
for(size_t i = 0; i < batch->m_runs.size(); ++i) {
const nmDataSensitive::RunResult& run = batch->m_runs[i];
// 显示勾选不改变统计总体,失败、取消或无有效统计值的运行不参与统计。
double value;
if(batch->statisticValue(run, variable, value)) values.append(value);
}
double minimum = 0.0, maximum = 1.0;
if(!values.isEmpty()) {
minimum = maximum = values.first();
for(int i = 1; i < values.size(); ++i) {
minimum = qMin(minimum, values[i]);
maximum = qMax(maximum, values[i]);
}
// 单个结果或全部相同的统计值也需要非零横轴范围。
if(minimum == maximum) {
const double padding = qMax(1e-6, qAbs(maximum) * 0.05);
minimum -= padding;
if(variable < 0) minimum = qMax(0.0, minimum);
maximum += padding;
}
}
const double width = (maximum - minimum) / binCount;
QVector<int> counts(binCount, 0);
for(int i = 0; i < values.size(); ++i) {
// 等宽分箱,左闭右开,最大值归入最后一箱。
const int index = qMin(binCount - 1, int((values[i] - minimum) / width));
++counts[index];
}
for(int i = plot->m_pPlot->getObjCount() - 1; i >= 0; --i)
if(qobject_cast<ZxObjCurve*>(plot->m_pPlot->getObjByIndex(i))) plot->m_pPlot->removeObjByIndex(i);
plot->clearUndoStack();
QVector<iCurveDesc> curves;
int largestCount = 0;
if(!values.isEmpty()) {
QVector<QPointF> bars;
iCurveDesc cumulative;
cumulative.m_sCurveName = tr("Cumulative probability");
cumulative.m_oCurveType = POT_CurveDiscrete;
cumulative.m_bUseY2 = true;
cumulative.m_bLineVisible = true;
cumulative.m_bPointVisible = true;
cumulative.m_oPen.setColor(Qt::red);
cumulative.m_oDot = ZxDot(DTS_Circle, Qt::red, 0.6, true);
int total = 0;
for(int i = 0; i < binCount; ++i) {
const double center = minimum + (i + 0.5) * width;
bars.append(QPointF(center, counts[i]));
largestCount = qMax(largestCount, counts[i]);
total += counts[i];
cumulative.m_vecX.append(center);
cumulative.m_vecY.append(100.0 * total / values.size());
}
// 普通曲线入口不支持柱状图,直接使用框架柱状图元,累计概率仍走已有曲线入口。
nmSensitivityHistogramBars* histogram = new nmSensitivityHistogramBars(width);
histogram->init(tr("Count"), plot->m_pPlot->getMainAxisX(), plot->m_pPlot->getMainAxisY());
plot->m_pPlot->addOneObj(histogram);
histogram->setAllValues(bars);
histogram->setCurvePty(QPen(Qt::black), ZxDot(), true, false);
histogram->setBarFill(true);
histogram->setBarFillClr(QColor(85, 107, 47));
histogram->setShow3D(false);
histogram->setPinValue(false);
curves.append(cumulative);
}
plot->setCurveData(curves);
plot->runUpdate();
plot->setReadOnly(true);
VecDouble ticksX, ticksY, ticksY2;
for(int i = 0; i <= 5; ++i) {
ticksX.append(minimum + i * (maximum - minimum) / 5.0);
}
// 累计概率轴每隔 10% 标注,100% 以上保留顶部留白。
for(int i = 0; i <= 10; ++i) ticksY2.append(i * 10.0);
// 小样本时放大计数轴范围,使柱子保持较低;样本较多时自动扩展并留出顶部空间。
const double countRange = qMax(16.0, largestCount * 1.2);
const double step = std::ceil(countRange / 8.0);
const double top = std::ceil(countRange / step) * step;
// 保持原轴范围,将主刻度加密一倍,计数刻度仍使用整数。
const double tickStep = qMax(1.0, std::ceil(step / 2.0));
for(double tick = 0.0; tick < top; tick += tickStep) ticksY.append(tick);
ticksY.append(top);
plot->setAxisScaleX(minimum, maximum, ticksX);
plot->setAxisScaleY(0.0, top, ticksY);
plot->setAxisScaleY2(0.0, 110.0, ticksY2);
const bool restore = window->isHidden();
plot->show();
window->widget()->show();
window->show();
if(created || restore) area->relayoutSubWnds();
}
void nmSubWndMain::onSensitiveHistogramOptions()
{
// 设置窗口复用当前批次,分箱数在 Apply 或 OK 后才应用到直方图。
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
nmDataAnalyzeManager* manager = m_pResultRefreshDataManager.data();
if(!fitting || !fitting->m_pMdiArea || !manager || !manager->getSensitiveData()) return;
QMdiSubWindow* window = fitting->m_pMdiArea->findChild<QMdiSubWindow*>("nmSensitivityHistogram");
const QSharedPointer<nmDataSensitive::BatchResult> batch = manager->getSensitiveData()->getResults();
if(!window || batch.isNull()) return;
nmWxSensitivityHistogramOptions* dialog = window->findChild<nmWxSensitivityHistogramOptions*>();
if(!dialog) {
dialog = new nmWxSensitivityHistogramOptions(batch, batch->m_nHistogramBins, window->widget());
connect(dialog, SIGNAL(settingsChanged(int,int)), this, SLOT(onSensitiveHistogramSettingsChanged(int,int)));
connect(manager, SIGNAL(sigSensitiveResultsChanged()), dialog, SLOT(reject()));
}
dialog->show();
dialog->raise();
dialog->activateWindow();
}
void nmSubWndMain::onSensitiveHistogramSettingsChanged(int bins, int variable)
{
// 分箱数与统计变量随批次保存,散点图独立选择自己的纵轴。
nmDataAnalyzeManager* manager = m_pResultRefreshDataManager.data();
if(!manager || !manager->getSensitiveData()) return;
const QSharedPointer<nmDataSensitive::BatchResult> batch = manager->getSensitiveData()->getResults();
if(batch.isNull()) return;
batch->m_nHistogramVariable = variable;
batch->m_nHistogramBins = bins;
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
if(!fitting || !fitting->m_pMdiArea) return;
QMdiSubWindow* window = fitting->m_pMdiArea->findChild<QMdiSubWindow*>("nmSensitivityHistogram");
if(!window) return;
fitting->setModified(true);
updateSensitiveHistogramPlot();
}
void nmSubWndMain::onSensitiveFilterChanged()
{
// 只刷新所属分析的结果参数页,不触发会关闭散点窗的批次更新信号。
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
if(!fitting) return;
QWidget* page = fitting->getFitSubRstWxOf(FSRT_NmRst, false);
nmWxParaPropertyPebi* parameters = page ? page->findChild<nmWxParaPropertyPebi*>() : nullptr;
if(parameters) parameters->refreshSensitivityResults();
// 只有用户调整阈值才标记修改,加载成果时的面板刷新不触发保存提示。
if(qobject_cast<nmWxSensitivityScatter*>(sender())) fitting->setModified(true);
}
void nmSubWndMain::onSensitiveScatter()
{
// 与 Show 一样打开独立弹窗,重复点击时激活已有窗口。
iSubWndFitting* fitting = m_pResultRefreshFitting.data();
nmDataAnalyzeManager* manager = m_pResultRefreshDataManager.data();
if(!fitting || !fitting->m_pMdiArea || !manager || !manager->getSensitiveData()) return;
const QSharedPointer<nmDataSensitive::BatchResult> batch = manager->getSensitiveData()->getResults();
if(batch.isNull() || batch->m_calcType != nmDataSensitive::CALC_MONTE_CARLO) return;
QMdiSubWindow* window = fitting->m_pMdiArea->findChild<QMdiSubWindow*>("nmSensitivityHistogram");
if(!window) return;
nmWxSensitivityScatter* dialog = window->findChild<nmWxSensitivityScatter*>();
if(!dialog) {
double baseError = -1.0;
const QSharedPointer<const nmPebiResultSnapshot> snapshot = manager->getPebiResultSnapshot();
for(int i = 0; !snapshot.isNull() && i < snapshot->getWellCount(); ++i) {
const nmPebiResultWellSnapshot* well = snapshot->getWellAt(i);
if(well->m_sWellCode != batch->m_sWellCode) continue;
baseError = nmCalculationDllPebiSolverTask::calculatePressureRmse(
well->m_oCurves.m_vecHistoryPressure, well->m_oCurves.m_vecResultPressure);
break;
}
dialog = new nmWxSensitivityScatter(batch, baseError, window->widget());
connect(dialog, SIGNAL(filterChanged()), this, SLOT(onSensitiveFilterChanged()));
// 构造时已应用初始阈值,连接完成后补一次结果面板同步。
onSensitiveFilterChanged();
// 新结果或新模型生成后关闭弹窗,避免保留旧批次的点和阈值。
connect(manager, SIGNAL(sigSensitiveResultsChanged()), dialog, SLOT(reject()));
}
dialog->show();
dialog->raise();
dialog->activateWindow();
}
void nmSubWndMain::startSolverTask()
{
if(!m_bSolverStartPending) {
return;
}
if(m_pProgressDlg == nullptr || m_pSolverDataManager.isNull() ||
m_pSolverFitting.isNull()) {
m_bSolverStartPending = false;
if(s_pPendingSolverWindow == this) {
s_pPendingSolverWindow = nullptr;
}
if(m_pProgressDlg != nullptr) {
m_pProgressDlg->hide();
}
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
return;
}
if(waitForPebiGridGeneration()) {
return;
}
// 先创建不自动捕获的任务,使停止按钮在第一批井输入开始前就有明确接收者。
m_pSolverTask = new nmCalculationDllPebiSolverTask(
QString(),
m_pSolverDataManager.data(),
QString(),
nullptr,
true);
s_pRunningSolverTask = m_pSolverTask;
connect(m_pSolverTask, SIGNAL(sig_calculateDone(bool)),
this, SLOT(on_solverTaskFinished(bool)),
Qt::QueuedConnection);
connect(m_pSolverTask, SIGNAL(sigStageChanged(QString,int,int)),
this, SLOT(onSolverStageChanged(QString,int,int)),
Qt::QueuedConnection);
// 给 Windows 一次完成首帧绘制的机会;后续每个事件循环只捕获一口井。
QTimer::singleShot(20, this, SLOT(continueSolverInputCapture()));
}
void nmSubWndMain::continueSolverInputCapture()
{
if(!m_bSolverStartPending || m_pSolverTask == nullptr) {
return;
}
if(waitForPebiGridGeneration()) {
return;
}
bool bFinished = false;
const bool bSucceeded =
m_pSolverTask->captureManualInputStep(1, bFinished);
if(!bSucceeded) {
nmCalculationDllPebiSolverTask* pTask = m_pSolverTask;
const bool bCancelled = pTask->wasCancelled();
const bool bInputChanged =
pTask->hasInputChanged(m_pSolverDataManager.data());
const QString sFailureReason = pTask->getFailureReason();
m_pSolverTask = nullptr;
if(pTask == s_pRunningSolverTask) {
s_pRunningSolverTask = nullptr;
}
if(s_pPendingSolverWindow == this) {
s_pPendingSolverWindow = nullptr;
}
m_bSolverStartPending = false;
disconnectPebiGridGenerationWait();
disconnect(pTask, nullptr, this, nullptr);
pTask->deleteLater();
if(bCancelled) {
if(m_pProgressDlg != nullptr) {
m_pProgressDlg->hide();
}
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
} else {
scheduleSolverResultCompletion(false,
-1,
bInputChanged,
sFailureReason);
}
m_bSolverCancelRequested = false;
return;
}
if(bFinished) {
// 临时井对象指针已全部释放,只有完整值快照可以进入后台线程。
m_bSolverStartPending = false;
if(s_pPendingSolverWindow == this) {
s_pPendingSolverWindow = nullptr;
}
disconnectPebiGridGenerationWait();
m_pSolverTask->start();
return;
}
QTimer::singleShot(0, this, SLOT(continueSolverInputCapture()));
}
void nmSubWndMain::onSolverStageChanged(
QString sStage,
int nCurrent,
int nTotal)
{
if(sender() != m_pSolverTask || m_pProgressDlg == nullptr ||
m_bSolverCancelRequested) {
return;
}
// 普通求解根据当前阶段更新;没有精确进度时滚动显示。
m_pProgressDlg->setLabelText(sStage);
if(nTotal <= 0) {
m_pProgressDlg->setRange(0, 0);
} else {
m_pProgressDlg->setRange(0, nTotal);
m_pProgressDlg->setValue(qBound(0, nCurrent, nTotal));
}
}
void nmSubWndMain::onSolverCancelRequested()
{
if(m_bSolverStartPending &&
(m_pSolverTask == nullptr || !m_pSolverTask->isRunning())) {
// 等待建网或分批捕获期间尚未进入工作线程,可以立即撤销并释放值快照。
nmCalculationDllPebiSolverTask* pTask = m_pSolverTask;
m_pSolverTask = nullptr;
m_bSolverStartPending = false;
if(s_pPendingSolverWindow == this) {
s_pPendingSolverWindow = nullptr;
}
disconnectPebiGridGenerationWait();
if(pTask != nullptr) {
pTask->requestCancel();
if(pTask == s_pRunningSolverTask) {
s_pRunningSolverTask = nullptr;
}
disconnect(pTask, nullptr, this, nullptr);
pTask->deleteLater();
}
// QProgressDialog::cancel() 已隐藏窗口;保留对象供下一次求解复用。
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_bSolverCancelRequested = false;
return;
}
// 共享停止按钮只控制当前占用进度框的任务,DLL 返回后的处理各自保持原路径。
nmCalculationDllPebiSolverTask* pRunningTask = !m_pAuxiliarySolverTask.isNull()
? m_pAuxiliarySolverTask.data() : m_pSolverTask;
if(pRunningTask != nullptr && pRunningTask->isRunning()) {
m_bSolverCancelRequested = true;
pRunningTask->requestCancel();
if(m_pProgressDlg != nullptr) {
// DLL 没有取消导出;若已进入 DLL,只能等待其返回后丢弃结果。
m_pProgressDlg->setLabelText(
tr("Stopping; waiting for the current solver call to return..."));
m_pProgressDlg->setRange(0, 0);
QPushButton* pCancelButton =
m_pProgressDlg->findChild<QPushButton*>();
if(pCancelButton != nullptr) {
pCancelButton->setEnabled(false);
}
m_pProgressDlg->show();
m_pProgressDlg->raise();
}
}
}
bool nmSubWndMain::waitForPebiGridGeneration()
{
nmDataAnalyzeManager* pDataManager = m_pSolverDataManager.data();
if(!m_bSolverStartPending || pDataManager == nullptr ||
!pDataManager->isPebiGridGenerationActive()) {
disconnectPebiGridGenerationWait();
return false;
}
// 这里只等待主线程提交信号,不获取网格锁;进度框和停止按钮保持可响应。
connect(pDataManager,
SIGNAL(sigPebiGridGenerationFinished(quint64,bool)),
this,
SLOT(onPebiGridGenerationFinished(quint64,bool)),
Qt::UniqueConnection);
if(m_pProgressDlg != nullptr) {
m_pProgressDlg->setLabelText(
tr("Waiting for current grid generation..."));
m_pProgressDlg->setRange(0, 0);
}
return true;
}
void nmSubWndMain::disconnectPebiGridGenerationWait()
{
nmDataAnalyzeManager* pDataManager = m_pSolverDataManager.data();
if(pDataManager == nullptr) {
return;
}
disconnect(pDataManager,
SIGNAL(sigPebiGridGenerationFinished(quint64,bool)),
this,
SLOT(onPebiGridGenerationFinished(quint64,bool)));
}
void nmSubWndMain::onPebiGridGenerationFinished(
quint64 nGridInputRevision,
bool bSucceeded)
{
Q_UNUSED(nGridInputRevision);
Q_UNUSED(bSucceeded);
nmDataAnalyzeManager* pDataManager = m_pSolverDataManager.data();
if(sender() != pDataManager || !m_bSolverStartPending ||
pDataManager == nullptr ||
pDataManager->isPebiGridGenerationActive()) {
return;
}
disconnectPebiGridGenerationWait();
if(m_pProgressDlg != nullptr) {
m_pProgressDlg->setLabelText(tr("Preparing input snapshot..."));
m_pProgressDlg->setRange(0, 0);
}
// 失败或版本过期也恢复原流程,由快照缓存检查决定是否重新生成一次网格。
if(m_pSolverTask == nullptr) {
QTimer::singleShot(0, this, SLOT(startSolverTask()));
} else {
QTimer::singleShot(0, this, SLOT(continueSolverInputCapture()));
}
}
void nmSubWndMain::triggerToolBarAction(int index)
{
// 获取多边形边界的绘制,并模拟触发点击操作
// 目前多边形边界绘制是工具栏的第4个,所以用 3
if (m_pPlotToolBar == nullptr) return;
QList<QAction *> actions = m_pPlotToolBar->actions();
if(!actions.isEmpty() && index >= 0 && index < actions.count()) {
QAction *action = actions[index];
action->trigger();
}
}
bool nmSubWndMain::mergeAnaResultToFitting()
{
//QMessageBox::information(this, tr("solver success"), tr("solver succeed!"));
/*if (m_parameterPropertyWindow != nullptr) {
delete m_parameterPropertyWindow;
m_parameterPropertyWindow = nullptr;
}
m_parameterPropertyWindow = new nmWxParameterProperty(this);
m_parameterPropertyWindow->show();*/
ZxMainWindow* pMainWnd1 = const_cast<ZxMainWindow*>(m_pMainWindow);
if(nullptr == pMainWnd1) {
return false;
}
iSubWndFitting* pSubWndFit = !m_pSolverFitting.isNull()
? m_pSolverFitting.data() : nmDataAnalyzeManager::getCurrentFitting();
Q_ASSERT(pSubWndFit != nullptr);
if(pSubWndFit == nullptr) {
return false;
}
nmDataAnalyzeManager* pInstance = !m_pSolverDataManager.isNull()
? m_pSolverDataManager.data()
: nmDataAnalyzeManager::getCurrentInstance();
if(pInstance == nullptr) {
return false;
}
// 捕获的拟合窗口和数据管理器必须仍保持原来的映射关系。
if(nmDataAnalyzeManager::findManagerByFitting(pSubWndFit) != pInstance) {
return false;
}
// 新一轮主动刷新替代尚未执行的旧请求;已经投递的旧槽会检测标记并直接返回。
m_bPendingResultWidgetRefresh = false;
m_pPendingResultDataManager = nullptr;
m_pPendingResultFitting = nullptr;
m_bPendingResultCompletionMessage = false;
m_bPendingResultWindowReady = false;
m_nPendingResultSolveTimeMs = -1;
if(pInstance->getGridType() == NM_Grid_PEBI) {
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
pInstance->getPebiResultSnapshot();
if(pSnapshot.isNull()) {
return false;
}
QString sWellInstanceId =
pInstance->getCurrentResultWellInstanceId();
if(!pSnapshot->isDisplayWell(sWellInstanceId)) {
pInstance->setCurrentResultWellInstanceId(QString());
sWellInstanceId =
pInstance->getCurrentResultWellInstanceId();
}
// 曲线及下拉框都使用同一 UUID;显示列表为空时曲线入口收到空值并被清空。
if(!refreshPebiResultCurves(pSubWndFit,
pInstance,
sWellInstanceId)) {
return false;
}
nmWxNumericalDesign::notifyResultWellSelectorChanged(
pInstance, sWellInstanceId);
}
if(pInstance->getGridType() != NM_Grid_PEBI) {
return true;
}
QString errorMessage;
// getFitSubRstWxOf(..., true) 会同步创建 iWxResult、挂入 MDI 并切换当前页。
// 首次创建时只建立外层载体,待本轮 show/resize/layout 事件结束后再填充内容。
QWidget* p3DWidget = pSubWndFit->getFitSubRstWxOf(
FSRT_Nm3D, false, &errorMessage);
if(p3DWidget == nullptr) {
p3DWidget = pSubWndFit->getFitSubRstWxOf(
FSRT_Nm3D, true, &errorMessage);
if(p3DWidget == nullptr) {
return false;
}
m_pPendingResultDataManager = pInstance;
m_pPendingResultFitting = pSubWndFit;
m_bPendingResultWidgetRefresh = true;
return true;
}
return populateResultWidgets(pSubWndFit, pInstance);
}
bool nmSubWndMain::populateResultWidgets(
iSubWndFitting* pSubWndFit,
nmDataAnalyzeManager* pDataManager)
{
return nmPebiResultPageInstaller::installOrSynchronize(
pSubWndFit, pDataManager);
}
void nmSubWndMain::schedulePendingResultWidgets()
{
if(m_bPendingResultWidgetRefresh) {
QTimer::singleShot(0, this, SLOT(populatePendingResultWidgets()));
}
}
void nmSubWndMain::populatePendingResultWidgets()
{
if(!m_bPendingResultWidgetRefresh) {
return;
}
iSubWndFitting* pSubWndFit = m_pPendingResultFitting.data();
nmDataAnalyzeManager* pDataManager = m_pPendingResultDataManager.data();
m_bPendingResultWidgetRefresh = false;
m_pPendingResultDataManager = nullptr;
m_pPendingResultFitting = nullptr;
const bool bResultWindowReady =
populateResultWidgets(pSubWndFit, pDataManager);
if(m_bPendingResultCompletionMessage) {
m_bPendingResultWindowReady = bResultWindowReady;
QTimer::singleShot(
0,
this,
SLOT(showPendingSolverResultCompletion()));
}
}
void nmSubWndMain::showPendingSolverResultCompletion()
{
if(!m_bPendingResultCompletionMessage) {
return;
}
const bool bResultWindowReady = m_bPendingResultWindowReady;
const int nSolveTimeMs = m_nPendingResultSolveTimeMs;
m_bPendingResultCompletionMessage = false;
m_bPendingResultWindowReady = false;
m_nPendingResultSolveTimeMs = -1;
showSolverResultCompletion(bResultWindowReady,
nSolveTimeMs);
}
bool nmSubWndMain::inithalfLog(QVector<QVector<double >> & vvecLogPreData, QVector<iCurveDesc>& vecDescs, bool isHistoryData)
{
// 半对数压力曲线
iCurveDesc curveDesc;
if(vvecLogPreData.size() != 2) {
vecDescs.append(curveDesc);
return false;
}
// X轴数据
curveDesc.m_vecX = vvecLogPreData[0];
// Y轴数据
curveDesc.m_vecY = vvecLogPreData[1];
curveDesc.m_oCurveType = POT_CurvePressure;
// 判断是历史数据还是计算数据
if(isHistoryData) {
curveDesc.m_sCurveName = s_SemiSouce_Curve;
curveDesc.m_bLineVisible = false; // 关闭线模式可见性
curveDesc.m_bPointVisible = true; // 开启点模式可见性
curveDesc.m_oDot.setColor(Qt::red); // 设置点的颜色
curveDesc.m_oDot.setStyle(ZxDotType::DTS_Cross); // x形状
} else {
curveDesc.m_sCurveName = s_SemiTheorySouce_Curve; // 理论曲线
curveDesc.m_bLineVisible = true; // 开启线模式可见性
curveDesc.m_bPointVisible = false; // 关闭点模式可见性
curveDesc.m_oPen.setColor(Qt::green); // 设置线的颜色
}
// 将 curveDesc 添加到 QVector
vecDescs.append(curveDesc);
return true;
}
bool nmSubWndMain::initloglog(QVector<QVector<double> > &vvecLogPreData, QVector<iCurveDesc>& vecDescs, bool isHistoryData)
{
// 双对数压力曲线
iCurveDesc curveDesc1, curveDesc2;
if(vvecLogPreData.size() != 3) {
vecDescs.append(curveDesc1);
vecDescs.append(curveDesc2);
return false;
}
// 确保每个内部 QVector 都不为空,然后移除其最后一个元素
// 移除 vvecLogPreData[0] (vX) 中的最后一个元素
if(!vvecLogPreData[0].isEmpty()) {
vvecLogPreData[0].remove(vvecLogPreData[0].size() - 1);
} else {
return false;
}
// 移除 vvecLogPreData[1] (vY) 中的最后一个元素
if(!vvecLogPreData[1].isEmpty()) {
vvecLogPreData[1].remove(vvecLogPreData[1].size() - 1);
} else {
return false;
}
// 移除 vvecLogPreData[2] (vZ) 中的最后一个元素
if(!vvecLogPreData[2].isEmpty()) {
vvecLogPreData[2].remove(vvecLogPreData[2].size() - 1);
} else {
return false;
}
// 曲线1
curveDesc1.m_vecX = vvecLogPreData[0];
curveDesc1.m_vecY = vvecLogPreData[1];
// 曲线2
curveDesc2.m_vecX = vvecLogPreData[0];
curveDesc2.m_vecY = vvecLogPreData[2];
curveDesc1.m_oCurveType = POT_CurveDiscrete;
curveDesc2.m_oCurveType = POT_CurveDiscrete;
// 判断是历史数据还是计算数据
if(isHistoryData) {
curveDesc1.m_sCurveName = s_Souce_Curve;
curveDesc2.m_sCurveName = s_Deriv_Curve;
curveDesc1.m_bLineVisible = false; // 关闭线模式可见性
curveDesc1.m_bPointVisible = true; // 开启点模式可见性
curveDesc1.m_oDot.setColor(Qt::green); // 设置点的颜色
curveDesc1.m_oDot.setStyle(ZxDotType::DTS_Cross); // X形状
curveDesc2.m_bLineVisible = false; // 关闭线模式可见性
curveDesc2.m_bPointVisible = true; // 开启点模式可见性
curveDesc2.m_oDot.setColor(Qt::red); // 设置点的颜色
curveDesc2.m_oDot.setStyle(ZxDotType::DTS_Circle); // O形状
curveDesc2.m_oDot.setFilling(false); // 不填充
} else {
curveDesc1.m_sCurveName = s_TheorySouce_Curve;
curveDesc2.m_sCurveName = s_TheoryDeriv_Curve;
curveDesc1.m_bLineVisible = true; // 关闭线模式可见性
curveDesc1.m_bPointVisible = false; // 开启点模式可见性
curveDesc1.m_oPen.setColor(Qt::red); // 设置线的颜色
curveDesc2.m_bLineVisible = true; // 关闭线模式可见性
curveDesc2.m_bPointVisible = false; // 开启点模式可见性
curveDesc2.m_oPen.setColor(Qt::green); // 设置线的颜色
}
// 将 curveDesc 添加到 QVector
vecDescs.append(curveDesc1);
// 将 curveDesc 添加到 QVector
vecDescs.append(curveDesc2);
return true;
}
bool nmSubWndMain::initPreHistory(QVector<QVector<double> > &vvecHistoryData, QVector<iCurveDesc>& vecDescs, bool isHistoryData)
{
// 压力曲线
iCurveDesc curveDesc;
if(vvecHistoryData.size() != 2) {
vecDescs.append(curveDesc);
return false;
}
// X轴数据
curveDesc.m_vecX = vvecHistoryData[0];
// Y轴数据
curveDesc.m_vecY = vvecHistoryData[1];
curveDesc.m_oCurveType = POT_CurvePressure;
// 判断是历史数据还是计算数据
if(isHistoryData) {
curveDesc.m_sCurveName = s_HistorySouce_CurveP;
curveDesc.m_bLineVisible = false; // 关闭线模式可见性
curveDesc.m_bPointVisible = true; // 开启点模式可见性
curveDesc.m_oDot.setColor(Qt::red); // 设置点的颜色
curveDesc.m_oDot.setStyle(ZxDotType::DTS_Plus); // +形状
} else {
curveDesc.m_sCurveName = s_HistoryFit_CurveP;
curveDesc.m_bLineVisible = true; // 关闭线模式可见性
curveDesc.m_bPointVisible = false; // 开启点模式可见性
curveDesc.m_oPen.setColor(Qt::green); // 设置线的颜色
}
// 将 curveDesc 添加到 QVector
vecDescs.append(curveDesc);
return true;
}
bool nmSubWndMain::onConfirmClosing()
{
// 正常关闭入口在求解结束前保持当前成果和 DataManager 存活;应用退出等绕过
// 该确认流程的路径仍由析构函数 wait() 兜底。
if(m_bSolverStartPending || !m_pAuxiliarySolverTask.isNull() ||
(m_pSolverTask != nullptr && m_pSolverTask->isRunning())) {
QMessageBox::information(this, tr("solver error"), tr("task is running!"));
return false;
}
return iSubWndBaseAF::onConfirmClosing();
}
void nmSubWndMain::reservoirCharacteristics()
{
// 第一步:取得当前分析窗口对应的数据管理器,避免新对话框访问全局单例。
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance();
if (pDataManager == nullptr)
{
QMessageBox::warning(this, tr("Reservoir properties"),
tr("The current numerical well-test data is unavailable."));
return;
}
// 第二步:在入口处组装数据源和编辑器,两个对象的生命周期覆盖整个模态会话。
nmAnalyzeManagerReservoirDataSource oDataSource(pDataManager);
nmWxReservoirPropertiesEditor oEditor(&oDataSource, this);
// 第三步:先建立工作副本;初始化失败时不打开不完整的参数界面。
QString sError;
if (!oEditor.initialize(sError))
{
QMessageBox::warning(this, tr("Reservoir properties"), sError);
return;
}
// 第四步:确定时由编辑会话一次性提交,取消时真实数据保持不变。
oEditor.exec();
}
void nmSubWndMain::geoLayering()
{
nmWxGeometryLayerDlg dlg;
if(dlg.exec() == QDialog::Rejected) {
return;
} else {
QVector<nmDataLayer*> layers = dlg.getLayerData();
// 添加到数据中心
nmDataAnalyzeManager::getCurrentInstance()->setLayers(layers);
//for (int i = 0; i < layers.size(); ++i) {
// nmDataLayer* layer = layers[i];
// qDebug() << "Layer" << i + 1 << "Top:" << layer->getTop()
// << "Bottom:" << layer->getBottom()
// << "Thickness:" << layer->getThickness()
// << "IsChecked:" << layer->getIsChecked();
//}
}
}
bool nmSubWndMain::on_calculationFinished(
NM_Calculation_Result result,
const QString& sFailureReason)
{
if(result == NM_Calculation_Result_Success) {
return this->mergeAnaResultToFitting();
} else {
// 如果失败,进度条其实没必要拉满了,直接提示错误
const QString sMessage = sFailureReason.isEmpty()
? tr("No specific failure reason is available.")
: sFailureReason;
QMessageBox::warning(this, tr("Solver failed"), sMessage);
return false;
}
}
void nmSubWndMain::scheduleSolverResultCompletion(
bool bSucceeded,
int nSolveTimeMs,
bool bInputChanged,
const QString& sFailureReason)
{
s_pPendingSolverWindow = this;
m_bSolverResultCompletionPending = true;
m_bPendingSolverSucceeded = bSucceeded;
m_bPendingSolverInputChanged = bInputChanged;
m_sPendingSolverFailureReason = sFailureReason;
m_nPendingSolverTimeMs = nSolveTimeMs;
// 当前完成信号栈只登记后续工作;隐藏和结果刷新各占一轮独立事件。
QTimer::singleShot(0, this, SLOT(dismissPendingSolverProgress()));
}
void nmSubWndMain::dismissPendingSolverProgress()
{
if(m_pProgressDlg != nullptr) {
m_pProgressDlg->hide();
}
QTimer::singleShot(0, this, SLOT(completePendingSolverResult()));
}
void nmSubWndMain::completePendingSolverResult()
{
if(!m_bSolverResultCompletionPending) {
return;
}
const bool bSucceeded = m_bPendingSolverSucceeded;
const bool bInputChanged = m_bPendingSolverInputChanged;
const QString sFailureReason = m_sPendingSolverFailureReason;
const int nSolveTimeMs = m_nPendingSolverTimeMs;
m_bSolverResultCompletionPending = false;
m_bPendingSolverSucceeded = false;
m_bPendingSolverInputChanged = false;
m_sPendingSolverFailureReason.clear();
m_nPendingSolverTimeMs = -1;
bool bResultWindowReady = false;
if(bInputChanged) {
QMessageBox::warning(
this,
tr("Solver result discarded"),
tr("The input changed while the solver was preparing or running. "
"The outdated result was discarded."));
} else {
bResultWindowReady = on_calculationFinished(
bSucceeded
? NM_Calculation_Result_Success
: NM_Calculation_Result_Fail,
sFailureReason);
}
// mergeAnaResultToFitting() 已使用完启动时捕获的上下文。
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_bSolverCancelRequested = false;
if(bSucceeded && bResultWindowReady &&
m_bPendingResultWidgetRefresh) {
// 首次结果页尚未填充,最终提示必须等页面真实创建结果返回后再显示。
m_bPendingResultCompletionMessage = true;
m_nPendingResultSolveTimeMs = nSolveTimeMs;
} else if(bSucceeded) {
showSolverResultCompletion(bResultWindowReady,
nSolveTimeMs);
}
// 首次结果载体创建后再跨一个事件循环填充,避免 Qt 4.8 布局重入。
schedulePendingResultWidgets();
if(s_pPendingSolverWindow == this) {
s_pPendingSolverWindow = nullptr;
}
}
void nmSubWndMain::showSolverResultCompletion(
bool bResultWindowReady,
int nSolveTimeMs)
{
// 完成提示只显示 DLL 返回的本次求解耗时。
if(bResultWindowReady && nSolveTimeMs >= 0) {
QMessageBox oMessageBox(QMessageBox::Information, tr("Solver completed"),
tr("Solve time: %1 ms").arg(nSolveTimeMs),
QMessageBox::Ok, this);
// 单行耗时在提示区域水平、垂直居中,与左侧图标对齐。
QLabel* pMessageLabel = oMessageBox.findChild<QLabel*>("qt_msgbox_label");
if(pMessageLabel != nullptr) {
pMessageLabel->setAlignment(Qt::AlignCenter);
pMessageLabel->setMinimumHeight(
oMessageBox.iconPixmap().height());
}
oMessageBox.exec();
} else if(!bResultWindowReady) {
QMessageBox::warning(
this,
tr("Result display error"),
tr("Solver completed and the results were committed, but the "
"result window could not be opened."));
}
}
void nmSubWndMain::on_solverTaskFinished(bool isSuccessed)
{
// 第一步:只接受当前窗口实际启动的任务,忽略已经失效的旧回调。
nmCalculationDllPebiSolverTask* pTask = qobject_cast<nmCalculationDllPebiSolverTask*>(sender());
if(pTask == nullptr || pTask != m_pSolverTask) {
return;
}
int nSolveTimeMs = pTask != nullptr ? pTask->getSolveTimeMs() : -1;
const bool bCancelled = pTask->wasCancelled();
QString sFailureReason = pTask->getFailureReason();
bool bInputChanged = false;
// 后台线程只生成局部结果快照。回到主线程后先验证所属 DataManager 和
// 输入版本,再整体替换旧成果;输入变化与真正的求解失败分别提示。
if(isSuccessed && !bCancelled) {
if(m_pProgressDlg != nullptr) {
m_pProgressDlg->setLabelText(tr("Committing results..."));
m_pProgressDlg->setRange(0, 0);
}
bInputChanged =
pTask->hasInputChanged(m_pSolverDataManager.data());
isSuccessed = pTask->commitResult(m_pSolverDataManager.data());
bInputChanged = !isSuccessed && bInputChanged;
if(!isSuccessed) {
sFailureReason = pTask->getFailureReason();
}
} else if(bCancelled) {
isSuccessed = false;
}
m_pSolverTask = nullptr;
if(pTask == s_pRunningSolverTask) {
s_pRunningSolverTask = nullptr;
}
// 正常完成路径由主线程延迟释放任务;析构路径则会等待并直接释放。
pTask->deleteLater();
// 第二步:停止不属于失败,也不允许触发结果提交或覆盖旧成果。
if(bCancelled) {
if(m_pProgressDlg != nullptr) {
m_pProgressDlg->hide();
}
m_pSolverDataManager = nullptr;
m_pSolverFitting = nullptr;
m_bSolverCancelRequested = false;
} else {
scheduleSolverResultCompletion(isSuccessed,
nSolveTimeMs,
bInputChanged,
sFailureReason);
}
}
void nmSubWndMain::deleteOneObj()
{
this->triggerToolBarAction(NMOT_Delete);
}
void nmSubWndMain::setLocked()
{
if(m_lockState) { // 锁定
m_lockState = false;
int objCount = this->m_pWxPlot->m_pPlot->getObjCount();
for(int i = 0; i < objCount; i++) {
ZxObjBase* obj = this->m_pWxPlot->m_pPlot->getObjByIndex(i);
if(!_isSame("nObjPointWell", obj->getTagName())) {
obj->setReadOnly(false);
obj->setLockPos(false);
}
}
QStringList nIDs;
nIDs.append("5125");
nIDs.append("5124");
nIDs.append("5123");
nIDs.append("5114");
nIDs.append("5115");
nIDs.append("5116");
nIDs.append("5117");
nIDs.append("5118");
nIDs.append("5119");
nIDs.append("5102");
nIDs.append("5120");
nIDs.append("5103");
nIDs.append("5104");
nIDs.append("5105");
nIDs.append("5106");
nIDs.append("5107");
// nIDs.append("5108"); // 新建井菜单暂时停用
nIDs.append("5109");
nIDs.append("5110");
nIDs.append("5111");
nIDs.append("5121");
nIDs.append("5112");
nIDs.append("5113");
nIDs.append("5122");
nIDs.append("9002");
nIDs.append("9003");
nIDs.append("9101");
nIDs.append("9102");
nIDs.append("9103");
nIDs.append("9104");
nIDs.append("3104");
emit sigFreshRnStates(nIDs);
/*
if (m_pMainWindow != NULL) {
//m_pMainWindow->slotFreshRnStates(nIDs);
}
*/
//m_pMainWindow->getMainRibbon
} else {
m_lockState = true;
int objCount = this->m_pWxPlot->m_pPlot->getObjCount();
for(int i = 0; i < objCount; i++) {
ZxObjBase* obj = this->m_pWxPlot->m_pPlot->getObjByIndex(i);
if(!_isSame("nObjPointWell", obj->getTagName())) {
obj->setReadOnly(true);
obj->setLockPos(true);
}
}
QStringList nIDs;
nIDs.append("5125");
nIDs.append("5124");
nIDs.append("5123");
nIDs.append("5114");
nIDs.append("5115");
nIDs.append("5116");
nIDs.append("5117");
nIDs.append("5118");
nIDs.append("5119");
nIDs.append("5102");
nIDs.append("5120");
nIDs.append("5103");
nIDs.append("5104");
nIDs.append("5105");
nIDs.append("5106");
nIDs.append("5107");
// nIDs.append("5108"); // 新建井菜单暂时停用
nIDs.append("5109");
nIDs.append("5110");
nIDs.append("5111");
nIDs.append("5121");
nIDs.append("5112");
nIDs.append("5113");
nIDs.append("5122");
//nIDs.append("9002");
//nIDs.append("9003");
nIDs.append("9101");
nIDs.append("9102");
nIDs.append("9103");
nIDs.append("9104");
nIDs.append("3104");
emit sigFreshRnStates(nIDs);
/*
if (m_pMainWindow != NULL) {
m_pMainWindow->slotFreshRnStates(nIDs);
}
*/
/*
emit sigFreshRnStates(nIDs);
if (m_pMainWindow != NULL) {
m_pMainWindow->slotFreshRnStates(nIDs);
}
bool b;
QString sName = "NmUnlocked";
int nID = 5119;
bool bLicensed;
nmSubWndMain::checkCmdEnable(b, sName, nID, bLicensed);
nID = 5125;
sName = "NmPrint";
nmSubWndMain::checkCmdEnable(b, sName, nID, bLicensed);
*/
}
}
nmGuiPlot * nmSubWndMain::getWxPlot() const
{
return m_pWxPlot;
}
bool nmSubWndMain::refreshPebiResultCurves(
iSubWndFitting* pSubWndFit,
nmDataAnalyzeManager* pDataManager,
const QString& sWellInstanceId)
{
if(pSubWndFit == nullptr || pDataManager == nullptr ||
pDataManager->getGridType() != NM_Grid_PEBI) {
return false;
}
QSharedPointer<const nmPebiResultSnapshot> pSnapshot =
pDataManager->getPebiResultSnapshot();
if(pSnapshot.isNull() ||
(!sWellInstanceId.isEmpty() &&
!pSnapshot->isDisplayWell(sWellInstanceId))) {
return false;
}
const nmPebiResultWellSnapshot* pWell = sWellInstanceId.isEmpty()
? NULL : pSnapshot->findWell(sWellInstanceId);
if(!sWellInstanceId.isEmpty() && pWell == NULL) {
return false;
}
if(!pDataManager->setCurrentResultWellInstanceId(sWellInstanceId)) {
return false;
}
nmPebiResultWellCurves oEmptyCurves;
const nmPebiResultWellCurves& oCurves = pWell != NULL
? pWell->m_oCurves : oEmptyCurves;
QString sErrorMessage;
// 旧绘图辅助函数会裁掉双对数末点,必须使用隐式共享局部副本,
// 触发写时复制后也不能改动发布后的快照容器。
QVector<QVector<double> > vecHistorySemiLog =
oCurves.m_vecHistorySemiLog;
QVector<QVector<double> > vecResultSemiLog =
oCurves.m_vecResultSemiLog;
QVector<QVector<double> > vecHistoryLogLog =
oCurves.m_vecHistoryLogLog;
QVector<QVector<double> > vecResultLogLog =
oCurves.m_vecResultLogLog;
QVector<QVector<double> > vecHistoryPressure =
oCurves.m_vecHistoryPressure;
QVector<QVector<double> > vecResultPressureData =
oCurves.m_vecResultPressure;
// 每个窗口先发布求解时冻结的历史曲线,再叠加同一快照中的计算曲线。
QVector<iCurveDesc> vecHistoryHalfLog;
this->inithalfLog(vecHistorySemiLog,
vecHistoryHalfLog, true);
pSubWndFit->adjustFitSubPlotBy(
FSRT_SemiLog, vecHistoryHalfLog, true, &sErrorMessage);
QVector<iCurveDesc> vecHalfLog;
this->inithalfLog(vecResultSemiLog, vecHalfLog, false);
pSubWndFit->adjustFitSubPlotBy(
FSRT_SemiLog, vecHalfLog, true, &sErrorMessage);
QVector<iCurveDesc> vecHistoryLog;
this->initloglog(vecHistoryLogLog,
vecHistoryLog, true);
pSubWndFit->adjustFitSubPlotBy(
FSRT_DoubleLog, vecHistoryLog, true, &sErrorMessage);
QVector<iCurveDesc> vecLogLog;
this->initloglog(vecResultLogLog, vecLogLog, false);
pSubWndFit->adjustFitSubPlotBy(
FSRT_DoubleLog, vecLogLog, true, &sErrorMessage);
QVector<iCurveDesc> vecHistoryPre;
this->initPreHistory(vecHistoryPressure,
vecHistoryPre, true);
pSubWndFit->adjustFitSubPlotBy(
FSRT_Hist, vecHistoryPre, true, &sErrorMessage);
QVector<iCurveDesc> vecResultPressure;
this->initPreHistory(vecResultPressureData,
vecResultPressure, false);
pSubWndFit->adjustFitSubPlotBy(
FSRT_Hist, vecResultPressure, true, &sErrorMessage);
// 压力和流量属于同一口结果井;切井时必须一起替换,避免下图残留上一口井制度。
const QVector<double>* pReferenceRates =
resultReferenceRatesOf(pWell);
iCurveDesc oFlowCurve;
oFlowCurve.m_oCurveType = POT_CurveFlow;
oFlowCurve.m_sCurveName = s_HistorySouce_CurveF;
oFlowCurve.m_bLineVisible = true;
oFlowCurve.m_bPointVisible = false;
oFlowCurve.m_oPen.setColor(Qt::red);
if(pWell != NULL && pReferenceRates != NULL &&
pReferenceRates->size() == pWell->m_vecFlowDurations.size()) {
// 快照只保存真实流量段;绘图对象仍按框架原始格式使用首个
// (0,0) 占位点,一基流动段索引因此可以直接恢复而无需换算。
oFlowCurve.m_vecX = pWell->m_vecFlowDurations;
oFlowCurve.m_vecY = *pReferenceRates;
oFlowCurve.m_vecX.prepend(0.0);
oFlowCurve.m_vecY.prepend(0.0);
}
QVector<iCurveDesc> vecHistoryFlow;
vecHistoryFlow.append(oFlowCurve);
pSubWndFit->adjustFitSubPlotBy(
FSRT_Hist, vecHistoryFlow, true, &sErrorMessage);
// 框架流动段索引与 ZxSegmentInfo 相同,均为一基。这里只更新只读结果图元,
// 阻断选择信号,不能让结果井切换反向修改当前分析井的流动段配置。
QWidget* pHistoryWidget = pSubWndFit->getFitSubRstWxOf(
FSRT_Hist, false, &sErrorMessage);
iGuiPlotPF* pHistoryPlot = qobject_cast<iGuiPlotPF*>(pHistoryWidget);
if(pHistoryPlot != NULL) {
ZxObjCurveBase* pFlowObject = NULL;
if(pHistoryPlot->getChartDataObjOfPF(pFlowObject, false) &&
pFlowObject != NULL) {
ZxObjCurveFlow* pFlowCurve =
dynamic_cast<ZxObjCurveFlow*>(pFlowObject);
if(pFlowCurve != NULL) {
const bool bSignalsBlocked = pFlowCurve->blockSignals(true);
pFlowCurve->setCurSegPtIndex(
pWell == NULL ? 0 :
pWell->m_nFlowSectionIndex);
pFlowCurve->blockSignals(bSignalsBlocked);
pHistoryPlot->runUpdate();
}
}
}
return true;
}
void nmSubWndMain::onWellSelected(const QString& sWellInstanceId)
{
nmDataAnalyzeManager* pInstance =
nmDataAnalyzeManager::getCurrentInstance();
iSubWndFitting* pSubWndFit =
nmDataAnalyzeManager::getCurrentFitting();
if(pInstance == nullptr || pSubWndFit == nullptr) {
return;
}
// 删除的实时井仍可按快照 UUID 切换;本路径不得查询或重算实时井数据。
refreshPebiResultCurves(
pSubWndFit, pInstance, sWellInstanceId);
}
#include <Windows.h>
//void nmSubWndMain::calculationLogData(
// nmDataWellBase* pWellData,
// QVector<QVector<double>>& vvecHistoryData,
// QVector<QVector<double>>& vvecLogPreData,
// QVector<QVector<double>>& vvecSemiLogPreData)
//{
// // 清空输出参数
// vvecHistoryData.clear();
// vvecLogPreData.clear();
// vvecSemiLogPreData.clear();
//
// if(pWellData == nullptr) {
// return;
// }
//
// // 初始化二维数组结构
// vvecHistoryData.resize(2); // [0]=x, [1]=y
// vvecLogPreData.resize(3); // [0]=x, [1]=y, [2]=z
// vvecSemiLogPreData.resize(2); // [0]=x, [1]=pointData[0]
//
// // 准备压力数据
// QVector<QPointF> vecPressure = pWellData->getPressurePoints();
// std::vector<Point> wellPressureData;
//
// // 填充 wellPressureData 和 vvecHistoryData
// foreach(const QPointF& qpoint, vecPressure) {
// // wellPressureData
// Point pt;
// pt.x = qpoint.x();
// pt.y = qpoint.y();
// pt.z = 0.0;
// wellPressureData.push_back(pt);
//
// // vvecHistoryData
// vvecHistoryData[0].append(qpoint.x()); // x
// vvecHistoryData[1].append(qpoint.y()); // y
// }
//
// // 准备流量段数据
// QVector<QPointF> vecTimeQ = pWellData->getFlowPoints();
// int nTimeNumQ = vecTimeQ.size() - 1;
// std::vector<double> timeQ(nTimeNumQ);
// std::vector<double> q(nTimeNumQ);
//
// for(int i = 0; i < nTimeNumQ; ++i) {
// timeQ[i] = vecTimeQ[i + 1].x();
// q[i] = vecTimeQ[i + 1].y();
// }
//
// // 压力曲线统一由 HX_NWTM_PLOT 计算,旧曲线库调用示例已删除。
//}
void nmSubWndMain::onGenerateButtonClicked()
{
if(nmDataAnalyzeManager::getCurrentInstance()->getGridType() == NM_Grid_PEBI) {
// 求解任务创建时会捕获完整输入。网格无效时,工作线程只基于值快照生成
// 一次局部网格,求解成功后再和全部结果一起回主线程提交。
this->solveAndAnalyze();
}
}
void nmSubWndMain::generateDFN()
{
nmWxDFN dfnDialog(nullptr);
dfnDialog.exec();
}
void nmSubWndMain::displaySetting()
{
nmWxDisplaySettings disPlayDlg;
disPlayDlg.exec();
}
void nmSubWndMain::updateMapByDataManager(nmDataAnalyzeManager* pDataManager)
{
// 删除原来的图元信息
if(m_pWxPlot == nullptr || pDataManager == nullptr) {
return;
}
// 一个Map同一时间只展示一个分析的数据,切换前先解除其他DataManager对当前Plot的绑定。
foreach(nmDataAnalyzeManager* pManager, nmDataAnalyzeManager::s_mapDataAnalManager.values()) {
if(pManager != nullptr && pManager != pDataManager && pManager->getPlot() == m_pWxPlot) {
pManager->setPlot(nullptr);
}
}
// 更新对应的Plot成员变量
pDataManager->setPlot(m_pWxPlot);
// 完整清理、Binder切换和快照刷新均由Map重建入口统一完成。
m_pWxPlot->setPlotsByDataManger(pDataManager);
}