完善网格属性插值结果预览功能

- 封装Kriging插值接口并校验输入输出数据
- 支持独立窗口分级色带预览、边界裁剪和测点标签显示
feature/Interpolation-20260803
lvjunjie 2 weeks ago
parent 0e091ff593
commit fde6316a37

Binary file not shown.

@ -6678,5 +6678,87 @@ Average pressure in contour: %2 MPa</source>
<source>Close</source>
<translation></translation>
</message>
<message>
<source>Preview Failed</source>
<translation></translation>
</message>
<message>
<source>Please create a valid reservoir boundary before previewing.</source>
<translation></translation>
</message>
</context>
<context>
<name>nmWxPropertyInterpolationPreviewDlg</name>
<message>
<source>Interpolation Preview</source>
<translation></translation>
</message>
<message>
<source>Close</source>
<translation></translation>
</message>
</context>
<context>
<name>nmCalculationUtils</name>
<message>
<source>No interpolation points are available.</source>
<translation></translation>
</message>
<message>
<source>At least two measurement points are required.</source>
<translation></translation>
</message>
<message>
<source>Measurement point coordinates and values do not match.</source>
<translation></translation>
</message>
<message>
<source>Kriging parameters are invalid.</source>
<translation>Kriging </translation>
</message>
<message>
<source>The Kriging model is invalid.</source>
<translation>Kriging </translation>
</message>
<message>
<source>An interpolation point contains an invalid coordinate.</source>
<translation></translation>
</message>
<message>
<source>A measurement point contains invalid data.</source>
<translation></translation>
</message>
<message>
<source>Measurement point coordinates cannot be duplicated.</source>
<translation></translation>
</message>
<message>
<source>The solver license file was not found.</source>
<translation></translation>
</message>
<message>
<source>Failed to load HX_NWTM.dll.</source>
<translation> HX_NWTM.dll </translation>
</message>
<message>
<source>The Kriging interface was not found in HX_NWTM.dll.</source>
<translation>HX_NWTM.dll Kriging </translation>
</message>
<message>
<source>Kriging calculation failed: %1</source>
<translation>Kriging %1</translation>
</message>
<message>
<source>Kriging calculation failed.</source>
<translation>Kriging </translation>
</message>
<message>
<source>The Kriging result count does not match the interpolation points.</source>
<translation>Kriging </translation>
</message>
<message>
<source>The Kriging result contains an invalid value.</source>
<translation>Kriging </translation>
</message>
</context>
</TS>

@ -2,6 +2,8 @@
#define NMCALCULATIONUTILS_H
#include <QString>
#include <QPointF>
#include <QVector>
#include "nmCalculation_global.h"
#include "nmCalculationDefine.h"
#include <iostream>
@ -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<QPointF>& targetPoints,
const QVector<QPointF>& measurementPoints,
const QVector<double>& measurementValues,
double nugget,
double sill,
double range,
int model,
const QString& licensePath,
QVector<double>& outputValues,
QString* errorMessage = 0);
};
#endif // NMCALCULATIONUTILS_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<nmPropertyInterpolationDataSet> m_vecDataSets;
// 与数据组、测点下标一一对应,仅保存地图临时标记的对象名称.
QVector<QStringList> m_vecMarkerNames;
int m_nCurrentDataSetIndex;
int m_nNextPointId;
// 加载界面数据时阻止控件信号反向修改当前数据组.
bool m_bUpdatingUi;
// 当前数据组的属性及名称.

@ -0,0 +1,48 @@
#ifndef NMWXPROPERTYINTERPOLATIONPREVIEWDLG_H
#define NMWXPROPERTYINTERPOLATIONPREVIEWDLG_H
#include "iDlgBase.h"
#include "nmSubWxs_global.h"
#include <QPointF>
#include <QRectF>
#include <QString>
#include <QVector>
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<double>& interpolationValues,
const QVector<QPointF>& measurementPoints,
const QVector<double>& measurementValues,
const QVector<QPointF>& outlinePoints,
bool showMeasurementPoints,
bool showMeasurementLabels,
QWidget* parent = 0);
private:
void initializeRenderer(const QString& scalarTitle,
const QRectF& bounds,
int columnCount,
int rowCount,
const QVector<double>& interpolationValues,
const QVector<QPointF>& measurementPoints,
const QVector<double>& measurementValues,
const QVector<QPointF>& outlinePoints,
bool showMeasurementPoints,
bool showMeasurementLabels);
private:
QVTKWidget* m_pVtkWidget;
};
#endif // NMWXPROPERTYINTERPOLATIONPREVIEWDLG_H

@ -4,7 +4,35 @@
#include <QFileInfo>
#include <QDir>
#include <QDebug>
#include <QCoreApplication>
#include <fstream>
#include <float.h>
#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<QPointF>& targetPoints,
const QVector<QPointF>& measurementPoints,
const QVector<double>& measurementValues,
double nugget,
double sill,
double range,
int model,
const QString& licensePath,
QVector<double>& 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<size_t>(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;
}

@ -14,17 +14,23 @@
#include <QIcon>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QMouseEvent>
#include <QPainter>
#include <QPolygonF>
#include <QPushButton>
#include <QSize>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QTransform>
#include <QVBoxLayout>
#include <QtCore/qmath.h>
#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<QPointF>& 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<QWidget*>(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<QPointF> 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<QPointF> measurementPoints;
QVector<double> 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<QPointF> 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<double> 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()

@ -0,0 +1,387 @@
#include "nmWxPropertyInterpolationPreviewDlg.h"
#include <QCoreApplication>
#include <QHBoxLayout>
#include <QPushButton>
#include <QVBoxLayout>
#include <QVTKWidget.h>
#include <vtkActor.h>
#include <vtkActor2D.h>
#include <vtkBandedPolyDataContourFilter.h>
#include <vtkCamera.h>
#include <vtkCellArray.h>
#include <vtkDataArray.h>
#include <vtkDoubleArray.h>
#include <vtkExtractPolyDataGeometry.h>
#include <vtkImageData.h>
#include <vtkImageDataGeometryFilter.h>
#include <vtkImplicitSelectionLoop.h>
#include <vtkInteractorStyleImage.h>
#include <vtkLabeledDataMapper.h>
#include <vtkLookupTable.h>
#include <vtkPointData.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkScalarBarActor.h>
#include <vtkSmartPointer.h>
#include <vtkTextActor.h>
#include <vtkTextProperty.h>
#include <vtkVertexGlyphFilter.h>
namespace
{
QString previewText(const char* sourceText)
{
return QCoreApplication::translate("nmWxPropertyInterpolationPreviewDlg", sourceText);
}
vtkSmartPointer<vtkLookupTable> createPreviewLookupTable(double minimum,
double maximum)
{
vtkSmartPointer<vtkLookupTable> lookupTable =
vtkSmartPointer<vtkLookupTable>::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<vtkTextActor> messageActor =
vtkSmartPointer<vtkTextActor>::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<double>& interpolationValues,
const QVector<QPointF>& measurementPoints,
const QVector<double>& measurementValues,
const QVector<QPointF>& 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<double>& interpolationValues,
const QVector<QPointF>& measurementPoints,
const QVector<double>& measurementValues,
const QVector<QPointF>& outlinePoints,
bool showMeasurementPoints,
bool showMeasurementLabels)
{
if(m_pVtkWidget == NULL) {
return;
}
// 显式创建渲染窗口避免使用尚未初始化的QVTKWidget默认窗口.
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->SetBackground(0.88, 0.88, 0.88);
vtkSmartPointer<vtkRenderWindow> renderWindow =
vtkSmartPointer<vtkRenderWindow>::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<vtkDoubleArray> scalarArray =
vtkSmartPointer<vtkDoubleArray>::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<vtkImageData> imageData =
vtkSmartPointer<vtkImageData>::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<vtkImageDataGeometryFilter> geometryFilter =
vtkSmartPointer<vtkImageDataGeometryFilter>::New();
geometryFilter->SetInputData(imageData);
geometryFilter->Update();
// 默认使用完整规则网格;仅在裁剪结果有效且保留标量时切换.
vtkPolyData* visibleSurface = geometryFilter->GetOutput();
vtkSmartPointer<vtkExtractPolyDataGeometry> extractFilter;
vtkSmartPointer<vtkImplicitSelectionLoop> selectionLoop;
vtkSmartPointer<vtkPoints> selectionPoints;
if(outlinePoints.size() >= 3) {
selectionPoints = vtkSmartPointer<vtkPoints>::New();
for(int i = 0; i < outlinePoints.size(); ++i) {
selectionPoints->InsertNextPoint(outlinePoints[i].x(),
outlinePoints[i].y(), 0.0);
}
selectionLoop = vtkSmartPointer<vtkImplicitSelectionLoop>::New();
selectionLoop->SetLoop(selectionPoints);
selectionLoop->AutomaticNormalGenerationOff();
selectionLoop->SetNormal(0.0, 0.0, 1.0);
extractFilter = vtkSmartPointer<vtkExtractPolyDataGeometry>::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<vtkLookupTable> lookupTable =
createPreviewLookupTable(displayMinimum, displayMaximum);
// 将连续点标量划分为10级色带Mapper按CellData进行分级着色.
const int colorBandCount = 10;
vtkSmartPointer<vtkBandedPolyDataContourFilter> bandFilter =
vtkSmartPointer<vtkBandedPolyDataContourFilter>::New();
bandFilter->SetInputData(visibleSurface);
bandFilter->GenerateValues(colorBandCount + 1,
displayMinimum, displayMaximum);
bandFilter->SetScalarModeToValue();
bandFilter->GenerateContourEdgesOff();
vtkSmartPointer<vtkPolyDataMapper> surfaceMapper =
vtkSmartPointer<vtkPolyDataMapper>::New();
surfaceMapper->SetInputConnection(bandFilter->GetOutputPort());
surfaceMapper->SetScalarModeToUseCellData();
surfaceMapper->SetLookupTable(lookupTable);
surfaceMapper->SetScalarRange(displayMinimum, displayMaximum);
surfaceMapper->ScalarVisibilityOn();
vtkSmartPointer<vtkActor> surfaceActor = vtkSmartPointer<vtkActor>::New();
surfaceActor->SetMapper(surfaceMapper);
surfaceActor->GetProperty()->EdgeVisibilityOff();
renderer->AddActor(surfaceActor);
// 叠加储层边界并通过轻微Z偏移避免与色带表面重合.
if(outlinePoints.size() >= 3) {
vtkSmartPointer<vtkPoints> outlineVtkPoints =
vtkSmartPointer<vtkPoints>::New();
for(int i = 0; i < outlinePoints.size(); ++i) {
outlineVtkPoints->InsertNextPoint(outlinePoints[i].x(),
outlinePoints[i].y(), 0.01);
}
vtkSmartPointer<vtkCellArray> outlineLines =
vtkSmartPointer<vtkCellArray>::New();
outlineLines->InsertNextCell(outlinePoints.size() + 1);
for(int i = 0; i < outlinePoints.size(); ++i) {
outlineLines->InsertCellPoint(i);
}
outlineLines->InsertCellPoint(0);
vtkSmartPointer<vtkPolyData> outlineData =
vtkSmartPointer<vtkPolyData>::New();
outlineData->SetPoints(outlineVtkPoints);
outlineData->SetLines(outlineLines);
vtkSmartPointer<vtkPolyDataMapper> outlineMapper =
vtkSmartPointer<vtkPolyDataMapper>::New();
outlineMapper->SetInputData(outlineData);
outlineMapper->ScalarVisibilityOff();
vtkSmartPointer<vtkActor> outlineActor = vtkSmartPointer<vtkActor>::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<vtkPoints> measurementVtkPoints =
vtkSmartPointer<vtkPoints>::New();
vtkSmartPointer<vtkDoubleArray> measurementScalars =
vtkSmartPointer<vtkDoubleArray>::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<vtkPolyData> measurementData =
vtkSmartPointer<vtkPolyData>::New();
measurementData->SetPoints(measurementVtkPoints);
measurementData->GetPointData()->SetScalars(measurementScalars);
if(showMeasurementPoints) {
vtkSmartPointer<vtkVertexGlyphFilter> glyphFilter =
vtkSmartPointer<vtkVertexGlyphFilter>::New();
glyphFilter->SetInputData(measurementData);
vtkSmartPointer<vtkPolyDataMapper> measurementMapper =
vtkSmartPointer<vtkPolyDataMapper>::New();
measurementMapper->SetInputConnection(glyphFilter->GetOutputPort());
measurementMapper->SetLookupTable(lookupTable);
measurementMapper->SetScalarRange(displayMinimum, displayMaximum);
measurementMapper->SetScalarModeToUsePointData();
vtkSmartPointer<vtkActor> measurementActor =
vtkSmartPointer<vtkActor>::New();
measurementActor->SetMapper(measurementMapper);
measurementActor->GetProperty()->SetPointSize(8.0);
renderer->AddActor(measurementActor);
}
if(showMeasurementLabels) {
vtkSmartPointer<vtkLabeledDataMapper> labelMapper =
vtkSmartPointer<vtkLabeledDataMapper>::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<vtkActor2D> labelActor =
vtkSmartPointer<vtkActor2D>::New();
labelActor->SetMapper(labelMapper);
labelActor->SetPosition(0.0, 10.0);
renderer->AddActor2D(labelActor);
}
}
vtkSmartPointer<vtkScalarBarActor> scalarBar =
vtkSmartPointer<vtkScalarBarActor>::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<vtkInteractorStyleImage> interactorStyle =
vtkSmartPointer<vtkInteractorStyleImage>::New();
renderWindow->GetInteractor()->SetInteractorStyle(interactorStyle);
renderWindow->Render();
}
Loading…
Cancel
Save