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...

442 lines
14 KiB
C++

#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();
}
}
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();
}