Skip to content

Commit 504cd61

Browse files
authoredAug 25, 2018
[FEATURE] 3D maps in print layouts (part 2) - PR #7705
2 parents 6097b13 + 02a6b83 commit 504cd61

14 files changed

+894
-16
lines changed
 

‎images/images.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,7 @@
702702
<file>themes/default/mIconGPU.svg</file>
703703
<file>themes/default/mAddToProject.svg</file>
704704
<file>themes/default/mDockify.svg</file>
705+
<file>themes/default/mActionAdd3DMap.svg</file>
705706
</qresource>
706707
<qresource prefix="/images/tips">
707708
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
Lines changed: 163 additions & 0 deletions
Loading

‎src/3d/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ SET(QGIS_3D_MOC_HDRS
5454
qgs3dmapsettings.h
5555
qgsabstract3dengine.h
5656
qgscameracontroller.h
57+
qgslayoutitem3dmap.h
5758
qgsoffscreen3dengine.h
5859
qgswindow3dengine.h
5960

‎src/3d/qgs3dmapsettings.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ void Qgs3DMapSettings::readXml( const QDomElement &elem, const QgsReadWriteConte
6363
elemOrigin.attribute( "y" ).toDouble(),
6464
elemOrigin.attribute( "z" ).toDouble() );
6565

66+
QDomElement elemColor = elem.firstChildElement( "color" );
67+
if ( !elemColor.isNull() )
68+
{
69+
mBackgroundColor = QgsSymbolLayerUtils::decodeColor( elemColor.attribute( "background" ) );
70+
mSelectionColor = QgsSymbolLayerUtils::decodeColor( elemColor.attribute( "selection" ) );
71+
}
72+
6673
QDomElement elemCrs = elem.firstChildElement( "crs" );
6774
mCrs.readXml( elemCrs );
6875

@@ -147,6 +154,11 @@ QDomElement Qgs3DMapSettings::writeXml( QDomDocument &doc, const QgsReadWriteCon
147154
elemOrigin.setAttribute( "z", QString::number( mOrigin.z() ) );
148155
elem.appendChild( elemOrigin );
149156

157+
QDomElement elemColor = doc.createElement( "color" );
158+
elemColor.setAttribute( "background", QgsSymbolLayerUtils::encodeColor( mBackgroundColor ) );
159+
elemColor.setAttribute( "selection", QgsSymbolLayerUtils::encodeColor( mSelectionColor ) );
160+
elem.appendChild( elemColor );
161+
150162
QDomElement elemCrs = doc.createElement( "crs" );
151163
mCrs.writeXml( elemCrs, doc );
152164
elem.appendChild( elemCrs );

‎src/3d/qgscameracontroller.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,9 @@ void QgsCameraController::setLookingAtPoint( const QgsVector3D &point, float dis
419419

420420
void QgsCameraController::setCameraPose( const QgsCameraPose &camPose )
421421
{
422+
if ( camPose == mCameraPose )
423+
return;
424+
422425
mCameraPose = camPose;
423426

424427
if ( mCamera )

‎src/3d/qgscamerapose.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,31 @@
1717

1818
#include <Qt3DRender/QCamera>
1919

20+
#include <QDomDocument>
21+
22+
QDomElement QgsCameraPose::writeXml( QDomDocument &doc ) const
23+
{
24+
QDomElement elemCamera = doc.createElement( "camera-pose" );
25+
elemCamera.setAttribute( QStringLiteral( "x" ), mCenterPoint.x() );
26+
elemCamera.setAttribute( QStringLiteral( "y" ), mCenterPoint.y() );
27+
elemCamera.setAttribute( QStringLiteral( "z" ), mCenterPoint.z() );
28+
elemCamera.setAttribute( QStringLiteral( "dist" ), mDistanceFromCenterPoint );
29+
elemCamera.setAttribute( QStringLiteral( "pitch" ), mPitchAngle );
30+
elemCamera.setAttribute( QStringLiteral( "heading" ), mHeadingAngle );
31+
return elemCamera;
32+
}
33+
34+
void QgsCameraPose::readXml( const QDomElement &elem )
35+
{
36+
double x = elem.attribute( QStringLiteral( "x" ) ).toDouble();
37+
double y = elem.attribute( QStringLiteral( "y" ) ).toDouble();
38+
double z = elem.attribute( QStringLiteral( "z" ) ).toDouble();
39+
mCenterPoint = QgsVector3D( x, y, z );
40+
41+
mDistanceFromCenterPoint = elem.attribute( QStringLiteral( "dist" ) ).toFloat();
42+
mPitchAngle = elem.attribute( QStringLiteral( "pitch" ) ).toFloat();
43+
mHeadingAngle = elem.attribute( QStringLiteral( "heading" ) ).toFloat();
44+
}
2045

2146
void QgsCameraPose::updateCamera( Qt3DRender::QCamera *camera )
2247
{

‎src/3d/qgscamerapose.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ namespace Qt3DRender
2525
class QCamera;
2626
}
2727

28+
class QDomDocument;
29+
class QDomElement;
30+
2831
/**
2932
* \ingroup 3d
3033
* Class that encapsulates camera pose in a 3D scene. The pose is defined with the following parameters:
@@ -62,6 +65,11 @@ class _3D_EXPORT QgsCameraPose
6265
//! Update Qt3D camera view matrix based on the pose
6366
void updateCamera( Qt3DRender::QCamera *camera );
6467

68+
//! Writes configuration to a new DOM element and returns it
69+
QDomElement writeXml( QDomDocument &doc ) const;
70+
//! Reads configuration from a DOM element previously written using writeXml()
71+
void readXml( const QDomElement &elem );
72+
6573
bool operator==( const QgsCameraPose &other ) const
6674
{
6775
return mCenterPoint == other.mCenterPoint &&

‎src/3d/qgslayoutitem3dmap.cpp

Lines changed: 195 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,22 @@
1919
#include "qgs3dutils.h"
2020
#include "qgscameracontroller.h"
2121
#include "qgslayout.h"
22+
#include "qgslayoutmodel.h"
2223
#include "qgslayoutitemregistry.h"
2324
#include "qgsoffscreen3dengine.h"
2425

2526

2627
QgsLayoutItem3DMap::QgsLayoutItem3DMap( QgsLayout *layout )
2728
: QgsLayoutItem( layout )
2829
{
30+
assignFreeId();
2931

32+
connect( this, &QgsLayoutItem::sizePositionChanged, this, &QgsLayoutItem3DMap::onSizePositionChanged );
3033
}
3134

35+
QgsLayoutItem3DMap::~QgsLayoutItem3DMap() = default;
36+
37+
3238
QgsLayoutItem3DMap *QgsLayoutItem3DMap::create( QgsLayout *layout )
3339
{
3440
return new QgsLayoutItem3DMap( layout );
@@ -39,30 +45,206 @@ int QgsLayoutItem3DMap::type() const
3945
return QgsLayoutItemRegistry::Layout3DMap;
4046
}
4147

48+
void QgsLayoutItem3DMap::assignFreeId()
49+
{
50+
if ( !mLayout )
51+
return;
52+
53+
QList<QgsLayoutItem3DMap *> mapsList;
54+
mLayout->layoutItems( mapsList );
55+
56+
int maxId = -1;
57+
bool used = false;
58+
for ( QgsLayoutItem3DMap *map : qgis::as_const( mapsList ) )
59+
{
60+
if ( map == this )
61+
continue;
62+
63+
if ( map->mMapId == mMapId )
64+
used = true;
65+
66+
maxId = std::max( maxId, map->mMapId );
67+
}
68+
if ( used )
69+
{
70+
mMapId = maxId + 1;
71+
mLayout->itemsModel()->updateItemDisplayName( this );
72+
}
73+
updateToolTip();
74+
}
75+
76+
QString QgsLayoutItem3DMap::displayName() const
77+
{
78+
if ( !QgsLayoutItem::id().isEmpty() )
79+
{
80+
return QgsLayoutItem::id();
81+
}
82+
83+
return tr( "3D Map %1" ).arg( mMapId );
84+
}
85+
86+
void QgsLayoutItem3DMap::updateToolTip()
87+
{
88+
setToolTip( displayName() );
89+
}
90+
4291
void QgsLayoutItem3DMap::draw( QgsLayoutItemRenderContext &context )
4392
{
44-
QgsOffscreen3DEngine engine;
93+
QgsRenderContext &ctx = context.renderContext();
94+
QPainter *painter = ctx.painter();
95+
96+
int w = static_cast<int>( std::ceil( rect().width() * ctx.scaleFactor() ) );
97+
int h = static_cast<int>( std::ceil( rect().height() * ctx.scaleFactor() ) );
98+
QRect r( 0, 0, w, h );
99+
100+
painter->save();
101+
102+
if ( !mSettings )
103+
{
104+
painter->drawText( r, Qt::AlignCenter, tr( "Scene not set" ) );
105+
painter->restore();
106+
return;
107+
}
108+
109+
if ( !mCapturedImage.isNull() )
110+
{
111+
painter->drawImage( r, mCapturedImage );
112+
painter->restore();
113+
return;
114+
}
115+
116+
// we do not have a cached image of the rendered scene - let's request one from the engine
117+
118+
if ( mLayout->renderContext().isPreviewRender() )
119+
{
120+
painter->drawText( r, Qt::AlignCenter, tr( "Loading" ) );
121+
painter->restore();
122+
}
123+
45124
QSizeF sizePixels = mLayout->renderContext().measurementConverter().convert( sizeWithUnits(), QgsUnitTypes::LayoutPixels ).toQSizeF();
46-
engine.setSize( QSize( static_cast<int>( std::ceil( sizePixels.width() ) ),
47-
static_cast<int>( std::ceil( sizePixels.height() ) ) ) );
125+
QSize sizePixelsInt = QSize( static_cast<int>( std::ceil( sizePixels.width() ) ),
126+
static_cast<int>( std::ceil( sizePixels.height() ) ) );
48127

49-
Qgs3DMapScene *scene = new Qgs3DMapScene( *mSettings, &engine );
50-
engine.setRootEntity( scene );
128+
if ( !mEngine )
129+
{
130+
mEngine.reset( new QgsOffscreen3DEngine );
131+
connect( mEngine.get(), &QgsAbstract3DEngine::imageCaptured, this, &QgsLayoutItem3DMap::onImageCaptured );
51132

52-
scene->cameraController()->setCameraPose( mCameraPose );
133+
mEngine->setSize( sizePixelsInt );
53134

54-
// XXX this should not be needed, but without it the scene often
55-
// is not completely ready (e.g. a missing terrain tile).
56-
// leaving it here until a more robust solution is found...
57-
Qgs3DUtils::captureSceneImage( engine, scene );
135+
mScene = new Qgs3DMapScene( *mSettings, mEngine.get() );
136+
connect( mScene, &Qgs3DMapScene::sceneStateChanged, this, &QgsLayoutItem3DMap::onSceneStateChanged );
58137

59-
QImage img = Qgs3DUtils::captureSceneImage( engine, scene );
138+
mEngine->setRootEntity( mScene );
139+
}
60140

61-
QgsRenderContext &ctx = context.renderContext();
62-
ctx.painter()->drawImage( 0, 0, img );
141+
if ( mEngine->size() != sizePixelsInt )
142+
mEngine->setSize( sizePixelsInt );
143+
144+
mScene->cameraController()->setCameraPose( mCameraPose );
145+
146+
if ( mLayout->renderContext().isPreviewRender() )
147+
{
148+
onSceneStateChanged();
149+
}
150+
else
151+
{
152+
// we can't just request a capture and hope it will arrive at some point later.
153+
// this is not a preview, we need the rendered scene now!
154+
if ( mDrawing )
155+
return;
156+
mDrawing = true;
157+
Qgs3DUtils::captureSceneImage( *mEngine.get(), mScene );
158+
QImage img = Qgs3DUtils::captureSceneImage( *mEngine.get(), mScene );
159+
painter->drawImage( r, img );
160+
painter->restore();
161+
mDrawing = false;
162+
}
163+
}
164+
165+
void QgsLayoutItem3DMap::onImageCaptured( const QImage &img )
166+
{
167+
mCapturedImage = img;
168+
update();
169+
}
170+
171+
void QgsLayoutItem3DMap::onSceneStateChanged()
172+
{
173+
if ( mCapturedImage.isNull() && mScene->sceneState() == Qgs3DMapScene::Ready )
174+
{
175+
mEngine->requestCaptureImage();
176+
}
177+
}
178+
179+
void QgsLayoutItem3DMap::onSizePositionChanged()
180+
{
181+
mCapturedImage = QImage();
182+
update();
183+
}
184+
185+
186+
bool QgsLayoutItem3DMap::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
187+
{
188+
if ( mSettings )
189+
{
190+
QDomElement elemSettings = mSettings->writeXml( document, context );
191+
element.appendChild( elemSettings );
192+
}
193+
194+
QDomElement elemCameraPose = mCameraPose.writeXml( document );
195+
element.appendChild( elemCameraPose );
196+
197+
return true;
198+
}
199+
200+
bool QgsLayoutItem3DMap::readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context )
201+
{
202+
Q_UNUSED( document );
203+
QDomElement elemSettings = element.firstChildElement( "qgis3d" );
204+
if ( !elemSettings.isNull() )
205+
{
206+
mSettings.reset( new Qgs3DMapSettings );
207+
mSettings->readXml( elemSettings, context );
208+
if ( mLayout->project() )
209+
mSettings->resolveReferences( *mLayout->project() );
210+
}
211+
212+
QDomElement elemCameraPose = element.firstChildElement( "camera-pose" );
213+
if ( !elemCameraPose.isNull() )
214+
mCameraPose.readXml( elemCameraPose );
215+
216+
return true;
217+
}
218+
219+
void QgsLayoutItem3DMap::finalizeRestoreFromXml()
220+
{
221+
assignFreeId();
63222
}
64223

65224
void QgsLayoutItem3DMap::setMapSettings( Qgs3DMapSettings *settings )
66225
{
67226
mSettings.reset( settings );
227+
228+
mEngine.reset();
229+
mScene = nullptr;
230+
231+
mCapturedImage = QImage();
232+
update();
233+
}
234+
235+
void QgsLayoutItem3DMap::refresh()
236+
{
237+
QgsLayoutItem::refresh();
238+
239+
mCapturedImage = QImage();
240+
}
241+
242+
void QgsLayoutItem3DMap::setCameraPose( const QgsCameraPose &pose )
243+
{
244+
if ( mCameraPose == pose )
245+
return;
246+
247+
mCameraPose = pose;
248+
mCapturedImage = QImage();
249+
update();
68250
}

‎src/3d/qgslayoutitem3dmap.h

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
#include "qgscamerapose.h"
2424

2525

26+
class Qgs3DMapScene;
2627
class Qgs3DMapSettings;
28+
class QgsOffscreen3DEngine;
2729

2830
/**
2931
* \ingroup 3d
@@ -34,6 +36,7 @@ class Qgs3DMapSettings;
3436
*/
3537
class _3D_EXPORT QgsLayoutItem3DMap : public QgsLayoutItem
3638
{
39+
Q_OBJECT
3740

3841
#ifdef SIP_RUN
3942
SIP_CONVERT_TO_SUBCLASS_CODE
@@ -61,6 +64,8 @@ class _3D_EXPORT QgsLayoutItem3DMap : public QgsLayoutItem
6164
*/
6265
QgsLayoutItem3DMap( QgsLayout *layout SIP_TRANSFERTHIS );
6366

67+
~QgsLayoutItem3DMap();
68+
6469
/**
6570
* Returns a new 3D map item for the specified \a layout.
6671
*
@@ -70,10 +75,8 @@ class _3D_EXPORT QgsLayoutItem3DMap : public QgsLayoutItem
7075

7176
virtual int type() const override;
7277

73-
virtual void draw( QgsLayoutItemRenderContext &context ) override;
74-
7578
//! Configures camera view
76-
void setCameraPose( const QgsCameraPose &pose ) { mCameraPose = pose; }
79+
void setCameraPose( const QgsCameraPose &pose );
7780
//! Returns camera view
7881
QgsCameraPose cameraPose() const { return mCameraPose; }
7982

@@ -86,9 +89,43 @@ class _3D_EXPORT QgsLayoutItem3DMap : public QgsLayoutItem
8689
//! Returns map scene. May be a null pointer if not yet configured.
8790
Qgs3DMapSettings *mapSettings() const { return mSettings.get(); }
8891

92+
/**
93+
* Sets the map id() to a number not yet used in the layout. The existing id() is kept if it is not in use.
94+
*/
95+
void assignFreeId();
96+
97+
//! overridden to show "3D Map 1" type names
98+
QString displayName() const override;
99+
100+
void finalizeRestoreFromXml() override;
101+
102+
public slots:
103+
void refresh() override;
104+
105+
protected:
106+
void draw( QgsLayoutItemRenderContext &context ) override;
107+
bool writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const override;
108+
bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ) override;
109+
110+
private slots:
111+
void onImageCaptured( const QImage &img );
112+
void onSceneStateChanged();
113+
void onSizePositionChanged();
114+
115+
private:
116+
//! Resets the item tooltip to reflect current map id
117+
void updateToolTip();
118+
89119
private:
90120
std::unique_ptr<Qgs3DMapSettings> mSettings;
121+
std::unique_ptr<QgsOffscreen3DEngine> mEngine;
122+
Qgs3DMapScene *mScene = nullptr; //!< 3D scene (owned by the 3D engine)
123+
QImage mCapturedImage;
91124
QgsCameraPose mCameraPose;
125+
bool mDrawing = false;
126+
127+
//! Unique identifier
128+
int mMapId = 1;
92129
};
93130

94131
#endif // QGSLAYOUTITEM3DMAP_H

‎src/app/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ IF (WITH_3D)
446446
3d/qgspolygon3dsymbolwidget.cpp
447447
3d/qgsphongmaterialwidget.cpp
448448
3d/qgsvectorlayer3drendererwidget.cpp
449+
layout/qgslayout3dmapwidget.cpp
449450
)
450451

451452
SET (QGIS_APP_MOC_HDRS
@@ -459,6 +460,7 @@ IF (WITH_3D)
459460
3d/qgspolygon3dsymbolwidget.h
460461
3d/qgsphongmaterialwidget.h
461462
3d/qgsvectorlayer3drendererwidget.h
463+
layout/qgslayout3dmapwidget.h
462464
)
463465
ENDIF (WITH_3D)
464466

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/***************************************************************************
2+
qgslayout3dmapwidget.cpp
3+
--------------------------------------
4+
Date : August 2018
5+
Copyright : (C) 2018 by Martin Dobias
6+
Email : wonder dot sk at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgslayout3dmapwidget.h"
17+
18+
#include "qgisapp.h"
19+
#include "qgs3dmapcanvas.h"
20+
#include "qgs3dmapcanvasdockwidget.h"
21+
#include "qgs3dmapsettings.h"
22+
#include "qgscameracontroller.h"
23+
#include <QMenu>
24+
25+
26+
float _normalizedAngle( float x )
27+
{
28+
x = std::fmod( x, 360 );
29+
if ( x < 0 ) x += 360;
30+
return x;
31+
}
32+
33+
template<typename Func1>
34+
void _prepare3DViewsMenu( QMenu *menu, QgsLayout3DMapWidget *w, Func1 slot )
35+
{
36+
QObject::connect( menu, &QMenu::aboutToShow, w, [menu, w, slot]
37+
{
38+
const QList<Qgs3DMapCanvasDockWidget *> lst = QgisApp::instance()->findChildren<Qgs3DMapCanvasDockWidget *>();
39+
menu->clear();
40+
for ( auto dock : lst )
41+
{
42+
QAction *a = menu->addAction( dock->mapCanvas3D()->objectName(), w, slot );
43+
// need to use a custom property for identification because Qt likes to add "&" to the action text
44+
a->setProperty( "name", dock->mapCanvas3D()->objectName() );
45+
}
46+
if ( lst.isEmpty() )
47+
{
48+
menu->addAction( QObject::tr( "No 3D maps defined" ) )->setEnabled( false );
49+
}
50+
} );
51+
}
52+
53+
Qgs3DMapCanvasDockWidget *_dock3DViewFromSender( QObject *sender )
54+
{
55+
QAction *action = qobject_cast<QAction *>( sender );
56+
if ( !action )
57+
return nullptr;
58+
59+
QString actionText = action->property( "name" ).toString();
60+
const QList<Qgs3DMapCanvasDockWidget *> lst = QgisApp::instance()->findChildren<Qgs3DMapCanvasDockWidget *>();
61+
for ( auto dock : lst )
62+
{
63+
QString objName = dock->mapCanvas3D()->objectName();
64+
if ( objName == actionText )
65+
{
66+
return dock;
67+
}
68+
}
69+
return nullptr;
70+
}
71+
72+
73+
QgsLayout3DMapWidget::QgsLayout3DMapWidget( QgsLayoutItem3DMap *map3D )
74+
: QgsLayoutItemBaseWidget( nullptr, map3D )
75+
, mMap3D( map3D )
76+
{
77+
setupUi( this );
78+
79+
mCenterXSpinBox->setClearValue( 0 );
80+
mCenterYSpinBox->setClearValue( 0 );
81+
mCenterZSpinBox->setClearValue( 0 );
82+
mDistanceToCenterSpinBox->setClearValue( 1000 );
83+
84+
//add widget for general composer item properties
85+
mItemPropertiesWidget = new QgsLayoutItemPropertiesWidget( this, map3D );
86+
mainLayout->addWidget( mItemPropertiesWidget );
87+
88+
mMenu3DCanvases = new QMenu( this );
89+
mCopySettingsButton->setMenu( mMenu3DCanvases );
90+
_prepare3DViewsMenu( mMenu3DCanvases, this, &QgsLayout3DMapWidget::copy3DMapSettings );
91+
92+
mMenu3DCanvasesPose = new QMenu( this );
93+
mPoseFromViewButton->setMenu( mMenu3DCanvasesPose );
94+
_prepare3DViewsMenu( mMenu3DCanvasesPose, this, &QgsLayout3DMapWidget::copeCameraPose );
95+
96+
QList<QgsDoubleSpinBox *> lst;
97+
lst << mCenterXSpinBox << mCenterYSpinBox << mCenterZSpinBox << mDistanceToCenterSpinBox << mPitchAngleSpinBox << mHeadingAngleSpinBox;
98+
for ( QgsDoubleSpinBox *spinBox : lst )
99+
connect( spinBox, qgis::overload<double>::of( &QgsDoubleSpinBox::valueChanged ), this, &QgsLayout3DMapWidget::updateCameraPose );
100+
101+
updateCameraPoseWidgetsFromItem();
102+
}
103+
104+
void QgsLayout3DMapWidget::updateCameraPoseWidgetsFromItem()
105+
{
106+
QgsCameraPose pose = mMap3D->cameraPose();
107+
whileBlocking( mCenterXSpinBox )->setValue( pose.centerPoint().x() );
108+
whileBlocking( mCenterYSpinBox )->setValue( pose.centerPoint().y() );
109+
whileBlocking( mCenterZSpinBox )->setValue( pose.centerPoint().z() );
110+
whileBlocking( mDistanceToCenterSpinBox )->setValue( pose.distanceFromCenterPoint() );
111+
whileBlocking( mPitchAngleSpinBox )->setValue( _normalizedAngle( pose.pitchAngle() ) );
112+
whileBlocking( mHeadingAngleSpinBox )->setValue( _normalizedAngle( pose.headingAngle() ) );
113+
}
114+
115+
void QgsLayout3DMapWidget::copy3DMapSettings()
116+
{
117+
Qgs3DMapCanvasDockWidget *dock = _dock3DViewFromSender( sender() );
118+
if ( dock )
119+
mMap3D->setMapSettings( new Qgs3DMapSettings( *dock->mapCanvas3D()->map() ) );
120+
}
121+
122+
void QgsLayout3DMapWidget::copeCameraPose()
123+
{
124+
Qgs3DMapCanvasDockWidget *dock = _dock3DViewFromSender( sender() );
125+
if ( dock )
126+
{
127+
mMap3D->setCameraPose( dock->mapCanvas3D()->cameraController()->cameraPose() );
128+
updateCameraPoseWidgetsFromItem();
129+
}
130+
}
131+
132+
void QgsLayout3DMapWidget::updateCameraPose()
133+
{
134+
QgsCameraPose pose;
135+
pose.setCenterPoint( QgsVector3D( mCenterXSpinBox->value(), mCenterYSpinBox->value(), mCenterZSpinBox->value() ) );
136+
pose.setDistanceFromCenterPoint( mDistanceToCenterSpinBox->value() );
137+
pose.setPitchAngle( mPitchAngleSpinBox->value() );
138+
pose.setHeadingAngle( mHeadingAngleSpinBox->value() );
139+
mMap3D->setCameraPose( pose );
140+
}
141+
142+
bool QgsLayout3DMapWidget::setNewItem( QgsLayoutItem *item )
143+
{
144+
QgsLayoutItem3DMap *newItem = qobject_cast< QgsLayoutItem3DMap * >( item );
145+
if ( !newItem )
146+
return false;
147+
148+
mMap3D = newItem;
149+
mItemPropertiesWidget->setItem( newItem );
150+
151+
updateCameraPoseWidgetsFromItem();
152+
153+
return true;
154+
}

‎src/app/layout/qgslayout3dmapwidget.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/***************************************************************************
2+
qgslayout3dmapwidget.h
3+
--------------------------------------
4+
Date : August 2018
5+
Copyright : (C) 2018 by Martin Dobias
6+
Email : wonder dot sk at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSLAYOUT3DMAPWIDGET_H
17+
#define QGSLAYOUT3DMAPWIDGET_H
18+
19+
#include "qgslayoutitemwidget.h"
20+
#include "ui_qgslayout3dmapwidgetbase.h"
21+
#include "qgslayoutitem3dmap.h"
22+
23+
class QgsLayoutItem3DMap;
24+
25+
class QgsLayout3DMapWidget : public QgsLayoutItemBaseWidget, private Ui::QgsLayout3DMapWidgetBase
26+
{
27+
Q_OBJECT
28+
public:
29+
explicit QgsLayout3DMapWidget( QgsLayoutItem3DMap *map3D );
30+
31+
protected:
32+
bool setNewItem( QgsLayoutItem *item ) override;
33+
34+
private:
35+
void updateCameraPoseWidgetsFromItem();
36+
37+
private slots:
38+
void copy3DMapSettings();
39+
void copeCameraPose();
40+
void updateCameraPose();
41+
42+
private:
43+
QPointer< QgsLayoutItem3DMap > mMap3D;
44+
QgsLayoutItemPropertiesWidget *mItemPropertiesWidget = nullptr;
45+
QMenu *mMenu3DCanvases = nullptr;
46+
QMenu *mMenu3DCanvasesPose = nullptr;
47+
};
48+
49+
#endif // QGSLAYOUT3DMAPWIDGET_H

‎src/app/layout/qgslayoutapputils.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
#include "qgisapp.h"
4444
#include "qgsmapcanvas.h"
4545

46+
#ifdef HAVE_3D
47+
#include "qgslayout3dmapwidget.h"
48+
#endif
49+
4650
void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
4751
{
4852
QgsLayoutItemGuiRegistry *registry = QgsGui::layoutItemGuiRegistry();
@@ -92,6 +96,16 @@ void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
9296
} );
9397
registry->addLayoutItemGuiMetadata( mapItemMetadata.release() );
9498

99+
// 3D map item
100+
#ifdef HAVE_3D
101+
std::unique_ptr< QgsLayoutItemGuiMetadata > map3dMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata>(
102+
QgsLayoutItemRegistry::Layout3DMap, QObject::tr( "3D Map" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAdd3DMap.svg" ) ),
103+
[ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
104+
{
105+
return new QgsLayout3DMapWidget( qobject_cast< QgsLayoutItem3DMap * >( item ) );
106+
}, createRubberBand );
107+
registry->addLayoutItemGuiMetadata( map3dMetadata.release() );
108+
#endif
95109

96110
// picture item
97111

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>QgsLayout3DMapWidgetBase</class>
4+
<widget class="QWidget" name="QgsLayout3DMapWidgetBase">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>540</width>
10+
<height>613</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>3D Map</string>
15+
</property>
16+
<layout class="QVBoxLayout" name="verticalLayout_2">
17+
<property name="spacing">
18+
<number>0</number>
19+
</property>
20+
<property name="leftMargin">
21+
<number>0</number>
22+
</property>
23+
<property name="topMargin">
24+
<number>0</number>
25+
</property>
26+
<property name="rightMargin">
27+
<number>0</number>
28+
</property>
29+
<property name="bottomMargin">
30+
<number>0</number>
31+
</property>
32+
<item>
33+
<widget class="QLabel" name="mLabel">
34+
<property name="sizePolicy">
35+
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
36+
<horstretch>0</horstretch>
37+
<verstretch>0</verstretch>
38+
</sizepolicy>
39+
</property>
40+
<property name="styleSheet">
41+
<string notr="true">padding: 2px; font-weight: bold; background-color: rgb(200, 200, 200);</string>
42+
</property>
43+
<property name="text">
44+
<string>3D Map</string>
45+
</property>
46+
</widget>
47+
</item>
48+
<item>
49+
<widget class="QScrollArea" name="scrollArea">
50+
<property name="widgetResizable">
51+
<bool>true</bool>
52+
</property>
53+
<widget class="QWidget" name="scrollAreaWidgetContents_2">
54+
<property name="geometry">
55+
<rect>
56+
<x>0</x>
57+
<y>-128</y>
58+
<width>510</width>
59+
<height>698</height>
60+
</rect>
61+
</property>
62+
<layout class="QVBoxLayout" name="mainLayout">
63+
<item>
64+
<widget class="QGroupBox" name="groupBox_3">
65+
<property name="title">
66+
<string>Scene settings</string>
67+
</property>
68+
<layout class="QVBoxLayout" name="verticalLayout_5">
69+
<item>
70+
<widget class="QPushButton" name="mCopySettingsButton">
71+
<property name="text">
72+
<string>Copy settings from a 3D view...</string>
73+
</property>
74+
</widget>
75+
</item>
76+
</layout>
77+
</widget>
78+
</item>
79+
<item>
80+
<widget class="QGroupBox" name="groupBox_4">
81+
<property name="title">
82+
<string>Camera pose</string>
83+
</property>
84+
<layout class="QGridLayout" name="gridLayout_3">
85+
<item row="1" column="1">
86+
<widget class="QgsDoubleSpinBox" name="mCenterYSpinBox">
87+
<property name="decimals">
88+
<number>1</number>
89+
</property>
90+
<property name="minimum">
91+
<double>-9999999.000000000000000</double>
92+
</property>
93+
<property name="maximum">
94+
<double>9999999.000000000000000</double>
95+
</property>
96+
</widget>
97+
</item>
98+
<item row="0" column="0">
99+
<widget class="QLabel" name="label">
100+
<property name="text">
101+
<string>Center X</string>
102+
</property>
103+
</widget>
104+
</item>
105+
<item row="3" column="1">
106+
<widget class="QgsDoubleSpinBox" name="mDistanceToCenterSpinBox">
107+
<property name="decimals">
108+
<number>1</number>
109+
</property>
110+
<property name="maximum">
111+
<double>9999999.000000000000000</double>
112+
</property>
113+
</widget>
114+
</item>
115+
<item row="2" column="0">
116+
<widget class="QLabel" name="label_4">
117+
<property name="text">
118+
<string>Center Z</string>
119+
</property>
120+
</widget>
121+
</item>
122+
<item row="1" column="0">
123+
<widget class="QLabel" name="label_3">
124+
<property name="text">
125+
<string>Center Y</string>
126+
</property>
127+
</widget>
128+
</item>
129+
<item row="5" column="1">
130+
<widget class="QgsDoubleSpinBox" name="mHeadingAngleSpinBox">
131+
<property name="suffix">
132+
<string> °</string>
133+
</property>
134+
<property name="decimals">
135+
<number>1</number>
136+
</property>
137+
<property name="maximum">
138+
<double>360.000000000000000</double>
139+
</property>
140+
</widget>
141+
</item>
142+
<item row="5" column="0">
143+
<widget class="QLabel" name="label_7">
144+
<property name="text">
145+
<string>Heading</string>
146+
</property>
147+
</widget>
148+
</item>
149+
<item row="4" column="0">
150+
<widget class="QLabel" name="label_6">
151+
<property name="text">
152+
<string>Pitch</string>
153+
</property>
154+
</widget>
155+
</item>
156+
<item row="4" column="1">
157+
<widget class="QgsDoubleSpinBox" name="mPitchAngleSpinBox">
158+
<property name="suffix">
159+
<string> °</string>
160+
</property>
161+
<property name="decimals">
162+
<number>1</number>
163+
</property>
164+
<property name="maximum">
165+
<double>360.000000000000000</double>
166+
</property>
167+
</widget>
168+
</item>
169+
<item row="0" column="1">
170+
<widget class="QgsDoubleSpinBox" name="mCenterXSpinBox">
171+
<property name="decimals">
172+
<number>1</number>
173+
</property>
174+
<property name="minimum">
175+
<double>-9999999.000000000000000</double>
176+
</property>
177+
<property name="maximum">
178+
<double>9999999.000000000000000</double>
179+
</property>
180+
</widget>
181+
</item>
182+
<item row="3" column="0">
183+
<widget class="QLabel" name="label_5">
184+
<property name="text">
185+
<string>Distance</string>
186+
</property>
187+
</widget>
188+
</item>
189+
<item row="2" column="1">
190+
<widget class="QgsDoubleSpinBox" name="mCenterZSpinBox">
191+
<property name="decimals">
192+
<number>1</number>
193+
</property>
194+
<property name="minimum">
195+
<double>-9999999.000000000000000</double>
196+
</property>
197+
<property name="maximum">
198+
<double>9999999.000000000000000</double>
199+
</property>
200+
</widget>
201+
</item>
202+
<item row="6" column="0" colspan="2">
203+
<widget class="QPushButton" name="mPoseFromViewButton">
204+
<property name="text">
205+
<string>Set from a 3D view...</string>
206+
</property>
207+
</widget>
208+
</item>
209+
</layout>
210+
</widget>
211+
</item>
212+
</layout>
213+
</widget>
214+
</widget>
215+
</item>
216+
</layout>
217+
</widget>
218+
<customwidgets>
219+
<customwidget>
220+
<class>QgsDoubleSpinBox</class>
221+
<extends>QDoubleSpinBox</extends>
222+
<header>qgsdoublespinbox.h</header>
223+
</customwidget>
224+
</customwidgets>
225+
<resources/>
226+
<connections/>
227+
</ui>

0 commit comments

Comments
 (0)
Please sign in to comment.