diff --git a/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.h b/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.h index 96704b2..2fd866f 100644 --- a/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.h +++ b/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.h @@ -18,8 +18,10 @@ class QPointF; class QPushButton; class QTableWidget; class QTableWidgetItem; +class QTimer; class nmGuiPlot; class nmObjPoint; +class nmWxPropertyInterpolationPreviewDlg; class NM_SUB_WXS_EXPORT nmWxPropertyInterpolationDlg : public iDlgBase { @@ -49,6 +51,7 @@ private slots: void onShowPointsToggled(bool checked); void onShowLabelsToggled(bool checked); void onPreview(); + void refreshPreview(); private: // 初始化界面及持久化数据组. @@ -80,6 +83,9 @@ private: void clearTableSelection(); void updatePointButtons(); void updateCurrentMarkerVisibility(); + void schedulePreviewRefresh(); + void updatePreviewWindow(bool activateWindow); + void showPreviewError(const QString& message, bool activateWindow); private: QPointer m_pPlot; @@ -119,6 +125,8 @@ private: // 结果预览及应用. QPushButton* m_pPreviewButton; QPushButton* m_pApplyButton; + QPointer m_pPreviewDialog; + QTimer* m_pPreviewRefreshTimer; }; #endif // NMWXPROPERTYINTERPOLATIONDLG_H diff --git a/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.h b/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.h index b8f96d3..3ff345d 100644 --- a/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.h +++ b/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.h @@ -29,6 +29,19 @@ public: bool showMeasurementLabels, QWidget* parent = 0); + void updatePreview(const QString& dataSetName, + const QString& scalarTitle, + const QRectF& bounds, + int columnCount, + int rowCount, + const QVector& interpolationValues, + const QVector& measurementPoints, + const QVector& measurementValues, + const QVector& outlinePoints, + bool showMeasurementPoints, + bool showMeasurementLabels); + void showMessage(const QString& dataSetName, const QString& message); + private: void initializeRenderer(const QString& scalarTitle, const QRectF& bounds, diff --git a/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.cpp b/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.cpp index e9a0623..2f9ecfc 100644 --- a/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.cpp +++ b/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -185,13 +186,22 @@ nmWxPropertyInterpolationDlg::nmWxPropertyInterpolationDlg( m_pRangeSpin(NULL), m_pModelCombo(NULL), m_pPreviewButton(NULL), - m_pApplyButton(NULL) + m_pApplyButton(NULL), + m_pPreviewDialog(NULL), + m_pPreviewRefreshTimer(NULL) { setWindowTitle(interpolationText("Property Interpolation")); setWindowModality(Qt::NonModal); setModal(false); setMinimumSize(520, 620); resize(560, 680); + + m_pPreviewRefreshTimer = new QTimer(this); + m_pPreviewRefreshTimer->setSingleShot(true); + m_pPreviewRefreshTimer->setInterval(200); + connect(m_pPreviewRefreshTimer, SIGNAL(timeout()), + this, SLOT(refreshPreview())); + initInterpolationUI(); initializeDataSets(); @@ -554,6 +564,7 @@ void nmWxPropertyInterpolationDlg::onDataSetChanged(int index) syncDataSetsToManager(false); m_nCurrentDataSetIndex = index; loadDataSet(index); + schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onAddDataSet() @@ -572,6 +583,7 @@ void nmWxPropertyInterpolationDlg::onAddDataSet() loadDataSet(newIndex); updateDataSetButtons(); syncDataSetsToManager(true); + schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onDeleteDataSet() @@ -597,6 +609,7 @@ void nmWxPropertyInterpolationDlg::onDeleteDataSet() loadDataSet(newIndex); updateDataSetButtons(); syncDataSetsToManager(true); + schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onDataSetNameChanged(const QString& name) @@ -609,6 +622,7 @@ void nmWxPropertyInterpolationDlg::onDataSetNameChanged(const QString& name) m_vecDataSets[m_nCurrentDataSetIndex].name = name; m_pDataSetCombo->setItemText(m_nCurrentDataSetIndex, name); syncDataSetsToManager(true); + schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onCurrentDataSetEdited() @@ -619,6 +633,7 @@ void nmWxPropertyInterpolationDlg::onCurrentDataSetEdited() saveCurrentDataSet(); syncDataSetsToManager(true); + schedulePreviewRefresh(); } bool nmWxPropertyInterpolationDlg::eventFilter(QObject* watched, QEvent* event) @@ -706,6 +721,7 @@ void nmWxPropertyInterpolationDlg::appendPoint(const QPointF& valuePoint) m_pPointTable->scrollToBottom(); updatePointButtons(); syncDataSetsToManager(true); + schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::appendPointRow( @@ -871,6 +887,7 @@ void nmWxPropertyInterpolationDlg::onPointItemChanged(QTableWidgetItem* item) marker->update(); } syncDataSetsToManager(true); + schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onDeleteSelectedPoint() @@ -892,6 +909,7 @@ void nmWxPropertyInterpolationDlg::onDeleteSelectedPoint() clearTableSelection(); updatePointButtons(); syncDataSetsToManager(true); + schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onClearPoints() @@ -907,6 +925,7 @@ void nmWxPropertyInterpolationDlg::onClearPoints() clearTableSelection(); updatePointButtons(); syncDataSetsToManager(true); + schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onShowPointsToggled(bool checked) @@ -919,6 +938,7 @@ void nmWxPropertyInterpolationDlg::onShowPointsToggled(bool checked) m_vecDataSets[m_nCurrentDataSetIndex].showPoints = checked; updateCurrentMarkerVisibility(); syncDataSetsToManager(true); + schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::onShowLabelsToggled(bool checked) @@ -931,6 +951,7 @@ void nmWxPropertyInterpolationDlg::onShowLabelsToggled(bool checked) m_vecDataSets[m_nCurrentDataSetIndex].showLabels = checked; updateCurrentMarkerVisibility(); syncDataSetsToManager(true); + schedulePreviewRefresh(); } void nmWxPropertyInterpolationDlg::clearTableSelection() @@ -950,10 +971,64 @@ void nmWxPropertyInterpolationDlg::updatePointButtons() } void nmWxPropertyInterpolationDlg::onPreview() +{ + if(m_pPreviewRefreshTimer != NULL) { + m_pPreviewRefreshTimer->stop(); + } + updatePreviewWindow(true); +} + +void nmWxPropertyInterpolationDlg::schedulePreviewRefresh() +{ + if(m_pPreviewDialog.isNull() || !m_pPreviewDialog->isVisible() || + m_pPreviewRefreshTimer == NULL) { + return; + } + + // 连续编辑参数时重置计时,仅对最终状态执行一次插值计算. + m_pPreviewRefreshTimer->start(); +} + +void nmWxPropertyInterpolationDlg::refreshPreview() +{ + if(m_pPreviewDialog.isNull() || !m_pPreviewDialog->isVisible()) { + return; + } + + updatePreviewWindow(false); +} + +void nmWxPropertyInterpolationDlg::showPreviewError( + const QString& message, + bool activateWindow) +{ + const QString errorText = message.isEmpty() ? + interpolationText("Preview Failed") : message; + QString dataSetName; + if(m_nCurrentDataSetIndex >= 0 && + m_nCurrentDataSetIndex < m_vecDataSets.size()) { + dataSetName = m_vecDataSets[m_nCurrentDataSetIndex].name; + } + + if(!m_pPreviewDialog.isNull()) { + m_pPreviewDialog->showMessage(dataSetName, errorText); + if(activateWindow) { + m_pPreviewDialog->show(); + m_pPreviewDialog->raise(); + m_pPreviewDialog->activateWindow(); + } + } + else if(activateWindow) { + QMessageBox::warning(this, interpolationText("Preview Failed"), errorText); + } +} + +void nmWxPropertyInterpolationDlg::updatePreviewWindow(bool activateWindow) { if(m_nCurrentDataSetIndex < 0 || m_nCurrentDataSetIndex >= m_vecDataSets.size() || m_pDataManager.isNull()) { + showPreviewError(interpolationText("Preview Failed"), activateWindow); return; } @@ -966,9 +1041,9 @@ void nmWxPropertyInterpolationDlg::onPreview() QRectF previewBounds; if(!createPreviewOutline(m_pDataManager->getOutlineData(), outlinePoints, previewBounds)) { - QMessageBox::warning(this, - interpolationText("Preview Failed"), - interpolationText("Please create a valid reservoir boundary before previewing.")); + showPreviewError( + interpolationText("Please create a valid reservoir boundary before previewing."), + activateWindow); return; } @@ -1016,27 +1091,47 @@ void nmWxPropertyInterpolationDlg::onPreview() QApplication::restoreOverrideCursor(); if(!calculationSucceeded) { - QMessageBox::warning(this, - interpolationText("Preview Failed"), - errorMessage); + showPreviewError(errorMessage, activateWindow); return; } // 预览窗口只负责裁剪和渲染,不修改数据组及正式求解参数. - nmWxPropertyInterpolationPreviewDlg previewDialog( - dataSet.name, - m_pPropertyCombo->currentText(), - previewBounds, - columnCount, - rowCount, - interpolationValues, - measurementPoints, - measurementValues, - outlinePoints, - dataSet.showPoints, - dataSet.showLabels, - this); - previewDialog.exec(); + if(m_pPreviewDialog.isNull()) { + m_pPreviewDialog = new nmWxPropertyInterpolationPreviewDlg( + dataSet.name, + m_pPropertyCombo->currentText(), + previewBounds, + columnCount, + rowCount, + interpolationValues, + measurementPoints, + measurementValues, + outlinePoints, + dataSet.showPoints, + dataSet.showLabels, + this); + m_pPreviewDialog->setAttribute(Qt::WA_DeleteOnClose, true); + } + else { + m_pPreviewDialog->updatePreview( + dataSet.name, + m_pPropertyCombo->currentText(), + previewBounds, + columnCount, + rowCount, + interpolationValues, + measurementPoints, + measurementValues, + outlinePoints, + dataSet.showPoints, + dataSet.showLabels); + } + + if(activateWindow) { + m_pPreviewDialog->show(); + m_pPreviewDialog->raise(); + m_pPreviewDialog->activateWindow(); + } } void nmWxPropertyInterpolationDlg::updateCurrentMarkerVisibility() diff --git a/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.cpp b/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.cpp index 7fa7425..bd2ebfa 100644 --- a/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.cpp +++ b/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.cpp @@ -12,10 +12,9 @@ #include #include #include +#include #include #include -#include -#include #include #include #include @@ -55,7 +54,9 @@ vtkSmartPointer createPreviewLookupTable(double minimum, return lookupTable; } -void showPreviewFailure(vtkRenderer* renderer, vtkRenderWindow* renderWindow) +void showPreviewFailure(vtkRenderer* renderer, + vtkRenderWindow* renderWindow, + const QString& message) { if(renderer == NULL || renderWindow == NULL) { return; @@ -63,7 +64,9 @@ void showPreviewFailure(vtkRenderer* renderer, vtkRenderWindow* renderWindow) vtkSmartPointer messageActor = vtkSmartPointer::New(); - messageActor->SetInput(previewText("Preview Failed").toUtf8().constData()); + const QString displayMessage = message.isEmpty() ? + previewText("Preview Failed") : message; + messageActor->SetInput(displayMessage.toUtf8().constData()); messageActor->SetDisplayPosition(24, 24); messageActor->GetTextProperty()->SetColor(0.75, 0.15, 0.12); messageActor->GetTextProperty()->SetFontSize(16); @@ -88,8 +91,8 @@ nmWxPropertyInterpolationPreviewDlg::nmWxPropertyInterpolationPreviewDlg( : iDlgBase(parent), m_pVtkWidget(NULL) { - setWindowTitle(previewText("Interpolation Preview") + " - " + dataSetName); - setWindowModality(Qt::WindowModal); + setWindowModality(Qt::NonModal); + setModal(false); setMinimumSize(900, 700); resize(1200, 850); @@ -108,12 +111,57 @@ nmWxPropertyInterpolationPreviewDlg::nmWxPropertyInterpolationPreviewDlg( mainLayout->addLayout(buttonLayout); connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); + updatePreview(dataSetName, scalarTitle, bounds, columnCount, rowCount, + interpolationValues, measurementPoints, + measurementValues, outlinePoints, + showMeasurementPoints, showMeasurementLabels); +} + +void nmWxPropertyInterpolationPreviewDlg::updatePreview( + const QString& dataSetName, + const QString& scalarTitle, + const QRectF& bounds, + int columnCount, + int rowCount, + const QVector& interpolationValues, + const QVector& measurementPoints, + const QVector& measurementValues, + const QVector& outlinePoints, + bool showMeasurementPoints, + bool showMeasurementLabels) +{ + setWindowTitle(previewText("Interpolation Preview") + " - " + dataSetName); initializeRenderer(scalarTitle, bounds, columnCount, rowCount, interpolationValues, measurementPoints, measurementValues, outlinePoints, showMeasurementPoints, showMeasurementLabels); } +void nmWxPropertyInterpolationPreviewDlg::showMessage( + const QString& dataSetName, + const QString& message) +{ + if(m_pVtkWidget == NULL) { + return; + } + + QString windowTitle = previewText("Interpolation Preview"); + if(!dataSetName.isEmpty()) { + windowTitle += " - " + dataSetName; + } + setWindowTitle(windowTitle); + + vtkSmartPointer renderer = vtkSmartPointer::New(); + renderer->SetBackground(0.88, 0.88, 0.88); + + vtkSmartPointer renderWindow = + vtkSmartPointer::New(); + renderWindow->SetMultiSamples(0); + renderWindow->AddRenderer(renderer); + m_pVtkWidget->SetRenderWindow(renderWindow); + showPreviewFailure(renderer, renderWindow, message); +} + void nmWxPropertyInterpolationPreviewDlg::initializeRenderer( const QString& scalarTitle, const QRectF& bounds, @@ -143,35 +191,73 @@ void nmWxPropertyInterpolationPreviewDlg::initializeRenderer( if(columnCount < 2 || rowCount < 2 || bounds.width() <= 0.0 || bounds.height() <= 0.0 || interpolationValues.size() != columnCount * rowCount) { - showPreviewFailure(renderer, renderWindow); + showPreviewFailure(renderer, renderWindow, previewText("Preview Failed")); return; } - // 标量按行优先顺序保存,与对话框生成规则待插值点的顺序一致. + // 规则采样点使用Kriging结果,测点作为额外顶点直接使用已知值. + vtkSmartPointer surfacePoints = + vtkSmartPointer::New(); vtkSmartPointer scalarArray = vtkSmartPointer::New(); scalarArray->SetName("InterpolatedValue"); scalarArray->SetNumberOfComponents(1); - scalarArray->SetNumberOfTuples(interpolationValues.size()); - for(int i = 0; i < interpolationValues.size(); ++i) { - scalarArray->SetValue(i, interpolationValues[i]); + + const double spacingX = bounds.width() / (columnCount - 1); + const double spacingY = bounds.height() / (rowCount - 1); + for(int row = 0; row < rowCount; ++row) { + const double y = bounds.top() + spacingY * row; + for(int column = 0; column < columnCount; ++column) { + const int valueIndex = row * columnCount + column; + const double x = bounds.left() + spacingX * column; + surfacePoints->InsertNextPoint(x, y, 0.0); + scalarArray->InsertNextValue(interpolationValues[valueIndex]); + } } - vtkSmartPointer imageData = - vtkSmartPointer::New(); - imageData->SetDimensions(columnCount, rowCount, 1); - imageData->SetOrigin(bounds.left(), bounds.top(), 0.0); - imageData->SetSpacing(bounds.width() / (columnCount - 1), - bounds.height() / (rowCount - 1), 1.0); - imageData->GetPointData()->SetScalars(scalarArray); - - vtkSmartPointer geometryFilter = - vtkSmartPointer::New(); - geometryFilter->SetInputData(imageData); - geometryFilter->Update(); - - // 默认使用完整规则网格;仅在裁剪结果有效且保留标量时切换. - vtkPolyData* visibleSurface = geometryFilter->GetOutput(); + if(measurementPoints.size() == measurementValues.size()) { + const double coordinateTolerance = qMax( + qMax(bounds.width(), bounds.height()) * 1.0e-10, + 1.0e-12); + const int regularPointCount = surfacePoints->GetNumberOfPoints(); + for(int i = 0; i < measurementPoints.size(); ++i) { + vtkIdType matchingPointId = -1; + for(int pointIndex = 0; pointIndex < regularPointCount; ++pointIndex) { + double regularPoint[3] = { 0.0, 0.0, 0.0 }; + surfacePoints->GetPoint(pointIndex, regularPoint); + if(qAbs(regularPoint[0] - measurementPoints[i].x()) <= coordinateTolerance && + qAbs(regularPoint[1] - measurementPoints[i].y()) <= coordinateTolerance) { + matchingPointId = pointIndex; + break; + } + } + + if(matchingPointId >= 0) { + scalarArray->SetValue(matchingPointId, measurementValues[i]); + } + else { + surfacePoints->InsertNextPoint(measurementPoints[i].x(), + measurementPoints[i].y(), 0.0); + scalarArray->InsertNextValue(measurementValues[i]); + } + } + } + + vtkSmartPointer surfaceData = + vtkSmartPointer::New(); + surfaceData->SetPoints(surfacePoints); + surfaceData->GetPointData()->SetScalars(scalarArray); + + vtkSmartPointer triangulationFilter = + vtkSmartPointer::New(); + triangulationFilter->SetInputData(surfaceData); + triangulationFilter->SetProjectionPlaneMode(VTK_DELAUNAY_XY_PLANE); + triangulationFilter->SetTolerance(0.0); + triangulationFilter->BoundingTriangulationOff(); + triangulationFilter->Update(); + + // 默认使用完整三角网格;仅在裁剪结果有效且保留标量时切换. + vtkPolyData* visibleSurface = triangulationFilter->GetOutput(); vtkSmartPointer extractFilter; vtkSmartPointer selectionLoop; vtkSmartPointer selectionPoints; @@ -188,7 +274,7 @@ void nmWxPropertyInterpolationPreviewDlg::initializeRenderer( selectionLoop->SetNormal(0.0, 0.0, 1.0); extractFilter = vtkSmartPointer::New(); - extractFilter->SetInputConnection(geometryFilter->GetOutputPort()); + extractFilter->SetInputConnection(triangulationFilter->GetOutputPort()); extractFilter->SetImplicitFunction(selectionLoop); extractFilter->ExtractInsideOn(); extractFilter->ExtractBoundaryCellsOn(); @@ -205,7 +291,7 @@ void nmWxPropertyInterpolationPreviewDlg::initializeRenderer( if(visibleSurface == NULL || visibleSurface->GetNumberOfPoints() == 0 || visibleSurface->GetPointData() == NULL || visibleSurface->GetPointData()->GetScalars() == NULL) { - showPreviewFailure(renderer, renderWindow); + showPreviewFailure(renderer, renderWindow, previewText("Preview Failed")); return; } @@ -213,10 +299,23 @@ void nmWxPropertyInterpolationPreviewDlg::initializeRenderer( visibleSurface->GetPointData()->GetScalars(); visibleSurface->GetPointData()->SetScalars(interpolatedScalars); - double displayRange[2] = { 0.0, 0.0 }; - visibleSurface->GetPointData()->GetScalars()->GetRange(displayRange); - double displayMinimum = displayRange[0]; - double displayMaximum = displayRange[1]; + // 颜色条使用测点值范围,避免规则采样未命中测点时最大、最小值向内收缩. + double displayMinimum = 0.0; + double displayMaximum = 0.0; + if(!measurementValues.isEmpty()) { + displayMinimum = measurementValues[0]; + displayMaximum = measurementValues[0]; + for(int i = 1; i < measurementValues.size(); ++i) { + displayMinimum = qMin(displayMinimum, measurementValues[i]); + displayMaximum = qMax(displayMaximum, measurementValues[i]); + } + } + else { + double displayRange[2] = { 0.0, 0.0 }; + visibleSurface->GetPointData()->GetScalars()->GetRange(displayRange); + displayMinimum = displayRange[0]; + displayMaximum = displayRange[1]; + } if(qAbs(displayMaximum - displayMinimum) < 1.0e-12) { const double padding = qMax(qAbs(displayMinimum) * 0.01, 1.0e-6); displayMinimum -= padding; @@ -311,6 +410,19 @@ void nmWxPropertyInterpolationPreviewDlg::initializeRenderer( vtkSmartPointer::New(); glyphFilter->SetInputData(measurementData); + // 深色外层形成清晰描边,避免测点颜色与附近插值色带融为一体. + vtkSmartPointer borderMapper = + vtkSmartPointer::New(); + borderMapper->SetInputConnection(glyphFilter->GetOutputPort()); + borderMapper->ScalarVisibilityOff(); + + vtkSmartPointer borderActor = + vtkSmartPointer::New(); + borderActor->SetMapper(borderMapper); + borderActor->GetProperty()->SetColor(0.10, 0.10, 0.10); + borderActor->GetProperty()->SetPointSize(9.0); + renderer->AddActor(borderActor); + vtkSmartPointer measurementMapper = vtkSmartPointer::New(); measurementMapper->SetInputConnection(glyphFilter->GetOutputPort()); @@ -321,7 +433,8 @@ void nmWxPropertyInterpolationPreviewDlg::initializeRenderer( vtkSmartPointer measurementActor = vtkSmartPointer::New(); measurementActor->SetMapper(measurementMapper); - measurementActor->GetProperty()->SetPointSize(8.0); + measurementActor->GetProperty()->SetPointSize(5.0); + measurementActor->SetPosition(0.0, 0.0, 0.001); renderer->AddActor(measurementActor); }