Skip to content

Commit 1ddcac5

Browse files
authored
[3d] missing piece of the jigsaw: save 3d canvas as image (#5927)
1 parent 909a0fc commit 1ddcac5

4 files changed

+51
-0
lines changed

src/app/3d/qgs3dmapcanvas.cpp

+22
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include <QBoxLayout>
1919
#include <Qt3DExtras/Qt3DWindow>
20+
#include <Qt3DRender/QRenderCapture>
2021

2122
#include "qgscameracontroller.h"
2223
#include "qgs3dmapsettings.h"
@@ -27,6 +28,11 @@ Qgs3DMapCanvas::Qgs3DMapCanvas( QWidget *parent )
2728
: QWidget( parent )
2829
{
2930
mWindow3D = new Qt3DExtras::Qt3DWindow;
31+
32+
mCapture = new Qt3DRender::QRenderCapture;
33+
mWindow3D->activeFrameGraph()->setParent( mCapture );
34+
mWindow3D->setActiveFrameGraph( mCapture );
35+
3036
mContainer = QWidget::createWindowContainer( mWindow3D );
3137

3238
QHBoxLayout *hLayout = new QHBoxLayout( this );
@@ -89,3 +95,19 @@ void Qgs3DMapCanvas::setViewFromTop( const QgsPointXY &center, float distance, f
8995
float worldY = center.y() - mMap->origin().y();
9096
mScene->cameraController()->setViewFromTop( worldX, -worldY, distance, rotation );
9197
}
98+
99+
void Qgs3DMapCanvas::saveAsImage( const QString fileName, const QString fileFormat )
100+
{
101+
if ( !fileName.isEmpty() )
102+
{
103+
Qt3DRender::QRenderCaptureReply *captureReply;
104+
captureReply = mCapture->requestCapture();
105+
connect( captureReply, &Qt3DRender::QRenderCaptureReply::completed, this, [ = ]
106+
{
107+
captureReply->image().save( fileName, fileFormat.toLocal8Bit().data() );
108+
emit savedAsImage( fileName );
109+
110+
captureReply->deleteLater();
111+
} );
112+
}
113+
}

src/app/3d/qgs3dmapcanvas.h

+11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#define QGS3DMAPCANVAS_H
1818

1919
#include <QWidget>
20+
#include <Qt3DRender/QRenderCapture>
2021

2122
namespace Qt3DExtras
2223
{
@@ -54,12 +55,22 @@ class Qgs3DMapCanvas : public QWidget
5455
//! Sets camera position to look down at the given point (in map coordinates) in given distance from plane with zero elevation
5556
void setViewFromTop( const QgsPointXY &center, float distance, float rotation = 0 );
5657

58+
//! Saves the current scene as an image
59+
void saveAsImage( const QString fileName, const QString fileFormat );
60+
61+
signals:
62+
//! Emitted when the 3D map canvas was successfully saved as image
63+
void savedAsImage( const QString fileName );
64+
5765
protected:
5866
void resizeEvent( QResizeEvent *ev ) override;
5967

6068
private:
6169
//! 3D window with all the 3D magic inside
6270
Qt3DExtras::Qt3DWindow *mWindow3D = nullptr;
71+
//! Frame graph node for render capture
72+
Qt3DRender::QRenderCapture *mCapture = nullptr;
73+
6374
//! Container QWidget that encapsulates mWindow3D so we can use it embedded in ordinary widgets app
6475
QWidget *mContainer = nullptr;
6576
//! Description of the 3D scene

src/app/3d/qgs3dmapcanvasdockwidget.cpp

+17
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <QDialogButtonBox>
3131
#include <QProgressBar>
3232
#include <QToolBar>
33+
#include <QUrl>
3334

3435
Qgs3DMapCanvasDockWidget::Qgs3DMapCanvasDockWidget( QWidget *parent )
3536
: QgsDockWidget( parent )
@@ -42,13 +43,20 @@ Qgs3DMapCanvasDockWidget::Qgs3DMapCanvasDockWidget( QWidget *parent )
4243
toolBar->setIconSize( QgisApp::instance()->iconSize( true ) );
4344
toolBar->addAction( QgsApplication::getThemeIcon( QStringLiteral( "mActionZoomFullExtent.svg" ) ),
4445
tr( "Zoom Full" ), this, &Qgs3DMapCanvasDockWidget::resetView );
46+
toolBar->addAction( QgsApplication::getThemeIcon( QStringLiteral( "mActionSaveMapAsImage.svg" ) ),
47+
tr( "Save as image..." ), this, &Qgs3DMapCanvasDockWidget::saveAsImage );
4548
toolBar->addAction( QgsApplication::getThemeIcon( QStringLiteral( "mIconProperties.svg" ) ),
4649
tr( "Configure" ), this, &Qgs3DMapCanvasDockWidget::configure );
4750

4851
mCanvas = new Qgs3DMapCanvas( contentsWidget );
4952
mCanvas->setMinimumSize( QSize( 200, 200 ) );
5053
mCanvas->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
5154

55+
connect( mCanvas, &Qgs3DMapCanvas::savedAsImage, this, [ = ]( const QString fileName )
56+
{
57+
QgisApp::instance()->messageBar()->pushSuccess( tr( "Save as Image" ), tr( "Successfully saved the 3D map to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( QFileInfo( fileName ).path() ).toString(), fileName ) );
58+
} );
59+
5260
mLabelPendingJobs = new QLabel( this );
5361
mProgressPendingJobs = new QProgressBar( this );
5462
mProgressPendingJobs->setRange( 0, 0 );
@@ -74,6 +82,15 @@ Qgs3DMapCanvasDockWidget::Qgs3DMapCanvasDockWidget( QWidget *parent )
7482
onTerrainPendingJobsCountChanged();
7583
}
7684

85+
void Qgs3DMapCanvasDockWidget::saveAsImage()
86+
{
87+
QPair< QString, QString> fileNameAndFilter = QgsGuiUtils::getSaveAsImageName( this, tr( "Choose a file name to save the 3D map canvas to an image" ) );
88+
if ( !fileNameAndFilter.first.isEmpty() )
89+
{
90+
mCanvas->saveAsImage( fileNameAndFilter.first, fileNameAndFilter.second );
91+
}
92+
}
93+
7794
void Qgs3DMapCanvasDockWidget::setMapSettings( Qgs3DMapSettings *map )
7895
{
7996
mCanvas->setMap( map );

src/app/3d/qgs3dmapcanvasdockwidget.h

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class Qgs3DMapCanvasDockWidget : public QgsDockWidget
4242
private slots:
4343
void resetView();
4444
void configure();
45+
void saveAsImage();
4546

4647
void onMainCanvasLayersChanged();
4748
void onMainCanvasColorChanged();

0 commit comments

Comments
 (0)