diff --git a/CMakeLists.txt b/CMakeLists.txt index 3871987b1db..e0ed8410a31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -284,6 +284,7 @@ IF(WITH_CORE) FIND_PACKAGE(Qt53DInput REQUIRED) FIND_PACKAGE(Qt53DLogic REQUIRED) FIND_PACKAGE(Qt53DExtras REQUIRED) + SET(HAVE_3D TRUE) # used in qgsconfig.h ENDIF (WITH_3D) INCLUDE("cmake/modules/ECMQt4To5Porting.cmake") MESSAGE(STATUS "Found Qt version: ${Qt5Core_VERSION_STRING}") diff --git a/cmake_templates/qgsconfig.h.in b/cmake_templates/qgsconfig.h.in index 102f3a2d979..99007f6d596 100644 --- a/cmake_templates/qgsconfig.h.in +++ b/cmake_templates/qgsconfig.h.in @@ -13,7 +13,7 @@ //used in vim src/core/qgis.cpp //The way below should work but it resolves to a number like 0110 which the compiler treats as octal I think //because debuggin it out shows the decimal number 72 which results in incorrect version status. -//As a short term fix I (Tim) am defining the version in top level cmake. It would be good to +//As a short term fix I (Tim) am defining the version in top level cmake. It would be good to //reinstate this more generic approach below at some point though //#define VERSION_INT ${CPACK_PACKAGE_VERSION_MAJOR}${CPACK_PACKAGE_VERSION_MINOR}${CPACK_PACKAGE_VERSION_PATCH} #define VERSION_INT ${QGIS_VERSION_INT} @@ -58,5 +58,7 @@ #cmakedefine ENABLE_MODELTEST +#cmakedefine HAVE_3D + #endif diff --git a/images/images.qrc b/images/images.qrc index 535490fc61f..dbbd34252f2 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -570,6 +570,7 @@ themes/default/mGeoPackage.svg themes/default/mActionAddGeoPackageLayer.svg icons/qgis_icon.svg + themes/default/3d.svg qgis_tips/symbol_levels.png diff --git a/images/themes/default/3d.svg b/images/themes/default/3d.svg new file mode 100644 index 00000000000..330ea19e678 --- /dev/null +++ b/images/themes/default/3d.svg @@ -0,0 +1,38 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/python/core/qgsapplication.sip b/python/core/qgsapplication.sip index 8f051af9ceb..17aa06c84d2 100644 --- a/python/core/qgsapplication.sip +++ b/python/core/qgsapplication.sip @@ -704,6 +704,7 @@ Returns path to the build output directory. Valid only when running from build d :rtype: QgsFieldFormatterRegistry %End + static QString nullRepresentation(); %Docstring This string is used to represent the value `NULL` throughout QGIS. diff --git a/python/core/qgsmaplayer.sip b/python/core/qgsmaplayer.sip index e6e45cdebaa..be3c0691ad9 100644 --- a/python/core/qgsmaplayer.sip +++ b/python/core/qgsmaplayer.sip @@ -754,6 +754,8 @@ Return pointer to layer's undo stack :rtype: QgsMapLayerStyleManager %End + + bool isInScaleRange( double scale ) const; %Docstring Tests whether the layer should be visible at the specified ``scale``. @@ -1014,6 +1016,12 @@ Signal emitted when the blend mode is changed, through QgsMapLayer.setBlendMode( %Docstring Signal emitted when legend of the layer has changed .. versionadded:: 2.6 +%End + + void renderer3DChanged(); +%Docstring + Signal emitted when 3D renderer associated with the layer has changed. +.. versionadded:: 3.0 %End void configChanged(); diff --git a/src/3d/CMakeLists.txt b/src/3d/CMakeLists.txt index 711bd596f73..87000714393 100644 --- a/src/3d/CMakeLists.txt +++ b/src/3d/CMakeLists.txt @@ -2,7 +2,6 @@ # sources SET(QGIS_3D_SRCS - abstract3drenderer.cpp abstract3dsymbol.cpp cameracontroller.cpp lineentity.cpp @@ -15,6 +14,7 @@ SET(QGIS_3D_SRCS tessellator.cpp tilingscheme.cpp utils.cpp + vectorlayer3drenderer.cpp chunks/chunkboundsentity.cpp chunks/chunkedentity.cpp @@ -63,7 +63,6 @@ QT5_ADD_RESOURCES(QGIS_3D_RCC_SRCS shaders.qrc) SET(QGIS_3D_HDRS aabb.h - abstract3drenderer.h abstract3dsymbol.h cameracontroller.h lineentity.h @@ -76,6 +75,7 @@ SET(QGIS_3D_HDRS tessellator.h tilingscheme.h utils.h + vectorlayer3drenderer.h chunks/chunkboundsentity.h chunks/chunkedentity.h @@ -105,6 +105,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src/core/symbology-ng ${CMAKE_SOURCE_DIR}/src/core/metadata ${CMAKE_SOURCE_DIR}/src/core/expression + ${CMAKE_SOURCE_DIR}/src/core/3d ${CMAKE_BINARY_DIR}/src/core ${CMAKE_BINARY_DIR}/src/3d ) diff --git a/src/3d/map3d.cpp b/src/3d/map3d.cpp index 375d82b5027..a0a24f787a6 100644 --- a/src/3d/map3d.cpp +++ b/src/3d/map3d.cpp @@ -1,9 +1,9 @@ #include "map3d.h" -#include "abstract3drenderer.h" #include "flatterraingenerator.h" #include "demterraingenerator.h" //#include "quantizedmeshterraingenerator.h" +#include "vectorlayer3drenderer.h" #include #include @@ -44,7 +44,7 @@ Map3D::Map3D( const Map3D &other ) , mShowTerrainTileInfo( other.mShowTerrainTileInfo ) , mLayers( other.mLayers ) { - Q_FOREACH ( Abstract3DRenderer *renderer, other.renderers ) + Q_FOREACH ( QgsAbstract3DRenderer *renderer, other.renderers ) { renderers << renderer->clone(); } @@ -108,7 +108,7 @@ void Map3D::readXml( const QDomElement &elem, const QgsReadWriteContext &context QDomElement elemRenderer = elemRenderers.firstChildElement( "renderer" ); while ( !elemRenderer.isNull() ) { - Abstract3DRenderer *renderer = nullptr; + QgsAbstract3DRenderer *renderer = nullptr; QString type = elemRenderer.attribute( "type" ); if ( type == "vector" ) { @@ -167,7 +167,7 @@ QDomElement Map3D::writeXml( QDomDocument &doc, const QgsReadWriteContext &conte elem.appendChild( elemTerrain ); QDomElement elemRenderers = doc.createElement( "renderers" ); - Q_FOREACH ( const Abstract3DRenderer *renderer, renderers ) + Q_FOREACH ( const QgsAbstract3DRenderer *renderer, renderers ) { QDomElement elemRenderer = doc.createElement( "renderer" ); elemRenderer.setAttribute( "type", renderer->type() ); @@ -203,7 +203,7 @@ void Map3D::resolveReferences( const QgsProject &project ) for ( int i = 0; i < renderers.count(); ++i ) { - Abstract3DRenderer *renderer = renderers[i]; + QgsAbstract3DRenderer *renderer = renderers[i]; renderer->resolveReferences( project ); } } diff --git a/src/3d/map3d.h b/src/3d/map3d.h index 2d79724a1e7..f18f3486ceb 100644 --- a/src/3d/map3d.h +++ b/src/3d/map3d.h @@ -13,7 +13,7 @@ class QgsMapLayer; class QgsRasterLayer; -class Abstract3DRenderer; +class QgsAbstract3DRenderer; class TerrainGenerator; @@ -61,7 +61,7 @@ class _3D_EXPORT Map3D : public QObject // 3D renderers // - QList renderers; //!< Stuff to render as 3D object + QList renderers; //!< Stuff to render as 3D object bool skybox; //!< Whether to render skybox QString skyboxFileBase; diff --git a/src/3d/phongmaterialsettings.h b/src/3d/phongmaterialsettings.h index c8722f1decd..42070de13fd 100644 --- a/src/3d/phongmaterialsettings.h +++ b/src/3d/phongmaterialsettings.h @@ -12,10 +12,10 @@ class _3D_EXPORT PhongMaterialSettings { public: PhongMaterialSettings() - : mAmbient( QColor::fromRgbF( 0.05f, 0.05f, 0.05f, 1.0f ) ) + : mAmbient( QColor::fromRgbF( 0.1f, 0.1f, 0.1f, 1.0f ) ) , mDiffuse( QColor::fromRgbF( 0.7f, 0.7f, 0.7f, 1.0f ) ) - , mSpecular( QColor::fromRgbF( 0.01f, 0.01f, 0.01f, 1.0f ) ) - , mShininess( 150.0f ) + , mSpecular( QColor::fromRgbF( 1.0f, 1.0f, 1.0f, 1.0f ) ) + , mShininess( 0.0f ) { } diff --git a/src/3d/scene.cpp b/src/3d/scene.cpp index f009ac62d03..2e6da0b77cd 100644 --- a/src/3d/scene.cpp +++ b/src/3d/scene.cpp @@ -8,7 +8,7 @@ #include #include "aabb.h" -#include "abstract3drenderer.h" +#include "qgsabstract3drenderer.h" #include "cameracontroller.h" #include "map3d.h" #include "terrain.h" @@ -59,12 +59,26 @@ Scene::Scene( const Map3D &map, Qt3DExtras::QForwardRenderer *defaultFrameGraph, // create entities of renderers - Q_FOREACH ( const Abstract3DRenderer *renderer, map.renderers ) + Q_FOREACH ( const QgsAbstract3DRenderer *renderer, map.renderers ) { Qt3DCore::QEntity *p = renderer->createEntity( map ); p->setParent( this ); } + // create entities of renderers of layers + + Q_FOREACH ( QgsMapLayer *layer, map.layers() ) + { + if ( layer->renderer3D() ) + { + Qt3DCore::QEntity *p = layer->renderer3D()->createEntity( map ); + p->setParent( this ); + mLayerEntities.insert( layer, p ); + } + connect( layer, &QgsMapLayer::renderer3DChanged, this, &Scene::onLayerRenderer3DChanged ); + // TODO: connect( layer, &QgsMapLayer::willBeDeleted, this, &Scene::onLayerWillBeDeleted ); + } + Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity; Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform; lightTransform->setTranslation( QVector3D( 0, 1000, 0 ) ); @@ -83,10 +97,10 @@ Scene::Scene( const Map3D &map, Qt3DExtras::QForwardRenderer *defaultFrameGraph, ChunkedEntity *testChunkEntity = new ChunkedEntity( AABB( -500, 0, -500, 500, 100, 500 ), 2.f, 3.f, 7, new TestChunkLoaderFactory ); testChunkEntity->setEnabled( false ); testChunkEntity->setParent( this ); - chunkEntities << testChunkEntity + chunkEntities << testChunkEntity; #endif - connect( mCameraController, &CameraController::cameraChanged, this, &Scene::onCameraChanged ); + connect( mCameraController, &CameraController::cameraChanged, this, &Scene::onCameraChanged ); connect( mCameraController, &CameraController::viewportChanged, this, &Scene::onCameraChanged ); #if 0 @@ -190,3 +204,23 @@ void Scene::createTerrain() onCameraChanged(); // force update of the new terrain } + +void Scene::onLayerRenderer3DChanged() +{ + QgsMapLayer *layer = qobject_cast( sender() ); + Q_ASSERT( layer ); + + // remove old entity - if any + Qt3DCore::QEntity *entity = mLayerEntities.take( layer ); + if ( entity ) + entity->deleteLater(); + + // add new entity - if any 3D renderer + QgsAbstract3DRenderer *renderer = layer->renderer3D(); + if ( renderer ) + { + Qt3DCore::QEntity *newEntity = renderer->createEntity( mMap ); + newEntity->setParent( this ); + mLayerEntities.insert( layer, newEntity ); + } +} diff --git a/src/3d/scene.h b/src/3d/scene.h index 7b617d3dd9e..0810abaa09c 100644 --- a/src/3d/scene.h +++ b/src/3d/scene.h @@ -21,6 +21,7 @@ namespace Qt3DExtras class QForwardRenderer; } +class QgsMapLayer; class CameraController; class Map3D; class Terrain; @@ -42,6 +43,7 @@ class _3D_EXPORT Scene : public Qt3DCore::QEntity void onCameraChanged(); void onFrameTriggered( float dt ); void createTerrain(); + void onLayerRenderer3DChanged(); private: const Map3D &mMap; @@ -50,6 +52,8 @@ class _3D_EXPORT Scene : public Qt3DCore::QEntity CameraController *mCameraController; Terrain *mTerrain; QList chunkEntities; + //! Keeps track of entities that belong to a particular layer + QMap mLayerEntities; }; #endif // SCENE_H diff --git a/src/3d/testapp/main.cpp b/src/3d/testapp/main.cpp index 595c067925c..969bfaef846 100644 --- a/src/3d/testapp/main.cpp +++ b/src/3d/testapp/main.cpp @@ -5,10 +5,10 @@ #include #include -#include "abstract3drenderer.h" #include "abstract3dsymbol.h" #include "maptexturegenerator.h" #include "sidepanel.h" +#include "vectorlayer3drenderer.h" #include "window3d.h" #include "map3d.h" #include "flatterraingenerator.h" @@ -239,6 +239,7 @@ int main( int argc, char *argv[] ) hLayout->addWidget( container, 1 ); hLayout->addWidget( sidePanel ); + widget.setWindowTitle( "QGIS 3D" ); widget.resize( 800, 600 ); widget.show(); diff --git a/src/3d/abstract3drenderer.cpp b/src/3d/vectorlayer3drenderer.cpp similarity index 96% rename from src/3d/abstract3drenderer.cpp rename to src/3d/vectorlayer3drenderer.cpp index 7f162aad325..a1989f7589d 100644 --- a/src/3d/abstract3drenderer.cpp +++ b/src/3d/vectorlayer3drenderer.cpp @@ -1,4 +1,4 @@ -#include "abstract3drenderer.h" +#include "vectorlayer3drenderer.h" #include "abstract3dsymbol.h" #include "lineentity.h" @@ -18,7 +18,7 @@ VectorLayer3DRenderer::~VectorLayer3DRenderer() { } -Abstract3DRenderer *VectorLayer3DRenderer::clone() const +VectorLayer3DRenderer *VectorLayer3DRenderer::clone() const { VectorLayer3DRenderer *r = new VectorLayer3DRenderer( mSymbol ? mSymbol->clone() : nullptr ); r->layerRef = layerRef; diff --git a/src/3d/abstract3drenderer.h b/src/3d/vectorlayer3drenderer.h similarity index 57% rename from src/3d/abstract3drenderer.h rename to src/3d/vectorlayer3drenderer.h index 0b7cc2bf6b9..2537579ca2a 100644 --- a/src/3d/abstract3drenderer.h +++ b/src/3d/vectorlayer3drenderer.h @@ -1,8 +1,10 @@ -#ifndef ABSTRACT3DRENDERER_H -#define ABSTRACT3DRENDERER_H +#ifndef VECTORLAYER3DRENDERER_H +#define VECTORLAYER3DRENDERER_H #include "qgis_3d.h" +#include "qgsabstract3drenderer.h" + #include "phongmaterialsettings.h" #include "utils.h" @@ -13,34 +15,11 @@ class QgsVectorLayer; class Abstract3DSymbol; -class Map3D; - - -namespace Qt3DCore -{ - class QEntity; -} - -class _3D_EXPORT Abstract3DRenderer //: public QObject -{ - //Q_OBJECT - public: - virtual ~Abstract3DRenderer() {} - - virtual QString type() const = 0; - virtual Abstract3DRenderer *clone() const = 0; - virtual Qt3DCore::QEntity *createEntity( const Map3D &map ) const = 0; - - virtual void writeXml( QDomElement &elem ) const = 0; - virtual void readXml( const QDomElement &elem ) = 0; - virtual void resolveReferences( const QgsProject &project ) { Q_UNUSED( project ); } -}; - /** 3D renderer that renders all features of a vector layer with the same 3D symbol. - * The appearance if completely defined by the symbol. + * The appearance is completely defined by the symbol. */ -class _3D_EXPORT VectorLayer3DRenderer : public Abstract3DRenderer +class _3D_EXPORT VectorLayer3DRenderer : public QgsAbstract3DRenderer { public: //! Takes ownership of the symbol object @@ -55,7 +34,7 @@ class _3D_EXPORT VectorLayer3DRenderer : public Abstract3DRenderer const Abstract3DSymbol *symbol() const; QString type() const override { return "vector"; } - Abstract3DRenderer *clone() const override; + VectorLayer3DRenderer *clone() const override; Qt3DCore::QEntity *createEntity( const Map3D &map ) const override; void writeXml( QDomElement &elem ) const override; @@ -68,4 +47,4 @@ class _3D_EXPORT VectorLayer3DRenderer : public Abstract3DRenderer }; -#endif // ABSTRACT3DRENDERER_H +#endif // VECTORLAYER3DRENDERER_H diff --git a/src/app/3d/qgsphongmaterialwidget.cpp b/src/app/3d/qgsphongmaterialwidget.cpp new file mode 100644 index 00000000000..a884aa99a5f --- /dev/null +++ b/src/app/3d/qgsphongmaterialwidget.cpp @@ -0,0 +1,35 @@ +#include "qgsphongmaterialwidget.h" + +#include "phongmaterialsettings.h" + + +QgsPhongMaterialWidget::QgsPhongMaterialWidget( QWidget *parent ) + : QWidget( parent ) +{ + setupUi( this ); + + setMaterial( PhongMaterialSettings() ); + + connect( btnDiffuse, &QgsColorButton::colorChanged, this, &QgsPhongMaterialWidget::changed ); + connect( btnAmbient, &QgsColorButton::colorChanged, this, &QgsPhongMaterialWidget::changed ); + connect( btnSpecular, &QgsColorButton::colorChanged, this, &QgsPhongMaterialWidget::changed ); + connect( spinShininess, static_cast( &QDoubleSpinBox::valueChanged ), this, &QgsPhongMaterialWidget::changed ); +} + +void QgsPhongMaterialWidget::setMaterial( const PhongMaterialSettings &material ) +{ + btnDiffuse->setColor( material.diffuse() ); + btnAmbient->setColor( material.ambient() ); + btnSpecular->setColor( material.specular() ); + spinShininess->setValue( material.shininess() ); +} + +PhongMaterialSettings QgsPhongMaterialWidget::material() const +{ + PhongMaterialSettings m; + m.setDiffuse( btnDiffuse->color() ); + m.setAmbient( btnAmbient->color() ); + m.setSpecular( btnSpecular->color() ); + m.setShininess( spinShininess->value() ); + return m; +} diff --git a/src/app/3d/qgsphongmaterialwidget.h b/src/app/3d/qgsphongmaterialwidget.h new file mode 100644 index 00000000000..dc0acc742ad --- /dev/null +++ b/src/app/3d/qgsphongmaterialwidget.h @@ -0,0 +1,27 @@ +#ifndef QGSPHONGMATERIALWIDGET_H +#define QGSPHONGMATERIALWIDGET_H + +#include + +#include + +class PhongMaterialSettings; + + +//! Widget for configuration of Phong material settings +class QgsPhongMaterialWidget : public QWidget, private Ui::PhongMaterialWidget +{ + Q_OBJECT + public: + explicit QgsPhongMaterialWidget( QWidget *parent = nullptr ); + + void setMaterial( const PhongMaterialSettings &material ); + PhongMaterialSettings material() const; + + signals: + void changed(); + + public slots: +}; + +#endif // QGSPHONGMATERIALWIDGET_H diff --git a/src/app/3d/qgspolygon3dsymbolwidget.cpp b/src/app/3d/qgspolygon3dsymbolwidget.cpp new file mode 100644 index 00000000000..2335234fac4 --- /dev/null +++ b/src/app/3d/qgspolygon3dsymbolwidget.cpp @@ -0,0 +1,38 @@ +#include "qgspolygon3dsymbolwidget.h" + +#include "abstract3dsymbol.h" + + +QgsPolygon3DSymbolWidget::QgsPolygon3DSymbolWidget( QWidget *parent ) + : QWidget( parent ) +{ + setupUi( this ); + + setSymbol( Polygon3DSymbol() ); + + connect( spinHeight, static_cast( &QDoubleSpinBox::valueChanged ), this, &QgsPolygon3DSymbolWidget::changed ); + connect( spinExtrusion, static_cast( &QDoubleSpinBox::valueChanged ), this, &QgsPolygon3DSymbolWidget::changed ); + connect( cboAltClamping, static_cast( &QComboBox::currentIndexChanged ), this, &QgsPolygon3DSymbolWidget::changed ); + connect( cboAltBinding, static_cast( &QComboBox::currentIndexChanged ), this, &QgsPolygon3DSymbolWidget::changed ); + connect( widgetMaterial, &QgsPhongMaterialWidget::changed, this, &QgsPolygon3DSymbolWidget::changed ); +} + +void QgsPolygon3DSymbolWidget::setSymbol( const Polygon3DSymbol &symbol ) +{ + spinHeight->setValue( symbol.height ); + spinExtrusion->setValue( symbol.extrusionHeight ); + cboAltClamping->setCurrentIndex( ( int ) symbol.altClamping ); + cboAltBinding->setCurrentIndex( ( int ) symbol.altBinding ); + widgetMaterial->setMaterial( symbol.material ); +} + +Polygon3DSymbol QgsPolygon3DSymbolWidget::symbol() const +{ + Polygon3DSymbol sym; + sym.height = spinHeight->value(); + sym.extrusionHeight = spinExtrusion->value(); + sym.altClamping = ( AltitudeClamping ) cboAltClamping->currentIndex(); + sym.altBinding = ( AltitudeBinding ) cboAltBinding->currentIndex(); + sym.material = widgetMaterial->material(); + return sym; +} diff --git a/src/app/3d/qgspolygon3dsymbolwidget.h b/src/app/3d/qgspolygon3dsymbolwidget.h new file mode 100644 index 00000000000..ec71a28a1d9 --- /dev/null +++ b/src/app/3d/qgspolygon3dsymbolwidget.h @@ -0,0 +1,26 @@ +#ifndef QGSPOLYGON3DSYMBOLWIDGET_H +#define QGSPOLYGON3DSYMBOLWIDGET_H + +#include + +#include "ui_polygon3dsymbolwidget.h" + +class Polygon3DSymbol; + +//! A widget for configuration of 3D symbol for polygons +class QgsPolygon3DSymbolWidget : public QWidget, private Ui::Polygon3DSymbolWidget +{ + Q_OBJECT + public: + explicit QgsPolygon3DSymbolWidget( QWidget *parent = nullptr ); + + void setSymbol( const Polygon3DSymbol &symbol ); + Polygon3DSymbol symbol() const; + + signals: + void changed(); + + public slots: +}; + +#endif // QGSPOLYGON3DSYMBOLWIDGET_H diff --git a/src/app/3d/qgsvectorlayer3drendererwidget.cpp b/src/app/3d/qgsvectorlayer3drendererwidget.cpp new file mode 100644 index 00000000000..071023b7865 --- /dev/null +++ b/src/app/3d/qgsvectorlayer3drendererwidget.cpp @@ -0,0 +1,88 @@ +#include "qgsvectorlayer3drendererwidget.h" + +#include "abstract3dsymbol.h" +#include "qgspolygon3dsymbolwidget.h" +#include "vectorlayer3drenderer.h" + +#include "qgsvectorlayer.h" + +#include +#include + +QgsVectorLayer3DRendererWidget::QgsVectorLayer3DRendererWidget( QgsVectorLayer *layer, QgsMapCanvas *canvas, QWidget *parent ) + : QgsMapLayerConfigWidget( layer, canvas, parent ) +{ + setPanelTitle( tr( "3D View" ) ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + chkEnabled = new QCheckBox( "Enable 3D renderer", this ); + widgetPolygon = new QgsPolygon3DSymbolWidget( this ); + layout->addWidget( chkEnabled ); + layout->addWidget( widgetPolygon ); + + widgetPolygon->setEnabled( false ); + + connect( chkEnabled, &QCheckBox::clicked, this, &QgsVectorLayer3DRendererWidget::onEnabledClicked ); + connect( widgetPolygon, &QgsPolygon3DSymbolWidget::changed, this, &QgsVectorLayer3DRendererWidget::widgetChanged ); +} + +QgsVectorLayer3DRendererWidget::~QgsVectorLayer3DRendererWidget() +{ +} + +void QgsVectorLayer3DRendererWidget::setLayer( QgsVectorLayer *layer ) +{ + mLayer = layer; + + QgsAbstract3DRenderer *r = layer->renderer3D(); + if ( r && r->type() == "vector" ) + { + VectorLayer3DRenderer *vectorRenderer = static_cast( r ); + setRenderer( vectorRenderer ); + } + else + { + setRenderer( nullptr ); + } +} + +void QgsVectorLayer3DRendererWidget::setRenderer( const VectorLayer3DRenderer *renderer ) +{ + mRenderer.reset( renderer ? renderer->clone() : nullptr ); + + whileBlocking( chkEnabled )->setChecked( ( bool )mRenderer ); + widgetPolygon->setEnabled( chkEnabled->isChecked() ); + + if ( mRenderer && mRenderer->symbol() && mRenderer->symbol()->type() == "polygon" ) + { + whileBlocking( widgetPolygon )->setSymbol( *static_cast( mRenderer->symbol() ) ); + } +} + +VectorLayer3DRenderer *QgsVectorLayer3DRendererWidget::renderer() +{ + if ( chkEnabled->isChecked() ) + { + VectorLayer3DRenderer *r = new VectorLayer3DRenderer( new Polygon3DSymbol( widgetPolygon->symbol() ) ); + r->setLayer( qobject_cast( mLayer ) ); + mRenderer.reset( r ); + } + else + { + mRenderer.reset(); + } + + return mRenderer.get(); +} + +void QgsVectorLayer3DRendererWidget::apply() +{ + VectorLayer3DRenderer *r = renderer(); + mLayer->setRenderer3D( r ? r->clone() : nullptr ); +} + +void QgsVectorLayer3DRendererWidget::onEnabledClicked() +{ + widgetPolygon->setEnabled( chkEnabled->isChecked() ); + emit widgetChanged(); +} diff --git a/src/app/3d/qgsvectorlayer3drendererwidget.h b/src/app/3d/qgsvectorlayer3drendererwidget.h new file mode 100644 index 00000000000..07703c1e165 --- /dev/null +++ b/src/app/3d/qgsvectorlayer3drendererwidget.h @@ -0,0 +1,43 @@ +#ifndef QGSVECTORLAYER3DRENDERERWIDGET_H +#define QGSVECTORLAYER3DRENDERERWIDGET_H + +#include + +#include "qgsmaplayerconfigwidget.h" + +class QCheckBox; +class QgsPolygon3DSymbolWidget; +class QgsVectorLayer; +class QgsMapCanvas; +class VectorLayer3DRenderer; + + +//! Widget for configuration of 3D renderer of a vector layer +class QgsVectorLayer3DRendererWidget : public QgsMapLayerConfigWidget +{ + Q_OBJECT + public: + explicit QgsVectorLayer3DRendererWidget( QgsVectorLayer *layer, QgsMapCanvas *canvas, QWidget *parent = nullptr ); + ~QgsVectorLayer3DRendererWidget(); + + void setLayer( QgsVectorLayer *layer ); + + //! no transfer of ownership + void setRenderer( const VectorLayer3DRenderer *renderer ); + //! no transfer of ownership + VectorLayer3DRenderer *renderer(); + + public slots: + virtual void apply() override; + + private slots: + void onEnabledClicked(); + + private: + QCheckBox *chkEnabled; + QgsPolygon3DSymbolWidget *widgetPolygon; + + std::unique_ptr mRenderer; +}; + +#endif // QGSVECTORLAYER3DRENDERERWIDGET_H diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 33ca7904a24..18ee7ee17e7 100755 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -370,6 +370,9 @@ IF (WITH_3D) 3d/qgs3dmapcanvas.cpp 3d/qgs3dmapcanvasdockwidget.cpp 3d/qgs3dmapconfigwidget.cpp + 3d/qgspolygon3dsymbolwidget.cpp + 3d/qgsphongmaterialwidget.cpp + 3d/qgsvectorlayer3drendererwidget.cpp ) SET (QGIS_APP_MOC_HDRS @@ -377,6 +380,9 @@ IF (WITH_3D) 3d/qgs3dmapcanvas.h 3d/qgs3dmapcanvasdockwidget.h 3d/qgs3dmapconfigwidget.h + 3d/qgspolygon3dsymbolwidget.h + 3d/qgsphongmaterialwidget.h + 3d/qgsvectorlayer3drendererwidget.h ) ENDIF (WITH_3D) @@ -561,6 +567,7 @@ INCLUDE_DIRECTORIES( IF (WITH_3D) INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src/app/3d + ${CMAKE_SOURCE_DIR}/src/core/3d ${CMAKE_SOURCE_DIR}/src/3d ${CMAKE_SOURCE_DIR}/src/3d/terrain ${CMAKE_SOURCE_DIR}/src/3d/chunks diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 8601726717c..6b4a75f5d53 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -9879,6 +9879,7 @@ void QgisApp::newMapCanvas() } } +#include "qgsabstract3drenderer.h" #include "qgs3dmapcanvasdockwidget.h" #include "map3d.h" #include "flatterraingenerator.h" @@ -9890,8 +9891,6 @@ void QgisApp::new3DMapCanvas() QgsRectangle fullExtent = mMapCanvas->fullExtent(); Map3D *map = new Map3D; - map->setShowTerrainBoundingBoxes( true ); - map->setShowTerrainTilesInfo( true ); map->crs = prj->crs(); map->originX = fullExtent.center().x(); map->originY = fullExtent.center().y(); @@ -9910,7 +9909,7 @@ void QgisApp::new3DMapCanvas() map3DWidget->setGeometry( QRect( rect().width() * 0.75, rect().height() * 0.5, 400, 400 ) ); map3DWidget->setMap( map ); map3DWidget->setMainCanvas( mMapCanvas ); - addDockWidget( Qt::RightDockWidgetArea, map3DWidget ); + addDockWidget( Qt::BottomDockWidgetArea, map3DWidget ); } void QgisApp::setExtent( const QgsRectangle &rect ) diff --git a/src/app/qgslayerstylingwidget.cpp b/src/app/qgslayerstylingwidget.cpp index ecdaca3c615..4774fc8eb78 100644 --- a/src/app/qgslayerstylingwidget.cpp +++ b/src/app/qgslayerstylingwidget.cpp @@ -45,6 +45,10 @@ #include "qgsruntimeprofiler.h" #include "qgsrasterminmaxwidget.h" +#ifdef HAVE_3D +#include "qgsvectorlayer3drendererwidget.h" +#endif + QgsLayerStylingWidget::QgsLayerStylingWidget( QgsMapCanvas *canvas, const QList &pages, QWidget *parent ) : QWidget( parent ) @@ -158,6 +162,13 @@ void QgsLayerStylingWidget::setLayer( QgsMapLayer *layer ) labelItem->setData( Qt::UserRole, VectorLabeling ); labelItem->setToolTip( tr( "Labels" ) ); mOptionsListWidget->addItem( labelItem ); + +#ifdef HAVE_3D + QListWidgetItem *symbol3DItem = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "3d.svg" ) ), QString() ); + symbol3DItem->setData( Qt::UserRole, Symbology3D ); + symbol3DItem->setToolTip( tr( "3D View" ) ); + mOptionsListWidget->addItem( symbol3DItem ); +#endif } else if ( layer->type() == QgsMapLayer::RasterLayer ) { @@ -318,6 +329,12 @@ void QgsLayerStylingWidget::updateCurrentWidgetLayer() { mRasterStyleWidget = widget; } +#ifdef HAVE_3D + else if ( QgsVectorLayer3DRendererWidget *widget = qobject_cast( current ) ) + { + mVector3DWidget = widget; + } +#endif } @@ -369,6 +386,20 @@ void QgsLayerStylingWidget::updateCurrentWidgetLayer() mWidgetStack->setMainPanel( mLabelingWidget ); break; } +#ifdef HAVE_3D + case 2: // 3D View + { + if ( !mVector3DWidget ) + { + mVector3DWidget = new QgsVectorLayer3DRendererWidget( nullptr, mMapCanvas, mWidgetStack ); + mVector3DWidget->setDockMode( true ); + connect( mVector3DWidget, &QgsVectorLayer3DRendererWidget::widgetChanged, this, &QgsLayerStylingWidget::autoApply ); + } + mVector3DWidget->setLayer( vlayer ); + mWidgetStack->setMainPanel( mVector3DWidget ); + break; + } +#endif default: break; } diff --git a/src/app/qgslayerstylingwidget.h b/src/app/qgslayerstylingwidget.h index 977a9378502..c37f7d08704 100644 --- a/src/app/qgslayerstylingwidget.h +++ b/src/app/qgslayerstylingwidget.h @@ -39,6 +39,7 @@ class QgsRendererRasterPropertiesWidget; class QgsUndoWidget; class QgsRasterHistogramWidget; class QgsMapLayerStyleManagerWidget; +class QgsVectorLayer3DRendererWidget; class APP_EXPORT QgsLayerStyleManagerWidgetFactory : public QgsMapLayerConfigWidgetFactory { @@ -84,6 +85,7 @@ class APP_EXPORT QgsLayerStylingWidget : public QWidget, private Ui::QgsLayerSty RasterTransparency, RasterHistogram, History, + Symbology3D, }; QgsLayerStylingWidget( QgsMapCanvas *canvas, const QList &pages, QWidget *parent = 0 ); @@ -131,6 +133,9 @@ class APP_EXPORT QgsLayerStylingWidget : public QWidget, private Ui::QgsLayerSty QgsUndoWidget *mUndoWidget = nullptr; QgsMapLayer *mCurrentLayer = nullptr; QgsLabelingWidget *mLabelingWidget = nullptr; +#ifdef HAVE_3D + QgsVectorLayer3DRendererWidget *mVector3DWidget = nullptr; +#endif QgsRendererRasterPropertiesWidget *mRasterStyleWidget = nullptr; QList mPageFactories; QMap mUserPages; diff --git a/src/core/3d/qgs3drendererregistry.cpp b/src/core/3d/qgs3drendererregistry.cpp new file mode 100644 index 00000000000..355a0df146f --- /dev/null +++ b/src/core/3d/qgs3drendererregistry.cpp @@ -0,0 +1,45 @@ +#include "qgs3drendererregistry.h" + + +Qgs3DRendererAbstractMetadata::Qgs3DRendererAbstractMetadata( const QString &name ) + : mName( name ) +{ +} + +QString Qgs3DRendererAbstractMetadata::name() const +{ + return mName; +} + + +// ---------- + + +Qgs3DRendererRegistry::Qgs3DRendererRegistry() +{ +} + +Qgs3DRendererRegistry::~Qgs3DRendererRegistry() +{ + qDeleteAll( mRenderers ); +} + +void Qgs3DRendererRegistry::addRenderer( Qgs3DRendererAbstractMetadata *metadata ) +{ + mRenderers.insert( metadata->name(), metadata ); +} + +void Qgs3DRendererRegistry::removeRenderer( const QString &name ) +{ + delete mRenderers.take( name ); +} + +Qgs3DRendererAbstractMetadata *Qgs3DRendererRegistry::rendererMetadata( const QString &name ) const +{ + return mRenderers.value( name ); +} + +QStringList Qgs3DRendererRegistry::renderersList() const +{ + return mRenderers.keys(); +} diff --git a/src/core/3d/qgs3drendererregistry.h b/src/core/3d/qgs3drendererregistry.h new file mode 100644 index 00000000000..03eaf1efb79 --- /dev/null +++ b/src/core/3d/qgs3drendererregistry.h @@ -0,0 +1,61 @@ +#ifndef QGS3DRENDERERREGISTRY_H +#define QGS3DRENDERERREGISTRY_H + +#include "qgis_core.h" + +#include + +class QDomElement; +class QgsAbstract3DRenderer; +class QgsReadWriteContext; + + +/** + * Base metadata class for 3D renderers. Instances of derived classes may be registered in Qgs3DRendererRegistry. + * \since QGIS 3.0 + * \ingroup core + */ +class CORE_EXPORT Qgs3DRendererAbstractMetadata +{ + public: + + Qgs3DRendererAbstractMetadata( const QString &name ); + + QString name() const; + + /** Return new instance of the renderer given the DOM element. Returns NULL on error. + * Pure virtual function: must be implemented in derived classes. */ + virtual QgsAbstract3DRenderer *createRenderer( QDomElement &elem, const QgsReadWriteContext &context ) = 0; + + protected: + //! name used within QGIS for identification (the same what renderer's type() returns) + QString mName; +}; + + +/** + * Keeps track of available 3D renderers. Should be accessed through QgsApplication::renderer3DRegistry() singleton. + * \since QGIS 3.0 + * \ingroup core + */ +class CORE_EXPORT Qgs3DRendererRegistry +{ + public: + Qgs3DRendererRegistry(); + + ~Qgs3DRendererRegistry(); + + //! takes ownership + void addRenderer( Qgs3DRendererAbstractMetadata *metadata ); + + void removeRenderer( const QString &name ); + + Qgs3DRendererAbstractMetadata *rendererMetadata( const QString &name ) const; + + QStringList renderersList() const; + + private: + QMap mRenderers; +}; + +#endif // QGS3DRENDERERREGISTRY_H diff --git a/src/core/3d/qgsabstract3drenderer.cpp b/src/core/3d/qgsabstract3drenderer.cpp new file mode 100644 index 00000000000..a91a13ec801 --- /dev/null +++ b/src/core/3d/qgsabstract3drenderer.cpp @@ -0,0 +1,11 @@ +#include "qgsabstract3drenderer.h" + + +QgsAbstract3DRenderer::~QgsAbstract3DRenderer() +{ +} + +void QgsAbstract3DRenderer::resolveReferences( const QgsProject &project ) +{ + Q_UNUSED( project ); +} diff --git a/src/core/3d/qgsabstract3drenderer.h b/src/core/3d/qgsabstract3drenderer.h new file mode 100644 index 00000000000..14d2044d0c9 --- /dev/null +++ b/src/core/3d/qgsabstract3drenderer.h @@ -0,0 +1,35 @@ +#ifndef QGSABSTRACT3DRENDERER_H +#define QGSABSTRACT3DRENDERER_H + +#include "qgis_core.h" + +#include + +class QDomElement; +class QgsProject; +class Map3D; + +namespace Qt3DCore +{ + class QEntity; +} + +//! Base class for all renderers that may to participate in 3D view. +class CORE_EXPORT QgsAbstract3DRenderer //: public QObject +{ + //Q_OBJECT + public: + virtual ~QgsAbstract3DRenderer(); + + virtual QString type() const = 0; + virtual QgsAbstract3DRenderer *clone() const = 0; + virtual Qt3DCore::QEntity *createEntity( const Map3D &map ) const = 0; + + virtual void writeXml( QDomElement &elem ) const = 0; + virtual void readXml( const QDomElement &elem ) = 0; + virtual void resolveReferences( const QgsProject &project ); +}; + + + +#endif // QGSABSTRACT3DRENDERER_H diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7bbe7b18f02..160addce4dd 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -463,6 +463,9 @@ SET(QGIS_CORE_SRCS geometry/qgswkbptr.cpp geometry/qgswkbtypes.cpp + 3d/qgs3drendererregistry.cpp + 3d/qgsabstract3drenderer.cpp + fieldformatter/qgsdatetimefieldformatter.cpp fieldformatter/qgsfallbackfieldformatter.cpp fieldformatter/qgskeyvaluefieldformatter.cpp @@ -1093,6 +1096,9 @@ SET(QGIS_CORE_HDRS geometry/qgswkbptr.h geometry/qgswkbtypes.h + 3d/qgs3drendererregistry.h + 3d/qgsabstract3drenderer.h + fieldformatter/qgsdatetimefieldformatter.h fieldformatter/qgsfallbackfieldformatter.h fieldformatter/qgskeyvaluefieldformatter.h @@ -1118,6 +1124,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${LIBZIP_INCLUDE_DIR} + 3d annotations auth composer diff --git a/src/core/qgsapplication.cpp b/src/core/qgsapplication.cpp index 13d14d9d0b6..4448fce17ef 100644 --- a/src/core/qgsapplication.cpp +++ b/src/core/qgsapplication.cpp @@ -42,6 +42,7 @@ #include "qgsuserprofile.h" #include "qgsuserprofilemanager.h" #include "qgsreferencedgeometry.h" +#include "qgs3drendererregistry.h" #include "gps/qgsgpsconnectionregistry.h" #include "processing/qgsprocessingregistry.h" @@ -1574,6 +1575,11 @@ QgsFieldFormatterRegistry *QgsApplication::fieldFormatterRegistry() return members()->mFieldFormatterRegistry; } +Qgs3DRendererRegistry *QgsApplication::renderer3DRegistry() +{ + return members()->m3DRendererRegistry; +} + QgsApplication::ApplicationMembers::ApplicationMembers() { // don't use initializer lists or scoped pointers - as more objects are added here we @@ -1598,11 +1604,13 @@ QgsApplication::ApplicationMembers::ApplicationMembers() mLayoutItemRegistry->populate(); mProcessingRegistry->addProvider( new QgsNativeAlgorithms( mProcessingRegistry ) ); mAnnotationRegistry = new QgsAnnotationRegistry(); + m3DRendererRegistry = new Qgs3DRendererRegistry(); } QgsApplication::ApplicationMembers::~ApplicationMembers() { delete mActionScopeRegistry; + delete m3DRendererRegistry; delete mAnnotationRegistry; delete mColorSchemeRegistry; delete mFieldFormatterRegistry; diff --git a/src/core/qgsapplication.h b/src/core/qgsapplication.h index b51e0469e44..5b935c88f38 100644 --- a/src/core/qgsapplication.h +++ b/src/core/qgsapplication.h @@ -23,6 +23,7 @@ #include "qgis.h" #include "qgsconfig.h" +class Qgs3DRendererRegistry; class QgsActionScopeRegistry; class QgsRuntimeProfiler; class QgsTaskManager; @@ -583,6 +584,13 @@ class CORE_EXPORT QgsApplication : public QApplication */ static QgsFieldFormatterRegistry *fieldFormatterRegistry(); + /** + * Returns registry of available 3D renderers. + * \note not available in Python bindings + * \since QGIS 3.0 + */ + static Qgs3DRendererRegistry *renderer3DRegistry() SIP_SKIP; + /** * This string is used to represent the value `NULL` throughout QGIS. * @@ -698,6 +706,7 @@ class CORE_EXPORT QgsApplication : public QApplication struct ApplicationMembers { + Qgs3DRendererRegistry *m3DRendererRegistry = nullptr; QgsActionScopeRegistry *mActionScopeRegistry = nullptr; QgsAnnotationRegistry *mAnnotationRegistry = nullptr; QgsColorSchemeRegistry *mColorSchemeRegistry = nullptr; diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index 18544bb1f34..830f0e7a623 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -28,6 +28,7 @@ #include +#include "qgsabstract3drenderer.h" #include "qgsapplication.h" #include "qgscoordinatereferencesystem.h" #include "qgsdatasourceuri.h" @@ -88,6 +89,7 @@ QgsMapLayer::QgsMapLayer( QgsMapLayer::LayerType type, QgsMapLayer::~QgsMapLayer() { + delete m3DRenderer; delete mLegend; delete mStyleManager; } @@ -1673,6 +1675,21 @@ QgsMapLayerStyleManager *QgsMapLayer::styleManager() const return mStyleManager; } +void QgsMapLayer::setRenderer3D( QgsAbstract3DRenderer *renderer ) +{ + if ( renderer == m3DRenderer ) + return; + + delete m3DRenderer; + m3DRenderer = renderer; + emit renderer3DChanged(); +} + +QgsAbstract3DRenderer *QgsMapLayer::renderer3D() const +{ + return m3DRenderer; +} + void QgsMapLayer::triggerRepaint( bool deferredUpdate ) { emit repaintRequested( deferredUpdate ); diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index d2e5f1f10a9..0635263272c 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -36,6 +36,7 @@ #include "qgsmaplayerdependency.h" #include "qgslayermetadata.h" +class QgsAbstract3DRenderer; class QgsDataProvider; class QgsMapLayerLegend; class QgsMapLayerRenderer; @@ -675,6 +676,20 @@ class CORE_EXPORT QgsMapLayer : public QObject */ QgsMapLayerStyleManager *styleManager() const; + /** + * Sets 3D renderer for the layer. Takes ownership of the renderer. + * \note not available in Python bindings + * \since QGIS 3.0 + */ + void setRenderer3D( QgsAbstract3DRenderer *renderer SIP_TRANSFER ) SIP_SKIP; + + /** + * Returns 3D renderer associated with the layer. May be null. + * \note not available in Python bindings + * \since QGIS 3.0 + */ + QgsAbstract3DRenderer *renderer3D() const SIP_SKIP; + /** Tests whether the layer should be visible at the specified \a scale. * The \a scale value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map. * \returns true if the layer is visible at the given scale. @@ -897,6 +912,12 @@ class CORE_EXPORT QgsMapLayer : public QObject */ void legendChanged(); + /** + * Signal emitted when 3D renderer associated with the layer has changed. + * \since QGIS 3.0 + */ + void renderer3DChanged(); + /** * Emitted whenever the configuration is changed. The project listens to this signal * to be marked as dirty. @@ -1080,6 +1101,9 @@ class CORE_EXPORT QgsMapLayer : public QObject QgsLayerMetadata mMetadata; + //! Renderer for 3D views + QgsAbstract3DRenderer *m3DRenderer = nullptr; + }; Q_DECLARE_METATYPE( QgsMapLayer * ) diff --git a/src/ui/3d/phongmaterialwidget.ui b/src/ui/3d/phongmaterialwidget.ui new file mode 100644 index 00000000000..a4aedb45e83 --- /dev/null +++ b/src/ui/3d/phongmaterialwidget.ui @@ -0,0 +1,94 @@ + + + PhongMaterialWidget + + + + 0 + 0 + 334 + 252 + + + + Form + + + + + + Diffuse + + + + + + + + 0 + 0 + + + + + + + + Ambient + + + + + + + + 0 + 0 + + + + + + + + Specular + + + + + + + + 0 + 0 + + + + + + + + Shininess + + + + + + + 1000.000000000000000 + + + + + + + + QgsColorButton + QToolButton +
qgscolorbutton.h
+ 1 +
+
+ + +
diff --git a/src/ui/3d/polygon3dsymbolwidget.ui b/src/ui/3d/polygon3dsymbolwidget.ui new file mode 100644 index 00000000000..d638e98eaa8 --- /dev/null +++ b/src/ui/3d/polygon3dsymbolwidget.ui @@ -0,0 +1,110 @@ + + + Polygon3DSymbolWidget + + + + 0 + 0 + 538 + 452 + + + + Form + + + + + + + + Height + + + + + + + + + + Extrusion + + + + + + + + + + Altitude Clamping + + + + + + + + Absolute + + + + + Relative + + + + + Terrain + + + + + + + + Altitude Binding + + + + + + + + Vertex + + + + + Centroid + + + + + + + + + + Qt::Horizontal + + + + + + + + + + + QgsPhongMaterialWidget + QWidget +
qgsphongmaterialwidget.h
+ 1 +
+
+ + +
diff --git a/src/ui/symbollayer/widget_simplefill.ui b/src/ui/symbollayer/widget_simplefill.ui index e964bfbbc0c..9a19506acbe 100644 --- a/src/ui/symbollayer/widget_simplefill.ui +++ b/src/ui/symbollayer/widget_simplefill.ui @@ -6,8 +6,8 @@ 0 0 - 332 - 319 + 573 + 507 @@ -336,6 +336,12 @@ + + QgsColorButton + QToolButton +
qgscolorbutton.h
+ 1 +
QgsPropertyOverrideButton QToolButton @@ -352,12 +358,6 @@
qgsunitselectionwidget.h
1
- - QgsColorButton - QToolButton -
qgscolorbutton.h
- 1 -
QgsPenJoinStyleComboBox QComboBox