|
|
|
|
|
#include "nmWxPostprocessingAnimationWidget.h"
|
|
|
|
|
|
#include <QDebug> // 用于调试输出
|
|
|
|
|
|
#include <algorithm> // 用于 std::sort 排序算法
|
|
|
|
|
|
|
|
|
|
|
|
// UI 相关的 Qt 头文件
|
|
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
|
#include <QHBoxLayout>
|
|
|
|
|
|
#include <QFrame>
|
|
|
|
|
|
#include <QPushButton>
|
|
|
|
|
|
#include <QLabel>
|
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
|
#include <QComboBox>
|
|
|
|
|
|
#include <QSlider>
|
|
|
|
|
|
#include <QList>
|
|
|
|
|
|
#include <QEvent>
|
|
|
|
|
|
#include <QMouseEvent>
|
|
|
|
|
|
#include <QPrinter>
|
|
|
|
|
|
#include <QMenu>
|
|
|
|
|
|
#include <QAction>
|
|
|
|
|
|
#include <QPixmap>
|
|
|
|
|
|
#include <QFileDialog>
|
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
|
#include <QClipboard>
|
|
|
|
|
|
#include <QPrinter>
|
|
|
|
|
|
#include <QPrintDialog>
|
|
|
|
|
|
#include <QPrintPreviewDialog>
|
|
|
|
|
|
#include <QCoreApplication>
|
|
|
|
|
|
|
|
|
|
|
|
// VTK 相关的头文件
|
|
|
|
|
|
#include "QVTKWidget.h" // QVTKWidget 的实际头文件路径
|
|
|
|
|
|
#include <vtkSmartPointer.h>
|
|
|
|
|
|
#include <vtkRenderWindow.h>
|
|
|
|
|
|
#include <vtkRenderer.h>
|
|
|
|
|
|
#include <vtkDataSetReader.h> // 如果有文件读取操作,需要此头文件
|
|
|
|
|
|
#include <vtkUnstructuredGrid.h> // 非结构化网格数据类型
|
|
|
|
|
|
#include <vtkDataSetMapper.h>
|
|
|
|
|
|
#include <vtkPointData.h> // 点数据
|
|
|
|
|
|
#include <vtkActor.h>
|
|
|
|
|
|
#include <vtkProperty.h> // 演员属性,如颜色、线宽等
|
|
|
|
|
|
#include <vtkScalarBarActor.h>
|
|
|
|
|
|
#include <vtkLookupTable.h>
|
|
|
|
|
|
#include <vtkTextActor.h>
|
|
|
|
|
|
#include <vtkTextProperty.h> // 文本演员属性,如字体、大小、颜色
|
|
|
|
|
|
#include <vtkDoubleArray.h> // 双精度浮点数数组
|
|
|
|
|
|
#include <vtkCellData.h> // 单元格数据
|
|
|
|
|
|
#include <vtkWindowToImageFilter.h>
|
|
|
|
|
|
#include <vtkImageData.h>
|
|
|
|
|
|
#include <vtkImageFlip.h>
|
|
|
|
|
|
#include <vtkPNGWriter.h>
|
|
|
|
|
|
#include <vtkUnsignedCharArray.h>
|
|
|
|
|
|
|
|
|
|
|
|
// === 新增的 3D 绘制相关的 VTK 头文件 ===
|
|
|
|
|
|
#include <vtkBillboardTextActor3D.h> // 广告牌文本演员 (始终面向相机,适用于标签)
|
|
|
|
|
|
#include <vtkPolyData.h> // 多边形数据 (用于线、面等几何体)
|
|
|
|
|
|
#include <vtkPoints.h> // 点集合
|
|
|
|
|
|
#include <vtkCellArray.h> // 单元格数组 (用于定义线的拓扑结构)
|
|
|
|
|
|
#include <vtkCamera.h> // 相机对象,用于获取相机位置等信息
|
|
|
|
|
|
#include <vtkCoordinate.h> // 坐标系统转换,如果使用 vtkTextActor 且要精确放置
|
|
|
|
|
|
#include <vtkPolyDataMapper.h>
|
|
|
|
|
|
|
|
|
|
|
|
#include <vtkFFMPEGWriter.h>
|
|
|
|
|
|
#include <vtkThreshold.h>
|
|
|
|
|
|
|
|
|
|
|
|
#include <vtkContourWidget.h>
|
|
|
|
|
|
#include <vtkObject.h>
|
|
|
|
|
|
#include <vtkPolygonalSurfacePointPlacer.h>
|
|
|
|
|
|
#include <vtkOrientedGlyphContourRepresentation.h>
|
|
|
|
|
|
#include <vtkPolyDataCollection.h>
|
|
|
|
|
|
#include <vtkGeometryFilter.h>
|
|
|
|
|
|
#include <vtkContourRepresentation.h>
|
|
|
|
|
|
#include <vtkRendererCollection.h>
|
|
|
|
|
|
#include <vtkInteractorStyleTrackballCamera.h>
|
|
|
|
|
|
#include <vtkCellPicker.h>
|
|
|
|
|
|
#include <vtkPolyDataNormals.h>
|
|
|
|
|
|
#include <vtkCollectionIterator.h>
|
|
|
|
|
|
#include <vtkPolygonalSurfaceContourLineInterpolator.h>
|
|
|
|
|
|
#include <vtkExtractUnstructuredGrid.h>
|
|
|
|
|
|
#include <vtkImplicitSelectionLoop.h>
|
|
|
|
|
|
#include <vtkExtractGeometry.h>
|
|
|
|
|
|
#include <cmath>
|
|
|
|
|
|
|
|
|
|
|
|
// 业务逻辑相关的头文件
|
|
|
|
|
|
#include "nmDataAnalyzeManager.h" // 数据管理器
|
|
|
|
|
|
|
|
|
|
|
|
// 数据过滤对话框
|
|
|
|
|
|
#include "nmWxPostprocessingAnimationDataFiltering.h"
|
|
|
|
|
|
|
|
|
|
|
|
// 定义一个统一的 Z 坐标高度,用于绘制井和井名。
|
|
|
|
|
|
// 这个值非常重要,需要根据您的三维场景的实际尺寸和范围进行调整。
|
|
|
|
|
|
// 它应该足够高,以便井的绘制内容清晰可见,并且不会被地层模型遮挡。
|
|
|
|
|
|
const double WELL_DRAWING_Z_HEIGHT = 200.0; // 示例值,请根据您的模型范围进行调整
|
|
|
|
|
|
|
|
|
|
|
|
//=======================================================
|
|
|
|
|
|
//nmCustomInteractorStyle 的实现
|
|
|
|
|
|
//=======================================================
|
|
|
|
|
|
vtkStandardNewMacro(nmCustomInteractorStyle);
|
|
|
|
|
|
|
|
|
|
|
|
void nmCustomInteractorStyle::SetParent(nmWxPostprocessingAnimationWidget* parent) {
|
|
|
|
|
|
m_parent = parent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmCustomInteractorStyle::OnLeftButtonDown() {
|
|
|
|
|
|
if (!m_parent) {
|
|
|
|
|
|
vtkInteractorStyleTrackballCamera::OnLeftButtonDown();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (m_parent->isDrawingMode()) {
|
|
|
|
|
|
int* clickPos = this->GetInteractor()->GetEventPosition();
|
|
|
|
|
|
auto contourWidget = m_parent->getContourWidget();
|
|
|
|
|
|
// 使用 vtkContourRepresentation 类型保持一致
|
|
|
|
|
|
auto rep = vtkContourRepresentation::SafeDownCast(contourWidget->GetRepresentation());
|
|
|
|
|
|
|
|
|
|
|
|
if (rep) {
|
|
|
|
|
|
vtkSmartPointer<vtkCellPicker> picker = vtkSmartPointer<vtkCellPicker>::New();
|
|
|
|
|
|
picker->SetTolerance(0.01);
|
|
|
|
|
|
picker->Pick(clickPos[0], clickPos[1], 0, m_parent->getRenderer());
|
|
|
|
|
|
if (picker->GetCellId() != -1) {
|
|
|
|
|
|
double worldPos[3];
|
|
|
|
|
|
picker->GetPickPosition(worldPos);
|
|
|
|
|
|
m_parent->addContourPoint(worldPos);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
vtkInteractorStyleTrackballCamera::OnLeftButtonDown();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nmContourResultDialog::nmContourResultDialog(QWidget *parent)
|
|
|
|
|
|
: iDlgBase(parent)
|
|
|
|
|
|
{
|
|
|
|
|
|
setWindowTitle(tr("Contour Pressure"));
|
|
|
|
|
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
|
|
|
|
|
|
|
|
|
|
|
m_resultLabel = new QLabel(this);
|
|
|
|
|
|
m_resultLabel->setWordWrap(true);
|
|
|
|
|
|
|
|
|
|
|
|
m_clearButton = new QPushButton(tr("OK"), this);
|
|
|
|
|
|
connect(m_clearButton, SIGNAL(clicked()), this, SIGNAL(clearContourRequested()));
|
|
|
|
|
|
|
|
|
|
|
|
QVBoxLayout* layout = new QVBoxLayout(this);
|
|
|
|
|
|
layout->addWidget(m_resultLabel);
|
|
|
|
|
|
layout->addWidget(m_clearButton);
|
|
|
|
|
|
|
|
|
|
|
|
setLayout(layout);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmContourResultDialog::showResult(const QString& message)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_resultLabel->setText(message);
|
|
|
|
|
|
this->show(); // 使用 show() 来显示非模态对话框
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nmWxPostprocessingAnimationWidget::nmWxPostprocessingAnimationWidget(QWidget* parent) :
|
|
|
|
|
|
QWidget(parent) // 调用 QWidget 基类的构造函数
|
|
|
|
|
|
{
|
|
|
|
|
|
// 初始化成员变量
|
|
|
|
|
|
m_nCurrentIndex = 0;
|
|
|
|
|
|
m_bIsPlaying = false;
|
|
|
|
|
|
m_nShowMode = 0;
|
|
|
|
|
|
m_pSlider = nullptr; // 滑块指针初始化为空
|
|
|
|
|
|
m_pProgress = nullptr; // 进度文本标签指针初始化为空
|
|
|
|
|
|
|
|
|
|
|
|
// **** VTK SmartPointer 成员变量的初始化 ****
|
|
|
|
|
|
m_renderer = nullptr;
|
|
|
|
|
|
m_mapper = nullptr;
|
|
|
|
|
|
m_actor = nullptr;
|
|
|
|
|
|
m_scalarBar = nullptr;
|
|
|
|
|
|
m_lookupTable = nullptr;
|
|
|
|
|
|
m_textActor = nullptr;
|
|
|
|
|
|
m_pCachedBaseGrid = nullptr;
|
|
|
|
|
|
m_thresholdFilter = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化过滤器设置内容
|
|
|
|
|
|
m_bFilteringEnabled = false;
|
|
|
|
|
|
m_bAboveMinEnabled = false;
|
|
|
|
|
|
m_dMinValue = 0.0;
|
|
|
|
|
|
m_bBelowMaxEnabled = false;
|
|
|
|
|
|
m_dMaxValue = 0.0;
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化区域平均压力计算内容
|
|
|
|
|
|
m_bIsDrawingMode = false;
|
|
|
|
|
|
|
|
|
|
|
|
m_contourWidget = nullptr;
|
|
|
|
|
|
m_contourRepresentation = nullptr;
|
|
|
|
|
|
m_pointPlacer = nullptr;
|
|
|
|
|
|
m_polyDataForContour = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
m_trackballStyle = nullptr;
|
|
|
|
|
|
m_drawStyle = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
m_interactor = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
m_resultDialog = new nmContourResultDialog();
|
|
|
|
|
|
// 连接对话框的信号到槽函数
|
|
|
|
|
|
connect(m_resultDialog, SIGNAL(clearContourRequested()),
|
|
|
|
|
|
this, SLOT(slotClearContour()));
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化时间步列表 (在 initUI 之前,因为它会影响 UI 控件的最大值)
|
|
|
|
|
|
this->initTimeSteps();
|
|
|
|
|
|
// 初始化用户界面
|
|
|
|
|
|
this->initUI();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 析构函数:用于在对象销毁时进行清理
|
|
|
|
|
|
nmWxPostprocessingAnimationWidget::~nmWxPostprocessingAnimationWidget()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Qt 的父子对象机制会自动删除 QVTKWidget 和其他 Qt 控件(如 m_pSlider, m_pProgress, m_pPlayTimer)。
|
|
|
|
|
|
// vtkSmartPointer 会自动管理其内部 VTK 对象的内存。
|
|
|
|
|
|
// 但为了确保 VTK 渲染器从 RenderWindow 中被正确移除(避免潜在的 VTK 内部引用问题),
|
|
|
|
|
|
// 可以在这里显式执行移除操作。
|
|
|
|
|
|
if(m_pVtkWidget && m_pVtkWidget->GetRenderWindow() && m_renderer) {
|
|
|
|
|
|
m_pVtkWidget->GetRenderWindow()->RemoveRenderer(m_renderer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显式清除所有井相关的 Actor。
|
|
|
|
|
|
clearWellActors();
|
|
|
|
|
|
|
|
|
|
|
|
// 其他 vtkSmartPointer 成员 (m_mapper, m_actor, etc.) 会在 nmWxPostprocessingAnimationWidget 销毁时自动释放其 VTK 对象。
|
|
|
|
|
|
|
|
|
|
|
|
// 在销毁前禁用轮廓小部件
|
|
|
|
|
|
if (m_contourWidget) {
|
|
|
|
|
|
m_contourWidget->EnabledOff();
|
|
|
|
|
|
m_contourWidget = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (m_resultDialog != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
delete m_resultDialog;
|
|
|
|
|
|
m_resultDialog = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QImage nmWxPostprocessingAnimationWidget::createQImage1(int nWidth, int nHeight, vtkUnsignedCharArray* pScalars)
|
|
|
|
|
|
{
|
|
|
|
|
|
QImage qImage(nWidth, nHeight, QImage::Format_ARGB32);
|
|
|
|
|
|
vtkIdType tupleIndex = 0;
|
|
|
|
|
|
int nImageBitIndex = 0;
|
|
|
|
|
|
QRgb* pImageBits = (QRgb*)qImage.bits();
|
|
|
|
|
|
unsigned char* scalarTuples = pScalars->GetPointer(0);
|
|
|
|
|
|
|
|
|
|
|
|
for(int j = 0; j < nHeight; j++) {
|
|
|
|
|
|
for(int i = 0; i < nWidth; i++) {
|
|
|
|
|
|
unsigned char* tuple = scalarTuples + (tupleIndex++);
|
|
|
|
|
|
QRgb color = qRgba(tuple[0], tuple[0], tuple[0], 255);
|
|
|
|
|
|
*(pImageBits + (nImageBitIndex++)) = color;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return qImage;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QImage nmWxPostprocessingAnimationWidget::createQImage2(int nWidth, int nHeight, vtkUnsignedCharArray* pScalars)
|
|
|
|
|
|
{
|
|
|
|
|
|
QImage qImage(nWidth, nHeight, QImage::Format_ARGB32);
|
|
|
|
|
|
vtkIdType tupleIndex = 0;
|
|
|
|
|
|
int nImageBitIndex = 0;
|
|
|
|
|
|
QRgb* pImageBits = (QRgb*)qImage.bits();
|
|
|
|
|
|
unsigned char* scalarTuples = pScalars->GetPointer(0);
|
|
|
|
|
|
|
|
|
|
|
|
for(int j = 0; j < nHeight; j++) {
|
|
|
|
|
|
for(int i = 0; i < nWidth; i++) {
|
|
|
|
|
|
unsigned char* tuple = scalarTuples + (tupleIndex++ * 2);
|
|
|
|
|
|
QRgb color = qRgba(tuple[0], tuple[0], tuple[0], tuple[1]);
|
|
|
|
|
|
*(pImageBits + (nImageBitIndex++)) = color;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return qImage;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QImage nmWxPostprocessingAnimationWidget::createQImage3(int nWidth, int nHeight, vtkUnsignedCharArray* pScalars)
|
|
|
|
|
|
{
|
|
|
|
|
|
QImage qImage(nWidth, nHeight, QImage::Format_ARGB32);
|
|
|
|
|
|
vtkIdType tupleIndex = 0;
|
|
|
|
|
|
int nImageBitIndex = 0;
|
|
|
|
|
|
QRgb* pImageBits = (QRgb*)qImage.bits();
|
|
|
|
|
|
unsigned char* scalarTuples = pScalars->GetPointer(0);
|
|
|
|
|
|
|
|
|
|
|
|
for(int j = 0; j < nHeight; j++) {
|
|
|
|
|
|
for(int i = 0; i < nWidth; i++) {
|
|
|
|
|
|
unsigned char* tuple = scalarTuples + (tupleIndex++ * 3);
|
|
|
|
|
|
QRgb color = qRgba(tuple[0], tuple[1], tuple[2], 255);
|
|
|
|
|
|
*(pImageBits + (nImageBitIndex++)) = color;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return qImage;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QImage nmWxPostprocessingAnimationWidget::createQImage4(int nWidth, int nHeight, vtkUnsignedCharArray* pScalars)
|
|
|
|
|
|
{
|
|
|
|
|
|
QImage qImage(nWidth, nHeight, QImage::Format_ARGB32);
|
|
|
|
|
|
vtkIdType tupleIndex = 0;
|
|
|
|
|
|
int nImageBitIndex = 0;
|
|
|
|
|
|
QRgb* pImageBits = (QRgb*)qImage.bits();
|
|
|
|
|
|
unsigned char* scalarTuples = pScalars->GetPointer(0);
|
|
|
|
|
|
|
|
|
|
|
|
for(int j = 0; j < nHeight; j++) {
|
|
|
|
|
|
for(int i = 0; i < nWidth; i++) {
|
|
|
|
|
|
unsigned char* tuple = scalarTuples + (tupleIndex++ * 4);
|
|
|
|
|
|
QRgb color = qRgba(tuple[0], tuple[1], tuple[2], tuple[3]);
|
|
|
|
|
|
*(pImageBits + (nImageBitIndex++)) = color;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return qImage;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QImage nmWxPostprocessingAnimationWidget::createQImage(vtkImageData* pImageData)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!pImageData) {
|
|
|
|
|
|
return QImage();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int nWidth = pImageData->GetDimensions()[0];
|
|
|
|
|
|
int nHeight = pImageData->GetDimensions()[1];
|
|
|
|
|
|
vtkUnsignedCharArray* pScalars = vtkUnsignedCharArray::SafeDownCast(pImageData->GetPointData()->GetScalars());
|
|
|
|
|
|
|
|
|
|
|
|
if(!nWidth || !nHeight || !pScalars) {
|
|
|
|
|
|
return QImage();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch(pScalars->GetNumberOfComponents()) {
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
return createQImage1(nWidth, nHeight, pScalars);
|
|
|
|
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
return createQImage2(nWidth, nHeight, pScalars);
|
|
|
|
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
|
|
return createQImage3(nWidth, nHeight, pScalars);
|
|
|
|
|
|
|
|
|
|
|
|
case 4:
|
|
|
|
|
|
return createQImage4(nWidth, nHeight, pScalars);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return QImage();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool nmWxPostprocessingAnimationWidget::eventFilter(QObject *obj, QEvent *event)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查事件是否来自我们的 VTK 部件 (m_pVtkWidget),并且是鼠标按下事件
|
|
|
|
|
|
if(obj == m_pVtkWidget && event->type() == QEvent::MouseButtonPress) {
|
|
|
|
|
|
QMouseEvent *pMouseEVent = static_cast<QMouseEvent*>(event);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是鼠标右键点击
|
|
|
|
|
|
if(pMouseEVent->button() == Qt::RightButton) {
|
|
|
|
|
|
// 在右键点击 m_pVtkWidget 时,直接显示我们的自定义菜单
|
|
|
|
|
|
QMenu menu(this); // 菜单的父对象仍然是 nmWxPostprocessingAnimationWidget
|
|
|
|
|
|
|
|
|
|
|
|
QString appDir = QCoreApplication::applicationDirPath();
|
|
|
|
|
|
appDir = appDir.section('/', 0, -2); // 获取上一级目录(通常是应用程序的根目录)
|
|
|
|
|
|
|
|
|
|
|
|
// 设置菜单图标
|
|
|
|
|
|
QIcon saveIcon(appDir + "/Res/Icon/SaveImg.png");
|
|
|
|
|
|
QIcon copyIcon(appDir + "/Res/Icon/Copy.png");
|
|
|
|
|
|
QIcon printIcon(appDir + "/Res/Icon/Print.png");
|
|
|
|
|
|
QIcon printPreviewIcon(appDir + "/Res/Icon/PrePrint.png");
|
|
|
|
|
|
QIcon videoIcon(appDir + "/Res/Icon/SaveImg.png");
|
|
|
|
|
|
|
|
|
|
|
|
// 添加“保存为图片”动作,并为其添加图标
|
|
|
|
|
|
QAction *pSaveAction = new QAction(saveIcon, tr("Save as Image"), this);
|
|
|
|
|
|
// 连接动作的 triggered 信号到本类的 saveWidgetAsImage 槽函数
|
|
|
|
|
|
connect(pSaveAction, SIGNAL(triggered()), this, SLOT(saveWidgetAsImage()));
|
|
|
|
|
|
menu.addAction(pSaveAction);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加“复制图片”动作,并为其添加图标
|
|
|
|
|
|
QAction *pCopyAction = new QAction(copyIcon, tr("Copy Image"), this);
|
|
|
|
|
|
// 连接动作的 triggered 信号到本类的 copyWidgetImage 槽函数
|
|
|
|
|
|
connect(pCopyAction, SIGNAL(triggered()), this, SLOT(copyWidgetImage()));
|
|
|
|
|
|
menu.addAction(pCopyAction);
|
|
|
|
|
|
|
|
|
|
|
|
menu.addSeparator(); // 添加分隔线
|
|
|
|
|
|
|
|
|
|
|
|
QAction *pExportVideoAction = new QAction(videoIcon, tr("Export as Video"), this);
|
|
|
|
|
|
connect(pExportVideoAction, SIGNAL(triggered()), this, SLOT(exportAnimationAsVideo()));
|
|
|
|
|
|
menu.addAction(pExportVideoAction);
|
|
|
|
|
|
|
|
|
|
|
|
menu.addSeparator();
|
|
|
|
|
|
|
|
|
|
|
|
// 添加“打印”动作,并为其添加图标
|
|
|
|
|
|
QAction *pPrintAction = new QAction(printIcon, tr("Print"), this);
|
|
|
|
|
|
// 连接动作的 triggered 信号到本类的 printWidget 槽函数
|
|
|
|
|
|
connect(pPrintAction, SIGNAL(triggered()), this, SLOT(printWidget()));
|
|
|
|
|
|
menu.addAction(pPrintAction);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加“打印预览”动作,并为其添加图标
|
|
|
|
|
|
QAction *pPrintPreviewAction = new QAction(printPreviewIcon, tr("Print Preview"), this);
|
|
|
|
|
|
// 连接动作的 triggered 信号到本类的 printPreviewWidget 槽函数
|
|
|
|
|
|
connect(pPrintPreviewAction, SIGNAL(triggered()), this, SLOT(printPreviewWidget()));
|
|
|
|
|
|
menu.addAction(pPrintPreviewAction);
|
|
|
|
|
|
|
|
|
|
|
|
// 在鼠标的全局位置执行菜单,确保菜单在鼠标点击的位置弹出
|
|
|
|
|
|
menu.exec(pMouseEVent->globalPos());
|
|
|
|
|
|
|
|
|
|
|
|
return true; // **事件已处理,停止 QVTKWidget 的进一步处理**
|
|
|
|
|
|
}
|
|
|
|
|
|
// 左键、中键等事件继续传递给VTK
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 对于其他事件或对象,将它们传递给基类的事件过滤器,确保其他事件仍能正常处理
|
|
|
|
|
|
return QWidget::eventFilter(obj, event);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::initUI()
|
|
|
|
|
|
{
|
|
|
|
|
|
this->initLayout(); // 设置主布局和框架布局
|
|
|
|
|
|
this->initVTKWidget(); // 初始化 VTK 渲染管道和 QVTKWidget
|
|
|
|
|
|
this->initOperPannel(); // 初始化操作面板的按钮和滑块
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::initLayout()
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pMainLayout = new QVBoxLayout; // 创建主垂直布局
|
|
|
|
|
|
this->setLayout(m_pMainLayout); // 将此布局设置给当前 Widget
|
|
|
|
|
|
|
|
|
|
|
|
QFrame* frame = new QFrame; // 创建一个 QFrame 用于包含 QVTKWidget
|
|
|
|
|
|
m_pFrameLaoyt = new QVBoxLayout; // 创建框架的垂直布局
|
|
|
|
|
|
m_pFrameLaoyt->setContentsMargins(0, 0, 0, 0); // 设置布局边距为0
|
|
|
|
|
|
frame->setLayout(m_pFrameLaoyt); // 将布局设置给框架
|
|
|
|
|
|
m_pMainLayout->addWidget(frame); // 将框架添加到主布局
|
|
|
|
|
|
m_pMainLayout->setContentsMargins(0, 0, 0, 0); // 设置主布局边距为0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::initVTKWidget()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 创建基础VTK组件
|
|
|
|
|
|
m_pVtkWidget = new QVTKWidget(this); // 将 this (当前 nmWxPostprocessingAnimationWidget) 设置为父对象
|
|
|
|
|
|
m_pFrameLaoyt->addWidget(m_pVtkWidget); // 将 QVTKWidget 添加到框架布局中
|
|
|
|
|
|
|
|
|
|
|
|
// 为每个 nmWxPostprocessingAnimationWidget 实例创建独立的 VTK 渲染器
|
|
|
|
|
|
m_renderer = vtkSmartPointer<vtkRenderer>::New();
|
|
|
|
|
|
vtkRenderWindow* renderWindow = m_pVtkWidget->GetRenderWindow();
|
|
|
|
|
|
renderWindow->AddRenderer(m_renderer); // 将渲染器添加到 QVTKWidget 的渲染窗口
|
|
|
|
|
|
m_interactor = renderWindow->GetInteractor();
|
|
|
|
|
|
|
|
|
|
|
|
// 设置渲染器背景
|
|
|
|
|
|
m_renderer->SetGradientBackground(true);
|
|
|
|
|
|
m_renderer->SetBackground(0.67, 0.82, 0.94); // 浅蓝
|
|
|
|
|
|
m_renderer->SetBackground2(0.0, 0.5, 1.0); // 深蓝
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 安装事件过滤器,以便捕获 m_pVtkWidget 的鼠标事件
|
|
|
|
|
|
m_pVtkWidget->installEventFilter(this);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 创建颜色查找表 (Lookup Table)
|
|
|
|
|
|
m_lookupTable = vtkSmartPointer<vtkLookupTable>::New();
|
|
|
|
|
|
m_lookupTable->SetNumberOfColors(256); // 设置颜色数量
|
|
|
|
|
|
m_lookupTable->SetHueRange(0.67, 0); // 设置色调范围(从蓝色到红色)
|
|
|
|
|
|
m_lookupTable->Build(); // 构建颜色表
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 获取基础结果网格数据
|
|
|
|
|
|
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
|
|
|
|
|
|
|
if(pDataManager) {
|
|
|
|
|
|
//m_pCachedBaseGrid = pDataManager->getResultBaseGridCopy();
|
|
|
|
|
|
m_pCachedBaseGrid = pDataManager->getResultBaseGrid();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 创建阈值过滤器和mapper并设置输入数据
|
|
|
|
|
|
m_thresholdFilter = vtkSmartPointer<vtkThreshold>::New();
|
|
|
|
|
|
if(m_pCachedBaseGrid) {
|
|
|
|
|
|
m_thresholdFilter->SetInputData(m_pCachedBaseGrid);
|
|
|
|
|
|
}
|
|
|
|
|
|
m_thresholdFilter->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, "p");
|
|
|
|
|
|
// 上面这行是关键!它告诉过滤器对哪个数组进行操作,
|
|
|
|
|
|
// "p" 是压力数据的名称,vtkDataObject::FIELD_ASSOCIATION_CELLS 表示数据在单元格上。
|
|
|
|
|
|
|
|
|
|
|
|
// 创建mapper并设置输入数据
|
|
|
|
|
|
m_mapper = vtkSmartPointer<vtkDataSetMapper>::New();
|
|
|
|
|
|
m_mapper->ScalarVisibilityOn(); // 开启标量数据显示
|
|
|
|
|
|
m_mapper->SetLookupTable(m_lookupTable);
|
|
|
|
|
|
m_mapper->SetInputConnection(m_thresholdFilter->GetOutputPort()); // **将 mapper 的输入连接到阈值过滤器的输出**
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 创建actor并设置mapper
|
|
|
|
|
|
m_actor = vtkSmartPointer<vtkActor>::New();
|
|
|
|
|
|
m_actor->SetMapper(m_mapper); // 永久关联
|
|
|
|
|
|
vtkProperty* actorProperty = m_actor->GetProperty();
|
|
|
|
|
|
actorProperty->SetLineWidth(1.0); // 设置几何体的线宽
|
|
|
|
|
|
actorProperty->EdgeVisibilityOff(); // 默认关闭网格线的显示
|
|
|
|
|
|
actorProperty->SetRepresentationToSurface(); // 设置为表面渲染模式
|
|
|
|
|
|
m_renderer->AddActor(m_actor); // 将演员添加到渲染器中
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 创建颜色条(Scalar Bar),显示标量数据的范围和颜色映射
|
|
|
|
|
|
m_scalarBar = vtkSmartPointer<vtkScalarBarActor>::New();
|
|
|
|
|
|
m_scalarBar->SetLookupTable(m_lookupTable); // 设置颜色查找表
|
|
|
|
|
|
m_scalarBar->SetTitle("p MPa"); // 设置颜色条标题
|
|
|
|
|
|
|
|
|
|
|
|
// 8. 设置颜色条的标签文本字体属性 (保持不变或根据需要调整)
|
|
|
|
|
|
vtkSmartPointer<vtkTextProperty> labelTextProp = vtkSmartPointer<vtkTextProperty>::New();
|
|
|
|
|
|
labelTextProp->SetFontFamilyToArial();
|
|
|
|
|
|
labelTextProp->SetFontSize(12); // 标签字体大小
|
|
|
|
|
|
m_scalarBar->SetLabelTextProperty(labelTextProp); // 设置标签文本属性
|
|
|
|
|
|
|
|
|
|
|
|
// 设置颜色条的标题文本字体属性 (单独设置,使其更大)
|
|
|
|
|
|
vtkSmartPointer<vtkTextProperty> titleTextProp = vtkSmartPointer<vtkTextProperty>::New();
|
|
|
|
|
|
titleTextProp->SetFontFamilyToArial();
|
|
|
|
|
|
titleTextProp->SetFontSize(20);
|
|
|
|
|
|
//titleTextProp->SetBold(1); // 可以选择加粗
|
|
|
|
|
|
m_scalarBar->SetTitleTextProperty(titleTextProp); // 设置标题文本属性
|
|
|
|
|
|
|
|
|
|
|
|
m_scalarBar->SetPosition(0.05, 0.1); // 设置在渲染窗口中的位置 (归一化坐标)
|
|
|
|
|
|
m_scalarBar->SetWidth(0.1); // 设置宽度
|
|
|
|
|
|
m_scalarBar->SetHeight(0.8); // 设置高度
|
|
|
|
|
|
m_renderer->AddActor(m_scalarBar); // 将颜色条添加到渲染器中
|
|
|
|
|
|
|
|
|
|
|
|
// 创建文本演员,用于显示当前时间步的信息
|
|
|
|
|
|
//m_textActor = vtkSmartPointer<vtkTextActor>::New();
|
|
|
|
|
|
//m_textActor->GetPositionCoordinate()->SetCoordinateSystemToNormalizedDisplay(); // 使用归一化显示坐标
|
|
|
|
|
|
//m_textActor->SetPosition(0.05, 0.9); // 设置位置为左上角 (稍微向内偏移)
|
|
|
|
|
|
//m_textActor->GetTextProperty()->SetFontSize(20); // 设置字体大小
|
|
|
|
|
|
//m_textActor->GetTextProperty()->SetColor(1.0, 1.0, 1.0); // 设置字体颜色为白色 (RGB)
|
|
|
|
|
|
//m_textActor->GetTextProperty()->SetFontFamilyToArial(); // 设置字体
|
|
|
|
|
|
//m_textActor->GetTextProperty()->SetItalic(1); // 设置斜体
|
|
|
|
|
|
//m_textActor->GetTextProperty()->SetBold(1); // 设置加粗
|
|
|
|
|
|
//m_renderer->AddActor(m_textActor); // 将文本演员添加到渲染器中
|
|
|
|
|
|
|
|
|
|
|
|
// 9. 初始化井的绘制(井名和井线),这部分会创建 3D Actor
|
|
|
|
|
|
this->initWellDrawing();
|
|
|
|
|
|
|
|
|
|
|
|
//10. 初始渲染
|
|
|
|
|
|
// 设置压力标量范围
|
|
|
|
|
|
double scalarRange[2];
|
|
|
|
|
|
pDataManager->getScalarRangeP(scalarRange);
|
|
|
|
|
|
|
|
|
|
|
|
m_mapper->SetScalarRange(scalarRange);
|
|
|
|
|
|
m_lookupTable->SetRange(scalarRange);
|
|
|
|
|
|
m_lookupTable->Build();
|
|
|
|
|
|
|
|
|
|
|
|
// 在初始渲染前,为阈值过滤器设置一个默认的范围
|
|
|
|
|
|
// 默认设置为与颜色条相同的范围,这样会显示所有网格单元
|
|
|
|
|
|
m_thresholdFilter->ThresholdBetween(scalarRange[0], scalarRange[1]);
|
|
|
|
|
|
m_thresholdFilter->Modified(); // 通知过滤器其参数已修改
|
|
|
|
|
|
|
|
|
|
|
|
// 修改过滤设置
|
|
|
|
|
|
m_bFilteringEnabled = true;
|
|
|
|
|
|
m_bAboveMinEnabled = true;
|
|
|
|
|
|
m_dMinValue = scalarRange[0];
|
|
|
|
|
|
m_bBelowMaxEnabled = true;
|
|
|
|
|
|
m_dMaxValue = scalarRange[1];
|
|
|
|
|
|
|
|
|
|
|
|
// // 创建自定义交互样式
|
|
|
|
|
|
// m_trackballStyle = vtkSmartPointer<nmCustomInteractorStyle>::New();
|
|
|
|
|
|
// m_trackballStyle->SetParent(this);
|
|
|
|
|
|
|
|
|
|
|
|
//m_trackballStyle = vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
|
|
|
|
|
|
m_drawStyle = vtkSmartPointer<nmCustomInteractorStyle>::New();
|
|
|
|
|
|
m_drawStyle->SetParent(this);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置当前交互模式
|
|
|
|
|
|
m_pVtkWidget->GetRenderWindow()->GetInteractor()->SetInteractorStyle(m_drawStyle);
|
|
|
|
|
|
|
|
|
|
|
|
// 加载初始文件 (现在是从内存加载第一个时间步的数据)
|
|
|
|
|
|
if(!m_vecTimeStepKeys.isEmpty()) {
|
|
|
|
|
|
loadDataForIndex(m_nCurrentIndex); // 加载第一个时间步的数据
|
|
|
|
|
|
} else {
|
|
|
|
|
|
qDebug() << "No time step data available to load initially."; // 调试输出:没有时间步数据
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置相机
|
|
|
|
|
|
m_renderer->ResetCamera();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 新增函数:初始化井的绘制(井名和井线)
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::initWellDrawing()
|
|
|
|
|
|
{
|
|
|
|
|
|
clearWellActors(); // 确保每次初始化时清空旧的 Actor,避免重复添加
|
|
|
|
|
|
|
|
|
|
|
|
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
|
|
|
|
|
|
|
if(!pDataManager) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有井的二维位置信息 (QMap<井名, QPointF(X,Y)>)
|
|
|
|
|
|
QMap<QString, QPointF> mapWellLocations = pDataManager->getAllWellLocations();
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历所有井,创建并配置它们的 3D 文本和线 Actor
|
|
|
|
|
|
for(auto it = mapWellLocations.constBegin(); it != mapWellLocations.constEnd(); ++it) {
|
|
|
|
|
|
const QString& sWellName = it.key();
|
|
|
|
|
|
const QPointF& ptLocation = it.value(); // 井的二维位置 (X, Y)
|
|
|
|
|
|
|
|
|
|
|
|
// === 1. 创建井名称的 3D 文本 Actor ===
|
|
|
|
|
|
// 使用 vtkBillboardTextActor3D,它始终面向相机,文字可读性最好,且是 3D 对象。
|
|
|
|
|
|
vtkSmartPointer<vtkBillboardTextActor3D> textActor = vtkSmartPointer<vtkBillboardTextActor3D>::New();
|
|
|
|
|
|
textActor->SetInput(sWellName.toStdString().c_str()); // 设置文本内容为井名称 (转换为 C 字符串)
|
|
|
|
|
|
// 设置 3D 位置:使用井的二维坐标 (X, Y) 和预设的统一 Z 高度
|
|
|
|
|
|
textActor->SetPosition(ptLocation.x(), ptLocation.y(), WELL_DRAWING_Z_HEIGHT);
|
|
|
|
|
|
vtkTextProperty* textProperty = textActor->GetTextProperty();
|
|
|
|
|
|
textProperty->SetFontSize(15); // 调整字体大小,使其在 3D 视图中清晰可见
|
|
|
|
|
|
textProperty->SetColor(0.898, 0.898, 0.0);
|
|
|
|
|
|
textProperty->SetBold(true); // 文本加粗
|
|
|
|
|
|
//textActor->GetTextProperty()->SetJustificationToCentered(); // 文本中心对齐到其位置点
|
|
|
|
|
|
|
|
|
|
|
|
m_renderer->AddActor(textActor); // 对于所有 3D Actor,都使用 m_renderer->AddActor()
|
|
|
|
|
|
m_mapWellNameActors.insert(sWellName, textActor); // 将 Actor 存储在 QMap 中,以便后续管理和清除
|
|
|
|
|
|
|
|
|
|
|
|
// === 2. 绘制井的线 (为每口井绘制,基于 wellLocations 的 X, Y,并延伸到 WELL_DRAWING_Z_HEIGHT) ===
|
|
|
|
|
|
// 创建点集合
|
|
|
|
|
|
vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
|
|
|
|
|
|
// 井口位置:使用 location.x() + 20.0, location.y() 和 WELL_DRAWING_Z_HEIGHT
|
|
|
|
|
|
points->InsertNextPoint(ptLocation.x() + 20.0, ptLocation.y(), WELL_DRAWING_Z_HEIGHT);
|
|
|
|
|
|
// 井底位置
|
|
|
|
|
|
points->InsertNextPoint(ptLocation.x(), ptLocation.y(), 0); // 向下延伸 WELL_DRAWING_Z_HEIGHT 个单位
|
|
|
|
|
|
|
|
|
|
|
|
// 创建线段拓扑
|
|
|
|
|
|
vtkSmartPointer<vtkCellArray> lines = vtkSmartPointer<vtkCellArray>::New();
|
|
|
|
|
|
|
|
|
|
|
|
if(points->GetNumberOfPoints() >= 2) {
|
|
|
|
|
|
lines->InsertNextCell(2); // 这条线只包含两个点
|
|
|
|
|
|
lines->InsertCellPoint(0); // 第一个点
|
|
|
|
|
|
lines->InsertCellPoint(1); // 第二个点
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建 PolyData 并设置点和线
|
|
|
|
|
|
vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New();
|
|
|
|
|
|
polyData->SetPoints(points);
|
|
|
|
|
|
polyData->SetLines(lines);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建 Mapper
|
|
|
|
|
|
vtkSmartPointer<vtkPolyDataMapper> lineMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
|
|
|
|
|
|
lineMapper->SetInputData(polyData);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建 Actor 并设置属性
|
|
|
|
|
|
vtkSmartPointer<vtkActor> lineActor = vtkSmartPointer<vtkActor>::New();
|
|
|
|
|
|
lineActor->SetMapper(lineMapper);
|
|
|
|
|
|
lineActor->GetProperty()->SetColor(0.647, 0.165, 0.165);
|
|
|
|
|
|
lineActor->GetProperty()->SetLineWidth(1.1);
|
|
|
|
|
|
|
|
|
|
|
|
m_renderer->AddActor(lineActor); // 将 3D Actor 添加到渲染器
|
|
|
|
|
|
m_mapWellLineActors.insert(sWellName, lineActor); // 存储井线 Actor
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 强制渲染以显示新增的 Actor
|
|
|
|
|
|
m_pVtkWidget->GetRenderWindow()->Render();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 新增函数:清除所有井相关的 Actor
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::clearWellActors()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 移除所有井名称的 Actor
|
|
|
|
|
|
for(auto it = m_mapWellNameActors.constBegin(); it != m_mapWellNameActors.constEnd(); ++it) {
|
|
|
|
|
|
if(m_renderer && it.value()) {
|
|
|
|
|
|
m_renderer->RemoveActor(it.value()); // 对于 3D Actor 使用 RemoveActor
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_mapWellNameActors.clear(); // 清空 QMap,释放智能指针持有的 VTK 对象
|
|
|
|
|
|
|
|
|
|
|
|
// 移除所有井线的 Actor
|
|
|
|
|
|
for(auto it = m_mapWellLineActors.constBegin(); it != m_mapWellLineActors.constEnd(); ++it) {
|
|
|
|
|
|
if(m_renderer && it.value()) {
|
|
|
|
|
|
m_renderer->RemoveActor(it.value()); // 对于 3D Actor 使用 RemoveActor
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_mapWellLineActors.clear(); // 清空 QMap,释放智能指针持有的 VTK 对象
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染更新以反映移除,确保屏幕刷新
|
|
|
|
|
|
if(m_pVtkWidget && m_pVtkWidget->GetRenderWindow()) {
|
|
|
|
|
|
m_pVtkWidget->GetRenderWindow()->Render();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::initOperPannel()
|
|
|
|
|
|
{
|
|
|
|
|
|
QHBoxLayout* operLayout = new QHBoxLayout; // 创建操作面板的水平布局
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化进度条滑块
|
|
|
|
|
|
m_pSlider = new QSlider(Qt::Horizontal, this); // 创建水平滑块,并设置父对象
|
|
|
|
|
|
m_pSlider->setMinimum(1); // 滑块最小值设置为 1
|
|
|
|
|
|
m_pSlider->setMaximum(m_vecTimeStepKeys.size()); // 最大值是时间步的数量
|
|
|
|
|
|
m_pSlider->setValue(1); // 初始值设置为 1
|
|
|
|
|
|
m_pSlider->setTickInterval(1); // 刻度间隔为 1
|
|
|
|
|
|
m_pSlider->setTickPosition(QSlider::TicksBelow); // 刻度显示在滑块下方
|
|
|
|
|
|
|
|
|
|
|
|
// 连接滑块值改变信号到槽函数
|
|
|
|
|
|
connect(m_pSlider, SIGNAL(valueChanged(int)), this, SLOT(on_updateProgress(int)));
|
|
|
|
|
|
// 连接滑块的按下信号 (旧版连接方式)
|
|
|
|
|
|
connect(m_pSlider, SIGNAL(sliderPressed()), this, SLOT(on_sliderPressed()));
|
|
|
|
|
|
operLayout->addWidget(m_pSlider, 2); // 将滑块添加到布局,伸缩因子为 2
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化进度文本标签
|
|
|
|
|
|
m_pProgress = new QLabel(this); // 创建标签,并设置父对象
|
|
|
|
|
|
int totalSteps = m_vecTimeStepKeys.size(); // 获取总时间步数
|
|
|
|
|
|
|
|
|
|
|
|
// 设置进度文本的初始值
|
|
|
|
|
|
if(totalSteps > 0) {
|
|
|
|
|
|
int iProgress = (int)(((m_nCurrentIndex + 1) * 100) / totalSteps);
|
|
|
|
|
|
m_pProgress->setText(QString("%1%(%2/%3)").arg(iProgress).arg(m_nCurrentIndex + 1).arg(totalSteps));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
m_pProgress->setText("0%(0/0)"); // 如果没有数据,显示 0/0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
operLayout->addWidget(m_pProgress, 1); // 将标签添加到布局,伸缩因子为 1
|
|
|
|
|
|
|
|
|
|
|
|
// 创建“播放”按钮
|
|
|
|
|
|
QPushButton* startAutoClickButton = new QPushButton(tr("play"), this); // 创建按钮
|
|
|
|
|
|
operLayout->addWidget(startAutoClickButton, 2); // 添加到布局,伸缩因子为 2
|
|
|
|
|
|
connect(startAutoClickButton, SIGNAL(clicked()), this, SLOT(on_start())); // 连接点击信号到 on_start 槽
|
|
|
|
|
|
|
|
|
|
|
|
// 创建“停止”按钮
|
|
|
|
|
|
QPushButton* stopAutoClickButton = new QPushButton(tr("stop"), this); // 创建按钮
|
|
|
|
|
|
operLayout->addWidget(stopAutoClickButton, 2); // 添加到布局,伸缩因子为 2
|
|
|
|
|
|
connect(stopAutoClickButton, SIGNAL(clicked()), this, SLOT(on_stop())); // 连接点击信号到 on_stop 槽
|
|
|
|
|
|
|
|
|
|
|
|
// 添加一个用于弹出数据过滤设置对话框的按钮
|
|
|
|
|
|
QPushButton* filterButton = new QPushButton(tr("Data Filtering"), this);
|
|
|
|
|
|
operLayout->addWidget(filterButton, 2);
|
|
|
|
|
|
// 连接按钮的点击信号到新的槽函数
|
|
|
|
|
|
connect(filterButton, SIGNAL(clicked()), this, SLOT(slotShowDataFilterDialog()));
|
|
|
|
|
|
|
|
|
|
|
|
// 用于启动绘制模式的按钮
|
|
|
|
|
|
QPushButton* drawButton = new QPushButton(tr("Draw Region"), this);
|
|
|
|
|
|
operLayout->addWidget(drawButton, 2);
|
|
|
|
|
|
connect(drawButton, SIGNAL(clicked()), this, SLOT(slotStartDrawPolygon()));
|
|
|
|
|
|
|
|
|
|
|
|
// 创建切换显示模式的下拉框
|
|
|
|
|
|
QComboBox* showModeSelector = new QComboBox(this); // 创建下拉框
|
|
|
|
|
|
showModeSelector->addItem(tr("face")); // 添加“面”模式
|
|
|
|
|
|
showModeSelector->addItem(tr("face+edge")); // 添加“面+边”模式
|
|
|
|
|
|
connect(showModeSelector, SIGNAL(currentIndexChanged(int)), this,
|
|
|
|
|
|
SLOT(on_modeChanged(int))); // 连接选中项改变信号
|
|
|
|
|
|
operLayout->addWidget(showModeSelector, 2); // 添加到布局,伸缩因子为 2
|
|
|
|
|
|
|
|
|
|
|
|
// 将操作面板的水平布局添加到主垂直布局中
|
|
|
|
|
|
m_pMainLayout->addLayout(operLayout);
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化定时器
|
|
|
|
|
|
m_pPlayTimer = new QTimer(this); // 创建定时器,设置父对象
|
|
|
|
|
|
connect(m_pPlayTimer, SIGNAL(timeout()), this, SLOT(on_play())); // 连接定时器超时信号到 on_play 槽
|
|
|
|
|
|
m_pPlayTimer->setInterval(50); // 设置定时器间隔为 50 毫秒(即每 50 毫秒更新一帧)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化时间步列表,从 nmDataAnalyzeManager 单例获取数据
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::initTimeSteps()
|
|
|
|
|
|
{
|
|
|
|
|
|
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance(); // 获取数据管理器单例实例
|
|
|
|
|
|
|
|
|
|
|
|
if(!pDataManager) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 通过公共方法获取所有时间步的键(时间戳)列表
|
|
|
|
|
|
QList<double> keys = pDataManager->getTimeStepKeys();
|
|
|
|
|
|
// 对时间步进行排序,确保动画播放顺序是正确的(从小到大)
|
|
|
|
|
|
std::sort(keys.begin(), keys.end());
|
|
|
|
|
|
m_vecTimeStepKeys = QVector<double>::fromList(keys); // 将 QList 转换为 QVector 存储
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据指定索引加载并渲染一个时间步的数据
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::loadDataForIndex(int index)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 确保索引在有效范围内
|
|
|
|
|
|
if(index < 0 || index >= m_vecTimeStepKeys.size() || !m_pCachedBaseGrid) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance(); // 获取数据管理器单例实例
|
|
|
|
|
|
|
|
|
|
|
|
if(!pDataManager) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 获取当前时间步对应的压力数据
|
|
|
|
|
|
double currentTime = m_vecTimeStepKeys[index]; // 获取当前时间步的时间戳
|
|
|
|
|
|
vtkSmartPointer<vtkDoubleArray> currentPressureData = pDataManager->getTimeStepData(
|
|
|
|
|
|
currentTime); // 获取对应的压力数据
|
|
|
|
|
|
|
|
|
|
|
|
if(!currentPressureData) return;
|
|
|
|
|
|
|
|
|
|
|
|
const char* arrayName = currentPressureData->GetName();
|
|
|
|
|
|
|
|
|
|
|
|
if (arrayName) {
|
|
|
|
|
|
qDebug() << "Loaded data array name is:" << arrayName;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
qDebug() << "Loaded data array has no name.";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 将压力数据设置到PEBI单元数据中
|
|
|
|
|
|
m_pCachedBaseGrid->GetCellData()->SetScalars(currentPressureData);
|
|
|
|
|
|
m_thresholdFilter->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, "p");
|
|
|
|
|
|
|
|
|
|
|
|
// 更新时间文本演员显示的时间戳
|
|
|
|
|
|
//m_textActor->SetInput(QString("Time step: %1").arg(currentTime, 0, 'f', 6).toStdString().c_str());
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染更新后的场景
|
|
|
|
|
|
m_pVtkWidget->GetRenderWindow()->Render(); // 触发 QVTKWidget 的渲染窗口进行更新显示
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 槽函数:动画播放的逻辑,由定时器周期性触发
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::on_play()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果没有时间步数据,则停止播放并退出
|
|
|
|
|
|
if(m_vecTimeStepKeys.isEmpty()) {
|
|
|
|
|
|
on_stop(); // 停止播放
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果当前索引已达到或超过总时间步数,则循环播放,将索引重置为 0
|
|
|
|
|
|
if(m_nCurrentIndex >= m_vecTimeStepKeys.size()) {
|
|
|
|
|
|
on_stop(); // 播放完毕,停止定时器
|
|
|
|
|
|
m_nCurrentIndex = 0; // 从头开始播放
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载并渲染当前帧的数据
|
|
|
|
|
|
loadDataForIndex(m_nCurrentIndex);
|
|
|
|
|
|
// 更新进度滑块的值 (滑块值从 1 开始,索引从 0 开始)
|
|
|
|
|
|
m_pSlider->setValue(m_nCurrentIndex + 1);
|
|
|
|
|
|
// 更新进度文本标签
|
|
|
|
|
|
int iProgress = (int)(((m_nCurrentIndex + 1) * 100) / m_vecTimeStepKeys.size());
|
|
|
|
|
|
m_pProgress->setText(QString("%1%(%2/%3)").arg(iProgress).arg(m_nCurrentIndex + 1).arg(m_vecTimeStepKeys.size()));
|
|
|
|
|
|
m_nCurrentIndex++; // 索引递增,准备播放下一帧
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 槽函数:点击“播放”按钮后触发,启动动画播放
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::on_start()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果没有时间步数据,无法开始播放
|
|
|
|
|
|
if(m_vecTimeStepKeys.isEmpty()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_bIsPlaying = true; // 设置播放状态为真
|
|
|
|
|
|
|
|
|
|
|
|
// 如果当前索引已达到或超过总时间步数,则从头开始播放
|
|
|
|
|
|
if(m_nCurrentIndex >= m_vecTimeStepKeys.size()) {
|
|
|
|
|
|
m_nCurrentIndex = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保滑块的最大值和当前值与时间步数量同步
|
|
|
|
|
|
m_pSlider->setMaximum(m_vecTimeStepKeys.size());
|
|
|
|
|
|
m_pSlider->setValue(m_nCurrentIndex + 1); // 设置滑块到当前帧的位置
|
|
|
|
|
|
|
|
|
|
|
|
m_pPlayTimer->start(); // 启动定时器,开始周期性调用 on_play()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 槽函数:点击“停止”按钮后触发,停止动画播放
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::on_stop()
|
|
|
|
|
|
{
|
|
|
|
|
|
m_pPlayTimer->stop(); // 停止定时器,停止周期性调用 on_play()
|
|
|
|
|
|
m_bIsPlaying = false; // 设置播放状态为假
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 槽函数:这个槽函数在您的代码中未被连接。
|
|
|
|
|
|
// 如果不再使用,可以从头文件和源文件中删除此函数。
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::on_currentIndexChanged()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果正在播放中,忽略此手动更改,避免冲突
|
|
|
|
|
|
if(m_bIsPlaying) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// (此处原代码中没有实际逻辑,若要使用应在此添加)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 槽函数:监听显示模式(例如面模式、面+边模式)变化
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::on_modeChanged(int newModeIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果新模式与当前模式相同,则不进行任何操作
|
|
|
|
|
|
if(m_nShowMode == newModeIndex) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_nShowMode = newModeIndex; // 更新当前显示模式
|
|
|
|
|
|
|
|
|
|
|
|
// 根据选择的新模式更新演员(Actor)的显示属性
|
|
|
|
|
|
if(m_nShowMode == 0) { // 0 代表“面”模式
|
|
|
|
|
|
m_actor->GetProperty()->EdgeVisibilityOff(); // 关闭网格线的显示
|
|
|
|
|
|
m_actor->GetProperty()->SetRepresentationToSurface(); // 设置为表面渲染模式
|
|
|
|
|
|
} else { // 1 代表“面+边”模式
|
|
|
|
|
|
m_actor->GetProperty()->SetEdgeVisibility(true); // 开启网格线的显示
|
|
|
|
|
|
m_actor->GetProperty()->SetRepresentationToSurface(); // 仍然是表面渲染模式,只是显示边
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染更新后的场景,使改变生效
|
|
|
|
|
|
m_pVtkWidget->GetRenderWindow()->Render(); // 触发 QVTKWidget 的渲染窗口进行更新显示
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 槽函数:监听进度滑块值变化时触发
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::on_updateProgress(int value)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果正在自动播放过程中,忽略滑块的手动拖动,防止冲突
|
|
|
|
|
|
if(m_bIsPlaying) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有时间步数据,则不进行更新
|
|
|
|
|
|
if(m_vecTimeStepKeys.isEmpty()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
value--; // 滑块值从 1 开始 (m_pSlider->setMinimum(1)),但内部索引从 0 开始,所以需要减 1
|
|
|
|
|
|
|
|
|
|
|
|
// 根据滑块值更新当前时间步索引
|
|
|
|
|
|
m_nCurrentIndex = value;
|
|
|
|
|
|
// 加载并渲染当前索引对应的数据帧
|
|
|
|
|
|
loadDataForIndex(m_nCurrentIndex);
|
|
|
|
|
|
// 更新进度文本标签
|
|
|
|
|
|
int iProgress = (int)(((m_nCurrentIndex + 1) * 100) / m_vecTimeStepKeys.size());
|
|
|
|
|
|
m_pProgress->setText(QString("%1%(%2/%3)").arg(iProgress).arg(m_nCurrentIndex + 1).arg(m_vecTimeStepKeys.size()));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 槽函数:监听进度滑块被按下(拖动开始)
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::on_sliderPressed()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果正在自动播放过程中,当滑块被手动按下时,停止自动播放
|
|
|
|
|
|
if(m_bIsPlaying) {
|
|
|
|
|
|
m_pPlayTimer->stop(); // 停止定时器
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_bIsPlaying = false; // 确保播放状态为停止,因为用户正在手动操作
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// saveWidgetAsImage: 将 m_pVtkWidget 的内容保存到图像文件。
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::saveWidgetAsImage()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查 QVTKWidget 及其底层 VTK 渲染窗口是否已初始化。
|
|
|
|
|
|
if(!m_pVtkWidget || !m_pVtkWidget->GetRenderWindow()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将 QVTKWidget 的当前内容抓取为 QPixmap。
|
|
|
|
|
|
QPixmap pixmap = QPixmap::grabWidget(m_pVtkWidget);
|
|
|
|
|
|
|
|
|
|
|
|
// 打开文件保存对话框,让用户选择保存位置和格式。
|
|
|
|
|
|
QString sFileName = QFileDialog::getSaveFileName(this,
|
|
|
|
|
|
tr("Save Image"), // 对话框标题
|
|
|
|
|
|
"", // 默认目录 (空字符串表示当前工作目录)
|
|
|
|
|
|
tr("PNG Image (*.png);;JPEG Image (*.jpg);;BMP Image (*.bmp)")); // 文件过滤器
|
|
|
|
|
|
|
|
|
|
|
|
// 如果用户提供了文件名 (没有取消对话框)。
|
|
|
|
|
|
if(!sFileName.isEmpty()) {
|
|
|
|
|
|
// 尝试将 pixmap 保存到选定的文件。
|
|
|
|
|
|
pixmap.save(sFileName);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// copyWidgetImage: 将 m_pVtkWidget 的内容复制到剪贴板。
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::copyWidgetImage()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查 QVTKWidget 及其底层 VTK 渲染窗口是否已初始化。
|
|
|
|
|
|
if(!m_pVtkWidget || !m_pVtkWidget->GetRenderWindow()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将 QVTKWidget 的当前内容抓取为 QPixmap。
|
|
|
|
|
|
QPixmap pixmap = QPixmap::grabWidget(m_pVtkWidget);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取全局剪贴板实例并设置抓取的 pixmap。
|
|
|
|
|
|
QClipboard *pClipboard = QApplication::clipboard();
|
|
|
|
|
|
pClipboard->setPixmap(pixmap);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// printWidget: 打印 m_pVtkWidget 的内容。
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::printWidget()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查 QVTKWidget 及其底层 VTK 渲染窗口是否已初始化。
|
|
|
|
|
|
if(!m_pVtkWidget || !m_pVtkWidget->GetRenderWindow()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QPrinter printer; // 创建 QPrinter 对象。
|
|
|
|
|
|
QPrintDialog printDialog(&printer, this); // 创建用于打印设置的打印对话框。
|
|
|
|
|
|
|
|
|
|
|
|
// 如果用户接受打印对话框(点击“打印”)。
|
|
|
|
|
|
if(printDialog.exec() == QDialog::Accepted) {
|
|
|
|
|
|
// 调用 renderWidgetForPrint 槽函数,将打印逻辑统一。
|
|
|
|
|
|
// 这样,实际打印也会应用缩放和居中。
|
|
|
|
|
|
renderWidgetForPrint(&printer);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// printPreviewWidget: 显示 m_pVtkWidget 的打印预览。
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::printPreviewWidget()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查 QVTKWidget 及其底层 VTK 渲染窗口是否已初始化。
|
|
|
|
|
|
if(!m_pVtkWidget || !m_pVtkWidget->GetRenderWindow()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QPrinter printer(QPrinter::HighResolution); // 创建高分辨率打印机以获得更好的预览质量。
|
|
|
|
|
|
QPrintPreviewDialog preview(&printer, this); // 创建打印预览对话框。
|
|
|
|
|
|
preview.resize(800, 600); // 设置预览窗体大小
|
|
|
|
|
|
|
|
|
|
|
|
// 将预览对话框的 paintRequested 信号连接到我们的自定义渲染槽。
|
|
|
|
|
|
// 当预览对话框需要绘制页面时,将调用此槽。
|
|
|
|
|
|
connect(&preview, SIGNAL(paintRequested(QPrinter*)),
|
|
|
|
|
|
this, SLOT(renderWidgetForPrint(QPrinter*)));
|
|
|
|
|
|
|
|
|
|
|
|
preview.exec(); // 显示打印预览对话框。
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// renderWidgetForPrint: 辅助槽函数,用于渲染 m_pVtkWidget 的内容以进行打印/预览。
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::renderWidgetForPrint(QPrinter *printer)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 调用辅助函数获取包含VTK渲染内容的QImage
|
|
|
|
|
|
QImage image = getVTKRenderWindowAsImage();
|
|
|
|
|
|
|
|
|
|
|
|
if(image.isNull()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QPainter painter(printer); // 创建与给定打印机关联的 QPainter。
|
|
|
|
|
|
|
|
|
|
|
|
// 计算缩放因子,以按比例将部件内容适应到打印页面。
|
|
|
|
|
|
// 这里使用捕获到的QImage的尺寸,而不是QVTKWidget的尺寸,因为QImage已经是实际渲染内容的准确捕获。
|
|
|
|
|
|
double xscale = printer->pageRect().width() / (double)image.width();
|
|
|
|
|
|
double yscale = printer->pageRect().height() / (double)image.height();
|
|
|
|
|
|
// 使用较小的缩放因子,以确保整个内容完全适应页面内,不会被裁剪。
|
|
|
|
|
|
double scale = qMin(xscale, yscale);
|
|
|
|
|
|
|
|
|
|
|
|
painter.scale(scale, scale); // 然后应用缩放变换
|
|
|
|
|
|
|
|
|
|
|
|
// 将捕获到的 QImage 绘制到打印机上。
|
|
|
|
|
|
// 这样,VTK渲染的所有内容(包括渐变背景)都会被完整地打印出来。
|
|
|
|
|
|
painter.drawImage(0, 0, image);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QImage nmWxPostprocessingAnimationWidget::getVTKRenderWindowAsImage()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查 QVTKWidget 及其底层的 VTK 渲染窗口是否已初始化。
|
|
|
|
|
|
if(!m_pVtkWidget || !m_pVtkWidget->GetRenderWindow()) {
|
|
|
|
|
|
qWarning() << "QVTKWidget or RenderWindow is not initialized.";
|
|
|
|
|
|
return QImage(); // 返回空图像
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取 VTK 渲染窗口的指针
|
|
|
|
|
|
vtkRenderWindow* pRenderWindow = m_pVtkWidget->GetRenderWindow();
|
|
|
|
|
|
|
|
|
|
|
|
// 确保在捕获前渲染最新帧
|
|
|
|
|
|
pRenderWindow->Render();
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 vtkWindowToImageFilter 将渲染窗口的内容转换为 VTK 图像数据
|
|
|
|
|
|
vtkSmartPointer<vtkWindowToImageFilter> windowToImageFilter =
|
|
|
|
|
|
vtkSmartPointer<vtkWindowToImageFilter>::New();
|
|
|
|
|
|
windowToImageFilter->SetInput(pRenderWindow);
|
|
|
|
|
|
// 明确设置为 RGB
|
|
|
|
|
|
windowToImageFilter->SetInputBufferTypeToRGB();
|
|
|
|
|
|
// 从后台缓冲区读取以确保完整渲染
|
|
|
|
|
|
windowToImageFilter->ReadFrontBufferOff();
|
|
|
|
|
|
windowToImageFilter->Update(); // 执行图像转换
|
|
|
|
|
|
|
|
|
|
|
|
// 获取 VTK 图像数据 (未经翻转)
|
|
|
|
|
|
vtkImageData* pImageData = windowToImageFilter->GetOutput();
|
|
|
|
|
|
|
|
|
|
|
|
// VTK 图像数据的原点在左下角,而 Qt 的 QImage 原点在左上角,需要垂直翻转
|
|
|
|
|
|
vtkSmartPointer<vtkImageFlip> flip = vtkSmartPointer<vtkImageFlip>::New();
|
|
|
|
|
|
flip->SetInputData(pImageData);
|
|
|
|
|
|
//flip->SetInputConnection(windowToImageFilter->GetOutputPort()); // 用这个方法翻转后图像显示的是黑色的!!!
|
|
|
|
|
|
flip->SetFilteredAxis(1); // 翻转 Y 轴(垂直翻转)
|
|
|
|
|
|
flip->Update(); // 翻转操作在这里执行
|
|
|
|
|
|
|
|
|
|
|
|
// 获取翻转后的 VTK 图像数据
|
|
|
|
|
|
vtkImageData* pFlippedImageData = flip->GetOutput(); // 获取翻转后的数据
|
|
|
|
|
|
|
|
|
|
|
|
return createQImage(pFlippedImageData);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::exportAnimationAsVideo()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查动画数据和渲染窗口是否可用,如果不可用则弹出警告并退出。
|
|
|
|
|
|
if(m_vecTimeStepKeys.isEmpty() || !m_pVtkWidget || !m_pVtkWidget->GetRenderWindow()) {
|
|
|
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("No animation data or render window available."));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 弹出文件保存对话框,让用户选择视频保存的路径和文件名。
|
|
|
|
|
|
// 这里列出了多种常见的视频格式作为过滤器,方便用户选择。
|
|
|
|
|
|
QString sFileName = QFileDialog::getSaveFileName(
|
|
|
|
|
|
this,
|
|
|
|
|
|
tr("Save Animation as Video"),
|
|
|
|
|
|
"",
|
|
|
|
|
|
tr("MP4 Video (*.mp4);;" // 最常用、兼容性最好的视频格式,几乎所有设备和软件都支持。
|
|
|
|
|
|
"AVI Video (*.avi);;" // 一种较老但兼容性好的格式,文件体积通常比MP4大。
|
|
|
|
|
|
"QuickTime Video (*.mov);;" // 苹果公司开发的视频格式,在Apple生态中应用广泛。
|
|
|
|
|
|
"MPEG Video (*.mpg);;" // 通用的视频文件格式,通常指MPEG-1或MPEG-2编码。
|
|
|
|
|
|
"WebM Video (*.webm);;" // 一种现代、开放、免版税的视频格式,由Google推出,常用于网页视频。
|
|
|
|
|
|
"Matroska Video (*.mkv);;" // 一种强大的“容器”格式,可以包含多种视频、音频和字幕轨道,但并非所有播放器都原生支持。
|
|
|
|
|
|
"Windows Media Video (*.wmv);;" // 微软开发的视频格式,在Windows平台常用。
|
|
|
|
|
|
"Flash Video (*.flv);;" // 一种曾广泛用于网页视频的格式,现在已逐渐被HTML5技术取代。
|
|
|
|
|
|
"All Files (*.*)")); // “所有文件”选项,允许用户选择或输入任何文件扩展名。
|
|
|
|
|
|
|
|
|
|
|
|
// 如果用户取消了保存对话框,则直接返回。
|
|
|
|
|
|
if(sFileName.isEmpty()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 记录动画当前的播放状态,以便在导出完成后恢复。
|
|
|
|
|
|
bool wasPlaying = m_bIsPlaying;
|
|
|
|
|
|
// 停止正在进行的动画播放,避免与导出过程冲突。
|
|
|
|
|
|
on_stop();
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 创建离屏渲染管线
|
|
|
|
|
|
// 创建一个VTK渲染窗口,但设置为离屏渲染,这意味着它不会在屏幕上显示。
|
|
|
|
|
|
vtkSmartPointer<vtkRenderWindow> offscreenRenderWindow = vtkSmartPointer<vtkRenderWindow>::New();
|
|
|
|
|
|
offscreenRenderWindow->OffScreenRenderingOn();
|
|
|
|
|
|
// 设置离屏渲染窗口的尺寸为 1920x1080,即 1080p,以生成高分辨率视频。
|
|
|
|
|
|
offscreenRenderWindow->SetSize(1920, 1080);
|
|
|
|
|
|
// 创建一个VTK渲染器。
|
|
|
|
|
|
vtkSmartPointer<vtkRenderer> offscreenRenderer = vtkSmartPointer<vtkRenderer>::New();
|
|
|
|
|
|
// 将渲染器添加到离屏渲染窗口中。
|
|
|
|
|
|
offscreenRenderWindow->AddRenderer(offscreenRenderer);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 深度复制所有渲染内容 (兼容 VTK 7.1)
|
|
|
|
|
|
|
|
|
|
|
|
// 复制相机:从主渲染器的相机深度复制到离屏渲染器,确保视角一致。
|
|
|
|
|
|
vtkSmartPointer<vtkCamera> camera = vtkSmartPointer<vtkCamera>::New();
|
|
|
|
|
|
camera->DeepCopy(m_renderer->GetActiveCamera());
|
|
|
|
|
|
offscreenRenderer->SetActiveCamera(camera);
|
|
|
|
|
|
|
|
|
|
|
|
// 复制渲染器背景颜色和渐变设置:将主渲染器的背景属性复制到离屏渲染器。
|
|
|
|
|
|
double background1[3], background2[3];
|
|
|
|
|
|
m_renderer->GetBackground(background1);
|
|
|
|
|
|
m_renderer->GetBackground2(background2);
|
|
|
|
|
|
offscreenRenderer->SetBackground(background1);
|
|
|
|
|
|
offscreenRenderer->SetBackground2(background2);
|
|
|
|
|
|
offscreenRenderer->SetGradientBackground(m_renderer->GetGradientBackground());
|
|
|
|
|
|
|
|
|
|
|
|
// 复制基础数据对象:深度复制网格数据,确保离屏渲染使用独立的数据副本。
|
|
|
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> offscreenGrid = vtkSmartPointer<vtkUnstructuredGrid>::New();
|
|
|
|
|
|
|
|
|
|
|
|
if(m_pCachedBaseGrid) {
|
|
|
|
|
|
offscreenGrid->DeepCopy(m_pCachedBaseGrid);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 复制主 Actor 的 Mapper、Actor 和 ScalarBar (手动复制属性)
|
|
|
|
|
|
// 复制 Mapper:创建一个新的Mapper,并设置其输入数据和颜色映射表等属性。
|
|
|
|
|
|
vtkSmartPointer<vtkDataSetMapper> offscreenMapper = vtkSmartPointer<vtkDataSetMapper>::New();
|
|
|
|
|
|
|
|
|
|
|
|
if(m_mapper) {
|
|
|
|
|
|
offscreenMapper->SetInputData(offscreenGrid);
|
|
|
|
|
|
offscreenMapper->SetLookupTable(m_mapper->GetLookupTable());
|
|
|
|
|
|
offscreenMapper->ScalarVisibilityOn();
|
|
|
|
|
|
offscreenMapper->SetScalarRange(m_mapper->GetScalarRange());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 复制 Actor:创建一个新的Actor,并设置其Mapper,同时深度复制其属性。
|
|
|
|
|
|
vtkSmartPointer<vtkActor> offscreenActor = vtkSmartPointer<vtkActor>::New();
|
|
|
|
|
|
|
|
|
|
|
|
if(m_actor) {
|
|
|
|
|
|
offscreenActor->SetMapper(offscreenMapper);
|
|
|
|
|
|
// 手动复制 vtkProperty
|
|
|
|
|
|
vtkSmartPointer<vtkProperty> prop = vtkSmartPointer<vtkProperty>::New();
|
|
|
|
|
|
prop->DeepCopy(m_actor->GetProperty());
|
|
|
|
|
|
offscreenActor->SetProperty(prop);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 复制颜色条:创建新的颜色条,并复制所有属性,包括位置、尺寸和字体。
|
|
|
|
|
|
vtkSmartPointer<vtkScalarBarActor> offscreenScalarBar = vtkSmartPointer<vtkScalarBarActor>::New();
|
|
|
|
|
|
|
|
|
|
|
|
if(m_scalarBar) {
|
|
|
|
|
|
offscreenScalarBar->SetLookupTable(m_scalarBar->GetLookupTable());
|
|
|
|
|
|
offscreenScalarBar->SetTitle(m_scalarBar->GetTitle());
|
|
|
|
|
|
offscreenScalarBar->SetPosition(m_scalarBar->GetPosition());
|
|
|
|
|
|
offscreenScalarBar->SetWidth(m_scalarBar->GetWidth());
|
|
|
|
|
|
offscreenScalarBar->SetHeight(m_scalarBar->GetHeight());
|
|
|
|
|
|
|
|
|
|
|
|
// 手动复制 TitleTextProperty 和 LabelTextProperty
|
|
|
|
|
|
vtkSmartPointer<vtkTextProperty> titleTextProp = vtkSmartPointer<vtkTextProperty>::New();
|
|
|
|
|
|
vtkTextProperty* sourceTitleTextProp = m_scalarBar->GetTitleTextProperty();
|
|
|
|
|
|
titleTextProp->SetFontFamily(sourceTitleTextProp->GetFontFamily());
|
|
|
|
|
|
titleTextProp->SetFontSize(sourceTitleTextProp->GetFontSize());
|
|
|
|
|
|
titleTextProp->SetColor(sourceTitleTextProp->GetColor());
|
|
|
|
|
|
offscreenScalarBar->SetTitleTextProperty(titleTextProp);
|
|
|
|
|
|
|
|
|
|
|
|
vtkSmartPointer<vtkTextProperty> labelTextProp = vtkSmartPointer<vtkTextProperty>::New();
|
|
|
|
|
|
vtkTextProperty* sourceLabelTextProp = m_scalarBar->GetLabelTextProperty();
|
|
|
|
|
|
labelTextProp->SetFontFamily(sourceLabelTextProp->GetFontFamily());
|
|
|
|
|
|
labelTextProp->SetFontSize(sourceLabelTextProp->GetFontSize());
|
|
|
|
|
|
labelTextProp->SetColor(sourceLabelTextProp->GetColor());
|
|
|
|
|
|
offscreenScalarBar->SetLabelTextProperty(labelTextProp);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 复制井名和井线的 Actor
|
|
|
|
|
|
QMap<QString, vtkSmartPointer<vtkBillboardTextActor3D>> offscreenWellNameActors;
|
|
|
|
|
|
QMapIterator<QString, vtkSmartPointer<vtkBillboardTextActor3D>> wellNameIter(m_mapWellNameActors);
|
|
|
|
|
|
|
|
|
|
|
|
while(wellNameIter.hasNext()) {
|
|
|
|
|
|
wellNameIter.next();
|
|
|
|
|
|
vtkSmartPointer<vtkBillboardTextActor3D> originalActor = wellNameIter.value();
|
|
|
|
|
|
vtkSmartPointer<vtkBillboardTextActor3D> newActor = vtkSmartPointer<vtkBillboardTextActor3D>::New();
|
|
|
|
|
|
newActor->SetInput(originalActor->GetInput());
|
|
|
|
|
|
newActor->SetPosition(originalActor->GetPosition());
|
|
|
|
|
|
|
|
|
|
|
|
// 手动复制文本属性
|
|
|
|
|
|
vtkSmartPointer<vtkTextProperty> textProp = vtkSmartPointer<vtkTextProperty>::New();
|
|
|
|
|
|
vtkTextProperty* sourceTextProp = originalActor->GetTextProperty();
|
|
|
|
|
|
textProp->SetFontFamily(sourceTextProp->GetFontFamily());
|
|
|
|
|
|
textProp->SetFontSize(sourceTextProp->GetFontSize());
|
|
|
|
|
|
textProp->SetColor(sourceTextProp->GetColor());
|
|
|
|
|
|
textProp->SetBold(sourceTextProp->GetBold());
|
|
|
|
|
|
newActor->SetTextProperty(textProp);
|
|
|
|
|
|
|
|
|
|
|
|
offscreenWellNameActors.insert(wellNameIter.key(), newActor);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QMap<QString, vtkSmartPointer<vtkActor>> offscreenWellLineActors;
|
|
|
|
|
|
QMapIterator<QString, vtkSmartPointer<vtkActor>> wellLineIter(m_mapWellLineActors);
|
|
|
|
|
|
|
|
|
|
|
|
while(wellLineIter.hasNext()) {
|
|
|
|
|
|
wellLineIter.next();
|
|
|
|
|
|
vtkSmartPointer<vtkActor> originalActor = wellLineIter.value();
|
|
|
|
|
|
vtkSmartPointer<vtkActor> newActor = vtkSmartPointer<vtkActor>::New();
|
|
|
|
|
|
// 复制 Mapper
|
|
|
|
|
|
vtkSmartPointer<vtkPolyDataMapper> originalMapper = vtkPolyDataMapper::SafeDownCast(originalActor->GetMapper());
|
|
|
|
|
|
|
|
|
|
|
|
if(originalMapper) {
|
|
|
|
|
|
vtkSmartPointer<vtkPolyDataMapper> newMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
|
|
|
|
|
|
newMapper->SetInputData(originalMapper->GetInput()); // 井线几何数据通常是静态的,可以共享
|
|
|
|
|
|
newActor->SetMapper(newMapper);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 复制 Property
|
|
|
|
|
|
vtkSmartPointer<vtkProperty> newProp = vtkSmartPointer<vtkProperty>::New();
|
|
|
|
|
|
newProp->DeepCopy(originalActor->GetProperty());
|
|
|
|
|
|
newActor->SetProperty(newProp);
|
|
|
|
|
|
|
|
|
|
|
|
offscreenWellLineActors.insert(wellLineIter.key(), newActor);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将所有复制的 Actor 添加到离屏渲染器
|
|
|
|
|
|
offscreenRenderer->AddActor(offscreenActor);
|
|
|
|
|
|
offscreenRenderer->AddActor(offscreenScalarBar);
|
|
|
|
|
|
QMapIterator<QString, vtkSmartPointer<vtkBillboardTextActor3D>> offscreenNameIter(offscreenWellNameActors);
|
|
|
|
|
|
|
|
|
|
|
|
while(offscreenNameIter.hasNext()) {
|
|
|
|
|
|
offscreenNameIter.next();
|
|
|
|
|
|
offscreenRenderer->AddActor(offscreenNameIter.value());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QMapIterator<QString, vtkSmartPointer<vtkActor>> offscreenLineIter(offscreenWellLineActors);
|
|
|
|
|
|
|
|
|
|
|
|
while(offscreenLineIter.hasNext()) {
|
|
|
|
|
|
offscreenLineIter.next();
|
|
|
|
|
|
offscreenRenderer->AddActor(offscreenLineIter.value());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 配置视频写入器和进度条
|
|
|
|
|
|
// 创建FFmpeg视频写入器。
|
|
|
|
|
|
vtkSmartPointer<vtkFFMPEGWriter> pVideoWriter = vtkSmartPointer<vtkFFMPEGWriter>::New();
|
|
|
|
|
|
// 创建一个滤镜,用于将VTK渲染窗口的内容转换为图像数据。
|
|
|
|
|
|
vtkSmartPointer<vtkWindowToImageFilter> pWindowToImageFilter = vtkSmartPointer<vtkWindowToImageFilter>::New();
|
|
|
|
|
|
pWindowToImageFilter->SetInput(offscreenRenderWindow);
|
|
|
|
|
|
pWindowToImageFilter->SetInputBufferTypeToRGB();
|
|
|
|
|
|
pWindowToImageFilter->ReadFrontBufferOff();
|
|
|
|
|
|
// 将滤镜的输出连接到视频写入器的输入。
|
|
|
|
|
|
pVideoWriter->SetInputConnection(pWindowToImageFilter->GetOutputPort());
|
|
|
|
|
|
// 设置输出文件名。
|
|
|
|
|
|
pVideoWriter->SetFileName(sFileName.toStdString().c_str());
|
|
|
|
|
|
// 设置视频帧率,每秒10帧。
|
|
|
|
|
|
pVideoWriter->SetRate(10);
|
|
|
|
|
|
// 设置视频质量(0-5,0是最高质量)。
|
|
|
|
|
|
pVideoWriter->SetQuality(2);
|
|
|
|
|
|
// 启动视频写入过程。
|
|
|
|
|
|
pVideoWriter->Start();
|
|
|
|
|
|
|
|
|
|
|
|
// 创建进度对话框,为用户提供导出进度反馈。
|
|
|
|
|
|
QProgressDialog progressDialog(tr("Exporting Video..."), tr("Cancel"), 0, m_vecTimeStepKeys.size(), this);
|
|
|
|
|
|
progressDialog.setWindowTitle(tr("Video Export"));
|
|
|
|
|
|
progressDialog.setWindowModality(Qt::WindowModal);
|
|
|
|
|
|
progressDialog.setMinimumDuration(0);
|
|
|
|
|
|
|
|
|
|
|
|
// 强制显示对话框并处理事件
|
|
|
|
|
|
progressDialog.show();
|
|
|
|
|
|
QApplication::processEvents(); // 确保对话框在进入循环前被渲染
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 遍历所有时间步,更新离屏数据,渲染并写入每一帧
|
|
|
|
|
|
for(int i = 0; i < m_vecTimeStepKeys.size(); ++i) {
|
|
|
|
|
|
// 如果用户点击了“取消”,则停止导出。
|
|
|
|
|
|
if(progressDialog.wasCanceled()) {
|
|
|
|
|
|
pVideoWriter->End();
|
|
|
|
|
|
|
|
|
|
|
|
if(wasPlaying) on_start();
|
|
|
|
|
|
|
|
|
|
|
|
QMessageBox::information(this, tr("Cancelled"), tr("Video export was cancelled by the user."));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取数据管理器实例。
|
|
|
|
|
|
nmDataAnalyzeManager* pDataManager = nmDataAnalyzeManager::getCurrentInstance();
|
|
|
|
|
|
|
|
|
|
|
|
if(pDataManager) {
|
|
|
|
|
|
// 获取当前时间步的数据。
|
|
|
|
|
|
double currentTime = m_vecTimeStepKeys[i];
|
|
|
|
|
|
vtkSmartPointer<vtkDoubleArray> currentPressureData = pDataManager->getTimeStepData(currentTime);
|
|
|
|
|
|
|
|
|
|
|
|
if(currentPressureData) {
|
|
|
|
|
|
// 将数据设置到离屏网格中,并标记数据已修改。
|
|
|
|
|
|
vtkDataSet* dataSet = vtkDataSet::SafeDownCast(offscreenGrid);
|
|
|
|
|
|
|
|
|
|
|
|
if(dataSet) {
|
|
|
|
|
|
dataSet->GetCellData()->SetScalars(currentPressureData);
|
|
|
|
|
|
|
|
|
|
|
|
dataSet->Modified();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染离屏窗口。
|
|
|
|
|
|
offscreenRenderWindow->Render();
|
|
|
|
|
|
// 通知滤镜数据已更新,需要重新处理。
|
|
|
|
|
|
pWindowToImageFilter->Modified();
|
|
|
|
|
|
// 将渲染结果写入视频文件。
|
|
|
|
|
|
pVideoWriter->Write();
|
|
|
|
|
|
// 更新进度对话框的进度条。
|
|
|
|
|
|
progressDialog.setValue(i + 1);
|
|
|
|
|
|
// 处理Qt事件,保持界面响应。
|
|
|
|
|
|
QApplication::processEvents();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 导出结束
|
|
|
|
|
|
pVideoWriter->End();
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 恢复动画播放状态
|
|
|
|
|
|
// 如果导出前动画正在播放,则重新启动动画。
|
|
|
|
|
|
if(wasPlaying) {
|
|
|
|
|
|
on_start();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 弹出成功消息框。
|
|
|
|
|
|
QMessageBox::information(this, tr("Success"), tr("Video exported successfully to %1.").arg(sFileName));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::slotShowDataFilterDialog()
|
|
|
|
|
|
{
|
|
|
|
|
|
nmWxPostprocessingAnimationDataFiltering dlg;
|
|
|
|
|
|
|
|
|
|
|
|
// 在显示对话框前,设置其初始值
|
|
|
|
|
|
dlg.setFilterSettings(m_bFilteringEnabled,
|
|
|
|
|
|
m_bAboveMinEnabled, m_dMinValue,
|
|
|
|
|
|
m_bBelowMaxEnabled, m_dMaxValue);
|
|
|
|
|
|
|
|
|
|
|
|
// 将对话框的自定义信号连接到本类的槽函数
|
|
|
|
|
|
connect(&dlg, SIGNAL(applySettingsRequested(bool, bool, double, bool, double)),
|
|
|
|
|
|
this, SLOT(slotApplyFilter(bool, bool, double, bool, double)));
|
|
|
|
|
|
|
|
|
|
|
|
// 运行对话框,它会阻塞直到用户点击 OK 或 Cancel
|
|
|
|
|
|
if (dlg.exec() == QDialog::Accepted) {
|
|
|
|
|
|
// 用户点击了 OK,获取对话框中的值
|
|
|
|
|
|
bool bEnableFiltering = dlg.isFilteringEnabled();
|
|
|
|
|
|
bool bEnableAboveMin = dlg.isAboveMinEnabled();
|
|
|
|
|
|
double minVal = dlg.getMinValue();
|
|
|
|
|
|
bool bEnableBelowMax = dlg.isBelowMaxEnabled();
|
|
|
|
|
|
double maxVal = dlg.getMaxValue();
|
|
|
|
|
|
|
|
|
|
|
|
// 调用槽函数应用过滤
|
|
|
|
|
|
slotApplyFilter(bEnableFiltering, bEnableAboveMin, minVal, bEnableBelowMax, maxVal);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::slotApplyFilter(bool bEnableFiltering,
|
|
|
|
|
|
bool bEnableAboveMin, double minVal,
|
|
|
|
|
|
bool bEnableBelowMax, double maxVal)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!m_thresholdFilter) {
|
|
|
|
|
|
qWarning() << "vtkThreshold filter is not initialized.";
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新成员变量来记录当前的过滤设置
|
|
|
|
|
|
m_bFilteringEnabled = bEnableFiltering;
|
|
|
|
|
|
m_bAboveMinEnabled = bEnableAboveMin;
|
|
|
|
|
|
m_dMinValue = minVal;
|
|
|
|
|
|
m_bBelowMaxEnabled = bEnableBelowMax;
|
|
|
|
|
|
m_dMaxValue = maxVal;
|
|
|
|
|
|
|
|
|
|
|
|
if (bEnableFiltering) {
|
|
|
|
|
|
// 如果启用过滤
|
|
|
|
|
|
|
|
|
|
|
|
// 阈值过滤的组合逻辑
|
|
|
|
|
|
if (bEnableAboveMin && bEnableBelowMax) {
|
|
|
|
|
|
// 设置一个完整的范围
|
|
|
|
|
|
m_thresholdFilter->ThresholdBetween(minVal, maxVal);
|
|
|
|
|
|
} else if (bEnableAboveMin) {
|
|
|
|
|
|
// 只设置下限
|
|
|
|
|
|
m_thresholdFilter->ThresholdByUpper(minVal); // 这里的 lower 在 VTK API 中实际被视为 upper
|
|
|
|
|
|
// 或者,为了更清晰,可以使用 VTK_DOUBLE_MAX 作为上限
|
|
|
|
|
|
m_thresholdFilter->ThresholdBetween(minVal, VTK_DOUBLE_MAX);
|
|
|
|
|
|
} else if (bEnableBelowMax) {
|
|
|
|
|
|
// 只设置上限
|
|
|
|
|
|
m_thresholdFilter->ThresholdByLower(maxVal); // 这里的 upper 在 VTK API 中实际被视为 lower
|
|
|
|
|
|
// 或者,为了更清晰,可以使用 -VTK_DOUBLE_MAX 作为下限
|
|
|
|
|
|
m_thresholdFilter->ThresholdBetween(-VTK_DOUBLE_MAX, maxVal);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果都未选中,则相当于不进行阈值过滤
|
|
|
|
|
|
// 此时可以显示全部数据
|
|
|
|
|
|
m_thresholdFilter->ThresholdBetween(-VTK_DOUBLE_MAX, VTK_DOUBLE_MAX);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 关键:告诉 VTK 过滤器已经被修改,需要重新执行
|
|
|
|
|
|
m_thresholdFilter->Modified();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果没有启用过滤
|
|
|
|
|
|
// 恢复到显示所有网格单元
|
|
|
|
|
|
m_thresholdFilter->ThresholdBetween(-VTK_DOUBLE_MAX, VTK_DOUBLE_MAX);
|
|
|
|
|
|
m_thresholdFilter->Modified();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_pVtkWidget->GetRenderWindow()->Render();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::createContourWidget()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果之前已存在,先清理(可选,但安全)
|
|
|
|
|
|
if (m_contourWidget) {
|
|
|
|
|
|
m_contourWidget->EnabledOff();
|
|
|
|
|
|
m_contourWidget->RemoveAllObservers();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 创建新的 ContourWidget
|
|
|
|
|
|
m_contourWidget = vtkSmartPointer<vtkContourWidget>::New();
|
|
|
|
|
|
m_contourWidget->SetInteractor(m_interactor);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 创建新的 Representation
|
|
|
|
|
|
m_contourRepresentation = vtkSmartPointer<vtkOrientedGlyphContourRepresentation>::New();
|
|
|
|
|
|
m_contourWidget->SetRepresentation(m_contourRepresentation);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 获取当前帧裁剪后的数据(阈值过滤器输出)
|
|
|
|
|
|
vtkSmartPointer<vtkGeometryFilter> geometry =
|
|
|
|
|
|
vtkSmartPointer<vtkGeometryFilter>::New();
|
|
|
|
|
|
geometry->SetInputConnection(m_thresholdFilter->GetOutputPort());
|
|
|
|
|
|
geometry->Update();
|
|
|
|
|
|
|
|
|
|
|
|
// 计算法线
|
|
|
|
|
|
vtkSmartPointer<vtkPolyDataNormals> normals =
|
|
|
|
|
|
vtkSmartPointer<vtkPolyDataNormals>::New();
|
|
|
|
|
|
normals->SetInputConnection(geometry->GetOutputPort());
|
|
|
|
|
|
normals->ComputePointNormalsOn(); // 关键点
|
|
|
|
|
|
normals->Update();
|
|
|
|
|
|
|
|
|
|
|
|
m_polyDataForContour = normals->GetOutput();
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 设置 PointPlacer(使用已有网格)
|
|
|
|
|
|
m_pointPlacer = vtkSmartPointer<vtkPolygonalSurfacePointPlacer>::New();
|
|
|
|
|
|
m_pointPlacer->GetPolys()->AddItem(m_polyDataForContour);
|
|
|
|
|
|
m_pointPlacer->AddProp(m_actor);
|
|
|
|
|
|
m_contourRepresentation->SetPointPlacer(m_pointPlacer);
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 设置样式
|
|
|
|
|
|
m_contourRepresentation->GetProperty()->SetColor(1.0, 1.0, 1.0);
|
|
|
|
|
|
m_contourRepresentation->GetProperty()->SetPointSize(8.0);
|
|
|
|
|
|
m_contourRepresentation->GetLinesProperty()->SetColor(1.0, 1.0, 1.0);
|
|
|
|
|
|
m_contourRepresentation->GetLinesProperty()->SetLineWidth(3.0);
|
|
|
|
|
|
m_contourRepresentation->SetRenderer(m_renderer);
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 设置插值器为空,轮廓线为直线
|
|
|
|
|
|
m_contourRepresentation->SetLineInterpolator(nullptr);
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 注册回调
|
|
|
|
|
|
m_contourWidget->AddObserver(vtkCommand::EndInteractionEvent, this,
|
|
|
|
|
|
&nmWxPostprocessingAnimationWidget::slotContourFinished);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::slotStartDrawPolygon() {
|
|
|
|
|
|
if (m_bIsDrawingMode) {
|
|
|
|
|
|
// 当前正在绘制,点击按钮表示“退出绘制”
|
|
|
|
|
|
slotClearContour();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 进入绘制模式
|
|
|
|
|
|
createContourWidget(); // 重新创建并初始化
|
|
|
|
|
|
m_contourWidget->EnabledOn();
|
|
|
|
|
|
m_bIsDrawingMode = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 可见性如果为不可见,则重新设置
|
|
|
|
|
|
if (m_contourRepresentation->GetVisibility() == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
m_contourRepresentation->SetVisibility(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_pVtkWidget->GetRenderWindow()->Render();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::slotContourFinished() {
|
|
|
|
|
|
// 1. 提取 contour 节点
|
|
|
|
|
|
vtkSmartPointer<vtkPoints> pts = vtkSmartPointer<vtkPoints>::New();
|
|
|
|
|
|
const int nNodes = m_contourRepresentation->GetNumberOfNodes();
|
|
|
|
|
|
for (int i = 0; i < nNodes; ++i) {
|
|
|
|
|
|
double p[3];
|
|
|
|
|
|
m_contourRepresentation->GetNthNodeWorldPosition(i, p);
|
|
|
|
|
|
p[2] = 0.0; // 强制投影到 Z=0
|
|
|
|
|
|
pts->InsertNextPoint(p);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保证首尾闭合
|
|
|
|
|
|
//double first[3], last[3];
|
|
|
|
|
|
//pts->GetPoint(0, first);
|
|
|
|
|
|
//pts->GetPoint(pts->GetNumberOfPoints() - 1, last);
|
|
|
|
|
|
//if (vtkMath::Distance2BetweenPoints(first, last) > 1e-12)
|
|
|
|
|
|
// pts->InsertNextPoint(first);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 2D 隐式选择环
|
|
|
|
|
|
vtkSmartPointer<vtkImplicitSelectionLoop> loop = vtkSmartPointer<vtkImplicitSelectionLoop>::New();
|
|
|
|
|
|
loop->SetLoop(pts);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 提取落在环内(含边界)的网格单元
|
|
|
|
|
|
vtkSmartPointer<vtkExtractGeometry> extract = vtkSmartPointer<vtkExtractGeometry>::New();
|
|
|
|
|
|
extract->SetInputConnection(m_thresholdFilter->GetOutputPort());
|
|
|
|
|
|
extract->SetImplicitFunction(loop);
|
|
|
|
|
|
extract->ExtractInsideOn();
|
|
|
|
|
|
extract->ExtractBoundaryCellsOn();
|
|
|
|
|
|
extract->Update();
|
|
|
|
|
|
|
|
|
|
|
|
vtkUnstructuredGrid* enclosedCellsGrid = extract->GetOutput();
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 计算平均压力
|
|
|
|
|
|
double avgPressure;
|
|
|
|
|
|
int enclosedCellCount;
|
|
|
|
|
|
vtkDataArray* pArray = enclosedCellsGrid->GetCellData()->GetArray("p");
|
|
|
|
|
|
if (!pArray)
|
|
|
|
|
|
{
|
|
|
|
|
|
qDebug() << "No 'p' array in cell data.";
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double total = 0.0;
|
|
|
|
|
|
enclosedCellCount = enclosedCellsGrid->GetNumberOfCells();
|
|
|
|
|
|
for (vtkIdType i = 0; i < enclosedCellCount; ++i)
|
|
|
|
|
|
total += pArray->GetTuple1(i);
|
|
|
|
|
|
|
|
|
|
|
|
QString message;
|
|
|
|
|
|
if (enclosedCellCount > 0) {
|
|
|
|
|
|
avgPressure = total / enclosedCellCount;
|
|
|
|
|
|
message = QString(tr("Total cells in contour: %1\nAverage pressure in contour: %2 MPa"))
|
|
|
|
|
|
.arg(enclosedCellCount)
|
|
|
|
|
|
.arg(avgPressure, 0, 'f', 4);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
message = tr("No cells were found within the defined contour.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调用非模态对话框显示结果
|
|
|
|
|
|
m_resultDialog->showResult(message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool nmWxPostprocessingAnimationWidget::isDrawingMode() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_bIsDrawingMode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
vtkRenderer* nmWxPostprocessingAnimationWidget::getRenderer() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_renderer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QVTKWidget* nmWxPostprocessingAnimationWidget::getVtkWidget() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return m_pVtkWidget;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::addContourPoint(double pos[3])
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!m_contourRepresentation) {
|
|
|
|
|
|
qDebug() << "Error: Contour representation is null.";
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double offsetPos[3] = {pos[0], pos[1], pos[2]};
|
|
|
|
|
|
|
|
|
|
|
|
int oldNodeCount = m_contourRepresentation->GetNumberOfNodes();
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试添加一个新节点
|
|
|
|
|
|
m_contourRepresentation->AddNodeAtWorldPosition(offsetPos);
|
|
|
|
|
|
|
|
|
|
|
|
int newNodeCount = m_contourRepresentation->GetNumberOfNodes();
|
|
|
|
|
|
|
|
|
|
|
|
if (newNodeCount > oldNodeCount) {
|
|
|
|
|
|
// 强制渲染更新
|
|
|
|
|
|
m_contourRepresentation->Modified();
|
|
|
|
|
|
m_contourWidget->Render();
|
|
|
|
|
|
m_pVtkWidget->GetRenderWindow()->Render();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
qDebug() << "Warning: Node was not added. PointPlacer might be rejecting the position.";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
vtkContourWidget* nmWxPostprocessingAnimationWidget::getContourWidget() const {
|
|
|
|
|
|
return m_contourWidget;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void nmWxPostprocessingAnimationWidget::slotClearContour() {
|
|
|
|
|
|
if (m_contourWidget) {
|
|
|
|
|
|
m_contourWidget->EnabledOff();
|
|
|
|
|
|
m_contourWidget = nullptr; // 让智能指针自动释放
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_bIsDrawingMode = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (m_resultDialog) {
|
|
|
|
|
|
m_resultDialog->hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_pVtkWidget->GetRenderWindow()->Render();
|
|
|
|
|
|
}
|