#include "nmWxGridVTKContainerWidget.h" // Qt 核心头文件 #include #include #include #include // 用于捕获部件内容为图像 #include // 用于文件保存对话框 #include // 用于显示消息框 #include // 用于访问全局应用程序对象 (如剪贴板) #include // 用于剪贴板操作 #include // 用于打印机抽象 #include // 用于打印设置对话框 #include // 用于打印预览对话框 #include // 用于绘制打印内容 #include // 用于创建右键上下文菜单 #include // 用于菜单动作 #include #include // VTK 核心头文件 #include VTK_MODULE_INIT(vtkRenderingOpenGL) VTK_MODULE_INIT(vtkInteractionStyle) // VTK 可视化相关 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 项目特定头文件 #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(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 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 pMapper = vtkSmartPointer::New(); pMapper->SetInputData(pGrid); // 传入非结构化网格 // 检查是否有Material场数据 vtkSmartPointer pCellData = pGrid->GetCellData(); vtkDataArray* pMaterialArray = pCellData->GetArray("Material"); if(pMaterialArray) { // 创建颜色映射表 vtkSmartPointer pLut = vtkSmartPointer::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 pActor = vtkSmartPointer::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 pRenderer = vtkSmartPointer::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 pRenderWindow = vtkSmartPointer::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 windowToImageFilter = vtkSmartPointer::New(); windowToImageFilter->SetInput(pRenderWindow); // 明确设置为 RGB windowToImageFilter->SetInputBufferTypeToRGB(); // 从后台缓冲区读取以确保完整渲染 windowToImageFilter->ReadFrontBufferOff(); windowToImageFilter->Update(); // 执行图像转换 // 获取 VTK 图像数据 (未经翻转) vtkImageData* pImageData = windowToImageFilter->GetOutput(); // VTK 图像数据的原点在左下角,而 Qt 的 QImage 原点在左上角,需要垂直翻转 vtkSmartPointer flip = vtkSmartPointer::New(); flip->SetInputData(pImageData); //flip->SetInputConnection(windowToImageFilter->GetOutputPort()); // 用这个方法翻转后图像显示的是黑色的!!! flip->SetFilteredAxis(1); // 翻转 Y 轴(垂直翻转) flip->Update(); // 翻转操作在这里执行 // 获取翻转后的 VTK 图像数据 vtkImageData* pFlippedImageData = flip->GetOutput(); // 获取翻转后的数据 return createQImage(pFlippedImageData); }