diff --git a/python/3d/3d_auto.sip b/python/3d/3d_auto.sip index e59e24d53e6..d6a80de7b66 100644 --- a/python/3d/3d_auto.sip +++ b/python/3d/3d_auto.sip @@ -29,6 +29,7 @@ %Include auto_generated/symbols/qgspoint3dsymbol.sip %Include auto_generated/symbols/qgspolygon3dsymbol.sip %Include auto_generated/symbols/qgspointcloud3dsymbol.sip +%Include auto_generated/terrain/qgs3dterrainregistry.sip %Include auto_generated/terrain/qgsdemterrainsettings.sip %Include auto_generated/terrain/qgsflatterrainsettings.sip %Include auto_generated/terrain/qgsmeshterrainsettings.sip diff --git a/python/3d/auto_additions/qgs3d.py b/python/3d/auto_additions/qgs3d.py index aaddcc4961c..a9079bd6193 100644 --- a/python/3d/auto_additions/qgs3d.py +++ b/python/3d/auto_additions/qgs3d.py @@ -3,5 +3,6 @@ try: Qgs3D.instance = staticmethod(Qgs3D.instance) Qgs3D.initialize = staticmethod(Qgs3D.initialize) Qgs3D.materialRegistry = staticmethod(Qgs3D.materialRegistry) + Qgs3D.terrainRegistry = staticmethod(Qgs3D.terrainRegistry) except (NameError, AttributeError): pass diff --git a/python/3d/auto_additions/qgs3dterrainregistry.py b/python/3d/auto_additions/qgs3dterrainregistry.py new file mode 100644 index 00000000000..78efe7b8c05 --- /dev/null +++ b/python/3d/auto_additions/qgs3dterrainregistry.py @@ -0,0 +1,9 @@ +# The following has been generated automatically from src/3d/terrain/qgs3dterrainregistry.h +try: + Qgs3DTerrainAbstractMetadata.__group__ = ['terrain'] +except (NameError, AttributeError): + pass +try: + Qgs3DTerrainRegistry.__group__ = ['terrain'] +except (NameError, AttributeError): + pass diff --git a/python/3d/auto_additions/qgsdemterrainsettings.py b/python/3d/auto_additions/qgsdemterrainsettings.py index e3578880fb1..f360647a7fd 100644 --- a/python/3d/auto_additions/qgsdemterrainsettings.py +++ b/python/3d/auto_additions/qgsdemterrainsettings.py @@ -1,5 +1,6 @@ # The following has been generated automatically from src/3d/terrain/qgsdemterrainsettings.h try: + QgsDemTerrainSettings.create = staticmethod(QgsDemTerrainSettings.create) QgsDemTerrainSettings.__group__ = ['terrain'] except (NameError, AttributeError): pass diff --git a/python/3d/auto_additions/qgsflatterrainsettings.py b/python/3d/auto_additions/qgsflatterrainsettings.py index 0026aeb0f4b..7f4bb9ec13d 100644 --- a/python/3d/auto_additions/qgsflatterrainsettings.py +++ b/python/3d/auto_additions/qgsflatterrainsettings.py @@ -1,5 +1,6 @@ # The following has been generated automatically from src/3d/terrain/qgsflatterrainsettings.h try: + QgsFlatTerrainSettings.create = staticmethod(QgsFlatTerrainSettings.create) QgsFlatTerrainSettings.__group__ = ['terrain'] except (NameError, AttributeError): pass diff --git a/python/3d/auto_additions/qgsmeshterrainsettings.py b/python/3d/auto_additions/qgsmeshterrainsettings.py index 02b93052d95..441bea8a126 100644 --- a/python/3d/auto_additions/qgsmeshterrainsettings.py +++ b/python/3d/auto_additions/qgsmeshterrainsettings.py @@ -1,5 +1,6 @@ # The following has been generated automatically from src/3d/terrain/qgsmeshterrainsettings.h try: + QgsMeshTerrainSettings.create = staticmethod(QgsMeshTerrainSettings.create) QgsMeshTerrainSettings.__group__ = ['terrain'] except (NameError, AttributeError): pass diff --git a/python/3d/auto_generated/qgs3d.sip.in b/python/3d/auto_generated/qgs3d.sip.in index f01b68940ff..2a02ec26c83 100644 --- a/python/3d/auto_generated/qgs3d.sip.in +++ b/python/3d/auto_generated/qgs3d.sip.in @@ -39,6 +39,11 @@ Initializes the 3D framework. static QgsMaterialRegistry *materialRegistry(); %Docstring Returns the material registry, used for managing 3D materials. +%End + + static Qgs3DTerrainRegistry *terrainRegistry(); +%Docstring +Returns the terrain registry, used for managing 3D terrains. %End private: diff --git a/python/3d/auto_generated/terrain/qgs3dterrainregistry.sip.in b/python/3d/auto_generated/terrain/qgs3dterrainregistry.sip.in new file mode 100644 index 00000000000..e33f1aab770 --- /dev/null +++ b/python/3d/auto_generated/terrain/qgs3dterrainregistry.sip.in @@ -0,0 +1,120 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/3d/terrain/qgs3dterrainregistry.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ + + + + + +class Qgs3DTerrainAbstractMetadata +{ +%Docstring(signature="appended") +Stores metadata about one 3D terrain class. + +.. note:: + + It's necessary to implement :py:func:`~createTerrainSettings` function. + In C++ you can use Qgs3DTerrainMetadata convenience class. + +.. versionadded:: 3.42 +%End + +%TypeHeaderCode +#include "qgs3dterrainregistry.h" +%End + public: + Qgs3DTerrainAbstractMetadata( const QString &type, const QString &visibleName, const QIcon &icon = QIcon() ); +%Docstring +Constructor for Qgs3DTerrainAbstractMetadata, with the specified ``type`` and ``visibleName``. + +An optional ``icon`` can be specified to represent the material type. +%End + + virtual ~Qgs3DTerrainAbstractMetadata(); + + QString type() const; +%Docstring +Returns the unique terrain type string. +%End + + QString visibleName() const; +%Docstring +Returns the terrain's visible (translated) name. +%End + + QIcon icon() const; +%Docstring +Returns an icon representing the terrain type, if available. +%End + + virtual QgsAbstractTerrainSettings *createTerrainSettings() = 0 /Factory/; +%Docstring +Creates a new instance of this terrain settings type. + +Caller takes ownership of the returned object. +%End + +}; + + + + +class Qgs3DTerrainRegistry +{ +%Docstring(signature="appended") +Registry of available 3d terrain classes. + +Qgs3DTerrainRegistry is not usually directly created, but rather accessed through +Qgs3D.terrainRegistry(). + +.. versionadded:: 3.42 +%End + +%TypeHeaderCode +#include "qgs3dterrainregistry.h" +%End + public: + Qgs3DTerrainRegistry(); + ~Qgs3DTerrainRegistry(); + + + Qgs3DTerrainAbstractMetadata *terrainMetadata( const QString &type ) const; +%Docstring +Returns metadata for specified terrain ``type``. Returns ``None`` if not found +%End + + QStringList types() const; +%Docstring +Returns a list of all available terrain types. +%End + + bool addType( Qgs3DTerrainAbstractMetadata *metadata /Transfer/ ); +%Docstring +Registers a new terrain type. Takes ownership of the ``metadata`` instance. +%End + + QgsAbstractTerrainSettings *createTerrainSettings( const QString &type ) const /Factory/; +%Docstring +Creates a new instance of the terrain settings of the specified ``type``. + +The caller takes ownership of the returned object. + +Returns ``None`` if the specified type is not found in the registry. +%End + + private: + Qgs3DTerrainRegistry( const Qgs3DTerrainRegistry &rh ); +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/3d/terrain/qgs3dterrainregistry.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ diff --git a/python/3d/auto_generated/terrain/qgsdemterrainsettings.sip.in b/python/3d/auto_generated/terrain/qgsdemterrainsettings.sip.in index 5ddf5b16207..310b5d4b32e 100644 --- a/python/3d/auto_generated/terrain/qgsdemterrainsettings.sip.in +++ b/python/3d/auto_generated/terrain/qgsdemterrainsettings.sip.in @@ -26,6 +26,11 @@ Terrain settings for a terrain generator that uses a raster DEM layer to build t #include "qgsdemterrainsettings.h" %End public: + static QgsAbstractTerrainSettings *create() /Factory/; +%Docstring +Creates a new instance of a QgsDemTerrainSettings object. +%End + QgsDemTerrainSettings *clone() const final /Factory/; QString type() const final; void readXml( const QDomElement &element, const QgsReadWriteContext &context ) final; diff --git a/python/3d/auto_generated/terrain/qgsflatterrainsettings.sip.in b/python/3d/auto_generated/terrain/qgsflatterrainsettings.sip.in index f3b06858d95..4158f9ce4a8 100644 --- a/python/3d/auto_generated/terrain/qgsflatterrainsettings.sip.in +++ b/python/3d/auto_generated/terrain/qgsflatterrainsettings.sip.in @@ -26,6 +26,11 @@ Terrain settings for a simple flat area terrain. #include "qgsflatterrainsettings.h" %End public: + static QgsAbstractTerrainSettings *create() /Factory/; +%Docstring +Creates a new instance of a QgsFlatTerrainSettings object. +%End + QgsFlatTerrainSettings *clone() const final /Factory/; QString type() const final; void readXml( const QDomElement &element, const QgsReadWriteContext &context ) final; diff --git a/python/3d/auto_generated/terrain/qgsmeshterrainsettings.sip.in b/python/3d/auto_generated/terrain/qgsmeshterrainsettings.sip.in index df12791ee5b..9c0a345a135 100644 --- a/python/3d/auto_generated/terrain/qgsmeshterrainsettings.sip.in +++ b/python/3d/auto_generated/terrain/qgsmeshterrainsettings.sip.in @@ -26,6 +26,11 @@ Terrain settings for a terrain generator that uses uses the Z values of a mesh l #include "qgsmeshterrainsettings.h" %End public: + static QgsAbstractTerrainSettings *create() /Factory/; +%Docstring +Creates a new instance of a QgsMeshTerrainSettings object. +%End + QgsMeshTerrainSettings *clone() const final /Factory/; QString type() const final; void readXml( const QDomElement &element, const QgsReadWriteContext &context ) final; diff --git a/python/PyQt6/3d/3d_auto.sip b/python/PyQt6/3d/3d_auto.sip index e59e24d53e6..d6a80de7b66 100644 --- a/python/PyQt6/3d/3d_auto.sip +++ b/python/PyQt6/3d/3d_auto.sip @@ -29,6 +29,7 @@ %Include auto_generated/symbols/qgspoint3dsymbol.sip %Include auto_generated/symbols/qgspolygon3dsymbol.sip %Include auto_generated/symbols/qgspointcloud3dsymbol.sip +%Include auto_generated/terrain/qgs3dterrainregistry.sip %Include auto_generated/terrain/qgsdemterrainsettings.sip %Include auto_generated/terrain/qgsflatterrainsettings.sip %Include auto_generated/terrain/qgsmeshterrainsettings.sip diff --git a/python/PyQt6/3d/auto_additions/qgs3d.py b/python/PyQt6/3d/auto_additions/qgs3d.py index aaddcc4961c..a9079bd6193 100644 --- a/python/PyQt6/3d/auto_additions/qgs3d.py +++ b/python/PyQt6/3d/auto_additions/qgs3d.py @@ -3,5 +3,6 @@ try: Qgs3D.instance = staticmethod(Qgs3D.instance) Qgs3D.initialize = staticmethod(Qgs3D.initialize) Qgs3D.materialRegistry = staticmethod(Qgs3D.materialRegistry) + Qgs3D.terrainRegistry = staticmethod(Qgs3D.terrainRegistry) except (NameError, AttributeError): pass diff --git a/python/PyQt6/3d/auto_additions/qgs3dterrainregistry.py b/python/PyQt6/3d/auto_additions/qgs3dterrainregistry.py new file mode 100644 index 00000000000..78efe7b8c05 --- /dev/null +++ b/python/PyQt6/3d/auto_additions/qgs3dterrainregistry.py @@ -0,0 +1,9 @@ +# The following has been generated automatically from src/3d/terrain/qgs3dterrainregistry.h +try: + Qgs3DTerrainAbstractMetadata.__group__ = ['terrain'] +except (NameError, AttributeError): + pass +try: + Qgs3DTerrainRegistry.__group__ = ['terrain'] +except (NameError, AttributeError): + pass diff --git a/python/PyQt6/3d/auto_additions/qgsdemterrainsettings.py b/python/PyQt6/3d/auto_additions/qgsdemterrainsettings.py index e3578880fb1..f360647a7fd 100644 --- a/python/PyQt6/3d/auto_additions/qgsdemterrainsettings.py +++ b/python/PyQt6/3d/auto_additions/qgsdemterrainsettings.py @@ -1,5 +1,6 @@ # The following has been generated automatically from src/3d/terrain/qgsdemterrainsettings.h try: + QgsDemTerrainSettings.create = staticmethod(QgsDemTerrainSettings.create) QgsDemTerrainSettings.__group__ = ['terrain'] except (NameError, AttributeError): pass diff --git a/python/PyQt6/3d/auto_additions/qgsflatterrainsettings.py b/python/PyQt6/3d/auto_additions/qgsflatterrainsettings.py index 0026aeb0f4b..7f4bb9ec13d 100644 --- a/python/PyQt6/3d/auto_additions/qgsflatterrainsettings.py +++ b/python/PyQt6/3d/auto_additions/qgsflatterrainsettings.py @@ -1,5 +1,6 @@ # The following has been generated automatically from src/3d/terrain/qgsflatterrainsettings.h try: + QgsFlatTerrainSettings.create = staticmethod(QgsFlatTerrainSettings.create) QgsFlatTerrainSettings.__group__ = ['terrain'] except (NameError, AttributeError): pass diff --git a/python/PyQt6/3d/auto_additions/qgsmeshterrainsettings.py b/python/PyQt6/3d/auto_additions/qgsmeshterrainsettings.py index 02b93052d95..441bea8a126 100644 --- a/python/PyQt6/3d/auto_additions/qgsmeshterrainsettings.py +++ b/python/PyQt6/3d/auto_additions/qgsmeshterrainsettings.py @@ -1,5 +1,6 @@ # The following has been generated automatically from src/3d/terrain/qgsmeshterrainsettings.h try: + QgsMeshTerrainSettings.create = staticmethod(QgsMeshTerrainSettings.create) QgsMeshTerrainSettings.__group__ = ['terrain'] except (NameError, AttributeError): pass diff --git a/python/PyQt6/3d/auto_generated/qgs3d.sip.in b/python/PyQt6/3d/auto_generated/qgs3d.sip.in index f01b68940ff..2a02ec26c83 100644 --- a/python/PyQt6/3d/auto_generated/qgs3d.sip.in +++ b/python/PyQt6/3d/auto_generated/qgs3d.sip.in @@ -39,6 +39,11 @@ Initializes the 3D framework. static QgsMaterialRegistry *materialRegistry(); %Docstring Returns the material registry, used for managing 3D materials. +%End + + static Qgs3DTerrainRegistry *terrainRegistry(); +%Docstring +Returns the terrain registry, used for managing 3D terrains. %End private: diff --git a/python/PyQt6/3d/auto_generated/terrain/qgs3dterrainregistry.sip.in b/python/PyQt6/3d/auto_generated/terrain/qgs3dterrainregistry.sip.in new file mode 100644 index 00000000000..e33f1aab770 --- /dev/null +++ b/python/PyQt6/3d/auto_generated/terrain/qgs3dterrainregistry.sip.in @@ -0,0 +1,120 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/3d/terrain/qgs3dterrainregistry.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ + + + + + +class Qgs3DTerrainAbstractMetadata +{ +%Docstring(signature="appended") +Stores metadata about one 3D terrain class. + +.. note:: + + It's necessary to implement :py:func:`~createTerrainSettings` function. + In C++ you can use Qgs3DTerrainMetadata convenience class. + +.. versionadded:: 3.42 +%End + +%TypeHeaderCode +#include "qgs3dterrainregistry.h" +%End + public: + Qgs3DTerrainAbstractMetadata( const QString &type, const QString &visibleName, const QIcon &icon = QIcon() ); +%Docstring +Constructor for Qgs3DTerrainAbstractMetadata, with the specified ``type`` and ``visibleName``. + +An optional ``icon`` can be specified to represent the material type. +%End + + virtual ~Qgs3DTerrainAbstractMetadata(); + + QString type() const; +%Docstring +Returns the unique terrain type string. +%End + + QString visibleName() const; +%Docstring +Returns the terrain's visible (translated) name. +%End + + QIcon icon() const; +%Docstring +Returns an icon representing the terrain type, if available. +%End + + virtual QgsAbstractTerrainSettings *createTerrainSettings() = 0 /Factory/; +%Docstring +Creates a new instance of this terrain settings type. + +Caller takes ownership of the returned object. +%End + +}; + + + + +class Qgs3DTerrainRegistry +{ +%Docstring(signature="appended") +Registry of available 3d terrain classes. + +Qgs3DTerrainRegistry is not usually directly created, but rather accessed through +Qgs3D.terrainRegistry(). + +.. versionadded:: 3.42 +%End + +%TypeHeaderCode +#include "qgs3dterrainregistry.h" +%End + public: + Qgs3DTerrainRegistry(); + ~Qgs3DTerrainRegistry(); + + + Qgs3DTerrainAbstractMetadata *terrainMetadata( const QString &type ) const; +%Docstring +Returns metadata for specified terrain ``type``. Returns ``None`` if not found +%End + + QStringList types() const; +%Docstring +Returns a list of all available terrain types. +%End + + bool addType( Qgs3DTerrainAbstractMetadata *metadata /Transfer/ ); +%Docstring +Registers a new terrain type. Takes ownership of the ``metadata`` instance. +%End + + QgsAbstractTerrainSettings *createTerrainSettings( const QString &type ) const /Factory/; +%Docstring +Creates a new instance of the terrain settings of the specified ``type``. + +The caller takes ownership of the returned object. + +Returns ``None`` if the specified type is not found in the registry. +%End + + private: + Qgs3DTerrainRegistry( const Qgs3DTerrainRegistry &rh ); +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/3d/terrain/qgs3dterrainregistry.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ diff --git a/python/PyQt6/3d/auto_generated/terrain/qgsdemterrainsettings.sip.in b/python/PyQt6/3d/auto_generated/terrain/qgsdemterrainsettings.sip.in index 5ddf5b16207..310b5d4b32e 100644 --- a/python/PyQt6/3d/auto_generated/terrain/qgsdemterrainsettings.sip.in +++ b/python/PyQt6/3d/auto_generated/terrain/qgsdemterrainsettings.sip.in @@ -26,6 +26,11 @@ Terrain settings for a terrain generator that uses a raster DEM layer to build t #include "qgsdemterrainsettings.h" %End public: + static QgsAbstractTerrainSettings *create() /Factory/; +%Docstring +Creates a new instance of a QgsDemTerrainSettings object. +%End + QgsDemTerrainSettings *clone() const final /Factory/; QString type() const final; void readXml( const QDomElement &element, const QgsReadWriteContext &context ) final; diff --git a/python/PyQt6/3d/auto_generated/terrain/qgsflatterrainsettings.sip.in b/python/PyQt6/3d/auto_generated/terrain/qgsflatterrainsettings.sip.in index f3b06858d95..4158f9ce4a8 100644 --- a/python/PyQt6/3d/auto_generated/terrain/qgsflatterrainsettings.sip.in +++ b/python/PyQt6/3d/auto_generated/terrain/qgsflatterrainsettings.sip.in @@ -26,6 +26,11 @@ Terrain settings for a simple flat area terrain. #include "qgsflatterrainsettings.h" %End public: + static QgsAbstractTerrainSettings *create() /Factory/; +%Docstring +Creates a new instance of a QgsFlatTerrainSettings object. +%End + QgsFlatTerrainSettings *clone() const final /Factory/; QString type() const final; void readXml( const QDomElement &element, const QgsReadWriteContext &context ) final; diff --git a/python/PyQt6/3d/auto_generated/terrain/qgsmeshterrainsettings.sip.in b/python/PyQt6/3d/auto_generated/terrain/qgsmeshterrainsettings.sip.in index df12791ee5b..9c0a345a135 100644 --- a/python/PyQt6/3d/auto_generated/terrain/qgsmeshterrainsettings.sip.in +++ b/python/PyQt6/3d/auto_generated/terrain/qgsmeshterrainsettings.sip.in @@ -26,6 +26,11 @@ Terrain settings for a terrain generator that uses uses the Z values of a mesh l #include "qgsmeshterrainsettings.h" %End public: + static QgsAbstractTerrainSettings *create() /Factory/; +%Docstring +Creates a new instance of a QgsMeshTerrainSettings object. +%End + QgsMeshTerrainSettings *clone() const final /Factory/; QString type() const final; void readXml( const QDomElement &element, const QgsReadWriteContext &context ) final; diff --git a/src/3d/CMakeLists.txt b/src/3d/CMakeLists.txt index b209cc8d8fe..7c3d684b903 100644 --- a/src/3d/CMakeLists.txt +++ b/src/3d/CMakeLists.txt @@ -97,6 +97,7 @@ set(QGIS_3D_SRCS symbols/qgspointcloud3dsymbol.cpp symbols/qgspointcloud3dsymbol_p.cpp + terrain/qgs3dterrainregistry.cpp terrain/qgsdemterraingenerator.cpp terrain/qgsdemterrainsettings.cpp terrain/qgsdemterraintilegeometry_p.cpp @@ -192,6 +193,7 @@ set(QGIS_3D_HDRS symbols/qgspolygon3dsymbol.h symbols/qgspointcloud3dsymbol.h + terrain/qgs3dterrainregistry.h terrain/qgsdemterraingenerator.h terrain/qgsdemterrainsettings.h terrain/qgsflatterraingenerator.h diff --git a/src/3d/qgs3d.cpp b/src/3d/qgs3d.cpp index 1684b273e12..ea3861b3591 100644 --- a/src/3d/qgs3d.cpp +++ b/src/3d/qgs3d.cpp @@ -42,6 +42,8 @@ #include "qgsphongtexturedmaterialsettings.h" #include "qgsnullmaterialsettings.h" +#include "qgs3dterrainregistry.h" + #include "qgsstyle.h" Qgs3D *Qgs3D::instance() @@ -88,7 +90,13 @@ QgsMaterialRegistry *Qgs3D::materialRegistry() return instance()->mMaterialRegistry; } +Qgs3DTerrainRegistry *Qgs3D::terrainRegistry() +{ + return instance()->mTerrainRegistry; +} + Qgs3D::Qgs3D() { mMaterialRegistry = new QgsMaterialRegistry(); + mTerrainRegistry = new Qgs3DTerrainRegistry(); } diff --git a/src/3d/qgs3d.h b/src/3d/qgs3d.h index a58b3fa28aa..55a2eb392e9 100644 --- a/src/3d/qgs3d.h +++ b/src/3d/qgs3d.h @@ -22,6 +22,7 @@ #include "qgis_sip.h" class QgsMaterialRegistry; +class Qgs3DTerrainRegistry; /** * \ingroup gui @@ -52,6 +53,11 @@ class _3D_EXPORT Qgs3D */ static QgsMaterialRegistry *materialRegistry(); + /** + * Returns the terrain registry, used for managing 3D terrains. + */ + static Qgs3DTerrainRegistry *terrainRegistry(); + private: Qgs3D(); @@ -62,6 +68,7 @@ class _3D_EXPORT Qgs3D bool mInitialized = false; QgsMaterialRegistry *mMaterialRegistry = nullptr; + Qgs3DTerrainRegistry *mTerrainRegistry = nullptr; }; #endif // QGS3D_H diff --git a/src/3d/qgs3dmapsettings.cpp b/src/3d/qgs3dmapsettings.cpp index c6297888e56..29b7accc28b 100644 --- a/src/3d/qgs3dmapsettings.cpp +++ b/src/3d/qgs3dmapsettings.cpp @@ -16,6 +16,7 @@ #include "qgs3dmapsettings.h" #include "moc_qgs3dmapsettings.cpp" +#include "qgs3d.h" #include "qgs3dutils.h" #include "qgsflatterraingenerator.h" #include "qgsdemterraingenerator.h" @@ -35,6 +36,7 @@ #include "qgsmaplayerlistutils_p.h" #include "qgsterrainsettings.h" #include "qgsflatterrainsettings.h" +#include "qgs3dterrainregistry.h" #include #include @@ -272,6 +274,14 @@ void Qgs3DMapSettings::readXml( const QDomElement &elem, const QgsReadWriteConte flatGen->setCrs( mCrs ); setTerrainGenerator( flatGen ); } + + std::unique_ptr terrainSettings( Qgs3D::terrainRegistry()->createTerrainSettings( terrainGenType ) ); + if ( terrainSettings ) + { + terrainSettings->readXml( elemTerrainGenerator, context ); + setTerrainSettings( terrainSettings.release() ); + } + mTerrainGenerator->readXml( elemTerrainGenerator ); QDomElement elemSkybox = elem.firstChildElement( QStringLiteral( "skybox" ) ); diff --git a/src/3d/terrain/qgs3dterrainregistry.cpp b/src/3d/terrain/qgs3dterrainregistry.cpp new file mode 100644 index 00000000000..612c557d385 --- /dev/null +++ b/src/3d/terrain/qgs3dterrainregistry.cpp @@ -0,0 +1,70 @@ +/*************************************************************************** + qgs3dterrainregistry.cpp + -------------------------------------- + Date : November 2024 + Copyright : (C) 2024 by Nyall Dawson + Email : nyall dot dawson 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 "qgs3dterrainregistry.h" +#include "qgis.h" + +#include "qgsflatterrainsettings.h" +#include "qgsdemterrainsettings.h" +#include "qgsmeshterrainsettings.h" + +#include + + +Qgs3DTerrainRegistry::Qgs3DTerrainRegistry() +{ + addType( new Qgs3DTerrainMetadata( QStringLiteral( "flat" ), QObject::tr( "Flat Terrain" ), &QgsFlatTerrainSettings::create ) ); + addType( new Qgs3DTerrainMetadata( QStringLiteral( "dem" ), QObject::tr( "DEM (Raster Layer)" ), &QgsDemTerrainSettings::create ) ); + addType( new Qgs3DTerrainMetadata( QStringLiteral( "mesh" ), QObject::tr( "Mesh" ), &QgsMeshTerrainSettings::create ) ); +} + +Qgs3DTerrainRegistry::~Qgs3DTerrainRegistry() +{ + qDeleteAll( mMetadata ); +} + +bool Qgs3DTerrainRegistry::addType( Qgs3DTerrainAbstractMetadata *metadata ) +{ + if ( !metadata || mMetadata.contains( metadata->type() ) ) + return false; + + mMetadata[metadata->type()] = metadata; + mTerrainOrder << metadata->type(); + return true; +} + +QgsAbstractTerrainSettings *Qgs3DTerrainRegistry::createTerrainSettings( const QString &type ) const +{ + if ( !mMetadata.contains( type ) ) + return nullptr; + + return mMetadata[type]->createTerrainSettings(); +} + +Qgs3DTerrainAbstractMetadata *Qgs3DTerrainRegistry::terrainMetadata( const QString &type ) const +{ + return mMetadata.value( type ); +} + +QStringList Qgs3DTerrainRegistry::types() const +{ + QStringList types; + for ( const QString &type : mTerrainOrder ) + { + if ( mMetadata.value( type ) ) + types << type; + } + return types; +} diff --git a/src/3d/terrain/qgs3dterrainregistry.h b/src/3d/terrain/qgs3dterrainregistry.h new file mode 100644 index 00000000000..54191793471 --- /dev/null +++ b/src/3d/terrain/qgs3dterrainregistry.h @@ -0,0 +1,174 @@ +/*************************************************************************** + qgs3dterrainregistry.h + -------------------------------------- + Date : November 2024 + Copyright : (C) 2024 by Nyall Dawson + Email : nyall dot dawson 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 QGS3DTERRAINREGISTRY_H +#define QGS3DTERRAINREGISTRY_H + +#include "qgis_3d.h" +#include "qgis_sip.h" +#include + +#include +#include +#include + +class QgsReadWriteContext; +class QgsAbstractTerrainSettings; + +/** + * \ingroup core + * \brief Stores metadata about one 3D terrain class. + * + * \note It's necessary to implement createTerrainSettings() function. + * In C++ you can use Qgs3DTerrainMetadata convenience class. + * + * \since QGIS 3.42 + */ +class _3D_EXPORT Qgs3DTerrainAbstractMetadata +{ + public: + /** + * Constructor for Qgs3DTerrainAbstractMetadata, with the specified \a type and \a visibleName. + * + * An optional \a icon can be specified to represent the material type. + */ + Qgs3DTerrainAbstractMetadata( const QString &type, const QString &visibleName, const QIcon &icon = QIcon() ) + : mType( type ) + , mVisibleName( visibleName ) + , mIcon( icon ) + {} + + virtual ~Qgs3DTerrainAbstractMetadata() = default; + + /** + * Returns the unique terrain type string. + */ + QString type() const { return mType; } + + /** + * Returns the terrain's visible (translated) name. + */ + QString visibleName() const { return mVisibleName; } + + /** + * Returns an icon representing the terrain type, if available. + */ + QIcon icon() const { return mIcon; } + + /** + * Creates a new instance of this terrain settings type. + * + * Caller takes ownership of the returned object. + */ + virtual QgsAbstractTerrainSettings *createTerrainSettings() = 0 SIP_FACTORY; + + private: + QString mType; + QString mVisibleName; + QIcon mIcon; +}; + +//! Terrain settings creation function +typedef QgsAbstractTerrainSettings *( *QgsTerrainSettingsCreateFunc )() SIP_SKIP; + +#ifndef SIP_RUN + +/** + * \ingroup core + * \brief Convenience metadata class that uses static functions to create 3D terrain objects. + * + * \note Not available in Python bindings. + * + * \since QGIS 3.42 + */ +class _3D_EXPORT Qgs3DTerrainMetadata : public Qgs3DTerrainAbstractMetadata +{ + public: + /** + * Constructor for Qgs3DTerrainMetadata, with the specified \a type and \a visibleName. + * + * The \a pfCreate argument is used to specify + * a static function for creating the terrain settings. + * + * An optional \a icon can be specified to represent the terrain type. + */ + Qgs3DTerrainMetadata( const QString &type, const QString &visibleName, QgsTerrainSettingsCreateFunc pfCreate, const QIcon &icon = QIcon() ) + : Qgs3DTerrainAbstractMetadata( type, visibleName, icon ) + , mCreateFunc( pfCreate ) + {} + + /** + * Returns the terrain setting's creation function. + */ + QgsTerrainSettingsCreateFunc createFunction() const { return mCreateFunc; } + + QgsAbstractTerrainSettings *createTerrainSettings() override SIP_FACTORY { return mCreateFunc ? mCreateFunc() : nullptr; } + + private: + QgsTerrainSettingsCreateFunc mCreateFunc; +}; +#endif + + +/** + * \ingroup core + * \brief Registry of available 3d terrain classes. + * + * Qgs3DTerrainRegistry is not usually directly created, but rather accessed through + * Qgs3D::terrainRegistry(). + * + * \since QGIS 3.42 + */ +class _3D_EXPORT Qgs3DTerrainRegistry +{ + public: + Qgs3DTerrainRegistry(); + ~Qgs3DTerrainRegistry(); + + Qgs3DTerrainRegistry( const Qgs3DTerrainRegistry &rh ) = delete; + Qgs3DTerrainRegistry &operator=( const Qgs3DTerrainRegistry &rh ) = delete; + + //! Returns metadata for specified terrain \a type. Returns NULLPTR if not found + Qgs3DTerrainAbstractMetadata *terrainMetadata( const QString &type ) const; + + /** + * Returns a list of all available terrain types. + */ + QStringList types() const; + + //! Registers a new terrain type. Takes ownership of the \a metadata instance. + bool addType( Qgs3DTerrainAbstractMetadata *metadata SIP_TRANSFER ); + + /** + * Creates a new instance of the terrain settings of the specified \a type. + * + * The caller takes ownership of the returned object. + * + * Returns NULLPTR if the specified type is not found in the registry. + */ + QgsAbstractTerrainSettings *createTerrainSettings( const QString &type ) const SIP_FACTORY; + + private: +#ifdef SIP_RUN + Qgs3DTerrainRegistry( const Qgs3DTerrainRegistry &rh ); +#endif + + QMap mMetadata; + //! List of terrains, maintained in the order that they have been added + QStringList mTerrainOrder; +}; + + +#endif // QGS3DTERRAINREGISTRY_H diff --git a/src/3d/terrain/qgsdemterrainsettings.cpp b/src/3d/terrain/qgsdemterrainsettings.cpp index 014d4ab0456..55f500fe7ad 100644 --- a/src/3d/terrain/qgsdemterrainsettings.cpp +++ b/src/3d/terrain/qgsdemterrainsettings.cpp @@ -16,6 +16,11 @@ #include "qgsdemterrainsettings.h" #include "qgsrasterlayer.h" +QgsAbstractTerrainSettings *QgsDemTerrainSettings::create() +{ + return new QgsDemTerrainSettings(); +} + QgsDemTerrainSettings *QgsDemTerrainSettings::clone() const { return new QgsDemTerrainSettings( *this ); diff --git a/src/3d/terrain/qgsdemterrainsettings.h b/src/3d/terrain/qgsdemterrainsettings.h index 72a613a77c4..0fb87deb112 100644 --- a/src/3d/terrain/qgsdemterrainsettings.h +++ b/src/3d/terrain/qgsdemterrainsettings.h @@ -35,6 +35,11 @@ class QgsRasterLayer; class _3D_EXPORT QgsDemTerrainSettings : public QgsAbstractTerrainSettings { public: + /** + * Creates a new instance of a QgsDemTerrainSettings object. + */ + static QgsAbstractTerrainSettings *create() SIP_FACTORY; + QgsDemTerrainSettings *clone() const final SIP_FACTORY; QString type() const final; void readXml( const QDomElement &element, const QgsReadWriteContext &context ) final; diff --git a/src/3d/terrain/qgsflatterrainsettings.cpp b/src/3d/terrain/qgsflatterrainsettings.cpp index 38b0ee18013..5e3cca1e9db 100644 --- a/src/3d/terrain/qgsflatterrainsettings.cpp +++ b/src/3d/terrain/qgsflatterrainsettings.cpp @@ -15,6 +15,11 @@ #include "qgsflatterrainsettings.h" +QgsAbstractTerrainSettings *QgsFlatTerrainSettings::create() +{ + return new QgsFlatTerrainSettings(); +} + QgsFlatTerrainSettings *QgsFlatTerrainSettings::clone() const { return new QgsFlatTerrainSettings( *this ); diff --git a/src/3d/terrain/qgsflatterrainsettings.h b/src/3d/terrain/qgsflatterrainsettings.h index f76863ef728..e911bdb6206 100644 --- a/src/3d/terrain/qgsflatterrainsettings.h +++ b/src/3d/terrain/qgsflatterrainsettings.h @@ -36,6 +36,11 @@ class QgsProject; class _3D_EXPORT QgsFlatTerrainSettings : public QgsAbstractTerrainSettings { public: + /** + * Creates a new instance of a QgsFlatTerrainSettings object. + */ + static QgsAbstractTerrainSettings *create() SIP_FACTORY; + QgsFlatTerrainSettings *clone() const final SIP_FACTORY; QString type() const final; void readXml( const QDomElement &element, const QgsReadWriteContext &context ) final; diff --git a/src/3d/terrain/qgsmeshterrainsettings.cpp b/src/3d/terrain/qgsmeshterrainsettings.cpp index 22a932cc8e9..37b2f79b913 100644 --- a/src/3d/terrain/qgsmeshterrainsettings.cpp +++ b/src/3d/terrain/qgsmeshterrainsettings.cpp @@ -16,6 +16,11 @@ #include "qgsmeshterrainsettings.h" #include "qgsmeshlayer.h" +QgsAbstractTerrainSettings *QgsMeshTerrainSettings::create() +{ + return new QgsMeshTerrainSettings(); +} + QgsMeshTerrainSettings *QgsMeshTerrainSettings::clone() const { return new QgsMeshTerrainSettings( *this ); diff --git a/src/3d/terrain/qgsmeshterrainsettings.h b/src/3d/terrain/qgsmeshterrainsettings.h index 4ef70027900..4bb5967491c 100644 --- a/src/3d/terrain/qgsmeshterrainsettings.h +++ b/src/3d/terrain/qgsmeshterrainsettings.h @@ -38,6 +38,11 @@ class QgsMeshLayer; class _3D_EXPORT QgsMeshTerrainSettings : public QgsAbstractTerrainSettings { public: + /** + * Creates a new instance of a QgsMeshTerrainSettings object. + */ + static QgsAbstractTerrainSettings *create() SIP_FACTORY; + QgsMeshTerrainSettings *clone() const final SIP_FACTORY; QString type() const final; void readXml( const QDomElement &element, const QgsReadWriteContext &context ) final; diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index abdfe28d0fa..4990500675f 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -535,6 +535,7 @@ endif() if (WITH_3D) ADD_PYTHON_TEST(PyQgs3DMaterials test_qgs3dmaterials.py) + ADD_PYTHON_TEST(PyQgs3DTerrain test_qgs3dterrain.py) ADD_PYTHON_TEST(PyQgsMesh3DSymbol test_qgsmesh3dsymbol.py) endif() diff --git a/tests/src/python/test_qgs3dterrain.py b/tests/src/python/test_qgs3dterrain.py new file mode 100644 index 00000000000..74c79e9fc1f --- /dev/null +++ b/tests/src/python/test_qgs3dterrain.py @@ -0,0 +1,295 @@ +"""QGIS Unit tests for 3D terrrain objects. + +.. note:: 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. +""" + +from qgis.PyQt.QtXml import ( + QDomDocument, +) +from qgis.core import QgsProject, QgsRasterLayer, QgsMeshLayer, QgsReadWriteContext +from qgis._3d import ( + QgsFlatTerrainSettings, + QgsDemTerrainSettings, + QgsMeshTerrainSettings, + Qgs3DTerrainRegistry, +) +import unittest +from qgis.testing import start_app, QgisTestCase + +from utilities import unitTestDataPath + +start_app() +TEST_DATA_DIR = unitTestDataPath() + + +class TestQgs3DTerrain(QgisTestCase): + + def test_flat_terrain(self): + settings = QgsFlatTerrainSettings.create() + self.assertIsInstance(settings, QgsFlatTerrainSettings) + + self.assertEqual(settings.verticalScale(), 1) + settings.setVerticalScale(3) + self.assertEqual(settings.verticalScale(), 3) + + self.assertEqual(settings.mapTileResolution(), 512) + settings.setMapTileResolution(36) + self.assertEqual(settings.mapTileResolution(), 36) + + self.assertEqual(settings.maximumScreenError(), 3.0) + settings.setMaximumScreenError(4.5) + self.assertEqual(settings.maximumScreenError(), 4.5) + + self.assertEqual(settings.maximumGroundError(), 1) + settings.setMaximumGroundError(14.5) + self.assertEqual(settings.maximumGroundError(), 14.5) + + self.assertEqual(settings.elevationOffset(), 0) + settings.setElevationOffset(24.5) + self.assertEqual(settings.elevationOffset(), 24.5) + + # clone + settings2 = settings.clone() + self.assertEqual(settings2.verticalScale(), 3) + self.assertEqual(settings2.mapTileResolution(), 36) + self.assertEqual(settings2.maximumScreenError(), 4.5) + self.assertEqual(settings2.maximumGroundError(), 14.5) + self.assertEqual(settings2.elevationOffset(), 24.5) + self.assertTrue(settings2.equals(settings)) + + # equals + settings2.setVerticalScale(4) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setMapTileResolution(136) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setMaximumScreenError(136) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setMaximumGroundError(136) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setElevationOffset(136) + self.assertFalse(settings2.equals(settings)) + + # read/write xml + doc = QDomDocument("testdoc") + elem = doc.createElement("test") + settings.writeXml(elem, QgsReadWriteContext()) + + settings3 = QgsFlatTerrainSettings() + settings3.readXml(elem, QgsReadWriteContext()) + self.assertEqual(settings3.verticalScale(), 3) + self.assertEqual(settings3.mapTileResolution(), 36) + self.assertEqual(settings3.maximumScreenError(), 4.5) + self.assertEqual(settings3.maximumGroundError(), 14.5) + self.assertEqual(settings3.elevationOffset(), 24.5) + + def test_dem_terrain(self): + p = QgsProject() + rl = QgsRasterLayer( + self.get_test_data_path("raster/dem.tif").as_posix(), "layer1" + ) + self.assertTrue(rl.isValid()) + rl2 = QgsRasterLayer( + self.get_test_data_path("raster/dem.tif").as_posix(), "layer2" + ) + self.assertTrue(rl2.isValid()) + p.addMapLayer(rl) + p.addMapLayer(rl2) + + settings = QgsDemTerrainSettings.create() + self.assertIsInstance(settings, QgsDemTerrainSettings) + + settings.setLayer(rl) + self.assertEqual(settings.layer(), rl) + + self.assertEqual(settings.resolution(), 16) + settings.setResolution(66) + self.assertEqual(settings.resolution(), 66) + + self.assertEqual(settings.skirtHeight(), 10) + settings.setSkirtHeight(366) + self.assertEqual(settings.skirtHeight(), 366) + + self.assertEqual(settings.verticalScale(), 1) + settings.setVerticalScale(3) + self.assertEqual(settings.verticalScale(), 3) + + self.assertEqual(settings.mapTileResolution(), 512) + settings.setMapTileResolution(36) + self.assertEqual(settings.mapTileResolution(), 36) + + self.assertEqual(settings.maximumScreenError(), 3.0) + settings.setMaximumScreenError(4.5) + self.assertEqual(settings.maximumScreenError(), 4.5) + + self.assertEqual(settings.maximumGroundError(), 1) + settings.setMaximumGroundError(14.5) + self.assertEqual(settings.maximumGroundError(), 14.5) + + self.assertEqual(settings.elevationOffset(), 0) + settings.setElevationOffset(24.5) + self.assertEqual(settings.elevationOffset(), 24.5) + + # clone + settings2 = settings.clone() + self.assertEqual(settings2.layer(), rl) + self.assertEqual(settings2.resolution(), 66) + self.assertEqual(settings2.skirtHeight(), 366) + self.assertEqual(settings2.verticalScale(), 3) + self.assertEqual(settings2.mapTileResolution(), 36) + self.assertEqual(settings2.maximumScreenError(), 4.5) + self.assertEqual(settings2.maximumGroundError(), 14.5) + self.assertEqual(settings2.elevationOffset(), 24.5) + self.assertTrue(settings2.equals(settings)) + + # equals + settings2.setLayer(rl2) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setResolution(555) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setSkirtHeight(1555) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setVerticalScale(4) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setMapTileResolution(136) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setMaximumScreenError(136) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setMaximumGroundError(136) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setElevationOffset(136) + self.assertFalse(settings2.equals(settings)) + + # read/write xml + doc = QDomDocument("testdoc") + elem = doc.createElement("test") + settings.writeXml(elem, QgsReadWriteContext()) + + settings3 = QgsDemTerrainSettings() + settings3.readXml(elem, QgsReadWriteContext()) + self.assertIsNone(settings3.layer()) + settings3.resolveReferences(p) + self.assertEqual(settings3.layer(), rl) + self.assertEqual(settings3.resolution(), 66) + self.assertEqual(settings3.skirtHeight(), 366) + self.assertEqual(settings3.verticalScale(), 3) + self.assertEqual(settings3.mapTileResolution(), 36) + self.assertEqual(settings3.maximumScreenError(), 4.5) + self.assertEqual(settings3.maximumGroundError(), 14.5) + self.assertEqual(settings3.elevationOffset(), 24.5) + + def test_mesh_terrain(self): + p = QgsProject() + ml = QgsMeshLayer( + self.get_test_data_path("mesh/quad_and_triangle.2dm").as_posix(), "layer1" + ) + self.assertTrue(ml.isValid()) + ml2 = QgsMeshLayer( + self.get_test_data_path("mesh/quad_and_triangle.2dm").as_posix(), "layer2" + ) + self.assertTrue(ml2.isValid()) + p.addMapLayer(ml) + p.addMapLayer(ml2) + + settings = QgsMeshTerrainSettings.create() + self.assertIsInstance(settings, QgsMeshTerrainSettings) + + settings.setLayer(ml) + self.assertEqual(settings.layer(), ml) + + self.assertEqual(settings.verticalScale(), 1) + settings.setVerticalScale(3) + self.assertEqual(settings.verticalScale(), 3) + + self.assertEqual(settings.mapTileResolution(), 512) + settings.setMapTileResolution(36) + self.assertEqual(settings.mapTileResolution(), 36) + + self.assertEqual(settings.maximumScreenError(), 3.0) + settings.setMaximumScreenError(4.5) + self.assertEqual(settings.maximumScreenError(), 4.5) + + self.assertEqual(settings.maximumGroundError(), 1) + settings.setMaximumGroundError(14.5) + self.assertEqual(settings.maximumGroundError(), 14.5) + + self.assertEqual(settings.elevationOffset(), 0) + settings.setElevationOffset(24.5) + self.assertEqual(settings.elevationOffset(), 24.5) + + # clone + settings2 = settings.clone() + self.assertEqual(settings2.layer(), ml) + self.assertEqual(settings2.verticalScale(), 3) + self.assertEqual(settings2.mapTileResolution(), 36) + self.assertEqual(settings2.maximumScreenError(), 4.5) + self.assertEqual(settings2.maximumGroundError(), 14.5) + self.assertEqual(settings2.elevationOffset(), 24.5) + self.assertTrue(settings2.equals(settings)) + + # equals + settings2.setLayer(ml2) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setVerticalScale(4) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setMapTileResolution(136) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setMaximumScreenError(136) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setMaximumGroundError(136) + self.assertFalse(settings2.equals(settings)) + settings2 = settings.clone() + settings2.setElevationOffset(136) + self.assertFalse(settings2.equals(settings)) + + # read/write xml + doc = QDomDocument("testdoc") + elem = doc.createElement("test") + settings.writeXml(elem, QgsReadWriteContext()) + + settings3 = QgsMeshTerrainSettings() + settings3.readXml(elem, QgsReadWriteContext()) + self.assertIsNone(settings3.layer()) + settings3.resolveReferences(p) + self.assertEqual(settings3.layer(), ml) + self.assertEqual(settings3.verticalScale(), 3) + self.assertEqual(settings3.mapTileResolution(), 36) + self.assertEqual(settings3.maximumScreenError(), 4.5) + self.assertEqual(settings3.maximumGroundError(), 14.5) + self.assertEqual(settings3.elevationOffset(), 24.5) + + def test_registry(self): + registry = Qgs3DTerrainRegistry() + # registry should be populated with known types + self.assertIn("flat", registry.types()) + self.assertIn("dem", registry.types()) + self.assertIn("mesh", registry.types()) + + # check settings + settings = registry.createTerrainSettings("flat") + self.assertIsInstance(settings, QgsFlatTerrainSettings) + settings = registry.createTerrainSettings("dem") + self.assertIsInstance(settings, QgsDemTerrainSettings) + settings = registry.createTerrainSettings("mesh") + self.assertIsInstance(settings, QgsMeshTerrainSettings) + + +if __name__ == "__main__": + unittest.main()