From a34eabd1fbcd8631d6863fef9600c39f87ffacb9 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 14 Jul 2020 19:59:33 +1000 Subject: [PATCH] [api] Add a proper registry for 3D symbol types --- .../3d/qgs3dsymbolregistry.sip.in | 117 ++++++++++ .../core/auto_generated/qgsapplication.sip.in | 7 + python/core/core_auto.sip | 1 + src/core/3d/qgs3dsymbolregistry.cpp | 52 +++++ src/core/3d/qgs3dsymbolregistry.h | 201 ++++++++++++++++++ src/core/CMakeLists.txt | 2 + src/core/qgsapplication.cpp | 12 ++ src/core/qgsapplication.h | 8 + tests/src/3d/CMakeLists.txt | 1 + tests/src/3d/testqgs3dsymbolregistry.cpp | 155 ++++++++++++++ 10 files changed, 556 insertions(+) create mode 100644 python/core/auto_generated/3d/qgs3dsymbolregistry.sip.in create mode 100644 src/core/3d/qgs3dsymbolregistry.cpp create mode 100644 src/core/3d/qgs3dsymbolregistry.h create mode 100644 tests/src/3d/testqgs3dsymbolregistry.cpp diff --git a/python/core/auto_generated/3d/qgs3dsymbolregistry.sip.in b/python/core/auto_generated/3d/qgs3dsymbolregistry.sip.in new file mode 100644 index 00000000000..e59c88e332c --- /dev/null +++ b/python/core/auto_generated/3d/qgs3dsymbolregistry.sip.in @@ -0,0 +1,117 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/./3d/qgs3dsymbolregistry.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class Qgs3DSymbolAbstractMetadata +{ +%Docstring +Stores metadata about one 3D symbol class. + +.. note:: + + It's necessary to implement :py:func:`~createSymbol` function. + In C++ you can use Qgs3DSymbolMetadata convenience class. + +.. versionadded:: 3.16 +%End + +%TypeHeaderCode +#include "qgs3dsymbolregistry.h" +%End + public: + + Qgs3DSymbolAbstractMetadata( const QString &type, const QString &visibleName ); +%Docstring +Constructor for Qgs3DSymbolAbstractMetadata, with the specified ``type`` and ``visibleName``. +%End + + virtual ~Qgs3DSymbolAbstractMetadata(); + + QString type() const; +%Docstring +Returns the unique symbol type string. +%End + + QString visibleName() const; +%Docstring +Returns the symbol's visible (translated) name. +%End + + virtual QgsAbstract3DSymbol *create() = 0 /Factory/; +%Docstring +Creates a new instance of this symbol type. + +Caller takes ownership of the returned symbol. +%End + + +}; + + + + + +class Qgs3DSymbolRegistry +{ +%Docstring +Registry of available 3D symbol classes. + +Qgs3DSymbolRegistry is not usually directly created, but rather accessed through +:py:func:`QgsApplication.symbol3DRegistry()`. + +.. versionadded:: 3.16 +%End + +%TypeHeaderCode +#include "qgs3dsymbolregistry.h" +%End + public: + + Qgs3DSymbolRegistry(); + ~Qgs3DSymbolRegistry(); + + + Qgs3DSymbolAbstractMetadata *symbolMetadata( const QString &type ) const; +%Docstring +Returns metadata for specified symbol ``type``. Returns ``None`` if not found +%End + + QStringList symbolTypes() const; +%Docstring +Returns a list of all available symbol types. +%End + + bool addSymbolType( Qgs3DSymbolAbstractMetadata *metadata /Transfer/ ); +%Docstring +Registers a new symbol type. Takes ownership of the ``metadata`` instance. +%End + + QgsAbstract3DSymbol *createSymbol( const QString &type ) const /Factory/; +%Docstring +Creates a new instance of a symbol of the specified ``type``. + +The caller takes ownership of the returned symbol. + +Returns ``None`` if the specified type is not found in the registry. +%End + + private: + Qgs3DSymbolRegistry( const Qgs3DSymbolRegistry &rh ); +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/./3d/qgs3dsymbolregistry.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/auto_generated/qgsapplication.sip.in b/python/core/auto_generated/qgsapplication.sip.in index 93c6a7284a8..7eb856196de 100644 --- a/python/core/auto_generated/qgsapplication.sip.in +++ b/python/core/auto_generated/qgsapplication.sip.in @@ -850,6 +850,13 @@ Gets the registry of available field formatters. Returns registry of available 3D renderers. .. versionadded:: 3.0 +%End + + static Qgs3DSymbolRegistry *symbol3DRegistry() /KeepReference/; +%Docstring +Returns registry of available 3D symbols. + +.. versionadded:: 3.16 %End static QgsScaleBarRendererRegistry *scaleBarRendererRegistry() /KeepReference/; diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 0ee324fcf5a..7afe2387c66 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -251,6 +251,7 @@ %Include auto_generated/qgsxmlutils.sip %Include auto_generated/qgsziputils.sip %Include auto_generated/./3d/qgs3drendererregistry.sip +%Include auto_generated/./3d/qgs3dsymbolregistry.sip %Include auto_generated/./3d/qgsabstract3dsymbol.sip %Include auto_generated/./3d/qgsabstract3drenderer.sip %Include auto_generated/annotations/qgsannotation.sip diff --git a/src/core/3d/qgs3dsymbolregistry.cpp b/src/core/3d/qgs3dsymbolregistry.cpp new file mode 100644 index 00000000000..7e5338e0811 --- /dev/null +++ b/src/core/3d/qgs3dsymbolregistry.cpp @@ -0,0 +1,52 @@ +/*************************************************************************** + qgs3dsymbolregistry.cpp + -------------------------------------- + Date : July 2020 + Copyright : (C) 2020 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 "qgs3dsymbolregistry.h" + +Qgs3DSymbolRegistry::Qgs3DSymbolRegistry() +{ +} + +Qgs3DSymbolRegistry::~Qgs3DSymbolRegistry() +{ + qDeleteAll( mMetadata ); +} + +bool Qgs3DSymbolRegistry::addSymbolType( Qgs3DSymbolAbstractMetadata *metadata ) +{ + if ( !metadata || mMetadata.contains( metadata->type() ) ) + return false; + + mMetadata[metadata->type()] = metadata; + return true; +} + +QgsAbstract3DSymbol *Qgs3DSymbolRegistry::createSymbol( const QString &type ) const +{ + if ( !mMetadata.contains( type ) ) + return nullptr; + + return mMetadata[type]->create(); +} + +Qgs3DSymbolAbstractMetadata *Qgs3DSymbolRegistry::symbolMetadata( const QString &type ) const +{ + return mMetadata.value( type ); +} + +QStringList Qgs3DSymbolRegistry::symbolTypes() const +{ + return mMetadata.keys(); +} diff --git a/src/core/3d/qgs3dsymbolregistry.h b/src/core/3d/qgs3dsymbolregistry.h new file mode 100644 index 00000000000..bbb23702d64 --- /dev/null +++ b/src/core/3d/qgs3dsymbolregistry.h @@ -0,0 +1,201 @@ +/*************************************************************************** + qgs3dsymbolregistry.h + -------------------------------------- + Date : July 2020 + Copyright : (C) 2020 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 QGS3DSYMBOLREGISTRY_H +#define QGS3DSYMBOLREGISTRY_H + +#include "qgis_core.h" +#include "qgis_sip.h" + +#include +#include + +class QgsAbstract3DSymbol; +class QgsReadWriteContext; +class Qgs3DSymbolWidget; +class QgsVectorLayer; + +/** + * \ingroup core + * Stores metadata about one 3D symbol class. + * + * \note It's necessary to implement createSymbol() function. + * In C++ you can use Qgs3DSymbolMetadata convenience class. + * + * \since QGIS 3.16 + */ +class CORE_EXPORT Qgs3DSymbolAbstractMetadata +{ + public: + + /** + * Constructor for Qgs3DSymbolAbstractMetadata, with the specified \a type and \a visibleName. + */ + Qgs3DSymbolAbstractMetadata( const QString &type, const QString &visibleName ) + : mType( type ) + , mVisibleName( visibleName ) + {} + + virtual ~Qgs3DSymbolAbstractMetadata() = default; + + /** + * Returns the unique symbol type string. + */ + QString type() const { return mType; } + + /** + * Returns the symbol's visible (translated) name. + */ + QString visibleName() const { return mVisibleName; } + + /** + * Creates a new instance of this symbol type. + * + * Caller takes ownership of the returned symbol. + */ + virtual QgsAbstract3DSymbol *create() = 0 SIP_FACTORY; + +#ifndef SIP_RUN + + /** + * Create a widget for configuring a symbol of this type. + * + * Can return NULLPTR if there's no GUI. + * + * \note Not available in Python bindings + */ + virtual Qgs3DSymbolWidget *createSymbolWidget( QgsVectorLayer * ) SIP_FACTORY { return nullptr; } +#endif + + private: + QString mType; + QString mVisibleName; +}; + +//! 3D symbol creation function +typedef QgsAbstract3DSymbol *( *Qgs3DSymbolCreateFunc )() SIP_SKIP; + +//! 3D symbol widget creation function +typedef Qgs3DSymbolWidget *( *Qgs3DSymbolWidgetFunc )( QgsVectorLayer * ) SIP_SKIP; + +#ifndef SIP_RUN + +/** + * \ingroup core + * Convenience metadata class that uses static functions to create a 3D symbol and its widget. + * + * \note Not available in Python bindings. + * + * \since QGIS 3.16 + */ +class CORE_EXPORT Qgs3DSymbolMetadata : public Qgs3DSymbolAbstractMetadata +{ + public: + + /** + * Constructor for Qgs3DSymbolMetadata, with the specified \a type and \a visibleName. + * + * The \a pfCreate and \a pfWidget arguments are used to specify + * static functions for creating the symbol type and configuration widget. + */ + Qgs3DSymbolMetadata( const QString &type, const QString &visibleName, + Qgs3DSymbolCreateFunc pfCreate, + Qgs3DSymbolWidgetFunc pfWidget = nullptr ) SIP_SKIP + : Qgs3DSymbolAbstractMetadata( type, visibleName ) + , mCreateFunc( pfCreate ) + , mWidgetFunc( pfWidget ) + {} + + /** + * Returns the symbol type's creation function. + */ + Qgs3DSymbolCreateFunc createFunction() const { return mCreateFunc; } + + /** + * Returns the symbol type's widget creation function. + * + * \see setWidgetFunction() + */ + Qgs3DSymbolWidgetFunc widgetFunction() const { return mWidgetFunc; } + + /** + * Sets the symbol type's widget creation \a function. + * + * \see widgetFunction() + */ + void setWidgetFunction( Qgs3DSymbolWidgetFunc function ) { mWidgetFunc = function; } + + QgsAbstract3DSymbol *create() override SIP_FACTORY { return mCreateFunc ? mCreateFunc() : nullptr; } + Qgs3DSymbolWidget *createSymbolWidget( QgsVectorLayer *vl ) override SIP_FACTORY { return mWidgetFunc ? mWidgetFunc( vl ) : nullptr; } + + private: + Qgs3DSymbolCreateFunc mCreateFunc; + Qgs3DSymbolWidgetFunc mWidgetFunc; + +}; +#endif + + +/** + * \ingroup core + * Registry of available 3D symbol classes. + * + * Qgs3DSymbolRegistry is not usually directly created, but rather accessed through + * QgsApplication::symbol3DRegistry(). + * + * \since QGIS 3.16 + */ +class CORE_EXPORT Qgs3DSymbolRegistry +{ + public: + + Qgs3DSymbolRegistry(); + ~Qgs3DSymbolRegistry(); + + //! Qgs3DSymbolRegistry cannot be copied. + Qgs3DSymbolRegistry( const Qgs3DSymbolRegistry &rh ) = delete; + //! Qgs3DSymbolRegistry cannot be copied. + Qgs3DSymbolRegistry &operator=( const Qgs3DSymbolRegistry &rh ) = delete; + + //! Returns metadata for specified symbol \a type. Returns NULLPTR if not found + Qgs3DSymbolAbstractMetadata *symbolMetadata( const QString &type ) const; + + /** + * Returns a list of all available symbol types. + */ + QStringList symbolTypes() const; + + //! Registers a new symbol type. Takes ownership of the \a metadata instance. + bool addSymbolType( Qgs3DSymbolAbstractMetadata *metadata SIP_TRANSFER ); + + /** + * Creates a new instance of a symbol of the specified \a type. + * + * The caller takes ownership of the returned symbol. + * + * Returns NULLPTR if the specified type is not found in the registry. + */ + QgsAbstract3DSymbol *createSymbol( const QString &type ) const SIP_FACTORY; + + private: +#ifdef SIP_RUN + Qgs3DSymbolRegistry( const Qgs3DSymbolRegistry &rh ); +#endif + + QMap mMetadata; +}; + + +#endif // QGS3DSYMBOLREGISTRY_H diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 05f40a9ed49..cb63be4a2c0 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -654,6 +654,7 @@ SET(QGIS_CORE_SRCS geometry/qgswkbtypes.cpp 3d/qgs3drendererregistry.cpp + 3d/qgs3dsymbolregistry.cpp 3d/qgsabstract3dsymbol.cpp 3d/qgsabstract3drenderer.cpp @@ -1033,6 +1034,7 @@ SET(QGIS_CORE_HDRS qobjectuniqueptr.h 3d/qgs3drendererregistry.h + 3d/qgs3dsymbolregistry.h 3d/qgsabstract3dsymbol.h 3d/qgsabstract3drenderer.h diff --git a/src/core/qgsapplication.cpp b/src/core/qgsapplication.cpp index 4bab72e67a3..192252f8deb 100644 --- a/src/core/qgsapplication.cpp +++ b/src/core/qgsapplication.cpp @@ -54,6 +54,7 @@ #include "qgsuserprofilemanager.h" #include "qgsreferencedgeometry.h" #include "qgs3drendererregistry.h" +#include "qgs3dsymbolregistry.h" #include "qgslayoutrendercontext.h" #include "qgssqliteutils.h" #include "qgsstyle.h" @@ -2235,6 +2236,11 @@ Qgs3DRendererRegistry *QgsApplication::renderer3DRegistry() return members()->m3DRendererRegistry; } +Qgs3DSymbolRegistry *QgsApplication::symbol3DRegistry() +{ + return members()->m3DSymbolRegistry; +} + QgsScaleBarRendererRegistry *QgsApplication::scaleBarRendererRegistry() { return members()->mScaleBarRendererRegistry; @@ -2355,6 +2361,11 @@ QgsApplication::ApplicationMembers::ApplicationMembers() mAnnotationRegistry = new QgsAnnotationRegistry(); profiler->end(); } + { + profiler->start( tr( "Setup 3D symbol registry" ) ); + m3DSymbolRegistry = new Qgs3DSymbolRegistry(); + profiler->end(); + } { profiler->start( tr( "Setup 3D renderer registry" ) ); m3DRendererRegistry = new Qgs3DRendererRegistry(); @@ -2399,6 +2410,7 @@ QgsApplication::ApplicationMembers::~ApplicationMembers() delete mValidityCheckRegistry; delete mActionScopeRegistry; delete m3DRendererRegistry; + delete m3DSymbolRegistry; delete mAnnotationRegistry; delete mColorSchemeRegistry; delete mFieldFormatterRegistry; diff --git a/src/core/qgsapplication.h b/src/core/qgsapplication.h index c39ebf5576a..8a5c34859df 100644 --- a/src/core/qgsapplication.h +++ b/src/core/qgsapplication.h @@ -60,6 +60,7 @@ class QgsStyleModel; class QgsNumericFormatRegistry; class QgsConnectionRegistry; class QgsScaleBarRendererRegistry; +class Qgs3DSymbolRegistry; /** * \ingroup core @@ -785,6 +786,12 @@ class CORE_EXPORT QgsApplication : public QApplication */ static Qgs3DRendererRegistry *renderer3DRegistry() SIP_KEEPREFERENCE; + /** + * Returns registry of available 3D symbols. + * \since QGIS 3.16 + */ + static Qgs3DSymbolRegistry *symbol3DRegistry() SIP_KEEPREFERENCE; + /** * Gets the registry of available scalebar renderers. * @@ -928,6 +935,7 @@ class CORE_EXPORT QgsApplication : public QApplication struct ApplicationMembers { Qgs3DRendererRegistry *m3DRendererRegistry = nullptr; + Qgs3DSymbolRegistry *m3DSymbolRegistry = nullptr; QgsActionScopeRegistry *mActionScopeRegistry = nullptr; QgsAnnotationRegistry *mAnnotationRegistry = nullptr; QgsColorSchemeRegistry *mColorSchemeRegistry = nullptr; diff --git a/tests/src/3d/CMakeLists.txt b/tests/src/3d/CMakeLists.txt index 2154ff2caef..7aae3960545 100644 --- a/tests/src/3d/CMakeLists.txt +++ b/tests/src/3d/CMakeLists.txt @@ -79,3 +79,4 @@ ADD_QGIS_TEST(3dutilstest testqgs3dutils.cpp) ADD_QGIS_TEST(3drenderingtest testqgs3drendering.cpp) ADD_QGIS_TEST(layout3dmaptest testqgslayout3dmap.cpp) ADD_QGIS_TEST(tessellatortest testqgstessellator.cpp) +ADD_QGIS_TEST(3dsymbolregistrytest testqgs3dsymbolregistry.cpp) diff --git a/tests/src/3d/testqgs3dsymbolregistry.cpp b/tests/src/3d/testqgs3dsymbolregistry.cpp new file mode 100644 index 00000000000..c6a691c6f24 --- /dev/null +++ b/tests/src/3d/testqgs3dsymbolregistry.cpp @@ -0,0 +1,155 @@ +/*************************************************************************** + TestQgs3DSymbolRegistry.cpp + ----------------------- + begin : July 2019 + copyright : (C) 2019 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 "qgs3dsymbolregistry.h" +#include "qgsabstract3dsymbol.h" + +#include +#include "qgstest.h" + +//dummy symbol for testing +class Dummy3DSymbol : public QgsAbstract3DSymbol +{ + public: + Dummy3DSymbol() = default; + QString type() const override { return QStringLiteral( "Dummy" ); } + QgsAbstract3DSymbol *clone() const override { return new Dummy3DSymbol(); } + void writeXml( QDomElement &, const QgsReadWriteContext & ) const override {} + void readXml( const QDomElement &, const QgsReadWriteContext & ) override {} + + static QgsAbstract3DSymbol *create() { return new Dummy3DSymbol(); } + +}; + +class TestQgs3DSymbolRegistry : public QObject +{ + Q_OBJECT + + private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + void metadata(); + void createInstance(); + void instanceHasDefaultSymbols(); + void addSymbol(); + void fetchTypes(); + void createSymbol(); + + private: + +}; + +void TestQgs3DSymbolRegistry::initTestCase() +{ + QgsApplication::init(); // init paths for CRS lookup + QgsApplication::initQgis(); +} + +void TestQgs3DSymbolRegistry::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +void TestQgs3DSymbolRegistry::init() +{ + +} + +void TestQgs3DSymbolRegistry::cleanup() +{ + +} + +void TestQgs3DSymbolRegistry::metadata() +{ + Qgs3DSymbolMetadata metadata = Qgs3DSymbolMetadata( QStringLiteral( "name" ), QStringLiteral( "display name" ), Dummy3DSymbol::create ); + QCOMPARE( metadata.type(), QString( "name" ) ); + QCOMPARE( metadata.visibleName(), QString( "display name" ) ); + + //test creating symbol from metadata + QVariantMap map; + std::unique_ptr< QgsAbstract3DSymbol > symbol( metadata.create() ); + QVERIFY( symbol ); + Dummy3DSymbol *dummySymbol = dynamic_cast( symbol.get() ); + QVERIFY( dummySymbol ); +} + +void TestQgs3DSymbolRegistry::createInstance() +{ + Qgs3DSymbolRegistry *registry = QgsApplication::symbol3DRegistry(); + QVERIFY( registry ); +} + +void TestQgs3DSymbolRegistry::instanceHasDefaultSymbols() +{ + //check that symbol registry is initially populated with some symbols + //(assumes that there is some default symbols) + Qgs3DSymbolRegistry *registry = QgsApplication::symbol3DRegistry(); + QVERIFY( registry->symbolTypes().length() > 0 ); +} + +void TestQgs3DSymbolRegistry::addSymbol() +{ + Qgs3DSymbolRegistry *registry = QgsApplication::symbol3DRegistry(); + int previousCount = registry->symbolTypes().length(); + + registry->addSymbolType( new Qgs3DSymbolMetadata( QStringLiteral( "Dummy" ), QStringLiteral( "Dummy symbol" ), Dummy3DSymbol::create ) ); + QCOMPARE( registry->symbolTypes().length(), previousCount + 1 ); + //try adding again, should have no effect + Qgs3DSymbolMetadata *dupe = new Qgs3DSymbolMetadata( QStringLiteral( "Dummy" ), QStringLiteral( "Dummy symbol" ), Dummy3DSymbol::create ); + QVERIFY( ! registry->addSymbolType( dupe ) ); + QCOMPARE( registry->symbolTypes().length(), previousCount + 1 ); + delete dupe; + + //try adding empty metadata + registry->addSymbolType( nullptr ); + QCOMPARE( registry->symbolTypes().length(), previousCount + 1 ); +} + +void TestQgs3DSymbolRegistry::fetchTypes() +{ + Qgs3DSymbolRegistry *registry = QgsApplication::symbol3DRegistry(); + QStringList types = registry->symbolTypes(); + + QVERIFY( types.contains( "Dummy" ) ); + + Qgs3DSymbolAbstractMetadata *metadata = registry->symbolMetadata( QStringLiteral( "Dummy" ) ); + QCOMPARE( metadata->type(), QString( "Dummy" ) ); + + //metadata for bad symbol + metadata = registry->symbolMetadata( QStringLiteral( "bad symbol" ) ); + QVERIFY( !metadata ); +} + +void TestQgs3DSymbolRegistry::createSymbol() +{ + Qgs3DSymbolRegistry *registry = QgsApplication::symbol3DRegistry(); + std::unique_ptr< QgsAbstract3DSymbol > symbol( registry->createSymbol( QStringLiteral( "Dummy" ) ) ); + + QVERIFY( symbol.get() ); + Dummy3DSymbol *dummySymbol = dynamic_cast( symbol.get() ); + QVERIFY( dummySymbol ); + + //try creating a bad symbol + symbol.reset( registry->createSymbol( QStringLiteral( "bad symbol" ) ) ); + QVERIFY( !symbol.get() ); +} + +QGSTEST_MAIN( TestQgs3DSymbolRegistry ) +#include "testqgs3dsymbolregistry.moc"