From fde6316a371ef2138cb950685ec415db10eb4eef Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Tue, 4 Aug 2026 15:34:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=BD=91=E6=A0=BC=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E6=8F=92=E5=80=BC=E7=BB=93=E6=9E=9C=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 封装Kriging插值接口并校验输入输出数据 - 支持独立窗口分级色带预览、边界裁剪和测点标签显示 --- Bin/Config/Lang/cn/nmNum_cn.qm | Bin 105525 -> 107692 bytes Bin/Config/Lang/cn/nmNum_cn.ts | 82 ++++ .../nmNum/nmCalculation/nmCalculationUtils.h | 16 + .../nmSubWxs/nmWxPropertyInterpolationDlg.h | 3 + .../nmWxPropertyInterpolationPreviewDlg.h | 48 +++ .../nmCalculation/nmCalculationUtils.cpp | 190 +++++++++ .../nmSubWxs/nmWxPropertyInterpolationDlg.cpp | 149 +++++++ .../nmWxPropertyInterpolationPreviewDlg.cpp | 387 ++++++++++++++++++ 8 files changed, 875 insertions(+) create mode 100644 Include/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.h create mode 100644 Src/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.cpp diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index f5e67e3c45de0ed215bc9870428d39999926b5ba..24a0ac6ba33869b41aa4c67137641a1f5a3e132d 100644 GIT binary patch delta 11953 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 delta 10012 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 RXO%p!FVi297qN7^{{ut`db9ul diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index 65b9b87..50089c7 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -6678,5 +6678,87 @@ Average pressure in contour: %2 MPa Close 关闭 + + Preview Failed + 预览失败 + + + Please create a valid reservoir boundary before previewing. + 请先创建有效的储层边界,再进行预览。 + + + + nmWxPropertyInterpolationPreviewDlg + + Interpolation Preview + 插值预览 + + + Close + 关闭 + + + + nmCalculationUtils + + No interpolation points are available. + 没有可用的待插值点。 + + + At least two measurement points are required. + 至少需要两个测点。 + + + Measurement point coordinates and values do not match. + 测点坐标与测点值的数量不一致。 + + + Kriging parameters are invalid. + Kriging 参数无效。 + + + The Kriging model is invalid. + Kriging 模型无效。 + + + An interpolation point contains an invalid coordinate. + 待插值点中包含无效坐标。 + + + A measurement point contains invalid data. + 测点中包含无效数据。 + + + Measurement point coordinates cannot be duplicated. + 测点坐标不能重复。 + + + The solver license file was not found. + 未找到求解器许可证文件。 + + + Failed to load HX_NWTM.dll. + 加载 HX_NWTM.dll 失败。 + + + The Kriging interface was not found in HX_NWTM.dll. + HX_NWTM.dll 中没有找到 Kriging 接口。 + + + Kriging calculation failed: %1 + Kriging 计算失败:%1 + + + Kriging calculation failed. + Kriging 计算失败。 + + + The Kriging result count does not match the interpolation points. + Kriging 结果数量与待插值点数量不一致。 + + + The Kriging result contains an invalid value. + Kriging 结果中包含无效数值。 + diff --git a/Include/nmNum/nmCalculation/nmCalculationUtils.h b/Include/nmNum/nmCalculation/nmCalculationUtils.h index 7a40459..76a5faa 100644 --- a/Include/nmNum/nmCalculation/nmCalculationUtils.h +++ b/Include/nmNum/nmCalculation/nmCalculationUtils.h @@ -2,6 +2,8 @@ #define NMCALCULATIONUTILS_H #include +#include +#include #include "nmCalculation_global.h" #include "nmCalculationDefine.h" #include @@ -16,6 +18,20 @@ class NMCALCULATION_EXPORT nmCalculationUtils { static bool removeDirectory(const QString &dirPath); static bool writeFile(const QStringList& content, const QString &filePath); static QStringList readFile(const QString & filePath); + + // 调用求解器Kriging接口,对targetPoints中的坐标依次进行属性插值. + // measurementPoints与measurementValues按下标一一对应. + // 成功时outputValues与targetPoints数量相同且顺序一致;失败时清空outputValues. + static bool calculateKriging(const QVector& targetPoints, + const QVector& measurementPoints, + const QVector& measurementValues, + double nugget, + double sill, + double range, + int model, + const QString& licensePath, + QVector& outputValues, + QString* errorMessage = 0); }; #endif // NMCALCULATIONUTILS_H diff --git a/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.h b/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.h index 4d9572f..96704b2 100644 --- a/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.h +++ b/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.h @@ -48,6 +48,7 @@ private slots: void onClearPoints(); void onShowPointsToggled(bool checked); void onShowLabelsToggled(bool checked); + void onPreview(); private: // 初始化界面及持久化数据组. @@ -89,9 +90,11 @@ private: QPushButton* m_pAddDataSetButton; QPushButton* m_pDeleteDataSetButton; QVector m_vecDataSets; + // 与数据组、测点下标一一对应,仅保存地图临时标记的对象名称. QVector m_vecMarkerNames; int m_nCurrentDataSetIndex; int m_nNextPointId; + // 加载界面数据时阻止控件信号反向修改当前数据组. bool m_bUpdatingUi; // 当前数据组的属性及名称. diff --git a/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.h b/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.h new file mode 100644 index 0000000..b8f96d3 --- /dev/null +++ b/Include/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.h @@ -0,0 +1,48 @@ +#ifndef NMWXPROPERTYINTERPOLATIONPREVIEWDLG_H +#define NMWXPROPERTYINTERPOLATIONPREVIEWDLG_H + +#include "iDlgBase.h" +#include "nmSubWxs_global.h" + +#include +#include +#include +#include + +class QVTKWidget; + +// 网格属性插值结果的独立VTK预览对话框. +class NM_SUB_WXS_EXPORT nmWxPropertyInterpolationPreviewDlg : public iDlgBase +{ +public: + explicit nmWxPropertyInterpolationPreviewDlg( + 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, + QWidget* parent = 0); + +private: + void initializeRenderer(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); + +private: + QVTKWidget* m_pVtkWidget; +}; + +#endif // NMWXPROPERTYINTERPOLATIONPREVIEWDLG_H diff --git a/Src/nmNum/nmCalculation/nmCalculationUtils.cpp b/Src/nmNum/nmCalculation/nmCalculationUtils.cpp index fdb33d9..5aca5ac 100644 --- a/Src/nmNum/nmCalculation/nmCalculationUtils.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationUtils.cpp @@ -4,7 +4,35 @@ #include #include #include +#include #include +#include + +#include "pch.h" + +namespace +{ +QString krigingText(const char* sourceText) +{ + return QCoreApplication::translate("nmCalculationUtils", sourceText); +} + +bool isFiniteValue(double value) +{ +#ifdef _MSC_VER + return _finite(value) != 0; +#else + return std::isfinite(value); +#endif +} + +void setKrigingError(QString* errorMessage, const QString& message) +{ + if(errorMessage != NULL) { + *errorMessage = message; + } +} +} nmCalculationUtils::nmCalculationUtils() { } @@ -159,3 +187,165 @@ QStringList nmCalculationUtils::readFile(const QString &filePath) { return content; } +bool nmCalculationUtils::calculateKriging( + const QVector& targetPoints, + const QVector& measurementPoints, + const QVector& measurementValues, + double nugget, + double sill, + double range, + int model, + const QString& licensePath, + QVector& outputValues, + QString* errorMessage) +{ + outputValues.clear(); + setKrigingError(errorMessage, QString()); + + if(targetPoints.isEmpty()) { + setKrigingError(errorMessage, krigingText("No interpolation points are available.")); + return false; + } + + if(measurementPoints.size() < 2) { + setKrigingError(errorMessage, + krigingText("At least two measurement points are required.")); + return false; + } + + if(measurementPoints.size() != measurementValues.size()) { + setKrigingError(errorMessage, + krigingText("Measurement point coordinates and values do not match.")); + return false; + } + + if(!isFiniteValue(nugget) || !isFiniteValue(sill) || + !isFiniteValue(range) || nugget < 0.0 || sill <= 0.0 || range <= 0.0) { + setKrigingError(errorMessage, krigingText("Kriging parameters are invalid.")); + return false; + } + + if(model < 0 || model > 2) { + setKrigingError(errorMessage, krigingText("The Kriging model is invalid.")); + return false; + } + + for(int i = 0; i < targetPoints.size(); ++i) { + if(!isFiniteValue(targetPoints[i].x()) || !isFiniteValue(targetPoints[i].y())) { + setKrigingError(errorMessage, + krigingText("An interpolation point contains an invalid coordinate.")); + return false; + } + } + + for(int i = 0; i < measurementPoints.size(); ++i) { + if(!isFiniteValue(measurementPoints[i].x()) || + !isFiniteValue(measurementPoints[i].y()) || + !isFiniteValue(measurementValues[i])) { + setKrigingError(errorMessage, + krigingText("A measurement point contains invalid data.")); + return false; + } + + for(int j = 0; j < i; ++j) { + if(measurementPoints[i] == measurementPoints[j]) { + setKrigingError(errorMessage, + krigingText("Measurement point coordinates cannot be duplicated.")); + return false; + } + } + } + + if(licensePath.isEmpty() || !QFileInfo(licensePath).exists()) { + setKrigingError(errorMessage, krigingText("The solver license file was not found.")); + return false; + } + + HMODULE dll = LoadLibrary(L"HX_NWTM.dll"); + if(dll == NULL) { + setKrigingError(errorMessage, krigingText("Failed to load HX_NWTM.dll.")); + return false; + } + + typedef void (*HX_NWTM_KRINGING_Func)(HX_KRING_OUTPUT&, + const HX_KRING_INPUT, + std::string); + HX_NWTM_KRINGING_Func krigingFunction = + (HX_NWTM_KRINGING_Func)GetProcAddress(dll, "HX_NWTM_KRINGING"); + if(krigingFunction == NULL) { + FreeLibrary(dll); + setKrigingError(errorMessage, + krigingText("The Kriging interface was not found in HX_NWTM.dll.")); + return false; + } + + // 求解器p参数每行保存{x, y},并保持targetPoints的原始顺序. + dVec2 targetData; + targetData.reserve(targetPoints.size()); + for(int i = 0; i < targetPoints.size(); ++i) { + dVec1 point(2, 0.0); + point[0] = targetPoints[i].x(); + point[1] = targetPoints[i].y(); + targetData.push_back(point); + } + + // 求解器v参数每行保存{x, y, value},坐标和值按相同下标配对. + dVec2 measurementData; + measurementData.reserve(measurementPoints.size()); + for(int i = 0; i < measurementPoints.size(); ++i) { + dVec1 point(3, 0.0); + point[0] = measurementPoints[i].x(); + point[1] = measurementPoints[i].y(); + point[2] = measurementValues[i]; + measurementData.push_back(point); + } + + bool calculationSucceeded = true; + QString calculationError; + + // 局部作用域保证求解器输入、输出对象在FreeLibrary前析构. + { + HX_KRING_INPUT input(nugget, sill, range, model, + targetData, measurementData); + HX_KRING_OUTPUT output; + + try { + krigingFunction(output, input, licensePath.toStdString()); + } catch(const std::exception& exception) { + calculationSucceeded = false; + calculationError = krigingText("Kriging calculation failed: %1") + .arg(QString::fromLocal8Bit(exception.what())); + } catch(...) { + calculationSucceeded = false; + calculationError = krigingText("Kriging calculation failed."); + } + + if(calculationSucceeded && + output.v.size() != static_cast(targetPoints.size())) { + calculationSucceeded = false; + calculationError = krigingText( + "The Kriging result count does not match the interpolation points."); + } + + if(calculationSucceeded) { + outputValues.reserve(targetPoints.size()); + for(size_t i = 0; i < output.v.size(); ++i) { + if(!isFiniteValue(output.v[i])) { + calculationSucceeded = false; + calculationError = krigingText( + "The Kriging result contains an invalid value."); + break; + } + outputValues.append(output.v[i]); + } + } + } + + FreeLibrary(dll); + if(!calculationSucceeded) { + outputValues.clear(); + setKrigingError(errorMessage, calculationError); + } + return calculationSucceeded; +} + diff --git a/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.cpp b/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.cpp index ac076ff..e9a0623 100644 --- a/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.cpp +++ b/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationDlg.cpp @@ -14,17 +14,23 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include #include +#include +#include "nmCalculationUtils.h" +#include "nmDataOutline.h" #include "nmGuiPlot.h" #include "nmObjPoint.h" +#include "nmWxPropertyInterpolationPreviewDlg.h" #include "tCurvePlotView.h" #include "ZxObjText.h" #include "ZxPlot.h" @@ -108,6 +114,48 @@ void setMarkerLabel(nmObjPoint* marker, const QString& text) label->setTextColor(QColor(30, 30, 30)); } } + +// 将储层边界转换成预览裁剪使用的多边形,并计算规则采样范围. +bool createPreviewOutline(nmDataOutline* outline, + QVector& outlinePoints, + QRectF& bounds) +{ + outlinePoints.clear(); + bounds = QRectF(); + if(outline == NULL) { + return false; + } + + if(outline->getOutlineType() == NM_Round_Outline_Type) { + // 圆形边界离散成多边形,便于预览窗口统一裁剪. + const QPointF center = outline->getCenter(); + const double radius = outline->getRadius(); + if(radius <= 0.0) { + return false; + } + + const int segmentCount = 72; + const double fullCircle = 6.28318530717958647692; + for(int i = 0; i < segmentCount; ++i) { + const double angle = fullCircle * i / segmentCount; + outlinePoints.append(QPointF(center.x() + radius * qCos(angle), + center.y() + radius * qSin(angle))); + } + } else { + outlinePoints = outline->getOutlinePoints(); + if(outlinePoints.size() > 3 && + outlinePoints.first() == outlinePoints.last()) { + outlinePoints.remove(outlinePoints.size() - 1); + } + } + + if(outlinePoints.size() < 3) { + return false; + } + + bounds = QPolygonF(outlinePoints).boundingRect().normalized(); + return bounds.width() > 0.0 && bounds.height() > 0.0; +} } nmWxPropertyInterpolationDlg::nmWxPropertyInterpolationDlg( @@ -305,6 +353,7 @@ void nmWxPropertyInterpolationDlg::initInterpolationUI() connect(m_pClearPointsButton, SIGNAL(clicked()), this, SLOT(onClearPoints())); connect(m_pShowPointsCheck, SIGNAL(toggled(bool)), this, SLOT(onShowPointsToggled(bool))); connect(m_pShowLabelsCheck, SIGNAL(toggled(bool)), this, SLOT(onShowLabelsToggled(bool))); + connect(m_pPreviewButton, SIGNAL(clicked()), this, SLOT(onPreview())); connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); } @@ -325,6 +374,7 @@ void nmWxPropertyInterpolationDlg::initializeDataSets() } m_vecMarkerNames.resize(m_vecDataSets.size()); + // 测点标记不参与持久化,打开对话框时根据已保存测点重新创建. createMarkersForAllDataSets(); bool oldBlock = m_pDataSetCombo->blockSignals(true); @@ -385,6 +435,7 @@ void nmWxPropertyInterpolationDlg::loadDataSet(int index) return; } + // 切换数据组时批量刷新控件,避免控件信号被误判为用户修改. bool wasUpdating = m_bUpdatingUi; m_bUpdatingUi = true; const nmPropertyInterpolationDataSet& dataSet = m_vecDataSets[index]; @@ -572,6 +623,7 @@ void nmWxPropertyInterpolationDlg::onCurrentDataSetEdited() bool nmWxPropertyInterpolationDlg::eventFilter(QObject* watched, QEvent* event) { + // 点击表格空白处或其他控件时取消测点行的选中状态. if(event != NULL && event->type() == QEvent::MouseButtonPress && m_pPointTable != NULL) { QWidget* clickedWidget = qobject_cast(watched); @@ -600,6 +652,7 @@ void nmWxPropertyInterpolationDlg::onMapPickToggled(bool checked) return; } + // 每次切换前先断开旧连接,防止一次地图点击被重复处理. disconnect(m_pPlot->m_pPlotView, SIGNAL(sigLeftClick(QPointF)), this, SLOT(onMapClicked(QPointF))); @@ -624,6 +677,7 @@ void nmWxPropertyInterpolationDlg::onMapClicked(const QPointF& position) return; } + // 点击位置为绘图区坐标,需要转换成实际数据坐标后保存. QPointF plotPosition = position; QPointF valuePoint = m_pPlot->m_pPlot->getValueForPos(plotPosition); appendPoint(valuePoint); @@ -642,6 +696,7 @@ void nmWxPropertyInterpolationDlg::appendPoint(const QPointF& valuePoint) QString markerName = createMarker(valuePoint, pointValue, dataSet.showPoints, dataSet.showLabels); + // 测点数据、地图标记和表格行按相同顺序同步追加. dataSet.points.append(point); m_vecMarkerNames[m_nCurrentDataSetIndex].append(markerName); @@ -687,6 +742,7 @@ QString nmWxPropertyInterpolationDlg::createMarker(const QPointF& valuePosition, markerName = QString("__PropertyInterpolationPoint_%1").arg(++m_nNextPointId); } while(m_pPlot->m_pPlot->getObjByName(markerName) != NULL); + // 地图标记仅用于临时显示,不应单独改变工程修改状态. bool wasModified = m_pPlot->isModified(); PropertyInterpolationMarker* marker = new PropertyInterpolationMarker( markerName, @@ -807,6 +863,7 @@ void nmWxPropertyInterpolationDlg::onPointItemChanged(QTableWidgetItem* item) return; } + // 数值修改后同步更新数据组和地图标签,坐标列保持只读. m_vecDataSets[m_nCurrentDataSetIndex].points[item->row()].value = item->text().toDouble(); nmObjPoint* marker = markerAt(item->row()); if(marker != NULL) { @@ -888,6 +945,98 @@ void nmWxPropertyInterpolationDlg::updatePointButtons() m_nCurrentDataSetIndex < m_vecDataSets.size(); m_pDeletePointButton->setEnabled(hasCurrent && m_pPointTable->currentRow() >= 0); m_pClearPointsButton->setEnabled(hasCurrent && m_pPointTable->rowCount() > 0); + m_pPreviewButton->setEnabled(hasCurrent && + m_vecDataSets[m_nCurrentDataSetIndex].points.size() >= 2); +} + +void nmWxPropertyInterpolationDlg::onPreview() +{ + if(m_nCurrentDataSetIndex < 0 || + m_nCurrentDataSetIndex >= m_vecDataSets.size() || + m_pDataManager.isNull()) { + return; + } + + saveCurrentDataSet(); + const nmPropertyInterpolationDataSet& dataSet = + m_vecDataSets[m_nCurrentDataSetIndex]; + + // 预览范围直接使用储层边界,与地图中的数据坐标保持一致. + QVector outlinePoints; + QRectF previewBounds; + if(!createPreviewOutline(m_pDataManager->getOutlineData(), + outlinePoints, previewBounds)) { + QMessageBox::warning(this, + interpolationText("Preview Failed"), + interpolationText("Please create a valid reservoir boundary before previewing.")); + return; + } + + QVector measurementPoints; + QVector measurementValues; + measurementPoints.reserve(dataSet.points.size()); + measurementValues.reserve(dataSet.points.size()); + for(int i = 0; i < dataSet.points.size(); ++i) { + measurementPoints.append(QPointF(dataSet.points[i].x, + dataSet.points[i].y)); + measurementValues.append(dataSet.points[i].value); + } + + // 按行生成规则待插值点,输出下标与预览网格下标保持一致; + // 较疏采样可使分级色带边界更清晰. + const int columnCount = 31; + const int rowCount = 31; + QVector targetPoints; + targetPoints.reserve(columnCount * rowCount); + for(int row = 0; row < rowCount; ++row) { + const double y = previewBounds.top() + + previewBounds.height() * row / (rowCount - 1); + for(int column = 0; column < columnCount; ++column) { + const double x = previewBounds.left() + + previewBounds.width() * column / (columnCount - 1); + targetPoints.append(QPointF(x, y)); + } + } + + QVector interpolationValues; + QString errorMessage; + QApplication::setOverrideCursor(Qt::WaitCursor); + // 通过统一封装调用Kriging接口,返回值顺序与待插值点一致. + const bool calculationSucceeded = nmCalculationUtils::calculateKriging( + targetPoints, + measurementPoints, + measurementValues, + dataSet.nugget, + dataSet.sill, + dataSet.range, + dataSet.model, + m_pDataManager->getLicensePath(), + interpolationValues, + &errorMessage); + QApplication::restoreOverrideCursor(); + + if(!calculationSucceeded) { + QMessageBox::warning(this, + interpolationText("Preview Failed"), + errorMessage); + return; + } + + // 预览窗口只负责裁剪和渲染,不修改数据组及正式求解参数. + nmWxPropertyInterpolationPreviewDlg previewDialog( + dataSet.name, + m_pPropertyCombo->currentText(), + previewBounds, + columnCount, + rowCount, + interpolationValues, + measurementPoints, + measurementValues, + outlinePoints, + dataSet.showPoints, + dataSet.showLabels, + this); + previewDialog.exec(); } void nmWxPropertyInterpolationDlg::updateCurrentMarkerVisibility() diff --git a/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.cpp b/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.cpp new file mode 100644 index 0000000..7fa7425 --- /dev/null +++ b/Src/nmNum/nmSubWxs/nmWxPropertyInterpolationPreviewDlg.cpp @@ -0,0 +1,387 @@ +#include "nmWxPropertyInterpolationPreviewDlg.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +QString previewText(const char* sourceText) +{ + return QCoreApplication::translate("nmWxPropertyInterpolationPreviewDlg", sourceText); +} + +vtkSmartPointer createPreviewLookupTable(double minimum, + double maximum) +{ + vtkSmartPointer lookupTable = + vtkSmartPointer::New(); + lookupTable->SetNumberOfTableValues(256); + lookupTable->SetRange(minimum, maximum); + lookupTable->SetHueRange(0.60, 0.02); + lookupTable->SetSaturationRange(0.50, 0.50); + lookupTable->SetValueRange(0.96, 0.96); + lookupTable->Build(); + return lookupTable; +} + +void showPreviewFailure(vtkRenderer* renderer, vtkRenderWindow* renderWindow) +{ + if(renderer == NULL || renderWindow == NULL) { + return; + } + + vtkSmartPointer messageActor = + vtkSmartPointer::New(); + messageActor->SetInput(previewText("Preview Failed").toUtf8().constData()); + messageActor->SetDisplayPosition(24, 24); + messageActor->GetTextProperty()->SetColor(0.75, 0.15, 0.12); + messageActor->GetTextProperty()->SetFontSize(16); + renderer->AddActor2D(messageActor); + renderWindow->Render(); +} +} + +nmWxPropertyInterpolationPreviewDlg::nmWxPropertyInterpolationPreviewDlg( + 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, + QWidget* parent) + : iDlgBase(parent), + m_pVtkWidget(NULL) +{ + setWindowTitle(previewText("Interpolation Preview") + " - " + dataSetName); + setWindowModality(Qt::WindowModal); + setMinimumSize(900, 700); + resize(1200, 850); + + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(8, 8, 8, 8); + mainLayout->setSpacing(8); + + m_pVtkWidget = new QVTKWidget(this); + m_pVtkWidget->setMinimumSize(860, 640); + mainLayout->addWidget(m_pVtkWidget, 1); + + QHBoxLayout* buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(); + QPushButton* closeButton = new QPushButton(previewText("Close"), this); + buttonLayout->addWidget(closeButton); + mainLayout->addLayout(buttonLayout); + connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); + + initializeRenderer(scalarTitle, bounds, columnCount, rowCount, + interpolationValues, measurementPoints, + measurementValues, outlinePoints, + showMeasurementPoints, showMeasurementLabels); +} + +void nmWxPropertyInterpolationPreviewDlg::initializeRenderer( + 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) +{ + if(m_pVtkWidget == NULL) { + return; + } + + // 显式创建渲染窗口,避免使用尚未初始化的QVTKWidget默认窗口. + 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); + + if(columnCount < 2 || rowCount < 2 || + bounds.width() <= 0.0 || bounds.height() <= 0.0 || + interpolationValues.size() != columnCount * rowCount) { + showPreviewFailure(renderer, renderWindow); + return; + } + + // 标量按行优先顺序保存,与对话框生成规则待插值点的顺序一致. + 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]); + } + + 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(); + vtkSmartPointer extractFilter; + vtkSmartPointer selectionLoop; + vtkSmartPointer selectionPoints; + if(outlinePoints.size() >= 3) { + selectionPoints = vtkSmartPointer::New(); + for(int i = 0; i < outlinePoints.size(); ++i) { + selectionPoints->InsertNextPoint(outlinePoints[i].x(), + outlinePoints[i].y(), 0.0); + } + + selectionLoop = vtkSmartPointer::New(); + selectionLoop->SetLoop(selectionPoints); + selectionLoop->AutomaticNormalGenerationOff(); + selectionLoop->SetNormal(0.0, 0.0, 1.0); + + extractFilter = vtkSmartPointer::New(); + extractFilter->SetInputConnection(geometryFilter->GetOutputPort()); + extractFilter->SetImplicitFunction(selectionLoop); + extractFilter->ExtractInsideOn(); + extractFilter->ExtractBoundaryCellsOn(); + extractFilter->Update(); + vtkPolyData* extractedSurface = extractFilter->GetOutput(); + if(extractedSurface != NULL && + extractedSurface->GetNumberOfPoints() > 0 && + extractedSurface->GetPointData() != NULL && + extractedSurface->GetPointData()->GetScalars() != NULL) { + visibleSurface = extractedSurface; + } + } + + if(visibleSurface == NULL || visibleSurface->GetNumberOfPoints() == 0 || + visibleSurface->GetPointData() == NULL || + visibleSurface->GetPointData()->GetScalars() == NULL) { + showPreviewFailure(renderer, renderWindow); + return; + } + + vtkDataArray* interpolatedScalars = + 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]; + if(qAbs(displayMaximum - displayMinimum) < 1.0e-12) { + const double padding = qMax(qAbs(displayMinimum) * 0.01, 1.0e-6); + displayMinimum -= padding; + displayMaximum += padding; + } + + vtkSmartPointer lookupTable = + createPreviewLookupTable(displayMinimum, displayMaximum); + + // 将连续点标量划分为10级色带,Mapper按CellData进行分级着色. + const int colorBandCount = 10; + vtkSmartPointer bandFilter = + vtkSmartPointer::New(); + bandFilter->SetInputData(visibleSurface); + bandFilter->GenerateValues(colorBandCount + 1, + displayMinimum, displayMaximum); + bandFilter->SetScalarModeToValue(); + bandFilter->GenerateContourEdgesOff(); + + vtkSmartPointer surfaceMapper = + vtkSmartPointer::New(); + surfaceMapper->SetInputConnection(bandFilter->GetOutputPort()); + surfaceMapper->SetScalarModeToUseCellData(); + surfaceMapper->SetLookupTable(lookupTable); + surfaceMapper->SetScalarRange(displayMinimum, displayMaximum); + surfaceMapper->ScalarVisibilityOn(); + + vtkSmartPointer surfaceActor = vtkSmartPointer::New(); + surfaceActor->SetMapper(surfaceMapper); + surfaceActor->GetProperty()->EdgeVisibilityOff(); + + renderer->AddActor(surfaceActor); + + // 叠加储层边界,并通过轻微Z偏移避免与色带表面重合. + if(outlinePoints.size() >= 3) { + vtkSmartPointer outlineVtkPoints = + vtkSmartPointer::New(); + for(int i = 0; i < outlinePoints.size(); ++i) { + outlineVtkPoints->InsertNextPoint(outlinePoints[i].x(), + outlinePoints[i].y(), 0.01); + } + + vtkSmartPointer outlineLines = + vtkSmartPointer::New(); + outlineLines->InsertNextCell(outlinePoints.size() + 1); + for(int i = 0; i < outlinePoints.size(); ++i) { + outlineLines->InsertCellPoint(i); + } + outlineLines->InsertCellPoint(0); + + vtkSmartPointer outlineData = + vtkSmartPointer::New(); + outlineData->SetPoints(outlineVtkPoints); + outlineData->SetLines(outlineLines); + + vtkSmartPointer outlineMapper = + vtkSmartPointer::New(); + outlineMapper->SetInputData(outlineData); + outlineMapper->ScalarVisibilityOff(); + + vtkSmartPointer outlineActor = vtkSmartPointer::New(); + outlineActor->SetMapper(outlineMapper); + outlineActor->GetProperty()->SetColor(0.55, 0.38, 0.22); + outlineActor->GetProperty()->SetLineWidth(1.2); + renderer->AddActor(outlineActor); + } + + // 测点和数值标签复用同一组坐标和值,叠加显示在插值面上. + if((showMeasurementPoints || showMeasurementLabels) && + !measurementPoints.isEmpty() && + measurementPoints.size() == measurementValues.size()) { + vtkSmartPointer measurementVtkPoints = + vtkSmartPointer::New(); + vtkSmartPointer measurementScalars = + vtkSmartPointer::New(); + measurementScalars->SetName("MeasurementValue"); + measurementScalars->SetNumberOfComponents(1); + + for(int i = 0; i < measurementPoints.size(); ++i) { + measurementVtkPoints->InsertNextPoint(measurementPoints[i].x(), + measurementPoints[i].y(), 0.02); + measurementScalars->InsertNextValue(measurementValues[i]); + } + + vtkSmartPointer measurementData = + vtkSmartPointer::New(); + measurementData->SetPoints(measurementVtkPoints); + measurementData->GetPointData()->SetScalars(measurementScalars); + + if(showMeasurementPoints) { + vtkSmartPointer glyphFilter = + vtkSmartPointer::New(); + glyphFilter->SetInputData(measurementData); + + vtkSmartPointer measurementMapper = + vtkSmartPointer::New(); + measurementMapper->SetInputConnection(glyphFilter->GetOutputPort()); + measurementMapper->SetLookupTable(lookupTable); + measurementMapper->SetScalarRange(displayMinimum, displayMaximum); + measurementMapper->SetScalarModeToUsePointData(); + + vtkSmartPointer measurementActor = + vtkSmartPointer::New(); + measurementActor->SetMapper(measurementMapper); + measurementActor->GetProperty()->SetPointSize(8.0); + renderer->AddActor(measurementActor); + } + + if(showMeasurementLabels) { + vtkSmartPointer labelMapper = + vtkSmartPointer::New(); + labelMapper->SetInputData(measurementData); + labelMapper->SetLabelModeToLabelScalars(); + labelMapper->SetLabelFormat("%.6g"); + labelMapper->GetLabelTextProperty()->SetColor(0.12, 0.12, 0.12); + labelMapper->GetLabelTextProperty()->SetFontSize(12); + labelMapper->GetLabelTextProperty()->BoldOff(); + labelMapper->GetLabelTextProperty()->ItalicOff(); + labelMapper->GetLabelTextProperty()->ShadowOff(); + + vtkSmartPointer labelActor = + vtkSmartPointer::New(); + labelActor->SetMapper(labelMapper); + labelActor->SetPosition(0.0, 10.0); + renderer->AddActor2D(labelActor); + } + } + + vtkSmartPointer scalarBar = + vtkSmartPointer::New(); + scalarBar->SetLookupTable(lookupTable); + scalarBar->SetTitle(scalarTitle.toUtf8().constData()); + scalarBar->SetNumberOfLabels(6); + scalarBar->SetPosition(0.88, 0.12); + scalarBar->SetWidth(0.08); + scalarBar->SetHeight(0.76); + scalarBar->UnconstrainedFontSizeOn(); + scalarBar->GetLabelTextProperty()->SetColor(0.15, 0.15, 0.15); + scalarBar->GetLabelTextProperty()->SetFontSize(11); + scalarBar->GetLabelTextProperty()->BoldOff(); + scalarBar->GetLabelTextProperty()->ItalicOff(); + scalarBar->GetLabelTextProperty()->ShadowOff(); + scalarBar->GetTitleTextProperty()->SetColor(0.15, 0.15, 0.15); + scalarBar->GetTitleTextProperty()->SetFontSize(13); + scalarBar->GetTitleTextProperty()->BoldOff(); + scalarBar->GetTitleTextProperty()->ItalicOff(); + scalarBar->GetTitleTextProperty()->ShadowOff(); + renderer->AddActor2D(scalarBar); + + // 使用正交相机按模型边界居中显示,保持平面比例并避免透视变形. + vtkCamera* camera = renderer->GetActiveCamera(); + const double centerX = bounds.left() + bounds.width() * 0.5; + const double centerY = bounds.top() + bounds.height() * 0.5; + camera->SetPosition(centerX, centerY, 1.0); + camera->SetFocalPoint(centerX, centerY, 0.0); + camera->SetViewUp(0.0, 1.0, 0.0); + camera->ParallelProjectionOn(); + double cameraBounds[6] = { + bounds.left(), bounds.right(), bounds.top(), bounds.bottom(), -0.1, 0.1 + }; + renderer->ResetCamera(cameraBounds); + camera->SetParallelScale(qMax(bounds.height(), bounds.width()) * 0.52); + + vtkSmartPointer interactorStyle = + vtkSmartPointer::New(); + renderWindow->GetInteractor()->SetInteractorStyle(interactorStyle); + renderWindow->Render(); +}