You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmWxGridVTKContainerWidget.cpp

440 lines
17 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmWxGridVTKContainerWidget.h"
// Qt 核心头文件
#include <QVBoxLayout>
#include <QFrame>
#include <QDebug>
#include <QPixmap> // 用于捕获部件内容为图像
#include <QFileDialog> // 用于文件保存对话框
#include <QMessageBox> // 用于显示消息框
#include <QApplication> // 用于访问全局应用程序对象 (如剪贴板)
#include <QClipboard> // 用于剪贴板操作
#include <QPrinter> // 用于打印机抽象
#include <QPrintDialog> // 用于打印设置对话框
#include <QPrintPreviewDialog> // 用于打印预览对话框
#include <QPainter> // 用于绘制打印内容
#include <QMenu> // 用于创建右键上下文菜单
#include <QAction> // 用于菜单动作
#include <QLinearGradient>
#include <QCoreApplication>
// VTK 核心头文件
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL)
VTK_MODULE_INIT(vtkInteractionStyle)
// VTK 可视化相关
#include <vtkActor.h>
#include <vtkDataSetMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkSmartPointer.h>
#include <vtkUnstructuredGrid.h>
#include <vtkCellData.h>
#include <vtkLookupTable.h>
#include <vtkDataArray.h>
#include <QVTKWidget.h>
#include <vtkRendererCollection.h>
#include <vtkWindowToImageFilter.h>
#include <vtkImageData.h>
#include <vtkImageFlip.h>
#include <vtkPNGWriter.h>
#include <vtkUnsignedCharArray.h>
#include <vtkPointData.h>
// 项目特定头文件
#include "nmDataAnalyzeManager.h"
// Windows 库链接 (保留必要项)
#pragma comment(lib,"User32.lib")
#pragma comment(lib,"gdi32.lib")
#pragma comment(lib,"OpenGL32.lib")
#pragma comment(lib,"AdvAPI32.lib")
#pragma comment(lib,"shell32.lib")
nmWxGridVTKContainerWidget::nmWxGridVTKContainerWidget(QWidget *parent) : QWidget(parent)
{
m_pVtkWidget = nullptr;
this->initLayout();
this->initVTKWidget();
}
QImage nmWxGridVTKContainerWidget::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 nmWxGridVTKContainerWidget::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 nmWxGridVTKContainerWidget::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 nmWxGridVTKContainerWidget::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 nmWxGridVTKContainerWidget::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 nmWxGridVTKContainerWidget::eventFilter(QObject *pObj, QEvent *event)
{
// 检查事件是否来自我们的 VTK 部件,并且是鼠标按下事件
if(pObj == m_pVtkWidget && event->type() == QEvent::MouseButtonPress) {
QMouseEvent *pMouseEVent = static_cast<QMouseEvent*>(event);
if(pMouseEVent->button() == Qt::RightButton) {
// 在右键点击 m_pVtkWidget 时,直接显示我们的自定义菜单
QMenu menu(this); // 菜单的父对象仍然是 nmWxGridVTKContainerWidget
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");
// 添加“保存为图片”动作,并为其添加图标
QAction *pSaveAction = new QAction(saveIcon, tr("Save as Image"), this);
connect(pSaveAction, SIGNAL(triggered()), this, SLOT(saveWidgetAsImage()));
menu.addAction(pSaveAction);
// 添加“复制图片”动作,并为其添加图标
QAction *pCopyAction = new QAction(copyIcon, tr("Copy Image"), this);
connect(pCopyAction, SIGNAL(triggered()), this, SLOT(copyWidgetImage()));
menu.addAction(pCopyAction);
menu.addSeparator(); // 添加分隔线
// 添加“打印”动作,并为其添加图标
QAction *pPrintAction = new QAction(printIcon, tr("Print"), this);
connect(pPrintAction, SIGNAL(triggered()), this, SLOT(printWidget()));
menu.addAction(pPrintAction);
// 添加“打印预览”动作,并为其添加图标
QAction *pPrintPreviewAction = new QAction(printPreviewIcon, tr("Print Preview"), this);
connect(pPrintPreviewAction, SIGNAL(triggered()), this, SLOT(printPreviewWidget()));
menu.addAction(pPrintPreviewAction);
menu.exec(pMouseEVent->globalPos()); // 在鼠标的全局位置执行菜单
return true; // **事件已处理,停止 QVTKWidget 的进一步处理**
}
}
// 对于其他事件或对象,将它们传递给基类的事件过滤器
return QWidget::eventFilter(pObj, event);
}
void nmWxGridVTKContainerWidget::initVTKWidget()
{
// 从数据中心获取当前非结构化网格对象
nmDataAnalyzeManager* pCurDataManager = nmDataAnalyzeManager::getCurrentInstance();
Q_ASSERT(nullptr != pCurDataManager);
vtkSmartPointer<vtkUnstructuredGrid> pGrid = pCurDataManager->getUnstructuredGrid();
Q_ASSERT(nullptr != pGrid);
// 创建自定义 nmWxQVTKWidget 的实例。
// 此部件将处理其自身的上下文菜单事件。
m_pVtkWidget = new QVTKWidget(this); // 'this' 是 QVTKWidget 的父部件。
m_pMainLayout->addWidget(m_pVtkWidget); // 将 VTK 部件添加到 m_pMainLayout 的布局中。
// 安装事件过滤器
m_pVtkWidget->installEventFilter(this);
// 创建映射器
vtkSmartPointer<vtkDataSetMapper> pMapper = vtkSmartPointer<vtkDataSetMapper>::New();
pMapper->SetInputData(pGrid); // 传入非结构化网格
// 检查是否有Material场数据
vtkSmartPointer<vtkCellData> pCellData = pGrid->GetCellData();
vtkDataArray* pMaterialArray = pCellData->GetArray("Material");
if(pMaterialArray) {
// 创建颜色映射表
vtkSmartPointer<vtkLookupTable> pLut = vtkSmartPointer<vtkLookupTable>::New();
pLut->SetNumberOfColors(256);
pLut->SetHueRange(0.67, 0.0); // 从蓝色到红色
pLut->SetSaturationRange(1.0, 1.0);
pLut->SetValueRange(1.0, 1.0);
pLut->Build();
pMapper->SetScalarModeToUseCellData();
pMapper->SelectColorArray("Material");
pMapper->SetScalarRange(pMaterialArray->GetRange());
pMapper->SetLookupTable(pLut);
}
// 创建actor并设置属性
vtkSmartPointer<vtkActor> pActor = vtkSmartPointer<vtkActor>::New();
pActor->SetMapper(pMapper);
vtkProperty* pActorProperty = pActor->GetProperty();
pActorProperty->SetRepresentationToSurface();
pActorProperty->EdgeVisibilityOn(); // 显示边缘
pActorProperty->SetEdgeVisibility(true); // 允许交互
pActorProperty->SetColor(0, 120.0 / 255.0, 215.0 / 255.0); // 设置面的颜色
// 创建渲染器并设置背景
vtkSmartPointer<vtkRenderer> pRenderer = vtkSmartPointer<vtkRenderer>::New();
pRenderer->AddActor(pActor);
pRenderer->SetGradientBackground(true);
pRenderer->SetBackground(0.67, 0.82, 0.94); // 浅蓝
pRenderer->SetBackground2(0.0, 0.5, 1.0); // 深蓝
pRenderer->ResetCamera();
// 创建渲染窗口
vtkSmartPointer<vtkRenderWindow> pRenderWindow = vtkSmartPointer<vtkRenderWindow>::New();
pRenderWindow->AddRenderer(pRenderer);
// 将 VTK 渲染窗口设置到我们的 nmWxQVTKWidget。
m_pVtkWidget->SetRenderWindow(pRenderWindow); // 设置渲染窗口
pRenderWindow->Render(); // 执行初始渲染
}
void nmWxGridVTKContainerWidget::initLayout()
{
m_pMainLayout = new QVBoxLayout(this); // 设置当前Widget的布局
// 移除布局的默认边距和间距
m_pMainLayout->setContentsMargins(0, 0, 0, 0); // 设置边距为0
m_pMainLayout->setSpacing(0); // 设置子控件间距为0
}
// --- 图片和打印操作 ---
// saveWidgetAsImage: 将 m_pVtkWidget 的内容保存到图像文件。
void nmWxGridVTKContainerWidget::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 nmWxGridVTKContainerWidget::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 nmWxGridVTKContainerWidget::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 nmWxGridVTKContainerWidget::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 nmWxGridVTKContainerWidget::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 nmWxGridVTKContainerWidget::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);
}