From e71660215c944ede6d042c9204bb455ca1d006ee Mon Sep 17 00:00:00 2001 From: Peter Petrik Date: Wed, 12 Dec 2018 10:58:43 +0100 Subject: [PATCH] [processing] add QgsProcessingParameterMeshLayer parameter --- .../processing/qgsprocessing.sip.in | 3 +- .../processing/qgsprocessingalgorithm.sip.in | 12 ++ .../processing/qgsprocessingparameters.sip.in | 64 ++++++++ .../processing/qgsprocessingutils.sip.in | 22 +++ .../qgsmaplayerproxymodel.sip.in | 1 + .../processing/gui/ParameterGuiUtils.py | 3 +- python/plugins/processing/gui/wrappers.py | 39 ++++- python/plugins/processing/tests/GuiTest.py | 3 + python/processing/algfactory.py | 4 + src/core/processing/qgsprocessing.h | 5 +- .../processing/qgsprocessingalgorithm.cpp | 6 + src/core/processing/qgsprocessingalgorithm.h | 13 ++ .../processing/qgsprocessingparameters.cpp | 91 +++++++++++ src/core/processing/qgsprocessingparameters.h | 59 +++++++ .../qgsprocessingparametertypeimpl.h | 38 +++++ src/core/processing/qgsprocessingregistry.cpp | 1 + src/core/processing/qgsprocessingutils.cpp | 72 +++++++-- src/core/processing/qgsprocessingutils.h | 21 ++- src/core/qgsmaplayerproxymodel.cpp | 3 + src/core/qgsmaplayerproxymodel.h | 3 +- .../qgsprocessingwidgetwrapperimpl.cpp | 2 + tests/src/analysis/CMakeLists.txt | 1 + tests/src/analysis/testqgsprocessing.cpp | 146 +++++++++++++++++- .../src/python/test_qgsmaplayerproxymodel.py | 20 ++- 24 files changed, 609 insertions(+), 23 deletions(-) diff --git a/python/core/auto_generated/processing/qgsprocessing.sip.in b/python/core/auto_generated/processing/qgsprocessing.sip.in index 5257ae3e55e..a01dd69ee26 100644 --- a/python/core/auto_generated/processing/qgsprocessing.sip.in +++ b/python/core/auto_generated/processing/qgsprocessing.sip.in @@ -36,10 +36,9 @@ and parameters. TypeRaster, TypeFile, TypeVector, + TypeMesh }; - - }; /************************************************************************ diff --git a/python/core/auto_generated/processing/qgsprocessingalgorithm.sip.in b/python/core/auto_generated/processing/qgsprocessingalgorithm.sip.in index cc865f48d2e..5674776b787 100644 --- a/python/core/auto_generated/processing/qgsprocessingalgorithm.sip.in +++ b/python/core/auto_generated/processing/qgsprocessingalgorithm.sip.in @@ -670,6 +670,18 @@ sources and stored temporarily in the ``context``. In either case, callers do no need to handle deletion of the returned layer. %End + QgsMeshLayer *parameterAsMeshLayer( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) const; +%Docstring +Evaluates the parameter with matching ``name`` to a mesh layer. + +Layers will either be taken from ``context``'s active project, or loaded from external +sources and stored temporarily in the ``context``. In either case, callers do not +need to handle deletion of the returned layer. + +.. versionadded:: 3.6 +%End + + QString parameterAsOutputLayer( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) const; %Docstring Evaluates the parameter with matching ``name`` to a output layer destination. diff --git a/python/core/auto_generated/processing/qgsprocessingparameters.sip.in b/python/core/auto_generated/processing/qgsprocessingparameters.sip.in index 7f52e8fe264..6dafe1c9c9d 100644 --- a/python/core/auto_generated/processing/qgsprocessingparameters.sip.in +++ b/python/core/auto_generated/processing/qgsprocessingparameters.sip.in @@ -155,6 +155,8 @@ their acceptable ranges, defaults, etc. sipType = sipType_QgsProcessingParameterRange; else if ( sipCpp->type() == QgsProcessingParameterRasterLayer::typeName() ) sipType = sipType_QgsProcessingParameterRasterLayer; + else if ( sipCpp->type() == QgsProcessingParameterMeshLayer::typeName() ) + sipType = sipType_QgsProcessingParameterMeshLayer; else if ( sipCpp->type() == QgsProcessingParameterEnum::typeName() ) sipType = sipType_QgsProcessingParameterEnum; else if ( sipCpp->type() == QgsProcessingParameterString::typeName() ) @@ -745,6 +747,29 @@ need to handle deletion of the returned layer. .. versionadded:: 3.4 %End + static QgsMeshLayer *parameterAsMeshLayer( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ); +%Docstring +Evaluates the parameter with matching ``definition`` and ``value`` to a mesh layer. + +Layers will either be taken from ``context``'s active project, or loaded from external +sources and stored temporarily in the ``context``. In either case, callers do not +need to handle deletion of the returned layer. + +.. versionadded:: 3.6 +%End + + static QgsMeshLayer *parameterAsMeshLayer( const QgsProcessingParameterDefinition *definition, const QVariant &value, QgsProcessingContext &context ); +%Docstring +Evaluates the parameter with matching ``definition`` and ``value`` to a mesh layer. + +Layers will either be taken from ``context``'s active project, or loaded from external +sources and stored temporarily in the ``context``. In either case, callers do not +need to handle deletion of the returned layer. + +.. versionadded:: 3.6 +%End + + static QgsCoordinateReferenceSystem parameterAsCrs( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ); %Docstring Evaluates the parameter with matching ``definition`` to a coordinate reference system. @@ -1955,6 +1980,45 @@ Creates a new parameter using the definition from a script code. }; +class QgsProcessingParameterMeshLayer : QgsProcessingParameterDefinition +{ +%Docstring +A mesh layer parameter for processing algorithms. + +.. versionadded:: 3.6 +%End + +%TypeHeaderCode +#include "qgsprocessingparameters.h" +%End + public: + + QgsProcessingParameterMeshLayer( const QString &name, + const QString &description = QString(), + const QVariant &defaultValue = QVariant(), + bool optional = false ); +%Docstring +Constructor for QgsProcessingParameterMeshLayer. +%End + + static QString typeName(); +%Docstring +Returns the type name for the parameter class. +%End + virtual QgsProcessingParameterDefinition *clone() const /Factory/; + + virtual QString type() const; + virtual bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = 0 ) const; + + virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const; + + + static QgsProcessingParameterMeshLayer *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) /Factory/; +%Docstring +Creates a new parameter using the definition from a script code. +%End +}; + class QgsProcessingParameterField : QgsProcessingParameterDefinition { %Docstring diff --git a/python/core/auto_generated/processing/qgsprocessingutils.sip.in b/python/core/auto_generated/processing/qgsprocessingutils.sip.in index d9e60e43667..6fbc885c74c 100644 --- a/python/core/auto_generated/processing/qgsprocessingutils.sip.in +++ b/python/core/auto_generated/processing/qgsprocessingutils.sip.in @@ -35,6 +35,8 @@ value. .. seealso:: :py:func:`compatibleVectorLayers` +.. seealso:: :py:func:`compatibleMeshLayers` + .. seealso:: :py:func:`compatibleLayers` %End @@ -55,7 +57,26 @@ value. .. seealso:: :py:func:`compatibleRasterLayers` +.. seealso:: :py:func:`compatibleMeshLayers` + .. seealso:: :py:func:`compatibleLayers` +%End + + static QList compatibleMeshLayers( QgsProject *project, bool sort = true ); +%Docstring +Returns a list of mesh layers from a ``project`` which are compatible with the processing +framework. + +If the ``sort`` argument is true then the layers will be sorted by their :py:func:`QgsMapLayer.name()` +value. + +.. seealso:: :py:func:`compatibleRasterLayers` + +.. seealso:: :py:func:`compatibleVectorLayers` + +.. seealso:: :py:func:`compatibleLayers` + +.. versionadded:: 3.6 %End static QList< QgsMapLayer * > compatibleLayers( QgsProject *project, bool sort = true ); @@ -76,6 +97,7 @@ value. UnknownType, Vector, Raster, + Mesh, }; static QgsMapLayer *mapLayerFromString( const QString &string, QgsProcessingContext &context, bool allowLoadingNewLayers = true, LayerHint typeHint = UnknownType ); diff --git a/python/core/auto_generated/qgsmaplayerproxymodel.sip.in b/python/core/auto_generated/qgsmaplayerproxymodel.sip.in index 32f73d10cd7..b90f9999c34 100644 --- a/python/core/auto_generated/qgsmaplayerproxymodel.sip.in +++ b/python/core/auto_generated/qgsmaplayerproxymodel.sip.in @@ -33,6 +33,7 @@ The QgsMapLayerProxyModel class provides an easy to use model to display the lis VectorLayer, PluginLayer, WritableLayer, + MeshLayer, All }; typedef QFlags Filters; diff --git a/python/plugins/processing/gui/ParameterGuiUtils.py b/python/plugins/processing/gui/ParameterGuiUtils.py index fae75573835..1dababe7cf5 100644 --- a/python/plugins/processing/gui/ParameterGuiUtils.py +++ b/python/plugins/processing/gui/ParameterGuiUtils.py @@ -83,7 +83,8 @@ def getFileFilter(param): return QgsProviderRegistry.instance().fileVectorFilters() elif param.type() == 'fileDestination': return param.fileFilter() + ';;' + tr('All files (*.*)') - + elif param.type() == 'mesh': + return tr('All files (*.*)') if param.defaultFileExtension(): return tr('Default extension') + ' (*.' + param.defaultFileExtension() + ')' else: diff --git a/python/plugins/processing/gui/wrappers.py b/python/plugins/processing/gui/wrappers.py index ed6a01cca3f..412684a4947 100755 --- a/python/plugins/processing/gui/wrappers.py +++ b/python/plugins/processing/gui/wrappers.py @@ -61,6 +61,7 @@ from qgis.core import ( QgsProcessingParameterString, QgsProcessingParameterExpression, QgsProcessingParameterVectorLayer, + QgsProcessingParameterMeshLayer, QgsProcessingParameterField, QgsProcessingParameterFeatureSource, QgsProcessingParameterMapLayer, @@ -331,6 +332,7 @@ class CrsWidgetWrapper(WidgetWrapper): self.combo.addItem(self.dialog.resolveValueDescription(crs), crs) layers = self.dialog.getAvailableValuesOfType([QgsProcessingParameterRasterLayer, QgsProcessingParameterVectorLayer, + QgsProcessingParameterMeshLayer, QgsProcessingParameterFeatureSource], [QgsProcessingOutputVectorLayer, QgsProcessingOutputRasterLayer, @@ -402,7 +404,8 @@ class ExtentWidgetWrapper(WidgetWrapper): widget.addItem(self.USE_MIN_COVERING_EXTENT, None) layers = self.dialog.getAvailableValuesOfType([QgsProcessingParameterFeatureSource, QgsProcessingParameterRasterLayer, - QgsProcessingParameterVectorLayer], + QgsProcessingParameterVectorLayer, + QgsProcessingParameterMeshLayer], [QgsProcessingOutputRasterLayer, QgsProcessingOutputVectorLayer, QgsProcessingOutputMapLayer]) @@ -638,10 +641,15 @@ class MultipleLayerWidgetWrapper(WidgetWrapper): [QgsProcessingOutputRasterLayer, QgsProcessingOutputMapLayer, QgsProcessingOutputMultipleLayers]) + elif self.parameterDefinition().layerType() == QgsProcessing.TypeMesh: + options = self.dialog.getAvailableValuesOfType( + (QgsProcessingParameterMeshLayer, QgsProcessingParameterMultipleLayers), + []) elif self.parameterDefinition().layerType() == QgsProcessing.TypeMapLayer: options = self.dialog.getAvailableValuesOfType((QgsProcessingParameterRasterLayer, QgsProcessingParameterFeatureSource, QgsProcessingParameterVectorLayer, + QgsProcessingParameterMeshLayer, QgsProcessingParameterMultipleLayers), [QgsProcessingOutputRasterLayer, QgsProcessingOutputVectorLayer, @@ -659,11 +667,14 @@ class MultipleLayerWidgetWrapper(WidgetWrapper): else: if self.parameterDefinition().layerType() == QgsProcessing.TypeRaster: options = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False) + elif self.parameterDefinition().layerType() == QgsProcessing.TypeMesh: + options = QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance(), False) elif self.parameterDefinition().layerType() in (QgsProcessing.TypeVectorAnyGeometry, QgsProcessing.TypeVector): options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False) elif self.parameterDefinition().layerType() == QgsProcessing.TypeMapLayer: options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False) options.extend(QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)) + options.extend(QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance(), False)) else: options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [self.parameterDefinition().layerType()], False) @@ -681,11 +692,14 @@ class MultipleLayerWidgetWrapper(WidgetWrapper): if self.parameterDefinition().layerType() != QgsProcessing.TypeFile: if self.parameterDefinition().layerType() == QgsProcessing.TypeRaster: options = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False) + elif self.parameterDefinition().layerType() == QgsProcessing.TypeMesh: + options = QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance(), False) elif self.parameterDefinition().layerType() in (QgsProcessing.TypeVectorAnyGeometry, QgsProcessing.TypeVector): options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False) elif self.parameterDefinition().layerType() == QgsProcessing.TypeMapLayer: options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False) options.extend(QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)) + options.extend(QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance(), False)) else: options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [self.parameterDefinition().layerType()], False) @@ -724,11 +738,14 @@ class MultipleLayerWidgetWrapper(WidgetWrapper): else: if self.parameterDefinition().layerType() == QgsProcessing.TypeRaster: options = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False) + elif self.parameterDefinition().layerType() == QgsProcessing.TypeMesh: + options = QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance(), False) elif self.parameterDefinition().layerType() in (QgsProcessing.TypeVectorAnyGeometry, QgsProcessing.TypeVector): options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False) elif self.parameterDefinition().layerType() == QgsProcessing.TypeMapLayer: options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False) options.extend(QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)) + options.extend(QgsProcessingUtils.compatibleMeshLayers(QgsProject.instance(), False)) else: options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [self.parameterDefinition().layerType()], False) @@ -896,7 +913,7 @@ class MapLayerWidgetWrapper(WidgetWrapper): def getAvailableLayers(self): return self.dialog.getAvailableValuesOfType( - [QgsProcessingParameterRasterLayer, QgsProcessingParameterVectorLayer, QgsProcessingParameterMapLayer, QgsProcessingParameterString], + [QgsProcessingParameterRasterLayer, QgsProcessingParameterMeshLayer, QgsProcessingParameterVectorLayer, QgsProcessingParameterMapLayer, QgsProcessingParameterString], [QgsProcessingOutputRasterLayer, QgsProcessingOutputVectorLayer, QgsProcessingOutputMapLayer, QgsProcessingOutputString, QgsProcessingOutputFile]) def selectFile(self): @@ -986,6 +1003,22 @@ class RasterWidgetWrapper(MapLayerWidgetWrapper): self.widgetValueHasChanged.emit(self) +class MeshWidgetWrapper(MapLayerWidgetWrapper): + + def getAvailableLayers(self): + return self.dialog.getAvailableValuesOfType((QgsProcessingParameterMeshLayer, QgsProcessingParameterString), + ()) + + def setComboBoxFilters(self, combo): + combo.setFilters(QgsMapLayerProxyModel.MeshLayer) + + def selectFile(self): + filename, selected_filter = self.getFileName(self.combo.currentText()) + if filename: + self.combo.setEditText(filename) + self.widgetValueHasChanged.emit(self) + + class EnumWidgetWrapper(WidgetWrapper): NOT_SELECTED = '[Not selected]' @@ -1856,6 +1889,8 @@ class WidgetWrapperFactory: wrapper = RangeWidgetWrapper elif param.type() == 'matrix': wrapper = FixedTableWidgetWrapper + elif param.type() == 'mesh': + wrapper = MeshWidgetWrapper else: assert False, param.type() return wrapper(param, dialog, row, col) diff --git a/python/plugins/processing/tests/GuiTest.py b/python/plugins/processing/tests/GuiTest.py index 1dbb8fdceb3..06ca67021f9 100644 --- a/python/plugins/processing/tests/GuiTest.py +++ b/python/plugins/processing/tests/GuiTest.py @@ -237,6 +237,9 @@ class WrappersTest(unittest.TestCase): def testMapLayer(self): self.checkConstructWrapper(QgsProcessingParameterMapLayer('test'), MapLayerWidgetWrapper) + def testMeshLayer(self): + self.checkConstructWrapper(QgsProcessingParameterMeshLayer('test'), MeshWidgetWrapper) + def testDistance(self): self.checkConstructWrapper(QgsProcessingParameterDistance('test'), DistanceWidgetWrapper) diff --git a/python/processing/algfactory.py b/python/processing/algfactory.py index db694a35c8c..b5c2c42b063 100644 --- a/python/processing/algfactory.py +++ b/python/processing/algfactory.py @@ -54,6 +54,7 @@ from qgis.core import (QgsProcessingParameterDefinition, QgsProcessingParameterPoint, QgsProcessingParameterRange, QgsProcessingParameterVectorLayer, + QgsProcessingParameterMeshLayer, QgsProcessingOutputString, QgsProcessingOutputFile, QgsProcessingOutputFolder, @@ -299,6 +300,7 @@ class ProcessingAlgFactory(): MULTILAYER = "MULTILAYER", RASTER_LAYER = "RASTER_LAYER", VECTOR_LAYER = "VECTOR_LAYER", + MESH_LAYER = "MESH_LAYER", FILE_DEST = "FILE_DEST", FOLDER_DEST = "FOLDER_DEST", RASTER_LAYER_DEST = "RASTER_LAYER_DEST", @@ -439,6 +441,7 @@ class ProcessingAlgFactory(): alg.RANGE: QgsProcessingParameterRange alg.VECTOR_LAYER: QgsProcessingParameterVectorLayer alg.AUTH_CFG: QgsProcessingParameterAuthConfig + alg.MESH_LAYER: QgsProcessingParameterMeshLayer :param type: The type of the input. This should be a type define on `alg` like alg.STRING, alg.DISTANCE @@ -485,6 +488,7 @@ input_type_mapping = { ProcessingAlgFactory.RANGE: QgsProcessingParameterRange, ProcessingAlgFactory.VECTOR_LAYER: QgsProcessingParameterVectorLayer, ProcessingAlgFactory.AUTH_CFG: QgsProcessingParameterAuthConfig, + ProcessingAlgFactory.MESH_LAYER: QgsProcessingParameterMeshLayer, } output_type_mapping = { diff --git a/src/core/processing/qgsprocessing.h b/src/core/processing/qgsprocessing.h index a45cb4fa5e6..b802329f499 100644 --- a/src/core/processing/qgsprocessing.h +++ b/src/core/processing/qgsprocessing.h @@ -43,7 +43,7 @@ class CORE_EXPORT QgsProcessing //! Data source types enum enum SourceType { - TypeMapLayer = -2, //!< Any map layer type (raster or vector) + TypeMapLayer = -2, //!< Any map layer type (raster or vector or mesh) TypeVectorAnyGeometry = -1, //!< Any vector layer with geometry TypeVectorPoint = 0, //!< Vector point layers TypeVectorLine = 1, //!< Vector line layers @@ -51,10 +51,9 @@ class CORE_EXPORT QgsProcessing TypeRaster = 3, //!< Raster layers TypeFile = 4, //!< Files (i.e. non map layer sources, such as text files) TypeVector = 5, //!< Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink has no geometry. + TypeMesh = 6 //!< Mesh layers \since QGIS 3.6 }; - - }; #endif // QGSPROCESSING_H diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index b80e7894d90..1a42e9bf6a5 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -27,6 +27,7 @@ #include "qgsmessagelog.h" #include "qgsvectorlayer.h" #include "qgsprocessingfeedback.h" +#include "qgsmeshlayer.h" QgsProcessingAlgorithm::~QgsProcessingAlgorithm() { @@ -611,6 +612,11 @@ QgsRasterLayer *QgsProcessingAlgorithm::parameterAsRasterLayer( const QVariantMa return QgsProcessingParameters::parameterAsRasterLayer( parameterDefinition( name ), parameters, context ); } +QgsMeshLayer *QgsProcessingAlgorithm::parameterAsMeshLayer( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) const +{ + return QgsProcessingParameters::parameterAsMeshLayer( parameterDefinition( name ), parameters, context ); +} + QString QgsProcessingAlgorithm::parameterAsOutputLayer( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) const { return QgsProcessingParameters::parameterAsOutputLayer( parameterDefinition( name ), parameters, context ); diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index 0db85384ad1..a82488ac57c 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -34,6 +34,7 @@ class QgsProcessingFeedback; class QgsFeatureSink; class QgsProcessingModelAlgorithm; class QgsProcessingAlgorithmConfigurationWidget; +class QgsMeshLayer; #ifdef SIP_RUN % ModuleHeaderCode @@ -664,6 +665,18 @@ class CORE_EXPORT QgsProcessingAlgorithm */ QgsRasterLayer *parameterAsRasterLayer( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) const; + /** + * Evaluates the parameter with matching \a name to a mesh layer. + * + * Layers will either be taken from \a context's active project, or loaded from external + * sources and stored temporarily in the \a context. In either case, callers do not + * need to handle deletion of the returned layer. + * + * \since QGIS 3.6 + */ + QgsMeshLayer *parameterAsMeshLayer( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) const; + + /** * Evaluates the parameter with matching \a name to a output layer destination. */ diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index b2ab952ee84..35660d04b97 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -29,6 +29,7 @@ #include "qgsprocessingparametertype.h" #include "qgsrasterfilewriter.h" #include "qgsvectorlayer.h" +#include "qgsmeshlayer.h" #include @@ -571,6 +572,16 @@ QgsRasterLayer *QgsProcessingParameters::parameterAsRasterLayer( const QgsProces return qobject_cast< QgsRasterLayer *>( parameterAsLayer( definition, value, context ) ); } +QgsMeshLayer *QgsProcessingParameters::parameterAsMeshLayer( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ) +{ + return qobject_cast< QgsMeshLayer *>( parameterAsLayer( definition, parameters, context ) ); +} + +QgsMeshLayer *QgsProcessingParameters::parameterAsMeshLayer( const QgsProcessingParameterDefinition *definition, const QVariant &value, QgsProcessingContext &context ) +{ + return qobject_cast< QgsMeshLayer *>( parameterAsLayer( definition, value, context ) ); +} + QString QgsProcessingParameters::parameterAsOutputLayer( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ) { QVariant val; @@ -1485,6 +1496,8 @@ QgsProcessingParameterDefinition *QgsProcessingParameters::parameterFromVariantM def.reset( new QgsProcessingParameterFolderDestination( name ) ); else if ( type == QgsProcessingParameterBand::typeName() ) def.reset( new QgsProcessingParameterBand( name ) ); + else if ( type == QgsProcessingParameterMeshLayer::typeName() ) + def.reset( new QgsProcessingParameterMeshLayer( name ) ); else { QgsProcessingParameterType *paramType = QgsApplication::instance()->processingRegistry()->parameterType( type ); @@ -1567,6 +1580,8 @@ QgsProcessingParameterDefinition *QgsProcessingParameters::parameterFromScriptCo return QgsProcessingParameterFolderDestination::fromScriptCode( name, description, isOptional, definition ); else if ( type == QStringLiteral( "band" ) ) return QgsProcessingParameterBand::fromScriptCode( name, description, isOptional, definition ); + else if ( type == QStringLiteral( "mesh" ) ) + return QgsProcessingParameterMeshLayer::fromScriptCode( name, description, isOptional, definition ); return nullptr; } @@ -3234,6 +3249,79 @@ QgsProcessingParameterVectorLayer *QgsProcessingParameterVectorLayer::fromScript return new QgsProcessingParameterVectorLayer( name, description, QList< int>(), definition.isEmpty() ? QVariant() : definition, isOptional ); } +QgsProcessingParameterMeshLayer::QgsProcessingParameterMeshLayer( const QString &name, + const QString &description, + const QVariant &defaultValue, + bool optional ) + : QgsProcessingParameterDefinition( name, description, defaultValue, optional ) +{ + +} + +QgsProcessingParameterDefinition *QgsProcessingParameterMeshLayer::clone() const +{ + return new QgsProcessingParameterMeshLayer( *this ); +} + +bool QgsProcessingParameterMeshLayer::checkValueIsAcceptable( const QVariant &v, QgsProcessingContext *context ) const +{ + if ( !v.isValid() ) + return mFlags & FlagOptional; + + QVariant var = v; + + if ( var.canConvert() ) + { + QgsProperty p = var.value< QgsProperty >(); + if ( p.propertyType() == QgsProperty::StaticProperty ) + { + var = p.staticValue(); + } + else + { + return true; + } + } + + if ( qobject_cast< QgsMeshLayer * >( qvariant_cast( var ) ) ) + return true; + + if ( var.type() != QVariant::String || var.toString().isEmpty() ) + return mFlags & FlagOptional; + + if ( !context ) + { + // that's as far as we can get without a context + return true; + } + + // try to load as layer + if ( QgsProcessingUtils::mapLayerFromString( var.toString(), *context, true, QgsProcessingUtils::Mesh ) ) + return true; + + return false; +} + +QString QgsProcessingParameterMeshLayer::valueAsPythonString( const QVariant &val, QgsProcessingContext &context ) const +{ + if ( !val.isValid() ) + return QStringLiteral( "None" ); + + if ( val.canConvert() ) + return QStringLiteral( "QgsProperty.fromExpression('%1')" ).arg( val.value< QgsProperty >().asExpression() ); + + QVariantMap p; + p.insert( name(), val ); + QgsMeshLayer *layer = QgsProcessingParameters::parameterAsMeshLayer( this, p, context ); + return layer ? QgsProcessingUtils::stringToPythonLiteral( QgsProcessingUtils::normalizeLayerSource( layer->source() ) ) + : QgsProcessingUtils::stringToPythonLiteral( val.toString() ); +} + +QgsProcessingParameterMeshLayer *QgsProcessingParameterMeshLayer::fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) +{ + return new QgsProcessingParameterMeshLayer( name, description, definition.isEmpty() ? QVariant() : definition, isOptional ); +} + QgsProcessingParameterField::QgsProcessingParameterField( const QString &name, const QString &description, const QVariant &defaultValue, const QString &parentLayerParameterName, DataType type, bool allowMultiple, bool optional ) : QgsProcessingParameterDefinition( name, description, defaultValue, optional ) , mParentLayerParameterName( parentLayerParameterName ) @@ -3243,6 +3331,7 @@ QgsProcessingParameterField::QgsProcessingParameterField( const QString &name, c } + QgsProcessingParameterDefinition *QgsProcessingParameterField::clone() const { return new QgsProcessingParameterField( *this ); @@ -3825,6 +3914,7 @@ bool QgsProcessingParameterFeatureSink::hasGeometry() const case QgsProcessing::TypeRaster: case QgsProcessing::TypeFile: case QgsProcessing::TypeVector: + case QgsProcessing::TypeMesh: return false; } return true; @@ -4391,6 +4481,7 @@ bool QgsProcessingParameterVectorDestination::hasGeometry() const case QgsProcessing::TypeRaster: case QgsProcessing::TypeFile: case QgsProcessing::TypeVector: + case QgsProcessing::TypeMesh: return false; } return true; diff --git a/src/core/processing/qgsprocessingparameters.h b/src/core/processing/qgsprocessingparameters.h index e97a30f10f9..c90e56df19b 100644 --- a/src/core/processing/qgsprocessingparameters.h +++ b/src/core/processing/qgsprocessingparameters.h @@ -30,6 +30,7 @@ class QgsProcessingContext; class QgsRasterLayer; +class QgsMeshLayer; class QgsVectorLayer; class QgsFeatureSink; class QgsProcessingFeatureSource; @@ -228,6 +229,8 @@ class CORE_EXPORT QgsProcessingParameterDefinition sipType = sipType_QgsProcessingParameterRange; else if ( sipCpp->type() == QgsProcessingParameterRasterLayer::typeName() ) sipType = sipType_QgsProcessingParameterRasterLayer; + else if ( sipCpp->type() == QgsProcessingParameterMeshLayer::typeName() ) + sipType = sipType_QgsProcessingParameterMeshLayer; else if ( sipCpp->type() == QgsProcessingParameterEnum::typeName() ) sipType = sipType_QgsProcessingParameterEnum; else if ( sipCpp->type() == QgsProcessingParameterString::typeName() ) @@ -805,6 +808,29 @@ class CORE_EXPORT QgsProcessingParameters */ static QgsVectorLayer *parameterAsVectorLayer( const QgsProcessingParameterDefinition *definition, const QVariant &value, QgsProcessingContext &context ); + /** + * Evaluates the parameter with matching \a definition and \a value to a mesh layer. + * + * Layers will either be taken from \a context's active project, or loaded from external + * sources and stored temporarily in the \a context. In either case, callers do not + * need to handle deletion of the returned layer. + * + * \since QGIS 3.6 + */ + static QgsMeshLayer *parameterAsMeshLayer( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ); + + /** + * Evaluates the parameter with matching \a definition and \a value to a mesh layer. + * + * Layers will either be taken from \a context's active project, or loaded from external + * sources and stored temporarily in the \a context. In either case, callers do not + * need to handle deletion of the returned layer. + * + * \since QGIS 3.6 + */ + static QgsMeshLayer *parameterAsMeshLayer( const QgsProcessingParameterDefinition *definition, const QVariant &value, QgsProcessingContext &context ); + + /** * Evaluates the parameter with matching \a definition to a coordinate reference system. */ @@ -1889,6 +1915,39 @@ class CORE_EXPORT QgsProcessingParameterVectorLayer : public QgsProcessingParame }; +/** + * \class QgsProcessingParameterMeshLayer + * \ingroup core + * A mesh layer parameter for processing algorithms. + * \since QGIS 3.6 + */ +class CORE_EXPORT QgsProcessingParameterMeshLayer : public QgsProcessingParameterDefinition +{ + public: + + /** + * Constructor for QgsProcessingParameterMeshLayer. + */ + QgsProcessingParameterMeshLayer( const QString &name, + const QString &description = QString(), + const QVariant &defaultValue = QVariant(), + bool optional = false ); + + /** + * Returns the type name for the parameter class. + */ + static QString typeName() { return QStringLiteral( "mesh" ); } + QgsProcessingParameterDefinition *clone() const override SIP_FACTORY; + QString type() const override { return typeName(); } + bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override; + QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const override; + + /** + * Creates a new parameter using the definition from a script code. + */ + static QgsProcessingParameterMeshLayer *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) SIP_FACTORY; +}; + /** * \class QgsProcessingParameterField * \ingroup core diff --git a/src/core/processing/qgsprocessingparametertypeimpl.h b/src/core/processing/qgsprocessingparametertypeimpl.h index 39d58f0941e..287f25f6b5c 100644 --- a/src/core/processing/qgsprocessingparametertypeimpl.h +++ b/src/core/processing/qgsprocessingparametertypeimpl.h @@ -64,6 +64,44 @@ class CORE_EXPORT QgsProcessingParameterTypeRasterLayer : public QgsProcessingPa } }; +/** + * A mesh layer parameter for processing algorithms. + * + * \ingroup core + * \note No Python bindings available. Get your copy from QgsApplication.processingRegistry().parameterType('mesh') + * \since QGIS 3.2 + */ +class CORE_EXPORT QgsProcessingParameterTypeMeshLayer : public QgsProcessingParameterType +{ + QgsProcessingParameterDefinition *create( const QString &name ) const override SIP_FACTORY + { + return new QgsProcessingParameterMeshLayer( name ); + } + + QString description() const override + { + return QCoreApplication::translate( "Processing", "A mesh layer parameter." ); + } + + QString name() const override + { + return QCoreApplication::translate( "Processing", "Mesh Layer" ); + } + + QString id() const override + { + return QStringLiteral( "mesh" ); + } + + QStringList acceptedPythonTypes() const override + { + return QStringList() << QObject::tr( "str: layer ID" ) + << QObject::tr( "str: layer name" ) + << QObject::tr( "str: layer source" ) + << QStringLiteral( "QgsMeshLayer" ); + } +}; + /** * A vector layer parameter for processing algorithms. * diff --git a/src/core/processing/qgsprocessingregistry.cpp b/src/core/processing/qgsprocessingregistry.cpp index 2ec96b1adbc..73c63d9f73b 100644 --- a/src/core/processing/qgsprocessingregistry.cpp +++ b/src/core/processing/qgsprocessingregistry.cpp @@ -24,6 +24,7 @@ QgsProcessingRegistry::QgsProcessingRegistry( QObject *parent SIP_TRANSFERTHIS ) { addParameterType( new QgsProcessingParameterTypeRasterLayer() ); addParameterType( new QgsProcessingParameterTypeVectorLayer() ); + addParameterType( new QgsProcessingParameterTypeMeshLayer() ); addParameterType( new QgsProcessingParameterTypeMapLayer() ); addParameterType( new QgsProcessingParameterTypeBoolean() ); addParameterType( new QgsProcessingParameterTypeExpression() ); diff --git a/src/core/processing/qgsprocessingutils.cpp b/src/core/processing/qgsprocessingutils.cpp index dbcd444679d..1f06d30ffe3 100644 --- a/src/core/processing/qgsprocessingutils.cpp +++ b/src/core/processing/qgsprocessingutils.cpp @@ -30,6 +30,7 @@ #include "qgsfileutils.h" #include "qgsvectorlayer.h" #include "qgsproviderregistry.h" +#include "qgsmeshlayer.h" QList QgsProcessingUtils::compatibleRasterLayers( QgsProject *project, bool sort ) { @@ -37,7 +38,9 @@ QList QgsProcessingUtils::compatibleRasterLayers( QgsProject * return QList(); QList layers; - Q_FOREACH ( QgsRasterLayer *l, project->layers() ) + + const auto rasterLayers = project->layers(); + for ( QgsRasterLayer *l : rasterLayers ) { if ( canUseLayer( l ) ) layers << l; @@ -59,7 +62,8 @@ QList QgsProcessingUtils::compatibleVectorLayers( QgsProject * return QList(); QList layers; - Q_FOREACH ( QgsVectorLayer *l, project->layers() ) + const auto vectorLayers = project->layers(); + for ( QgsVectorLayer *l : vectorLayers ) { if ( canUseLayer( l, geometryTypes ) ) layers << l; @@ -75,15 +79,46 @@ QList QgsProcessingUtils::compatibleVectorLayers( QgsProject * return layers; } +QList QgsProcessingUtils::compatibleMeshLayers( QgsProject *project, bool sort ) +{ + if ( !project ) + return QList(); + + QList layers; + const auto meshLayers = project->layers(); + for ( QgsMeshLayer *l : meshLayers ) + { + if ( canUseLayer( l ) ) + layers << l; + } + + if ( sort ) + { + std::sort( layers.begin(), layers.end(), []( const QgsMeshLayer * a, const QgsMeshLayer * b ) -> bool + { + return QString::localeAwareCompare( a->name(), b->name() ) < 0; + } ); + } + return layers; +} + QList QgsProcessingUtils::compatibleLayers( QgsProject *project, bool sort ) { if ( !project ) return QList(); QList layers; - Q_FOREACH ( QgsRasterLayer *rl, compatibleRasterLayers( project, false ) ) + + const auto rasterLayers = compatibleRasterLayers( project, false ); + for ( QgsRasterLayer *rl : rasterLayers ) layers << rl; - Q_FOREACH ( QgsVectorLayer *vl, compatibleVectorLayers( project, QList< int >(), false ) ) + + const auto vectorLayers = compatibleVectorLayers( project, QList< int >(), false ); + for ( QgsVectorLayer *vl : vectorLayers ) + layers << vl; + + const auto meshLayers = compatibleMeshLayers( project, false ); + for ( QgsMeshLayer *vl : meshLayers ) layers << vl; if ( sort ) @@ -114,7 +149,7 @@ QgsMapLayer *QgsProcessingUtils::mapLayerFromStore( const QString &string, QgsMa case QgsMapLayer::PluginLayer: return true; case QgsMapLayer::MeshLayer: - return false; + return !canUseLayer( qobject_cast< QgsMeshLayer * >( layer ) ); } return true; } ), layers.end() ); @@ -131,6 +166,9 @@ QgsMapLayer *QgsProcessingUtils::mapLayerFromStore( const QString &string, QgsMa case Raster: return l->type() == QgsMapLayer::RasterLayer; + + case Mesh: + return l->type() == QgsMapLayer::MeshLayer; } return true; }; @@ -216,6 +254,15 @@ QgsMapLayer *QgsProcessingUtils::loadMapLayerFromString( const QString &string, return rasterLayer.release(); } } + if ( typeHint == UnknownType || typeHint == Mesh ) + { + QgsMeshLayer::LayerOptions meshOptions; + std::unique_ptr< QgsMeshLayer > meshLayer( new QgsMeshLayer( string, name, QStringLiteral( "mdal" ), meshOptions ) ); + if ( meshLayer->isValid() ) + { + return meshLayer.release(); + } + } return nullptr; } @@ -312,6 +359,11 @@ QgsProcessingFeatureSource *QgsProcessingUtils::variantToSource( const QVariant } } +bool QgsProcessingUtils::canUseLayer( const QgsMeshLayer *layer ) +{ + return layer && layer->dataProvider(); +} + bool QgsProcessingUtils::canUseLayer( const QgsRasterLayer *layer ) { // only gdal file-based layers @@ -494,7 +546,7 @@ void QgsProcessingUtils::createFeatureSinkPython( QgsFeatureSink **sink, QString QgsRectangle QgsProcessingUtils::combineLayerExtents( const QList &layers, const QgsCoordinateReferenceSystem &crs ) { QgsRectangle extent; - Q_FOREACH ( QgsMapLayer *layer, layers ) + for ( const QgsMapLayer *layer : layers ) { if ( !layer ) continue; @@ -600,7 +652,9 @@ QString QgsProcessingUtils::formatHelpMapAsHtml( const QVariantMap &map, const Q s += QStringLiteral( "

" ) + getText( QStringLiteral( "ALG_DESC" ) ) + QStringLiteral( "

\n" ); QString inputs; - Q_FOREACH ( const QgsProcessingParameterDefinition *def, algorithm->parameterDefinitions() ) + + const auto parameterDefinitions = algorithm->parameterDefinitions(); + for ( const QgsProcessingParameterDefinition *def : parameterDefinitions ) { inputs += QStringLiteral( "

" ) + def->description() + QStringLiteral( "

\n" ); inputs += QStringLiteral( "

" ) + getText( def->name() ) + QStringLiteral( "

\n" ); @@ -609,7 +663,8 @@ QString QgsProcessingUtils::formatHelpMapAsHtml( const QVariantMap &map, const Q s += QObject::tr( "

Input parameters

\n" ) + inputs; QString outputs; - Q_FOREACH ( const QgsProcessingOutputDefinition *def, algorithm->outputDefinitions() ) + const auto outputDefinitions = algorithm->outputDefinitions(); + for ( const QgsProcessingOutputDefinition *def : outputDefinitions ) { outputs += QStringLiteral( "

" ) + def->description() + QStringLiteral( "

\n" ); outputs += QStringLiteral( "

" ) + getText( def->name() ) + QStringLiteral( "

\n" ); @@ -755,7 +810,6 @@ QgsFields QgsProcessingUtils::indicesToFields( const QList &indices, const return fieldsSubset; } - // // QgsProcessingFeatureSource // diff --git a/src/core/processing/qgsprocessingutils.h b/src/core/processing/qgsprocessingutils.h index 9dc5f90ad5d..3a3a612b06c 100644 --- a/src/core/processing/qgsprocessingutils.h +++ b/src/core/processing/qgsprocessingutils.h @@ -27,6 +27,7 @@ #include "qgsfeaturesink.h" #include "qgsfeaturesource.h" +class QgsMeshLayer; class QgsProject; class QgsProcessingContext; class QgsMapLayerStore; @@ -53,6 +54,7 @@ class CORE_EXPORT QgsProcessingUtils * If the \a sort argument is true then the layers will be sorted by their QgsMapLayer::name() * value. * \see compatibleVectorLayers() + * \see compatibleMeshLayers() * \see compatibleLayers() */ static QList< QgsRasterLayer * > compatibleRasterLayers( QgsProject *project, bool sort = true ); @@ -69,12 +71,28 @@ class CORE_EXPORT QgsProcessingUtils * If the \a sort argument is true then the layers will be sorted by their QgsMapLayer::name() * value. * \see compatibleRasterLayers() + * \see compatibleMeshLayers() * \see compatibleLayers() */ static QList< QgsVectorLayer * > compatibleVectorLayers( QgsProject *project, const QList< int > &sourceTypes = QList< int >(), bool sort = true ); + /** + * Returns a list of mesh layers from a \a project which are compatible with the processing + * framework. + * + * If the \a sort argument is true then the layers will be sorted by their QgsMapLayer::name() + * value. + * + * \see compatibleRasterLayers() + * \see compatibleVectorLayers() + * \see compatibleLayers() + * + * \since QGIS 3.6 + */ + static QList compatibleMeshLayers( QgsProject *project, bool sort = true ); + /** * Returns a list of map layers from a \a project which are compatible with the processing * framework. @@ -95,6 +113,7 @@ class CORE_EXPORT QgsProcessingUtils UnknownType, //!< Unknown layer type Vector, //!< Vector layer type Raster, //!< Raster layer type + Mesh, //!< Mesh layer type \since QGIS 3.6 }; /** @@ -263,8 +282,8 @@ class CORE_EXPORT QgsProcessingUtils static QgsFields indicesToFields( const QList &indices, const QgsFields &fields ); private: - static bool canUseLayer( const QgsRasterLayer *layer ); + static bool canUseLayer( const QgsMeshLayer *layer ); static bool canUseLayer( const QgsVectorLayer *layer, const QList< int > &sourceTypes = QList< int >() ); diff --git a/src/core/qgsmaplayerproxymodel.cpp b/src/core/qgsmaplayerproxymodel.cpp index 96cd9717b8d..8a27ca2c0a5 100644 --- a/src/core/qgsmaplayerproxymodel.cpp +++ b/src/core/qgsmaplayerproxymodel.cpp @@ -19,8 +19,10 @@ #include "qgsproject.h" #include "qgsvectorlayer.h" #include "qgsrasterlayer.h" +#include "qgsmeshlayer.h" #include "qgsvectordataprovider.h" #include "qgsrasterdataprovider.h" +#include "qgsmeshdataprovider.h" QgsMapLayerProxyModel::QgsMapLayerProxyModel( QObject *parent ) : QSortFilterProxyModel( parent ) @@ -127,6 +129,7 @@ bool QgsMapLayerProxyModel::filterAcceptsRow( int source_row, const QModelIndex // layer type if ( ( mFilters.testFlag( RasterLayer ) && layer->type() == QgsMapLayer::RasterLayer ) || ( mFilters.testFlag( VectorLayer ) && layer->type() == QgsMapLayer::VectorLayer ) || + ( mFilters.testFlag( MeshLayer ) && layer->type() == QgsMapLayer::MeshLayer ) || ( mFilters.testFlag( PluginLayer ) && layer->type() == QgsMapLayer::PluginLayer ) ) return true; diff --git a/src/core/qgsmaplayerproxymodel.h b/src/core/qgsmaplayerproxymodel.h index 208f63f400f..b57163e9575 100644 --- a/src/core/qgsmaplayerproxymodel.h +++ b/src/core/qgsmaplayerproxymodel.h @@ -50,7 +50,8 @@ class CORE_EXPORT QgsMapLayerProxyModel : public QSortFilterProxyModel VectorLayer = NoGeometry | HasGeometry, PluginLayer = 32, WritableLayer = 64, - All = RasterLayer | VectorLayer | PluginLayer + MeshLayer = 128, //!< QgsMeshLayer \since QGIS 3.6 + All = RasterLayer | VectorLayer | PluginLayer | MeshLayer }; Q_DECLARE_FLAGS( Filters, Filter ) Q_FLAG( Filters ) diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp index 7d805694917..b8d20dd86aa 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp @@ -138,6 +138,7 @@ QStringList QgsProcessingBooleanWidgetWrapper::compatibleParameterTypes() const << QgsProcessingParameterMapLayer::typeName() << QgsProcessingParameterRasterLayer::typeName() << QgsProcessingParameterVectorLayer::typeName() + << QgsProcessingParameterMeshLayer::typeName() << QgsProcessingParameterExpression::typeName(); } @@ -265,6 +266,7 @@ QStringList QgsProcessingCrsWidgetWrapper::compatibleParameterTypes() const << QgsProcessingParameterString::typeName() << QgsProcessingParameterRasterLayer::typeName() << QgsProcessingParameterVectorLayer::typeName() + << QgsProcessingParameterMeshLayer::typeName() << QgsProcessingParameterFeatureSource::typeName(); } diff --git a/tests/src/analysis/CMakeLists.txt b/tests/src/analysis/CMakeLists.txt index 7b7c604aefb..78fd9822298 100644 --- a/tests/src/analysis/CMakeLists.txt +++ b/tests/src/analysis/CMakeLists.txt @@ -11,6 +11,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src/core/auth ${CMAKE_SOURCE_DIR}/src/core/expression ${CMAKE_SOURCE_DIR}/src/core/geometry + ${CMAKE_SOURCE_DIR}/src/core/mesh ${CMAKE_SOURCE_DIR}/src/core/metadata ${CMAKE_SOURCE_DIR}/src/core/processing ${CMAKE_SOURCE_DIR}/src/core/processing/models diff --git a/tests/src/analysis/testqgsprocessing.cpp b/tests/src/analysis/testqgsprocessing.cpp index 3bda9f7a1c1..dd424ca4ee6 100644 --- a/tests/src/analysis/testqgsprocessing.cpp +++ b/tests/src/analysis/testqgsprocessing.cpp @@ -29,6 +29,7 @@ #include "qgis.h" #include "qgstest.h" #include "qgsrasterlayer.h" +#include "qgsmeshlayer.h" #include "qgsproject.h" #include "qgspoint.h" #include "qgsgeometry.h" @@ -40,6 +41,7 @@ #include "qgsmessagelog.h" #include "qgsvectorlayer.h" + class DummyAlgorithm : public QgsProcessingAlgorithm { public: @@ -547,6 +549,7 @@ class TestQgsProcessing: public QObject void parameterExpression(); void parameterField(); void parameterVectorLayer(); + void parameterMeshLayer(); void parameterFeatureSource(); void parameterFeatureSink(); void parameterVectorOut(); @@ -736,7 +739,12 @@ void TestQgsProcessing::compatibleLayers() QgsVectorLayer *v3 = new QgsVectorLayer( "LineString", "v3", "memory" ); QgsVectorLayer *v4 = new QgsVectorLayer( "none", "vvvv4", "memory" ); - p.addMapLayers( QList() << r1 << r2 << r3 << v1 << v2 << v3 << v4 ); + QFileInfo fm( testDataDir + "/mesh/quad_and_triangle.2dm" ); + QgsMeshLayer *m1 = new QgsMeshLayer( fm.filePath(), "MX", "mdal" ); + QVERIFY( m1->isValid() ); + QgsMeshLayer *m2 = new QgsMeshLayer( fm.filePath(), "mA", "mdal" ); + QVERIFY( m2->isValid() ); + p.addMapLayers( QList() << r1 << r2 << r3 << v1 << v2 << v3 << v4 << m1 << m2 ); // compatibleRasterLayers QVERIFY( QgsProcessingUtils::compatibleRasterLayers( nullptr ).isEmpty() ); @@ -753,6 +761,20 @@ void TestQgsProcessing::compatibleLayers() lIds << rl->name(); QCOMPARE( lIds, QStringList() << "R1" << "ar2" << "zz" ); + // compatibleMeshLayers + QVERIFY( QgsProcessingUtils::compatibleMeshLayers( nullptr ).isEmpty() ); + + // sorted + lIds.clear(); + Q_FOREACH ( QgsMeshLayer *rl, QgsProcessingUtils::compatibleMeshLayers( &p ) ) + lIds << rl->name(); + QCOMPARE( lIds, QStringList() << "mA" << "MX" ); + + // unsorted + lIds.clear(); + Q_FOREACH ( QgsMeshLayer *rl, QgsProcessingUtils::compatibleMeshLayers( &p, false ) ) + lIds << rl->name(); + QCOMPARE( lIds, QStringList() << "MX" << "mA" ); // compatibleVectorLayers QVERIFY( QgsProcessingUtils::compatibleVectorLayers( nullptr ).isEmpty() ); @@ -812,13 +834,13 @@ void TestQgsProcessing::compatibleLayers() lIds.clear(); Q_FOREACH ( QgsMapLayer *l, QgsProcessingUtils::compatibleLayers( &p ) ) lIds << l->name(); - QCOMPARE( lIds, QStringList() << "ar2" << "R1" << "v1" << "v3" << "V4" << "vvvv4" << "zz" ); + QCOMPARE( lIds, QStringList() << "ar2" << "mA" << "MX" << "R1" << "v1" << "v3" << "V4" << "vvvv4" << "zz" ); // unsorted lIds.clear(); Q_FOREACH ( QgsMapLayer *l, QgsProcessingUtils::compatibleLayers( &p, false ) ) lIds << l->name(); - QCOMPARE( lIds, QStringList() << "R1" << "ar2" << "zz" << "V4" << "v1" << "v3" << "vvvv4" ); + QCOMPARE( lIds, QStringList() << "R1" << "ar2" << "zz" << "V4" << "v1" << "v3" << "vvvv4" << "MX" << "mA" ); } void TestQgsProcessing::normalizeLayerSource() @@ -4518,6 +4540,124 @@ void TestQgsProcessing::parameterVectorLayer() QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); } +void TestQgsProcessing::parameterMeshLayer() +{ + // setup a context + QgsProject p; + p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); + QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt + QString vector1 = testDataDir + "multipoint.shp"; + QString raster = testDataDir + "landsat.tif"; + QString mesh = testDataDir + "mesh/quad_and_triangle.2dm"; + QFileInfo fi1( raster ); + QFileInfo fi2( vector1 ); + QFileInfo fi3( mesh ); + QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); + QgsVectorLayer *v1 = new QgsVectorLayer( fi2.filePath(), "V4", "ogr" ); + QgsMeshLayer *m1 = new QgsMeshLayer( fi3.filePath(), "M1", "mdal" ); + Q_ASSERT( m1 ); + p.addMapLayers( QList() << v1 << r1 << m1 ); + QgsProcessingContext context; + context.setProject( &p ); + + // not optional! + std::unique_ptr< QgsProcessingParameterMeshLayer > def( new QgsProcessingParameterMeshLayer( "non_optional", QString(), QString( "somelayer" ), false ) ); + QVERIFY( !def->checkValueIsAcceptable( false ) ); + QVERIFY( !def->checkValueIsAcceptable( true ) ); + QVERIFY( !def->checkValueIsAcceptable( 5 ) ); + QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); + QVERIFY( !def->checkValueIsAcceptable( "" ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); + QVERIFY( !def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) ); + QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( m1 ) ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) ); + QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( QStringLiteral( "layer12312312" ) ) ) ); + QVERIFY( !def->checkValueIsAcceptable( QgsProperty::fromValue( QString() ) ) ); + + // should be OK + QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.2dm" ) ); + // ... unless we use context, when the check that the layer actually exists is performed + QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.2dm", &context ) ); + + // using existing map layer ID + QVariantMap params; + params.insert( "non_optional", m1->id() ); + QCOMPARE( QgsProcessingParameters::parameterAsMeshLayer( def.get(), params, context )->id(), m1->id() ); + + // using existing layer + params.insert( "non_optional", QVariant::fromValue( m1 ) ); + QCOMPARE( QgsProcessingParameters::parameterAsMeshLayer( def.get(), params, context )->id(), m1->id() ); + + // not mesh layer + params.insert( "non_optional", r1->id() ); + QVERIFY( !QgsProcessingParameters::parameterAsMeshLayer( def.get(), params, context ) ); + + // using existing non-mesh layer + params.insert( "non_optional", QVariant::fromValue( r1 ) ); + QVERIFY( !QgsProcessingParameters::parameterAsMeshLayer( def.get(), params, context ) ); + + // string representing a layer source + params.insert( "non_optional", mesh ); + QCOMPARE( QgsProcessingParameters::parameterAsMeshLayer( def.get(), params, context )->publicSource(), mesh ); + + // nonsense string + params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) ); + QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) ); + + QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) ); + QCOMPARE( def->valueAsPythonString( mesh, context ), QString( "'" ) + testDataDir + QStringLiteral( "mesh/quad_and_triangle.2dm'" ) ); + QCOMPARE( def->valueAsPythonString( m1->id(), context ), QString( "'" ) + testDataDir + QStringLiteral( "mesh/quad_and_triangle.2dm'" ) ); + QCOMPARE( def->valueAsPythonString( QVariant::fromValue( m1 ), context ), QString( "'" ) + testDataDir + QStringLiteral( "mesh/quad_and_triangle.2dm'" ) ); + QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.2dm" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.2dm'" ) ); + + QString code = def->asScriptCode(); + QCOMPARE( code, QStringLiteral( "##non_optional=mesh somelayer" ) ); + std::unique_ptr< QgsProcessingParameterMeshLayer > fromCode( dynamic_cast< QgsProcessingParameterMeshLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); + QVERIFY( fromCode.get() ); + QCOMPARE( fromCode->name(), def->name() ); + QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); + QCOMPARE( fromCode->flags(), def->flags() ); + QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); + + QVariantMap map = def->toVariantMap(); + QgsProcessingParameterMeshLayer fromMap( "x" ); + QVERIFY( fromMap.fromVariantMap( map ) ); + QCOMPARE( fromMap.name(), def->name() ); + QCOMPARE( fromMap.description(), def->description() ); + QCOMPARE( fromMap.flags(), def->flags() ); + QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); + def.reset( dynamic_cast< QgsProcessingParameterMeshLayer *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); + QVERIFY( dynamic_cast< QgsProcessingParameterMeshLayer *>( def.get() ) ); + + // optional + def.reset( new QgsProcessingParameterMeshLayer( "optional", QString(), m1->id(), true ) ); + params.insert( "optional", QVariant() ); + QCOMPARE( QgsProcessingParameters::parameterAsMeshLayer( def.get(), params, context )->id(), m1->id() ); + QVERIFY( def->checkValueIsAcceptable( false ) ); + QVERIFY( def->checkValueIsAcceptable( true ) ); + QVERIFY( def->checkValueIsAcceptable( 5 ) ); + QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); + QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.2dm" ) ); + QVERIFY( def->checkValueIsAcceptable( "" ) ); + QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); + QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) ); + + code = def->asScriptCode(); + QCOMPARE( code, QStringLiteral( "##optional=optional mesh " ) + m1->id() ); + fromCode.reset( dynamic_cast< QgsProcessingParameterMeshLayer * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); + QVERIFY( fromCode.get() ); + QCOMPARE( fromCode->name(), def->name() ); + QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); + QCOMPARE( fromCode->flags(), def->flags() ); + QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); + + //optional with direct layer default + def.reset( new QgsProcessingParameterMeshLayer( "optional", QString(), QVariant::fromValue( m1 ), true ) ); + QCOMPARE( QgsProcessingParameters::parameterAsMeshLayer( def.get(), params, context )->id(), m1->id() ); +} + void TestQgsProcessing::parameterFeatureSource() { // setup a context diff --git a/tests/src/python/test_qgsmaplayerproxymodel.py b/tests/src/python/test_qgsmaplayerproxymodel.py index b29763397f5..7dc89abc956 100644 --- a/tests/src/python/test_qgsmaplayerproxymodel.py +++ b/tests/src/python/test_qgsmaplayerproxymodel.py @@ -14,7 +14,7 @@ __revision__ = '$Format:%H$' import qgis # NOQA -from qgis.core import QgsVectorLayer, QgsProject, QgsMapLayerModel, QgsMapLayerProxyModel +from qgis.core import QgsVectorLayer, QgsMeshLayer, QgsProject, QgsMapLayerModel, QgsMapLayerProxyModel from qgis.PyQt.QtCore import Qt, QModelIndex from qgis.testing import start_app, unittest @@ -28,6 +28,11 @@ def create_layer(name): return layer +def create_mesh_layer(name): + layer = QgsMeshLayer("1.0, 2.0\n2.0, 2.0\n3.0, 2.0\n---\n0, 1, 3", name, "mesh_memory") + return layer + + class TestQgsMapLayerProxyModel(unittest.TestCase): def testGettersSetters(self): @@ -56,6 +61,19 @@ class TestQgsMapLayerProxyModel(unittest.TestCase): m.setFilterString('c') self.assertEqual(m.filterString(), 'c') + def testMeshLayer(self): + m = QgsMapLayerProxyModel() + l1 = create_mesh_layer("l1") + QgsProject.instance().addMapLayer(l1) + l2 = create_layer('l2') + QgsProject.instance().addMapLayer(l2) + + m.setFilters(QgsMapLayerProxyModel.MeshLayer) + self.assertEqual(m.filters(), QgsMapLayerProxyModel.MeshLayer) + + self.assertEqual(m.rowCount(), 1) + self.assertEqual(m.data(m.index(0, 0)), 'l1') + def testFilterGeometryType(self): """ test filtering by geometry type """ QgsProject.instance().clear()