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/nmRender/nmWxVTKRenderContainerWidge...

450 lines
14 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 "nmWxVTKRenderContainerWidget.h"
#include "nmVTKCameraController.h"
#include "nmVTKImageExporter.h"
#include "nmVTKInteractionManager.h"
#include "nmVTKScene.h"
#include <QAction>
#include <QCoreApplication>
#include <QEvent>
#include <QFileDialog>
#include <QIcon>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QMouseEvent>
#include <QStackedWidget>
#include <QThread>
#include <QVBoxLayout>
#include <QVTKWidget.h>
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL)
VTK_MODULE_INIT(vtkInteractionStyle)
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
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 属于 QVTKWidgetRenderer 由本容器智能指针独占。
vtkRenderWindow* pRenderWindow = getRenderWindow();
vtkRenderWindowInteractor* pInteractor = getInteractor();
m_pRenderer = vtkSmartPointer<vtkRenderer>::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<QMouseEvent*>(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();
}