#include "nmWxVTKRenderContainerWidget.h" #include "nmVTKCameraController.h" #include "nmVTKImageExporter.h" #include "nmVTKInteractionManager.h" #include "nmVTKScene.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include VTK_MODULE_INIT(vtkRenderingOpenGL) VTK_MODULE_INIT(vtkInteractionStyle) #include #include #include namespace { // 资源目录位于可执行文件目录的上一级,与现有程序的图标部署结构一致。 QString renderResourceRoot() { QString sApplicationDir = QCoreApplication::applicationDirPath(); return sApplicationDir.section('/', 0, -2); } } nmWxVTKRenderContainerWidget::nmWxVTKRenderContainerWidget(QWidget* pParent) : QWidget(pParent), m_pMainLayout(NULL), m_pStateStack(NULL), m_pStatePage(NULL), m_pStateLabel(NULL), m_pVtkWidget(NULL), m_pRenderer(NULL), m_pScene(NULL), m_pCameraController(NULL), m_pInteractionManager(NULL), m_pImageExporter(NULL) { Q_ASSERT(isGuiThread()); // 1. 先创建稳定布局和状态页,避免无数据时改变容器尺寸。 m_pMainLayout = new QVBoxLayout(this); m_pMainLayout->setContentsMargins(0, 0, 0, 0); m_pMainLayout->setSpacing(0); m_pStateStack = new QStackedWidget(this); m_pStatePage = new QWidget(m_pStateStack); m_pStateLabel = new QLabel(m_pStatePage); QVBoxLayout* pStateLayout = new QVBoxLayout(m_pStatePage); pStateLayout->setContentsMargins(24, 24, 24, 24); pStateLayout->addStretch(); m_pStateLabel->setAlignment(Qt::AlignCenter); m_pStateLabel->setWordWrap(true); pStateLayout->addWidget(m_pStateLabel); pStateLayout->addStretch(); // 2. QVTKWidget 由 Qt 父子关系拥有,并作为就绪状态页面。 m_pVtkWidget = new QVTKWidget(m_pStateStack); m_pStateStack->addWidget(m_pVtkWidget); m_pStateStack->addWidget(m_pStatePage); m_pMainLayout->addWidget(m_pStateStack); // 3、4. RenderWindow 属于 QVTKWidget;Renderer 由本容器智能指针独占。 vtkRenderWindow* pRenderWindow = getRenderWindow(); vtkRenderWindowInteractor* pInteractor = getInteractor(); m_pRenderer = vtkSmartPointer::New(); if(pRenderWindow != NULL && m_pRenderer != NULL) { pRenderWindow->AddRenderer(m_pRenderer); m_pRenderer->SetGradientBackground(true); m_pRenderer->SetBackground(0.67, 0.82, 0.94); m_pRenderer->SetBackground2(0.0, 0.5, 1.0); } // 5. 三个管理对象和图片服务均只服务当前容器实例。 m_pScene = new nmVTKScene(m_pRenderer); m_pCameraController = new nmVTKCameraController(m_pRenderer); m_pInteractionManager = new nmVTKInteractionManager(); m_pImageExporter = new nmVTKImageExporter(); m_pImageExporter->setRenderWindow(pRenderWindow); const bool bInteractionReady = m_pInteractionManager->bindInteractor(pInteractor); // 6. 只接管右键菜单,左键、中键和滚轮继续交给 VTK。 m_pVtkWidget->installEventFilter(this); // 7. 基础 VTK 对象完整时进入就绪页,否则保留明确错误状态。 if(pRenderWindow != NULL && m_pRenderer != NULL && bInteractionReady) { showReadyState(); } else { showErrorState(tr("Unable to initialize the render view.")); } } nmWxVTKRenderContainerWidget::~nmWxVTKRenderContainerWidget() { Q_ASSERT(isGuiThread()); // 1. 先阻止新的右键菜单回调进入销毁中的对象。 if(m_pVtkWidget != NULL) { m_pVtkWidget->removeEventFilter(this); } // 图片服务不拥有 RenderWindow,先解除引用再销毁服务。 if(m_pImageExporter != NULL) { m_pImageExporter->setRenderWindow(NULL); delete m_pImageExporter; m_pImageExporter = NULL; } // 2. Interactor 必须先释放对默认 Style 的 VTK 引用。 if(m_pInteractionManager != NULL) { m_pInteractionManager->unbindInteractor(); delete m_pInteractionManager; m_pInteractionManager = NULL; } // 3. Scene 在 Renderer 移除前清空所有 Layer 和 Prop。 if(m_pScene != NULL) { m_pScene->clear(); delete m_pScene; m_pScene = NULL; } // 4. 相机控制器仅解除 Renderer 非所有权引用。 if(m_pCameraController != NULL) { m_pCameraController->setRenderer(NULL); delete m_pCameraController; m_pCameraController = NULL; } // 5、6. 最后从 RenderWindow 移除并释放当前实例 Renderer。 vtkRenderWindow* pRenderWindow = getRenderWindow(); if(pRenderWindow != NULL && m_pRenderer != NULL) { pRenderWindow->RemoveRenderer(m_pRenderer); } m_pRenderer = NULL; // 7. QVTKWidget、状态页和布局随后由 Qt 父子关系自动销毁。 } QVTKWidget* nmWxVTKRenderContainerWidget::getVtkWidget() const { return m_pVtkWidget; } vtkRenderer* nmWxVTKRenderContainerWidget::getRenderer() const { return m_pRenderer; } vtkRenderWindow* nmWxVTKRenderContainerWidget::getRenderWindow() const { return m_pVtkWidget != NULL ? m_pVtkWidget->GetRenderWindow() : NULL; } vtkRenderWindowInteractor* nmWxVTKRenderContainerWidget::getInteractor() const { vtkRenderWindow* pRenderWindow = getRenderWindow(); return pRenderWindow != NULL ? pRenderWindow->GetInteractor() : NULL; } nmVTKScene* nmWxVTKRenderContainerWidget::getScene() const { return m_pScene; } bool nmWxVTKRenderContainerWidget::installInteractionStyle( vtkInteractorStyle* pStyle) { Q_ASSERT(isGuiThread()); return isGuiThread() && m_pInteractionManager != NULL && m_pInteractionManager->installInteractionStyle(pStyle); } bool nmWxVTKRenderContainerWidget::restoreDefaultInteraction() { Q_ASSERT(isGuiThread()); return isGuiThread() && m_pInteractionManager != NULL && m_pInteractionManager->restoreDefaultInteraction(); } void nmWxVTKRenderContainerWidget::clearInteractionStyle() { Q_ASSERT(isGuiThread()); if(isGuiThread() && m_pInteractionManager != NULL) { m_pInteractionManager->clearInteractionStyle(); } } void nmWxVTKRenderContainerWidget::renderView() { Q_ASSERT(isGuiThread()); vtkRenderWindow* pRenderWindow = getRenderWindow(); // Scene、Layer 和相机均不隐式渲染,批量更新最终汇聚到此入口。 if(isGuiThread() && pRenderWindow != NULL) { pRenderWindow->Render(); } } bool nmWxVTKRenderContainerWidget::fitTopViewWithoutRender() { Q_ASSERT(isGuiThread()); // 相机控制器本身不触发 Render,便于业务图层完成整批更新后只渲染一次。 return isGuiThread() && m_pCameraController != NULL && m_pCameraController->setTopView(); } void nmWxVTKRenderContainerWidget::showReadyState() { Q_ASSERT(isGuiThread()); if(!isGuiThread()) { return; } // 状态页与 VTK 页尺寸稳定,切换页面不会重建 RenderWindow 或 Renderer。 m_pStateStack->setCurrentWidget(m_pVtkWidget); renderView(); } void nmWxVTKRenderContainerWidget::showEmptyState(const QString& sMessage) { showMessageState(ViewState_Empty, sMessage); } void nmWxVTKRenderContainerWidget::showErrorState(const QString& sMessage) { showMessageState(ViewState_Error, sMessage); } void nmWxVTKRenderContainerWidget::showRenderingState( const QString& sMessage) { showMessageState(ViewState_Rendering, sMessage); } void nmWxVTKRenderContainerWidget::resetCamera() { // 控制器用返回值表示相机是否确实改变,成功时只提交一次渲染。 if(m_pCameraController != NULL && m_pCameraController->resetCamera()) { renderView(); } } void nmWxVTKRenderContainerWidget::setTopView() { if(m_pCameraController != NULL && m_pCameraController->setTopView()) { renderView(); } } void nmWxVTKRenderContainerWidget::setFrontView() { if(m_pCameraController != NULL && m_pCameraController->setFrontView()) { renderView(); } } void nmWxVTKRenderContainerWidget::setRightView() { if(m_pCameraController != NULL && m_pCameraController->setRightView()) { renderView(); } } void nmWxVTKRenderContainerWidget::setIsometricView() { if(m_pCameraController != NULL && m_pCameraController->setIsometricView()) { renderView(); } } void nmWxVTKRenderContainerWidget::setRotationCenter( double dX, double dY, double dZ) { if(m_pCameraController != NULL && m_pCameraController->setRotationCenter(dX, dY, dZ)) { renderView(); } } void nmWxVTKRenderContainerWidget::resetRotationCenter() { if(m_pCameraController != NULL && m_pCameraController->resetRotationCenter()) { renderView(); } } bool nmWxVTKRenderContainerWidget::eventFilter( QObject* pObject, QEvent* pEvent) { if(pObject == m_pVtkWidget && pEvent != NULL && pEvent->type() == QEvent::MouseButtonPress) { QMouseEvent* pMouseEvent = static_cast(pEvent); if(pMouseEvent->button() == Qt::RightButton) { // 菜单为栈对象,扩展信号只能在同步处理期间使用该指针。 // 返回 true 只消费右键按下,避免 VTK 同时启动一次交互动作。 QMenu oMenu(this); const QString sResourceRoot = renderResourceRoot(); QAction* pSaveAction = oMenu.addAction( QIcon(sResourceRoot + "/Res/Icon/SaveImg.png"), tr("Save as Image")); connect(pSaveAction, SIGNAL(triggered()), this, SLOT(saveImage())); QAction* pCopyAction = oMenu.addAction( QIcon(sResourceRoot + "/Res/Icon/Copy.png"), tr("Copy Image")); connect(pCopyAction, SIGNAL(triggered()), this, SLOT(copyImage())); oMenu.addSeparator(); QMenu* pViewMenu = oMenu.addMenu(tr("View")); QAction* pResetAction = pViewMenu->addAction(tr("Reset Camera")); connect(pResetAction, SIGNAL(triggered()), this, SLOT(resetCamera())); pViewMenu->addSeparator(); QAction* pTopAction = pViewMenu->addAction(tr("Top View")); connect(pTopAction, SIGNAL(triggered()), this, SLOT(setTopView())); QAction* pFrontAction = pViewMenu->addAction(tr("Front View")); connect(pFrontAction, SIGNAL(triggered()), this, SLOT(setFrontView())); QAction* pRightAction = pViewMenu->addAction(tr("Right View")); connect(pRightAction, SIGNAL(triggered()), this, SLOT(setRightView())); QAction* pIsometricAction = pViewMenu->addAction(tr("Isometric View")); connect(pIsometricAction, SIGNAL(triggered()), this, SLOT(setIsometricView())); pViewMenu->addSeparator(); QAction* pCenterAction = pViewMenu->addAction(tr("Reset Rotation Center")); connect(pCenterAction, SIGNAL(triggered()), this, SLOT(resetRotationCenter())); oMenu.addSeparator(); const int nActionCount = oMenu.actions().size(); // 扩展项严格插入“视图”和“打印”之间;无扩展项时不产生多余分隔线。 emit contextMenuAboutToShow(&oMenu); if(oMenu.actions().size() > nActionCount) { oMenu.addSeparator(); } QAction* pPrintAction = oMenu.addAction( QIcon(sResourceRoot + "/Res/Icon/Print.png"), tr("Print")); connect(pPrintAction, SIGNAL(triggered()), this, SLOT(printImage())); QAction* pPreviewAction = oMenu.addAction( QIcon(sResourceRoot + "/Res/Icon/PrePrint.png"), tr("Print Preview")); connect(pPreviewAction, SIGNAL(triggered()), this, SLOT(printPreview())); oMenu.exec(pMouseEvent->globalPos()); return true; } return false; } return QWidget::eventFilter(pObject, pEvent); } void nmWxVTKRenderContainerWidget::saveImage() { if(m_pImageExporter == NULL) { return; } // 实际编码格式由 QImage 按调用方选择的扩展名决定。 const QString sFileName = QFileDialog::getSaveFileName( this, tr("Save Image"), QString(), tr("PNG Image (*.png);;JPEG Image (*.jpg);;BMP Image (*.bmp)")); if(!sFileName.isEmpty() && !m_pImageExporter->saveImage(sFileName)) { QMessageBox::warning(this, tr("Save Failed"), tr("Unable to save the image.")); } } void nmWxVTKRenderContainerWidget::copyImage() { if(m_pImageExporter != NULL && !m_pImageExporter->copyImageToClipboard()) { QMessageBox::warning(this, tr("Copy Failed"), tr("Unable to capture the image.")); } } void nmWxVTKRenderContainerWidget::printImage() { if(m_pImageExporter != NULL) { m_pImageExporter->printImage(this); } } void nmWxVTKRenderContainerWidget::printPreview() { if(m_pImageExporter != NULL) { m_pImageExporter->printPreview(this); } } void nmWxVTKRenderContainerWidget::showMessageState( ViewState eState, const QString& sMessage) { Q_ASSERT(isGuiThread()); if(!isGuiThread() || m_pStateStack == NULL || m_pStatePage == NULL || m_pStateLabel == NULL) { return; } // 所有非就绪状态共用同一页面,仅错误状态使用警示颜色。 if(eState == ViewState_Error) { m_pStateLabel->setStyleSheet("QLabel { color: #a32626; }"); } else { m_pStateLabel->setStyleSheet("QLabel { color: #4a4a4a; }"); } m_pStateLabel->setText(sMessage); m_pStateStack->setCurrentWidget(m_pStatePage); } bool nmWxVTKRenderContainerWidget::isGuiThread() const { QCoreApplication* pApplication = QCoreApplication::instance(); return pApplication == NULL || pApplication->thread() == QThread::currentThread(); }