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();
+}