diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index f5e67e3..24a0ac6 100644 Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ 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(); +}