diff --git a/src/3d/CMakeLists.txt b/src/3d/CMakeLists.txt index 73d3ba764867..8baef00edacb 100644 --- a/src/3d/CMakeLists.txt +++ b/src/3d/CMakeLists.txt @@ -4,6 +4,7 @@ SET(QGIS_3D_SRCS qgsaabb.cpp qgsabstract3dengine.cpp + qgs3danimationsettings.cpp qgs3dmapscene.cpp qgs3dmapsettings.cpp qgs3dutils.cpp @@ -90,6 +91,7 @@ QT5_ADD_RESOURCES(QGIS_3D_RCC_SRCS shaders.qrc) SET(QGIS_3D_HDRS qgsaabb.h qgsabstract3dengine.h + qgs3danimationsettings.h qgs3dmapscene.h qgs3dmapsettings.h qgs3dtypes.h diff --git a/src/app/3d/qgs3danimationsettings.cpp b/src/3d/qgs3danimationsettings.cpp similarity index 98% rename from src/app/3d/qgs3danimationsettings.cpp rename to src/3d/qgs3danimationsettings.cpp index 82a04fbfb5ed..e5e4db8aa5e1 100644 --- a/src/app/3d/qgs3danimationsettings.cpp +++ b/src/3d/qgs3danimationsettings.cpp @@ -18,6 +18,8 @@ #include #include +Qgs3DAnimationSettings::Qgs3DAnimationSettings() = default; + float Qgs3DAnimationSettings::duration() const { return mKeyframes.isEmpty() ? 0 : mKeyframes.constLast().time; diff --git a/src/app/3d/qgs3danimationsettings.h b/src/3d/qgs3danimationsettings.h similarity index 95% rename from src/app/3d/qgs3danimationsettings.h rename to src/3d/qgs3danimationsettings.h index 722a3406a1cf..817c5d7ce7fc 100644 --- a/src/app/3d/qgs3danimationsettings.h +++ b/src/3d/qgs3danimationsettings.h @@ -17,6 +17,7 @@ #define QGS3DANIMATIONSETTINGS_H #include "qgsvector3d.h" +#include "qgis_3d.h" #include #include @@ -26,13 +27,16 @@ class QDomElement; class QgsReadWriteContext; /** + * \ingroup 3d * Class that holds information about animation in 3D view. The animation is defined * as a series of keyframes + * \since QGIS 3.8 */ -class Qgs3DAnimationSettings +class _3D_EXPORT Qgs3DAnimationSettings { public: - Qgs3DAnimationSettings() = default; + //! ctor + Qgs3DAnimationSettings(); //! keyframe definition struct Keyframe diff --git a/src/3d/qgs3dutils.cpp b/src/3d/qgs3dutils.cpp index 4b6da1b9e771..1ebbdc10d28f 100644 --- a/src/3d/qgs3dutils.cpp +++ b/src/3d/qgs3dutils.cpp @@ -23,10 +23,15 @@ #include "qgsabstractgeometry.h" #include "qgsvectorlayer.h" #include "qgsexpressioncontextutils.h" +#include "qgsfeedback.h" +#include "qgsexpression.h" +#include "qgsexpressionutils.h" +#include "qgsoffscreen3dengine.h" #include "qgs3dmapscene.h" #include "qgsabstract3dengine.h" #include "qgsterraingenerator.h" +#include "qgscameracontroller.h" #include "qgsline3dsymbol.h" #include "qgspoint3dsymbol.h" @@ -34,7 +39,6 @@ #include - QImage Qgs3DUtils::captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene ) { QImage resImage; @@ -76,6 +80,93 @@ QImage Qgs3DUtils::captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene return resImage; } +bool Qgs3DUtils::exportAnimation( const Qgs3DAnimationSettings &animationSettings, + const Qgs3DMapSettings &mapSettings, + int framesPerSecond, + const QString &outputDirectory, + const QString &fileNameTemplate, + const QSize &outputSize, + QString &error, + QgsFeedback *feedback + ) +{ + QgsOffscreen3DEngine engine; + engine.setSize( outputSize ); + Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine ); + engine.setRootEntity( scene ); + + if ( animationSettings.keyFrames().size() < 2 ) + { + error = QObject::tr( "Unable to export 3D animation. Add at least 2 keyframes" ); + return false; + } + + const float duration = animationSettings.duration(); //in seconds + if ( duration <= 0 ) + { + error = QObject::tr( "Unable to export 3D animation (invalid duration)." ); + return false; + } + + float time = 0; + int frameNo = 0; + int totalFrames = static_cast( duration * framesPerSecond ); + + if ( fileNameTemplate.isEmpty() ) + { + error = QObject::tr( "Filename template is empty" ); + return false; + } + + int numberOfDigits = fileNameTemplate.count( QLatin1Char( '#' ) ); + if ( numberOfDigits < 0 ) + { + error = QObject::tr( "Wrong filename template format (must contain #)" ); + return false; + } + const QString token( numberOfDigits, QLatin1Char( '#' ) ); + if ( !fileNameTemplate.contains( token ) ) + { + error = QObject::tr( "Filename template must contain all # placeholders in one continuous group." ); + return false; + } + + while ( time <= duration ) + { + + if ( feedback ) + { + if ( feedback->isCanceled() ) + { + error = QObject::tr( "Export canceled" ); + return false; + } + feedback->setProgress( frameNo / static_cast( totalFrames ) * 100 ); + } + ++frameNo; + + Qgs3DAnimationSettings::Keyframe kf = animationSettings.interpolate( time ); + scene->cameraController()->setLookingAtPoint( kf.point, kf.dist, kf.pitch, kf.yaw ); + + QString fileName( fileNameTemplate ); + const QString frameNoPaddedLeft( QStringLiteral( "%1" ).arg( frameNo, numberOfDigits, 10, QChar( '0' ) ) ); // e.g. 0001 + fileName.replace( token, frameNoPaddedLeft ); + const QString path = QDir( outputDirectory ).filePath( fileName ); + + // It would initially return empty rendered image. + // Capturing the initial image and throwing it away fixes that. + // Hopefully we will find a better fix in the future. + Qgs3DUtils::captureSceneImage( engine, scene ); + QImage img = Qgs3DUtils::captureSceneImage( engine, scene ); + + img.save( path ); + + time += 1.0f / static_cast( framesPerSecond ); + } + + return true; +} + int Qgs3DUtils::maxZoomLevel( double tile0width, double tileResolution, double maxError ) { diff --git a/src/3d/qgs3dutils.h b/src/3d/qgs3dutils.h index 7ce82ddf9600..2f304377645e 100644 --- a/src/3d/qgs3dutils.h +++ b/src/3d/qgs3dutils.h @@ -20,6 +20,7 @@ class QgsLineString; class QgsPolygon; +class QgsFeedback; class QgsAbstract3DEngine; class QgsAbstract3DSymbol; @@ -31,6 +32,7 @@ namespace Qt3DExtras } #include "qgs3dmapsettings.h" +#include "qgs3danimationsettings.h" #include "qgs3dtypes.h" #include "qgsaabb.h" @@ -54,6 +56,34 @@ class _3D_EXPORT Qgs3DUtils */ static QImage captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene ); + /** + * Captures 3D animation frames to the selected folder + * + * \param animationSettings Settings for keyframes and camera + * \param mapSettings 3d map settings + * \param framesPerSecond number of frames per second to export + * \param outputDirectory output directory where to export frames + * \param fileNameTemplate template for exporting the frames. + * Must be in format prefix####.format, where number of + * # represents how many 0 should be left-padded to the frame number + * e.g. my###.jpg will create frames my001.jpg, my002.jpg, etc + * \param outputSize size of the frame in pixels + * \param error error string in case of failure + * \param feedback optional feedback object used to cancel export or report progress + * \return whether export succeeded. In case of failure, see error argument + * + * \since QGIS 3.8 + */ + static bool exportAnimation( const Qgs3DAnimationSettings &animationSettings, + const Qgs3DMapSettings &mapSettings, + int framesPerSecond, + const QString &outputDirectory, + const QString &fileNameTemplate, + const QSize &outputSize, + QString &error, + QgsFeedback *feedback = nullptr + ); + /** * Calculates the highest needed zoom level for tiles in quad-tree given width of the base tile (zoom level 0) * in map units, resolution of the tile (e.g. tile's texture width) and desired maximum error in map units. diff --git a/src/app/3d/qgs3danimationexportdialog.cpp b/src/app/3d/qgs3danimationexportdialog.cpp new file mode 100644 index 000000000000..d6e889046156 --- /dev/null +++ b/src/app/3d/qgs3danimationexportdialog.cpp @@ -0,0 +1,110 @@ +/*************************************************************************** + qgs3danimationexportdialog.cpp + ------------------------------ + Date : February 2019 + Copyright : (C) 2019 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgs3danimationexportdialog.h" +#include "qgsproject.h" +#include "qgsgui.h" +#include "qgssettings.h" +#include "qgsoffscreen3dengine.h" +#include "qgs3danimationsettings.h" +#include "qgs3dmapsettings.h" +#include "qgs3dmapscene.h" +#include "qgs3dutils.h" +#include "qgscameracontroller.h" +#include "qgsspinbox.h" + +#include + +Qgs3DAnimationExportDialog::Qgs3DAnimationExportDialog(): QDialog( nullptr ) +{ + setupUi( this ); + QgsSettings settings; + + const QString templateText = settings.value( QStringLiteral( "Export3DAnimation/fileNameTemplate" ), + QStringLiteral( "%1####.jpg" ).arg( QgsProject::instance()->baseName() ) + , QgsSettings::App ).toString(); + mTemplateLineEdit->setText( templateText ); + QRegExp rx( QStringLiteral( "\\w+#+\\.{1}\\w+" ) ); //e.g. anyprefix#####.png + QValidator *validator = new QRegExpValidator( rx, this ); + mTemplateLineEdit->setValidator( validator ); + + connect( mTemplateLineEdit, &QLineEdit::textChanged, this, [ = ] + { + QgsSettings settings; + settings.setValue( QStringLiteral( "Export3DAnimation/fileNameTemplate" ), mTemplateLineEdit->text() ); + } ); + + mOutputDirFileWidget->setStorageMode( QgsFileWidget::GetDirectory ); + mOutputDirFileWidget->setDialogTitle( tr( "Select directory for 3D animation frames" ) ); + mOutputDirFileWidget->lineEdit()->setShowClearButton( false ); + mOutputDirFileWidget->setDefaultRoot( settings.value( QStringLiteral( "Export3DAnimation/lastDir" ), QString(), QgsSettings::App ).toString() ); + mOutputDirFileWidget->setFilePath( settings.value( QStringLiteral( "Export3DAnimation/lastDir" ), QString(), QgsSettings::App ).toString() ); + + connect( mOutputDirFileWidget, &QgsFileWidget::fileChanged, this, [ = ] + { + QgsSettings settings; + settings.setValue( QStringLiteral( "Export3DAnimation/lastDir" ), mOutputDirFileWidget->filePath(), QgsSettings::App ); + } ); + + mFpsSpinBox->setValue( settings.value( QStringLiteral( "Export3DAnimation/fps" ), 30 ).toInt() ); + connect( mFpsSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QgsSpinBox::valueChanged ), this, [ = ] + { + QgsSettings settings; + settings.setValue( QStringLiteral( "Export3DAnimation/fps" ), mFpsSpinBox->value() ); + } ); + + mWidthSpinBox->setValue( settings.value( QStringLiteral( "Export3DAnimation/width" ), 800 ).toInt() ); + connect( mWidthSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QgsSpinBox::valueChanged ), this, [ = ] + { + QgsSettings settings; + settings.setValue( QStringLiteral( "Export3DAnimation/width" ), mWidthSpinBox->value() ); + } ); + + mHeightSpinBox->setValue( settings.value( QStringLiteral( "Export3DAnimation/height" ), 600 ).toInt() ); + connect( mHeightSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QgsSpinBox::valueChanged ), this, [ = ] + { + QgsSettings settings; + settings.setValue( QStringLiteral( "Export3DAnimation/height" ), mHeightSpinBox->value() ); + } ); + + QgsGui::enableAutoGeometryRestore( this ); +} + +QString Qgs3DAnimationExportDialog::outputDirectory() const +{ + const QString dir = mOutputDirFileWidget->filePath(); + return dir; +} + +QString Qgs3DAnimationExportDialog::fileNameExpression() const +{ + const QString name = mTemplateLineEdit->text(); + return name; +} + +Qgs3DAnimationExportDialog::~Qgs3DAnimationExportDialog() = default; + +int Qgs3DAnimationExportDialog::fps() const +{ + const int fps = mFpsSpinBox->value(); + return fps; +} + +QSize Qgs3DAnimationExportDialog::frameSize() const +{ + const int width = mWidthSpinBox->value(); + const int height = mHeightSpinBox->value(); + return QSize( width, height ); +} diff --git a/src/app/3d/qgs3danimationexportdialog.h b/src/app/3d/qgs3danimationexportdialog.h new file mode 100644 index 000000000000..ba85f12e533e --- /dev/null +++ b/src/app/3d/qgs3danimationexportdialog.h @@ -0,0 +1,51 @@ +/*************************************************************************** + qgs3danimationexportdialog.h + ---------------------------- + Date : February 2019 + Copyright : (C) 2019 by Peter Petrik + Email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGS3DANIMATIONEXPORTDIALOG_H +#define QGS3DANIMATIONEXPORTDIALOG_H + +#include +#include +#include + +#include "qgs3dmapsettings.h" +#include "qgs3danimationsettings.h" + +#include "ui_animationexport3ddialog.h" + +/** + * Dialog for settings for 3D animation export + */ +class Qgs3DAnimationExportDialog : public QDialog, private Ui::AnimationExport3DDialog +{ + Q_OBJECT + public: + explicit Qgs3DAnimationExportDialog(); + ~Qgs3DAnimationExportDialog() override; + + //! Returns output directory for frames + QString outputDirectory( ) const; + + //! Returns filename template for frames + QString fileNameExpression( ) const; + + //! Returns frames per second + int fps() const; + + //! Returns size of frame in pixels + QSize frameSize() const; +}; + +#endif // QGS3DANIMATIONEXPORTDIALOG_H diff --git a/src/app/3d/qgs3danimationwidget.cpp b/src/app/3d/qgs3danimationwidget.cpp index f93d7daa8eaf..3520a9ac8c4d 100644 --- a/src/app/3d/qgs3danimationwidget.cpp +++ b/src/app/3d/qgs3danimationwidget.cpp @@ -18,10 +18,17 @@ #include "qgs3danimationsettings.h" #include "qgsapplication.h" #include "qgscameracontroller.h" +#include "qgs3danimationexportdialog.h" +#include "qgs3dmapsettings.h" +#include "qgsoffscreen3dengine.h" +#include "qgs3dmapscene.h" +#include "qgs3dutils.h" +#include "qgsfeedback.h" #include #include #include +#include Qgs3DAnimationWidget::Qgs3DAnimationWidget( QWidget *parent ) : QWidget( parent ) @@ -34,7 +41,7 @@ Qgs3DAnimationWidget::Qgs3DAnimationWidget( QWidget *parent ) btnPlayPause->setIcon( QIcon( QgsApplication::iconPath( "mTaskRunning.svg" ) ) ); btnDuplicateKeyframe->setIcon( QIcon( QgsApplication::iconPath( "mActionEditCopy.svg" ) ) ); btnRepeat->setIcon( QIcon( QgsApplication::iconPath( "mActionRefresh.svg" ) ) ); - + btnExportAnimation->setIcon( QIcon( QgsApplication::iconPath( "mActionFileSave.svg" ) ) ); cboKeyframe->addItem( tr( "" ) ); mAnimationTimer = new QTimer( this ); @@ -45,6 +52,7 @@ Qgs3DAnimationWidget::Qgs3DAnimationWidget( QWidget *parent ) connect( btnRemoveKeyframe, &QToolButton::clicked, this, &Qgs3DAnimationWidget::onRemoveKeyframe ); connect( btnEditKeyframe, &QToolButton::clicked, this, &Qgs3DAnimationWidget::onEditKeyframe ); connect( btnDuplicateKeyframe, &QToolButton::clicked, this, &Qgs3DAnimationWidget::onDuplicateKeyframe ); + connect( btnExportAnimation, &QToolButton::clicked, this, &Qgs3DAnimationWidget::onExportAnimation ); connect( cboInterpolation, qgis::overload::of( &QComboBox::currentIndexChanged ), this, &Qgs3DAnimationWidget::onInterpolationChanged ); btnPlayPause->setCheckable( true ); @@ -136,6 +144,11 @@ void Qgs3DAnimationWidget::setEditControlsEnabled( bool enabled ) cboInterpolation->setEnabled( enabled ); } +void Qgs3DAnimationWidget::setMap( Qgs3DMapSettings *map ) +{ + mMap = map; +} + void Qgs3DAnimationWidget::onPlayPause() { if ( mAnimationTimer->isActive() ) @@ -175,6 +188,48 @@ void Qgs3DAnimationWidget::onAnimationTimer() } } +void Qgs3DAnimationWidget::onExportAnimation() +{ + if ( !mMap || !mAnimationSettings ) + QMessageBox::warning( this, tr( "Export Animation" ), tr( "Unable to export 3D animation" ) ); + + Qgs3DAnimationExportDialog dialog; + if ( dialog.exec() == QDialog::Accepted ) + { + QgsFeedback progressFeedback; + + QProgressDialog progressDialog( tr( "Exporting frames..." ), tr( "Abort" ), 0, 100, this ); + progressDialog.setWindowModality( Qt::WindowModal ); + QString error; + + connect( &progressFeedback, &QgsFeedback::progressChanged, this, + [&progressDialog, &progressFeedback] + { + progressDialog.setValue( static_cast( progressFeedback.progress() ) ); + QCoreApplication::processEvents(); + } ); + + connect( &progressDialog, &QProgressDialog::canceled, &progressFeedback, &QgsFeedback::cancel ); + + bool success = Qgs3DUtils::exportAnimation( + animation(), + *mMap, + dialog.fps(), + dialog.outputDirectory(), + dialog.fileNameExpression(), + dialog.frameSize(), + error, + &progressFeedback ); + + progressDialog.hide(); + if ( !success ) + { + QMessageBox::warning( this, tr( "Export Animation" ), error ); + return; + } + } +} + void Qgs3DAnimationWidget::onSliderValueChanged() { diff --git a/src/app/3d/qgs3danimationwidget.h b/src/app/3d/qgs3danimationwidget.h index 1153d849048a..2700d42676df 100644 --- a/src/app/3d/qgs3danimationwidget.h +++ b/src/app/3d/qgs3danimationwidget.h @@ -24,6 +24,7 @@ class Qgs3DAnimationSettings; class QgsCameraController; +class Qgs3DMapSettings; class Qgs3DAnimationWidget : public QWidget, private Ui::Animation3DWidget { @@ -34,6 +35,8 @@ class Qgs3DAnimationWidget : public QWidget, private Ui::Animation3DWidget void setCameraController( QgsCameraController *cameraController ); + void setMap( Qgs3DMapSettings *map ); + void setAnimation( const Qgs3DAnimationSettings &animation ); Qgs3DAnimationSettings animation() const; @@ -52,6 +55,7 @@ class Qgs3DAnimationWidget : public QWidget, private Ui::Animation3DWidget void onEditKeyframe(); void onDuplicateKeyframe(); void onInterpolationChanged(); + void onExportAnimation(); private: void initializeController( const Qgs3DAnimationSettings &animSettings ); @@ -62,6 +66,7 @@ class Qgs3DAnimationWidget : public QWidget, private Ui::Animation3DWidget private: std::unique_ptr mAnimationSettings; QgsCameraController *mCameraController; + Qgs3DMapSettings *mMap; QTimer *mAnimationTimer = nullptr; }; diff --git a/src/app/3d/qgs3dmapcanvasdockwidget.cpp b/src/app/3d/qgs3dmapcanvasdockwidget.cpp index 9a910a778d8a..e14f4f9874fc 100644 --- a/src/app/3d/qgs3dmapcanvasdockwidget.cpp +++ b/src/app/3d/qgs3dmapcanvasdockwidget.cpp @@ -154,6 +154,7 @@ void Qgs3DMapCanvasDockWidget::setMapSettings( Qgs3DMapSettings *map ) connect( mCanvas->scene(), &Qgs3DMapScene::terrainPendingJobsCountChanged, this, &Qgs3DMapCanvasDockWidget::onTerrainPendingJobsCountChanged ); mAnimationWidget->setCameraController( mCanvas->scene()->cameraController() ); + mAnimationWidget->setMap( map ); } void Qgs3DMapCanvasDockWidget::setMainCanvas( QgsMapCanvas *canvas ) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index a1f703214025..509145d9bda7 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -474,8 +474,8 @@ SET (QGIS_APP_MOC_HDRS IF (WITH_3D) SET(QGIS_APP_SRCS ${QGIS_APP_SRCS} - 3d/qgs3danimationsettings.cpp 3d/qgs3danimationwidget.cpp + 3d/qgs3danimationexportdialog.cpp 3d/qgs3dmapcanvas.cpp 3d/qgs3dmapcanvasdockwidget.cpp 3d/qgs3dmapconfigwidget.cpp @@ -497,6 +497,7 @@ IF (WITH_3D) SET (QGIS_APP_MOC_HDRS ${QGIS_APP_MOC_HDRS} 3d/qgs3danimationwidget.h + 3d/qgs3danimationexportdialog.h 3d/qgs3dmapcanvas.h 3d/qgs3dmapcanvasdockwidget.h 3d/qgs3dmapconfigwidget.h diff --git a/src/ui/3d/animation3dwidget.ui b/src/ui/3d/animation3dwidget.ui index 9ded060ae785..4c604eecdde2 100644 --- a/src/ui/3d/animation3dwidget.ui +++ b/src/ui/3d/animation3dwidget.ui @@ -6,8 +6,8 @@ 0 0 - 1301 - 138 + 986 + 129 @@ -66,6 +66,19 @@ + + + + true + + + Export Animation Frames + + + + + + diff --git a/src/ui/3d/animationexport3ddialog.ui b/src/ui/3d/animationexport3ddialog.ui new file mode 100644 index 000000000000..39121cd7c495 --- /dev/null +++ b/src/ui/3d/animationexport3ddialog.ui @@ -0,0 +1,198 @@ + + + AnimationExport3DDialog + + + + 0 + 0 + 434 + 314 + + + + Export 3D Animation + + + true + + + + + + + + + Output Height + + + mHeightSpinBox + + + + + + + Frames Per Second + + + + + + + Number of # represents number of digits (e.g. frame###.png -> frame001.png) + + + + + + + Output Directory + + + + + + + Template + + + mTemplateLineEdit + + + + + + + 1 + + + 30 + + + + + + + Output Width + + + mWidthSpinBox + + + + + + + px + + + 100 + + + 10000 + + + 100 + + + 800 + + + + + + + px + + + 100 + + + 10000 + + + 100 + + + 600 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + QgsFileWidget + QWidget +
qgsfilewidget.h
+ 1 +
+ + QgsSpinBox + QSpinBox +
qgsspinbox.h
+
+
+ + mTemplateLineEdit + mWidthSpinBox + mHeightSpinBox + + + + + mButtonBox + accepted() + AnimationExport3DDialog + accept() + + + 254 + 206 + + + 263 + 68 + + + + + mButtonBox + rejected() + AnimationExport3DDialog + reject() + + + 254 + 206 + + + 6 + 77 + + + + +
diff --git a/tests/src/3d/testqgs3drendering.cpp b/tests/src/3d/testqgs3drendering.cpp index 4144e4e86cc6..3bd4e73c8440 100644 --- a/tests/src/3d/testqgs3drendering.cpp +++ b/tests/src/3d/testqgs3drendering.cpp @@ -40,6 +40,9 @@ #include "qgsvectorlayer3drenderer.h" #include "qgsmeshlayer3drenderer.h" +#include +#include + class TestQgs3DRendering : public QObject { Q_OBJECT @@ -53,6 +56,7 @@ class TestQgs3DRendering : public QObject void testMapTheme(); void testMesh(); void testRuleBasedRenderer(); + void testAnimationExport(); private: bool renderCheck( const QString &testName, QImage &image, int mismatchCount = 0 ); @@ -394,5 +398,47 @@ bool TestQgs3DRendering::renderCheck( const QString &testName, QImage &image, in return myResultFlag; } +void TestQgs3DRendering::testAnimationExport() +{ + QgsRectangle fullExtent = mLayerDtm->extent(); + + Qgs3DMapSettings map; + map.setCrs( mProject->crs() ); + map.setOrigin( QgsVector3D( fullExtent.center().x(), fullExtent.center().y(), 0 ) ); + map.setLayers( QList() << mLayerRgb ); + + QgsFlatTerrainGenerator *flatTerrain = new QgsFlatTerrainGenerator; + flatTerrain->setCrs( map.crs() ); + flatTerrain->setExtent( fullExtent ); + map.setTerrainGenerator( flatTerrain ); + + Qgs3DAnimationSettings animSettings; + Qgs3DAnimationSettings::Keyframes keyframes; + Qgs3DAnimationSettings::Keyframe kf1; + kf1.dist = 2500; + Qgs3DAnimationSettings::Keyframe kf2; + kf2.time = 2; + kf2.dist = 3000; + keyframes << kf1; + keyframes << kf2; + animSettings.setKeyframes( keyframes ); + + QString dir = QDir::temp().path(); + QString error; + + bool success = Qgs3DUtils::exportAnimation( + animSettings, + map, + 1, + dir, + "test3danimation###.png", + QSize( 600, 400 ), + error, + nullptr ); + + QVERIFY( success ); + QVERIFY( QFileInfo( QDir( dir ).filePath( QStringLiteral( "test3danimation001.png" ) ) ).exists() ); +} + QGSTEST_MAIN( TestQgs3DRendering ) #include "testqgs3drendering.moc" diff --git a/tests/src/3d/testqgs3dutils.cpp b/tests/src/3d/testqgs3dutils.cpp index 44f45f9e507c..18a580d9fb80 100644 --- a/tests/src/3d/testqgs3dutils.cpp +++ b/tests/src/3d/testqgs3dutils.cpp @@ -17,6 +17,7 @@ #include "qgs3dutils.h" +#include /** * \ingroup UnitTests @@ -33,7 +34,6 @@ class TestQgs3DUtils : public QObject void cleanupTestCase();// will be called after the last testfunction was executed. void testTransforms(); - private: };