Select mesh elements by expression UI (#44835)

[mesh] [feature] Select mesh elements by expression
This commit is contained in:
Vincent Cloarec 2021-09-09 02:29:45 -04:00 committed by GitHub
parent b90b86b221
commit 45e07dd72f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 841 additions and 87 deletions

View File

@ -924,6 +924,7 @@
<file>themes/default/mActionMeasureBearing.svg</file>
<file>themes/default/mActionMeshDigitizing.svg</file>
<file>themes/default/mActionMeshSelectPolygon.svg</file>
<file>themes/default/mActionMeshSelectExpression.svg</file>
<file>themes/default/mActionNewMeshLayer.svg</file>
<file>themes/default/mActionMeshTransformByExpression.svg</file>
<file>themes/default/mIconGeometryCollectionLayer.svg</file>

View File

@ -0,0 +1 @@
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round"><g fill="none" stroke="#6d7281" stroke-width="1.003672" transform="matrix(1.013697 0 0 .98287877 -.369842 -.425299)"><path d="m13 2-11 11m0-11 11 11m-11.5-11.5h12v12h-12z"/><path d="m13.5 1.5h9v12h-9zm-12 12h12v9h-12zm20.5-11.5-8 11m-6.5 1v8"/><path d="m13 18h-11"/></g><g fill="#fce94f" stroke="#c4a000" stroke-linejoin="round" stroke-width="1.077"><path d="m1.1507031 1.0490189 5.9317031 5.9446938-5.9317031 5.8498513z"/><path d="m1.1507031 1.0490189h12.1643639l-6.2326608 5.9446938z"/><path d="m1.1507031 12.843564 5.9317031-5.8498513 6.2326608 5.8498513z"/><path d="m13.315067 12.843564-6.2326608-5.8498513 6.2326608-5.9446938z"/><path d="m13.315067 12.843564v-11.7945451h9.123273z"/><path d="m13.315067 12.843564 9.123273-11.7945451v11.7945451z"/><path d="m7.2 12.831149 6.115067.01242v4.344658l-6.0958115.03813z"/></g></g><g transform="translate(5.491525 -3.576271)"><path d="m-12.901812 19.194933c-1.92383-.683586-2.885744-1.718741-2.885742-3.105469-.000002-1.093739.50781-1.967761 1.523438-2.62207 1.025386-.654284 2.294916-.981432 3.808593-.981445 1.3769451.000013 2.470694.229505 3.2812505.688476.8105357.449232 1.2158087.991223 1.2158203 1.625977-.0000116.332042-.1269646.625011-.3808594.878906-.2539172.244151-.5468856.366221-.8789062.366211-.5468848.00001-.9961031-.380849-1.3476563-1.142578-.4882897-1.054675-1.2207109-1.582019-2.1972659-1.582031-.77149.000012-1.406255.253918-1.904297.761718-.498051.507824-.747074 1.215832-.74707 2.124024-.000004 1.787117.922847 2.680671 2.768555 2.680664.195305.000007.419914-.01952.6738279-.05859.4394451-.05859.7812417-.08788 1.0253906-.08789.595694.000008.8935452.170906.8935547.512696-.0000095.380866-.3027435.571295-.9082031.571289-.2148521.000006-.5371174-.03417-.9667969-.102539-.3222732-.05859-.5712962-.08788-.7470702-.08789-1.95313.000007-2.929692.996099-2.929688 2.988281-.000004.9668.258785 1.748049.776368 2.34375.517572.585939 1.240228.878907 2.167968.878907 1.1621017 0 1.9335853-.600585 2.3144535-1.801758.1953031-.634763.4101466-1.074216.6445312-1.31836.2441306-.244136.5663959-.366207.9667969-.366211.3320201.000004.6298714.122075.8935547.366211.2734255.234379.4101441.537113.4101562.908204-.0000121.888673-.4980585 1.625977-1.4941406 2.211914-.9961034.576171-2.2021568.864257-3.6181639.864257-1.55274 0-2.934575-.371093-4.145508-1.113281-1.201173-.742186-1.801759-1.733396-1.801758-2.973633-.000001-1.552729 1.196287-2.695306 3.588867-3.427734" fill="#5c3566" transform="translate(22.960894 .402232)"/><g fill="#fff"><path d="m8.8453703 15.335963c-1.8135886 1.95573-1.401671-.322256-.3145916-1.106748 1.1264312-.81289 1.7573183-.79349 1.7490763-.54418-.0096.290414-.8636237 1.035325-1.4344847 1.650928z" opacity=".5"/><path d="m8.2348719 21.645433c-1.8135886 1.95573-1.5022297.147018-.4151503-.637474 1.1264312-.81289 1.7573183-.79349 1.7490763-.54418-.0096.290414-.763065.566051-1.333926 1.181654z" opacity=".5"/><path d="m16.204463 23.831019c-1.74795 1.952651-1.613829.09846-.49057-.620714.858053-.549374 1.112902-.0745.49057.620714z" opacity=".5"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -367,9 +367,9 @@ Creates a new scope which contains variables and functions relating to provider
Registers all known core functions provided by :py:class:`QgsExpressionContextScope` objects.
%End
static QgsExpressionContextScope *meshExpressionScope() /Factory/;
static QgsExpressionContextScope *meshExpressionScope( QgsMesh::ElementType elementType ) /Factory/;
%Docstring
Creates a new scope which contains functions relating to mesh layer elements (face, vertex, ...)
Creates a new scope which contains functions relating to mesh layer element ``elementType``
.. versionadded:: 3.22
%End

View File

@ -660,13 +660,23 @@ The returned position is in map coordinates.
.. versionadded:: 3.14
%End
QList<int> selectVerticesByExpression( const QString &expression, const QgsExpressionContext &expressionContext = QgsExpressionContext() );
QList<int> selectVerticesByExpression( QgsExpression expression );
%Docstring
Returns a list of vertex indexes that meet the condition defined by ``expression`` with the context ``expressionContext``
To express the relation with a vertex, the expression can be defined with function returning value
linked to the current vertex, like " $vertex_z ", "$vertex_as_point"
.. versionadded:: 3.22
%End
QList<int> selectFacesByExpression( QgsExpression expression );
%Docstring
Returns a list of faces indexes that meet the condition defined by ``expression`` with the context ``expressionContext``
To express the relation with a face, the expression can be defined with function returning value
linked to the current face, like " $face_area "
.. versionadded:: 3.22
%End

View File

@ -288,6 +288,13 @@ Will be set to ``True`` if the current expression text reports a parser error
with the context.
.. versionadded:: 3.0
%End
void setExpressionPreviewVisible( bool isVisible );
%Docstring
Sets whether the expression preview is visible.
.. versionadded:: 3.22
%End
public slots:

View File

@ -0,0 +1,7 @@
{
"name": "$face_area",
"type": "function",
"groups": ["Meshes"],
"description": "Returns the area of the current mesh face. The area calculated by this function respects both the current project's ellipsoid setting and area unit settings. For example, if an ellipsoid has been set for the project then the calculated area will be ellipsoidal, and if no ellipsoid is set then the calculated area will be planimetric.",
"examples": [ { "expression":"$face_area", "returns":"42"}]
}

View File

@ -0,0 +1,7 @@
{
"name": "$face_index",
"type": "function",
"groups": ["Meshes"],
"description": "Returns the index of the current mesh face. ",
"examples": [ { "expression":"$face_index", "returns":"4581"}]
}

View File

@ -0,0 +1,8 @@
{
"name": "$vertex_index",
"type": "function",
"groups": ["Meshes"],
"description": "Returns the index of the current mesh vertex.",
"examples": [ { "expression":"$vertex_index", "returns":"9874"}
]
}

View File

@ -0,0 +1,8 @@
{
"name": "$vertex_x",
"type": "function",
"groups": ["Meshes"],
"description": "Returns the X coordinate of the current mesh vertex.",
"examples": [ { "expression":"$vertex_x", "returns":"42.12"}
]
}

View File

@ -0,0 +1,8 @@
{
"name": "$vertex_y",
"type": "function",
"groups": ["Meshes"],
"description": "Returns the Y coordinate of the current mesh vertex.",
"examples": [ { "expression":"$vertex_y", "returns":"12.24"}
]
}

View File

@ -251,6 +251,7 @@ set(QGIS_APP_SRCS
mesh/qgsnewmeshlayerdialog.cpp
mesh/qgsmaptooleditmeshframe.cpp
mesh/qgsmeshtransformcoordinatesdockwidget.cpp
mesh/qgsmeshselectbyexpressiondialog.cpp
)
if (WITH_SPATIALITE)

View File

@ -37,6 +37,12 @@
#include "qgsmeshtriangulation.h"
#include "qgsmeshtransformcoordinatesdockwidget.h"
#include "qgsexpressionbuilderwidget.h"
#include "qgsmeshselectbyexpressiondialog.h"
#include "qgsexpressioncontext.h"
#include "qgsexpressioncontextutils.h"
#include "qgsexpressionutils.h"
QgsZValueWidget::QgsZValueWidget( const QString &label, QWidget *parent ): QWidget( parent )
{
@ -94,7 +100,9 @@ QgsMapToolEditMeshFrame::QgsMapToolEditMeshFrame( QgsMapCanvas *canvas )
mActionDigitizing = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshDigitizing.svg" ) ), tr( "Digitize mesh elements" ), this );
mActionDigitizing->setCheckable( true );
mActionSelectByPolygon = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshSelectPolygon.svg" ) ), tr( "Select mesh element by polygon" ), this );
mActionSelectByPolygon = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshSelectPolygon.svg" ) ), tr( "Select mesh elements by polygon" ), this );
mActionSelectByPolygon->setCheckable( true );
mActionSelectByExpression = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshSelectExpression.svg" ) ), tr( "Select mesh elements by expression" ), this );
mActionTransformCoordinates = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshTransformByExpression.svg" ) ), tr( "Transform vertices coordinates" ), this );
mActionTransformCoordinates->setCheckable( true );
@ -154,6 +162,7 @@ QgsMapToolEditMeshFrame::QgsMapToolEditMeshFrame( QgsMapCanvas *canvas )
} );
connect( mActionTransformCoordinates, &QAction::triggered, this, &QgsMapToolEditMeshFrame::triggerTransformCoordinatesDockWidget );
connect( mActionSelectByExpression, &QAction::triggered, this, &QgsMapToolEditMeshFrame::showSelectByExpressionDialog );
setAutoSnapEnabled( true );
}
@ -163,6 +172,7 @@ void QgsMapToolEditMeshFrame::activateWithState( State state )
if ( mCanvas->mapTool() != this )
{
mCanvas->setMapTool( this );
mCanvas->setFocus();
onEditingStarted();
}
mCurrentState = state;
@ -176,7 +186,7 @@ void QgsMapToolEditMeshFrame::backToDigitizing()
QgsMapToolEditMeshFrame::~QgsMapToolEditMeshFrame()
{
deleteZvalueWidget();
deleteZValueWidget();
}
QList<QAction *> QgsMapToolEditMeshFrame::actions() const
@ -184,6 +194,7 @@ QList<QAction *> QgsMapToolEditMeshFrame::actions() const
return QList<QAction *>()
<< mActionDigitizing
<< mActionSelectByPolygon
<< mActionSelectByExpression
<< mActionTransformCoordinates;
}
@ -312,7 +323,7 @@ void QgsMapToolEditMeshFrame::deactivate()
{
clearSelection();
clearCanvasHelpers();
mZValueWidget->hide();
deleteZValueWidget();
qDeleteAll( mFreeVertexMarker );
mFreeVertexMarker.clear();
@ -354,15 +365,13 @@ void QgsMapToolEditMeshFrame::clearAll()
mSelectedFacesRubberband->deleteLater();
mSelectedFacesRubberband = nullptr;
deleteZvalueWidget();
deleteZValueWidget();
}
void QgsMapToolEditMeshFrame::activate()
{
QgsMapToolAdvancedDigitizing::activate();
if ( mZValueWidget )
mZValueWidget->show();
updateFreeVertices();
createZValueWidget();
}
bool QgsMapToolEditMeshFrame::populateContextMenuWithEvent( QMenu *menu, QgsMapMouseEvent *event )
@ -803,25 +812,33 @@ void QgsMapToolEditMeshFrame::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
void QgsMapToolEditMeshFrame::select( const QgsPointXY &mapPoint, Qt::KeyboardModifiers modifiers, double tolerance )
{
Qgis::SelectBehavior behavior;
if ( modifiers & Qt::ShiftModifier )
behavior = Qgis::SelectBehavior::AddToSelection;
else if ( modifiers & Qt::ControlModifier )
behavior = Qgis::SelectBehavior::RemoveFromSelection;
else
behavior = Qgis::SelectBehavior::SetSelection;
if ( mSelectFaceMarker->isVisible() &&
mapPoint.distance( mSelectFaceMarker->center() ) < tolerance
&& mCurrentFaceIndex >= 0 )
{
setSelectedVertices( nativeFace( mCurrentFaceIndex ).toList(), modifiers );
setSelectedVertices( nativeFace( mCurrentFaceIndex ).toList(), behavior );
}
else if ( mCurrentVertexIndex != -1 )
{
setSelectedVertices( QList<int>() << mCurrentVertexIndex, modifiers );
setSelectedVertices( QList<int>() << mCurrentVertexIndex, behavior );
}
else if ( mSelectEdgeMarker->isVisible() &&
mapPoint.distance( mSelectEdgeMarker->center() ) < tolerance &&
mCurrentEdge.first != -1 && mCurrentEdge.second != -1 )
{
QVector<int> edgeVert = edgeVertices( mCurrentEdge );
setSelectedVertices( edgeVert.toList(), modifiers );
setSelectedVertices( edgeVert.toList(), behavior );
}
else
setSelectedVertices( QList<int>(), modifiers );
setSelectedVertices( QList<int>(), behavior );
}
void QgsMapToolEditMeshFrame::keyPressEvent( QKeyEvent *e )
@ -1125,55 +1142,113 @@ bool QgsMapToolEditMeshFrame::testNewVertexInFaceCanditate( int vertexIndex )
return mCurrentEditor->faceCanBeAdded( face );
}
void QgsMapToolEditMeshFrame::setSelectedVertices( const QList<int> newSelectedVertex, Qt::KeyboardModifiers modifiers )
void QgsMapToolEditMeshFrame::addNewSelectedVertex( int vertexIndex )
{
if ( !( modifiers & Qt::ControlModifier ) && !( modifiers & Qt::ShiftModifier ) )
clearSelection();
mSelectedVertices.insert( vertexIndex, SelectedVertexData() );
QgsVertexMarker *marker = new QgsVertexMarker( canvas() );
marker->setIconType( QgsVertexMarker::ICON_CIRCLE );
marker->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
marker->setPenWidth( QgsGuiUtils::scaleIconSize( 2 ) );
marker->setColor( Qt::blue );
marker->setCenter( mapVertexXY( vertexIndex ) );
marker->setZValue( 2 );
mSelectedVerticesMarker[vertexIndex] = marker;
}
bool removeVertices = modifiers & Qt::ControlModifier;
void QgsMapToolEditMeshFrame::removeFromSelection( int vertexIndex )
{
mSelectedVertices.remove( vertexIndex );
delete mSelectedVerticesMarker.value( vertexIndex );
mSelectedVerticesMarker.remove( vertexIndex );
}
for ( const int vertexIndex : newSelectedVertex )
bool QgsMapToolEditMeshFrame::isFaceSelected( int faceIndex )
{
const QgsMeshFace &face = nativeFace( faceIndex );
for ( int i = 0; i < face.size(); ++i )
{
if ( mSelectedVertices.contains( vertexIndex ) && removeVertices )
{
mSelectedVertices.remove( vertexIndex );
delete mSelectedVerticesMarker.value( vertexIndex );
mSelectedVerticesMarker.remove( vertexIndex );
}
else if ( ! removeVertices && !mSelectedVertices.contains( vertexIndex ) )
{
mSelectedVertices.insert( vertexIndex, SelectedVertexData() );
QgsVertexMarker *marker = new QgsVertexMarker( canvas() );
marker->setIconType( QgsVertexMarker::ICON_CIRCLE );
marker->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
marker->setPenWidth( QgsGuiUtils::scaleIconSize( 2 ) );
marker->setColor( Qt::blue );
marker->setCenter( mapVertexXY( vertexIndex ) );
marker->setZValue( 2 );
mSelectedVerticesMarker[vertexIndex] = marker;
}
if ( !mSelectedVertices.contains( face.at( i ) ) )
return false;
}
return true;
}
void QgsMapToolEditMeshFrame::setSelectedVertices( const QList<int> newSelectedVertices, Qgis::SelectBehavior behavior )
{
bool removeVertices = false;
switch ( behavior )
{
case Qgis::SelectBehavior::SetSelection:
clearSelection();
break;
case Qgis::SelectBehavior::AddToSelection:
break;
case Qgis::SelectBehavior::RemoveFromSelection:
removeVertices = true;
break;
case Qgis::SelectBehavior::IntersectSelection:
return;
break;
}
if ( !mSelectedVertices.isEmpty() )
for ( const int vertexIndex : newSelectedVertices )
{
double vertexZValue = 0;
for ( int i : mSelectedVertices.keys() )
vertexZValue += mapVertex( i ).z();
vertexZValue /= mSelectedVertices.count();
mZValueWidget->setDefaultValue( vertexZValue );
bool contained = mSelectedVertices.contains( vertexIndex );
if ( contained && removeVertices )
removeFromSelection( vertexIndex );
else if ( ! removeVertices && !contained )
addNewSelectedVertex( vertexIndex );
}
prepareSelection();
}
void QgsMapToolEditMeshFrame::clearSelectedvertex()
void QgsMapToolEditMeshFrame::setSelectedFaces( const QList<int> newSelectedFaces, Qgis::SelectBehavior behavior )
{
mSelectedVertices.clear();
mSelectedFaces.clear();
qDeleteAll( mSelectedVerticesMarker );
mSelectedVerticesMarker.clear();
mSelectedFacesRubberband->reset();
bool removeFaces = false;
switch ( behavior )
{
case Qgis::SelectBehavior::SetSelection:
clearSelection();
break;
case Qgis::SelectBehavior::AddToSelection:
break;
case Qgis::SelectBehavior::RemoveFromSelection:
removeFaces = true;
break;
case Qgis::SelectBehavior::IntersectSelection:
return;
break;
}
const QSet<int> facesToTreat = qgis::listToSet( newSelectedFaces );
for ( const int faceIndex : newSelectedFaces )
{
const QgsMeshFace &face = nativeFace( faceIndex );
for ( int i = 0; i < face.size(); ++i )
{
int vertexIndex = face.at( i );
bool vertexContained = mSelectedVertices.contains( vertexIndex );
if ( vertexContained && removeFaces )
{
const QList<int> facesAround = mCurrentEditor->topologicalMesh().facesAroundVertex( vertexIndex );
bool keepVertex = false;
for ( const int faceAroundIndex : facesAround )
keepVertex |= !facesToTreat.contains( faceAroundIndex ) && isFaceSelected( faceAroundIndex );
if ( !keepVertex )
removeFromSelection( vertexIndex );
}
else if ( !removeFaces && !vertexContained )
addNewSelectedVertex( vertexIndex );
}
}
prepareSelection();
}
@ -1206,7 +1281,7 @@ void QgsMapToolEditMeshFrame::removeFacesFromMesh()
}
else
{
clearSelectedvertex();
clearSelection();
}
}
@ -1399,7 +1474,16 @@ void QgsMapToolEditMeshFrame::selectInGeometry( const QgsGeometry &geometry, Qt:
if ( engine->contains( &vertex ) )
selectedVertices.insert( freeVertexIndex );
}
setSelectedVertices( selectedVertices.values(), modifiers );
Qgis::SelectBehavior behavior;
if ( modifiers & Qt::ShiftModifier )
behavior = Qgis::SelectBehavior::RemoveFromSelection;
else if ( modifiers & Qt::ControlModifier )
behavior = Qgis::SelectBehavior::RemoveFromSelection;
else
behavior = Qgis::SelectBehavior::SetSelection;
setSelectedVertices( selectedVertices.values(), behavior );
}
void QgsMapToolEditMeshFrame::applyZValueOnSelectedVertices()
@ -1421,22 +1505,48 @@ void QgsMapToolEditMeshFrame::applyZValueOnSelectedVertices()
void QgsMapToolEditMeshFrame::prepareSelection()
{
if ( !mSelectedVertices.isEmpty() )
{
double vertexZValue = 0;
for ( int i : mSelectedVertices.keys() )
vertexZValue += mapVertex( i ).z();
vertexZValue /= mSelectedVertices.count();
mZValueWidget->setDefaultValue( vertexZValue );
}
mConcernedFaceBySelection.clear();
QMap<int, SelectedVertexData> movingVertices;
double xMin = std::numeric_limits<double>::max();
double xMax = -std::numeric_limits<double>::max();
double yMin = std::numeric_limits<double>::max();
double yMax = -std::numeric_limits<double>::max();
// search for moving edges and mesh fixed edges
for ( QMap<int, SelectedVertexData>::iterator it = mSelectedVertices.begin(); it != mSelectedVertices.end(); ++it )
{
SelectedVertexData &vertexData = it.value();
int vertexIndex = it.key();
QgsPointXY vert = mapVertex( vertexIndex );
if ( vert.x() < xMin )
xMin = vert.x();
if ( vert.x() > xMax )
xMax = vert.x();
if ( vert.y() < yMin )
yMin = vert.y();
if ( vert.y() > yMax )
yMax = vert.y();
vertexData.borderEdges.clear();
vertexData.meshFixedEdges.clear();
QgsMeshVertexCirculator circulator = mCurrentEditor->vertexCirculator( vertexIndex );
if ( !circulator.isValid() )
continue;
circulator.goBoundaryClockwise();
int firstface = circulator.currentFaceIndex();
do
@ -1462,6 +1572,8 @@ void QgsMapToolEditMeshFrame::prepareSelection()
}
}
mSelectedMapExtent = QgsRectangle( xMin, yMin, xMax, yMax );
// remove faces that have at least one vertex not selected
mSelectedFaces = mConcernedFaceBySelection;
for ( const int faceIndex : std::as_const( mConcernedFaceBySelection ) )
@ -1865,17 +1977,16 @@ bool QgsMapToolEditMeshFrame::faceCanBeInteractive( int faceIndex ) const
void QgsMapToolEditMeshFrame::createZValueWidget()
{
if ( !mCanvas )
{
return;
}
deleteZvalueWidget();
deleteZValueWidget();
mZValueWidget = new QgsZValueWidget( tr( "Vertex Z value:" ) );
mZValueWidget->setDefaultValue( mOrdinaryZValue );
QgisApp::instance()->addUserInputWidget( mZValueWidget );
}
void QgsMapToolEditMeshFrame::deleteZvalueWidget()
void QgsMapToolEditMeshFrame::deleteZValueWidget()
{
if ( mZValueWidget )
{
@ -2026,3 +2137,46 @@ int QgsMapToolEditMeshFrame::closeVertex( const QgsPointXY &mapPoint ) const
return -1;
}
void QgsMapToolEditMeshFrame::selectByExpression( const QString &textExpression, Qgis::SelectBehavior behavior, QgsMesh::ElementType elementType )
{
if ( !mCurrentEditor || !mCurrentLayer )
return;
QgsExpression expression( textExpression );
std::unique_ptr<QgsDistanceArea> distArea = std::make_unique<QgsDistanceArea>();
distArea->setSourceCrs( mCurrentLayer->crs(), QgsProject::instance()->transformContext() );
distArea->setEllipsoid( QgsProject::instance()->ellipsoid() );
expression.setAreaUnits( QgsProject::instance()->areaUnits() );
expression.setDistanceUnits( QgsProject::instance()->distanceUnits() );
expression.setGeomCalculator( distArea.release() );
switch ( elementType )
{
case QgsMesh::Vertex:
setSelectedVertices( mCurrentLayer->selectVerticesByExpression( expression ), behavior );
break;
case QgsMesh::Face:
setSelectedFaces( mCurrentLayer->selectFacesByExpression( expression ), behavior );
break;
case QgsMesh::Edge:
//not supported
break;
}
}
void QgsMapToolEditMeshFrame::onZoomToSelected()
{
canvas()->zoomToFeatureExtent( mSelectedMapExtent );
}
void QgsMapToolEditMeshFrame::showSelectByExpressionDialog()
{
onEditingStarted();
QgsMeshSelectByExpressionDialog *dialog = new QgsMeshSelectByExpressionDialog( canvas() );
dialog->setAttribute( Qt::WA_DeleteOnClose );
dialog->show();
connect( dialog, &QgsMeshSelectByExpressionDialog::select, this, &QgsMapToolEditMeshFrame::selectByExpression );
connect( dialog, &QgsMeshSelectByExpressionDialog::zoomToSelected, this, &QgsMapToolEditMeshFrame::onZoomToSelected );
}

View File

@ -18,6 +18,7 @@
#include <QWidget>
#include <QPointer>
#include <QDialog>
#include "qgis_app.h"
#include "qgsmaptooladvanceddigitizing.h"
@ -34,6 +35,10 @@ class QgsSnapIndicator;
class QgsMeshTransformCoordinatesDockWidget;
class QgsExpressionBuilderWidget;
class QgsMeshSelectByExpressionDialog;
class APP_EXPORT QgsZValueWidget : public QWidget
{
Q_OBJECT
@ -49,7 +54,7 @@ class APP_EXPORT QgsZValueWidget : public QWidget
void setZValue( double z );
/**
* Sets the current value of the widget and set it as the default one ,
* Sets the current value of the widget and set it as the default one,
* that is the value that is retrieve if the z value spin box is cleared
*/
void setDefaultValue( double z );
@ -101,6 +106,10 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
void triggerTransformCoordinatesDockWidget( bool checked );
void showSelectByExpressionDialog();
void selectByExpression( const QString &textExpression, Qgis::SelectBehavior behavior, QgsMesh::ElementType elementType );
void onZoomToSelected();
private:
enum State
@ -130,9 +139,8 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
bool faceCanBeInteractive( int faceIndex ) const;
void createZValueWidget();
void deleteZvalueWidget();
void deleteZValueWidget();
void clearSelection();
void clearCanvasHelpers();
void clearEdgeHelpers();
@ -155,12 +163,16 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
// selection methods
void select( const QgsPointXY &mapPoint, Qt::KeyboardModifiers modifiers, double tolerance );
void setSelectedVertices( const QList<int> newSelectedVertex, Qt::KeyboardModifiers modifiers );
void clearSelectedvertex();
void addNewSelectedVertex( int vertexIndex );
void removeFromSelection( int vertexIndex );
bool isFaceSelected( int faceIndex );
void setSelectedVertices( const QList<int> newSelectedVertices, Qgis::SelectBehavior behavior );
void setSelectedFaces( const QList<int> newSelectedFaces, Qgis::SelectBehavior behavior );
void selectInGeometry( const QgsGeometry &geometry, Qt::KeyboardModifiers modifiers );
void applyZValueOnSelectedVertices();
void prepareSelection();
void updateSelectecVerticesMarker();
void clearSelection();
void setMovingRubberBandValidity( bool valid );
@ -177,7 +189,6 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
bool mLeftButtonPressed = false;
bool mKeepSelectionOnEdit = false;
QPointer<QgsMeshLayer> mCurrentLayer = nullptr; //not own
QPointer<QgsMeshEditor> mCurrentEditor = nullptr; // own by mesh layer
std::unique_ptr<QgsSnapIndicator> mSnapIndicator;
@ -211,6 +222,7 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
//! members for selection of vertices/faces
QMap<int, SelectedVertexData> mSelectedVertices;
QgsRectangle mSelectedMapExtent;
QSet<int> mSelectedFaces;
QSet<int> mConcernedFaceBySelection;
QgsVertexMarker *mSelectFaceMarker = nullptr; //own by map canvas
@ -240,6 +252,7 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
//! members for split face
int mSplittableFaceCount = 0;
// assiociated widget
QgsZValueWidget *mZValueWidget = nullptr; //own by QgsUserInputWidget instance
QgsMeshTransformCoordinatesDockWidget *mTransformDockWidget = nullptr; //own by the application
@ -257,6 +270,8 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
QAction *mActionTransformCoordinates = nullptr;
QAction *mActionSelectByExpression = nullptr;
friend class TestQgsMapToolEditMesh;
};

View File

@ -0,0 +1,113 @@
/***************************************************************************
qgsmeshselectbyexpressiondialog.cpp - QgsMeshSelectByExpressionDialog
---------------------
begin : 23.8.2021
copyright : (C) 2021 by Vincent Cloarec
email : vcloarec 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 "qgsmeshselectbyexpressiondialog.h"
#include <QAction>
#include "qgsapplication.h"
#include "qgsexpressioncontextutils.h"
#include "qgshelp.h"
#include "qgsgui.h"
QgsMeshSelectByExpressionDialog::QgsMeshSelectByExpressionDialog( QWidget *parent ):
QDialog( parent )
{
setupUi( this );
QgsGui::enableAutoGeometryRestore( this );
setWindowTitle( tr( "Select Mesh Elements by Expression" ) );
QString elementText = tr( "Vertices" );
mActionSelect = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mIconExpressionSelect.svg" ) ), tr( "Select" ), this );
mActionAddToSelection = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectAdd.svg" ) ), tr( "Add to current selection" ), this );
mActionRemoveFromSelection = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectRemove.svg" ) ), tr( "Remove from current selection" ), this );
mButtonSelect->addAction( mActionSelect );
mButtonSelect->setDefaultAction( mActionSelect );
mButtonSelect->addAction( mActionAddToSelection );
mButtonSelect->addAction( mActionRemoveFromSelection );
mComboBoxElementType->addItem( tr( "Select by Vertices" ), QgsMesh::Vertex );
mComboBoxElementType->addItem( tr( "Select by Faces" ), QgsMesh::Face );
QgsSettings settings;
QgsMesh::ElementType elementType = QgsMesh::Vertex;
if ( settings.contains( QStringLiteral( "/meshSelection/elementType" ) ) )
elementType = static_cast<QgsMesh::ElementType>( settings.value( QStringLiteral( "/meshSelection/elementType" ) ).toInt() );
int comboIndex = mComboBoxElementType->findData( elementType );
if ( comboIndex >= 0 )
mComboBoxElementType->setCurrentIndex( comboIndex );
onElementTypeChanged();
connect( mActionSelect, &QAction::triggered, this, [this]
{
emit select( mExpressionBuilder->expressionText(), Qgis::SelectBehavior::SetSelection, currentElementType() );
} );
connect( mActionAddToSelection, &QAction::triggered, this, [this]
{
emit select( mExpressionBuilder->expressionText(), Qgis::SelectBehavior::AddToSelection, currentElementType() );
} );
connect( mActionRemoveFromSelection, &QAction::triggered, this, [this]
{
emit select( mExpressionBuilder->expressionText(), Qgis::SelectBehavior::RemoveFromSelection, currentElementType() );
} );
connect( mActionSelect, &QAction::triggered, this, &QgsMeshSelectByExpressionDialog::saveRecent );
connect( mActionAddToSelection, &QAction::triggered, this, &QgsMeshSelectByExpressionDialog::saveRecent );
connect( mActionRemoveFromSelection, &QAction::triggered, this, &QgsMeshSelectByExpressionDialog::saveRecent );
connect( mButtonClose, &QPushButton::clicked, this, &QgsMeshSelectByExpressionDialog::close );
connect( mButtonZoomToSelected, &QToolButton::clicked, this, &QgsMeshSelectByExpressionDialog::zoomToSelected );
connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsMeshSelectByExpressionDialog::showHelp );
connect( mComboBoxElementType, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsMeshSelectByExpressionDialog::onElementTypeChanged );
mExpressionBuilder->setExpressionPreviewVisible( false );
}
QString QgsMeshSelectByExpressionDialog::expression() const
{
return mExpressionBuilder->expressionText();
}
void QgsMeshSelectByExpressionDialog::showHelp() const
{
QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html" ) );
}
void QgsMeshSelectByExpressionDialog::saveRecent() const
{
mExpressionBuilder->expressionTree()->saveToRecent( mExpressionBuilder->expressionText(), QStringLiteral( "mesh_vertex_selection" ) );
}
void QgsMeshSelectByExpressionDialog::onElementTypeChanged() const
{
QgsMesh::ElementType elementType = currentElementType() ;
QgsSettings settings;
settings.setValue( QStringLiteral( "/meshSelection/elementType" ), elementType );
QgsExpressionContext expressionContext( {QgsExpressionContextUtils::meshExpressionScope( elementType )} );
mExpressionBuilder->init( expressionContext, QStringLiteral( "mesh_vertex_selection" ), QgsExpressionBuilderWidget::LoadAll );
}
QgsMesh::ElementType QgsMeshSelectByExpressionDialog::currentElementType() const
{
return static_cast<QgsMesh::ElementType>( mComboBoxElementType->currentData().toInt() );
}

View File

@ -0,0 +1,64 @@
/***************************************************************************
qgsmeshselectbyexpressiondialog.h - QgsMeshSelectByExpressionDialog
---------------------
begin : 23.8.2021
copyright : (C) 2021 by Vincent Cloarec
email : vcloarec 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 QGSMESHSELECTBYEXPRESSIONDIALOG_H
#define QGSMESHSELECTBYEXPRESSIONDIALOG_H
#include "ui_qgsmeshselectbyexpressiondialogbase.h"
#include "qgis_app.h"
#include "qgsmeshdataprovider.h"
/**
* \brief A Dialog Widget that is used select mesh element by expression during mesh editing.
*
* The selected elements by expression are either faces or vertices, depending of the choice of the user.
*
* The instance emits signals when select actions are triggered.
*
* \since QGIS 3.22
*/
class APP_EXPORT QgsMeshSelectByExpressionDialog : public QDialog, private Ui::QgsMeshSelectByExpressionDialogBase
{
Q_OBJECT
public:
//! Constructor
QgsMeshSelectByExpressionDialog( QWidget *parent = nullptr );
//! Returns the text expression defined in the dialog
QString expression() const;
signals:
//! Emitted when one of the select tool button is clicked
void select( const QString &expression, Qgis::SelectBehavior behavior, QgsMesh::ElementType elementType );
//! Emittes when the zoom to selected element button is clicked
void zoomToSelected();
private slots:
void showHelp() const;
void saveRecent() const;
void onElementTypeChanged() const;
private:
QAction *mActionSelect = nullptr;
QAction *mActionAddToSelection = nullptr;
QAction *mActionRemoveFromSelection = nullptr;
QgsMesh::ElementType currentElementType() const;
};
#endif // QGSMESHSELECTBYEXPRESSIONDIALOG_H

View File

@ -55,7 +55,7 @@ QgsMeshTransformCoordinatesDockWidget::QgsMeshTransformCoordinatesDockWidget( QW
QgsExpressionContext QgsMeshTransformCoordinatesDockWidget::createExpressionContext() const
{
return QgsExpressionContext( {QgsExpressionContextUtils::meshExpressionScope()} );
return QgsExpressionContext( {QgsExpressionContextUtils::meshExpressionScope( QgsMesh::Vertex )} );
}
QgsMeshVertex QgsMeshTransformCoordinatesDockWidget::transformedVertex( int i )

View File

@ -34,6 +34,7 @@
#include "qgsmaplayerlistutils.h"
#include "qgsprojoperation.h"
#include "qgsmarkersymbol.h"
#include "qgstriangularmesh.h"
QgsExpressionContextScope *QgsExpressionContextUtils::globalScope()
{
@ -1133,18 +1134,148 @@ class CurrentVertexExpressionFunction: public QgsScopedExpressionFunction
}
};
QgsExpressionContextScope *QgsExpressionContextUtils::meshExpressionScope()
class CurrentVertexIndexExpressionFunction: public QgsScopedExpressionFunction
{
QgsExpression::registerFunction( new CurrentVertexExpressionFunction, true );
QgsExpression::registerFunction( new CurrentVertexXValueExpressionFunction, true );
QgsExpression::registerFunction( new CurrentVertexYValueExpressionFunction, true );
QgsExpression::registerFunction( new CurrentVertexZValueExpressionFunction, true );
public:
CurrentVertexIndexExpressionFunction():
QgsScopedExpressionFunction( "$vertex_index",
0,
QStringLiteral( "Meshes" ) )
{}
QgsScopedExpressionFunction *clone() const override {return new CurrentVertexIndexExpressionFunction();}
QVariant func( const QVariantList &, const QgsExpressionContext *context, QgsExpression *, const QgsExpressionNodeFunction * ) override
{
if ( !context )
return QVariant();
if ( !context->hasVariable( QStringLiteral( "_mesh_vertex_index" ) ) || !context->hasVariable( QStringLiteral( "_mesh_layer" ) ) )
return QVariant();
return context->variable( QStringLiteral( "_mesh_vertex_index" ) );
}
bool isStatic( const QgsExpressionNodeFunction *, QgsExpression *, const QgsExpressionContext * ) const override
{
return false;
}
};
class CurrentFaceAreaExpressionFunction: public QgsScopedExpressionFunction
{
public:
CurrentFaceAreaExpressionFunction():
QgsScopedExpressionFunction( "$face_area",
0,
QStringLiteral( "Meshes" ) )
{}
QgsScopedExpressionFunction *clone() const override {return new CurrentFaceAreaExpressionFunction();}
QVariant func( const QVariantList &, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * ) override
{
if ( !context )
return QVariant();
if ( !context->hasVariable( QStringLiteral( "_mesh_face_index" ) ) || !context->hasVariable( QStringLiteral( "_mesh_layer" ) ) )
return QVariant();
int faceIndex = context->variable( QStringLiteral( "_mesh_face_index" ) ).toInt();
QgsMeshLayer *layer = qobject_cast<QgsMeshLayer *>( qvariant_cast<QgsMapLayer *>( context->variable( QStringLiteral( "_mesh_layer" ) ) ) );
if ( !layer || !layer->nativeMesh() || layer->nativeMesh()->faceCount() <= faceIndex )
return QVariant();
const QgsMeshFace &face = layer->nativeMesh()->face( faceIndex );
if ( !face.isEmpty() )
{
QgsDistanceArea *calc = parent->geomCalculator();
QgsGeometry geom = QgsMeshUtils::toGeometry( layer->nativeMesh()->face( faceIndex ), layer->nativeMesh()->vertices );
if ( calc )
{
double area = calc->measureArea( geom );
area = calc->convertAreaMeasurement( area, parent->areaUnits() );
return QVariant( area );
}
else
{
return QVariant( geom.area() );
}
}
else
return QVariant();
}
bool isStatic( const QgsExpressionNodeFunction *, QgsExpression *, const QgsExpressionContext * ) const override
{
return false;
}
};
class CurrentFaceIndexExpressionFunction: public QgsScopedExpressionFunction
{
public:
CurrentFaceIndexExpressionFunction():
QgsScopedExpressionFunction( "$face_index",
0,
QStringLiteral( "Meshes" ) )
{}
QgsScopedExpressionFunction *clone() const override {return new CurrentFaceIndexExpressionFunction();}
QVariant func( const QVariantList &, const QgsExpressionContext *context, QgsExpression *, const QgsExpressionNodeFunction * ) override
{
if ( !context )
return QVariant();
if ( !context->hasVariable( QStringLiteral( "_mesh_face_index" ) ) || !context->hasVariable( QStringLiteral( "_mesh_layer" ) ) )
return QVariant();
return context->variable( QStringLiteral( "_mesh_face_index" ) ).toInt();
}
bool isStatic( const QgsExpressionNodeFunction *, QgsExpression *, const QgsExpressionContext * ) const override
{
return false;
}
};
QgsExpressionContextScope *QgsExpressionContextUtils::meshExpressionScope( QgsMesh::ElementType elementType )
{
std::unique_ptr<QgsExpressionContextScope> scope = std::make_unique<QgsExpressionContextScope>();
scope->addFunction( "$vertex_as_point", new CurrentVertexExpressionFunction );
scope->addFunction( "$vertex_x", new CurrentVertexXValueExpressionFunction );
scope->addFunction( "$vertex_y", new CurrentVertexYValueExpressionFunction );
scope->addFunction( "$vertex_z", new CurrentVertexZValueExpressionFunction );
switch ( elementType )
{
case QgsMesh::Vertex:
{
QgsExpression::registerFunction( new CurrentVertexExpressionFunction, true );
QgsExpression::registerFunction( new CurrentVertexXValueExpressionFunction, true );
QgsExpression::registerFunction( new CurrentVertexYValueExpressionFunction, true );
QgsExpression::registerFunction( new CurrentVertexZValueExpressionFunction, true );
QgsExpression::registerFunction( new CurrentVertexIndexExpressionFunction, true );
scope->addFunction( "$vertex_as_point", new CurrentVertexExpressionFunction );
scope->addFunction( "$vertex_x", new CurrentVertexXValueExpressionFunction );
scope->addFunction( "$vertex_y", new CurrentVertexYValueExpressionFunction );
scope->addFunction( "$vertex_z", new CurrentVertexZValueExpressionFunction );
scope->addFunction( "$vertex_index", new CurrentVertexIndexExpressionFunction );
}
break;
case QgsMesh::Face:
{
QgsExpression::registerFunction( new CurrentFaceAreaExpressionFunction, true );
QgsExpression::registerFunction( new CurrentFaceIndexExpressionFunction, true );
scope->addFunction( "$face_area", new CurrentFaceAreaExpressionFunction );
scope->addFunction( "$face_index", new CurrentFaceIndexExpressionFunction );
}
break;
case QgsMesh::Edge:
break;
}
return scope.release();
}

View File

@ -20,6 +20,7 @@
#include "qgsfeature.h"
#include "qgspointlocator.h"
#include "qgsexpressioncontext.h"
#include "qgsmeshdataprovider.h"
#include <QString>
#include <QVariantMap>
@ -322,10 +323,10 @@ class CORE_EXPORT QgsExpressionContextUtils
static void registerContextFunctions();
/**
* Creates a new scope which contains functions relating to mesh layer elements (face, vertex, ...)
* Creates a new scope which contains functions relating to mesh layer element \a elementType
* \since QGIS 3.22
*/
static QgsExpressionContextScope *meshExpressionScope() SIP_FACTORY;
static QgsExpressionContextScope *meshExpressionScope( QgsMesh::ElementType elementType ) SIP_FACTORY;
private:

View File

@ -608,7 +608,7 @@ bool QgsMeshTransformVerticesByExpression::calculate( QgsMeshLayer *layer )
QSet<int> concernedFaces;
mChangingVertexMap = QHash<int, int>();
std::unique_ptr<QgsExpressionContextScope> expScope( QgsExpressionContextUtils::meshExpressionScope() );
std::unique_ptr<QgsExpressionContextScope> expScope( QgsExpressionContextUtils::meshExpressionScope( QgsMesh::Vertex ) );
QgsExpressionContext context;
context.appendScope( expScope.release() );
context.lastScope()->setVariable( QStringLiteral( "_mesh_layer" ), QVariant::fromValue( layer ) );

View File

@ -1139,7 +1139,7 @@ QgsPointXY QgsMeshLayer::snapOnElement( QgsMesh::ElementType elementType, const
return QgsPointXY(); // avoid warnings
}
QList<int> QgsMeshLayer::selectVerticesByExpression( const QString &expressionString, const QgsExpressionContext &expressionContext )
QList<int> QgsMeshLayer::selectVerticesByExpression( QgsExpression expression )
{
if ( !mNativeMesh )
{
@ -1152,11 +1152,8 @@ QList<int> QgsMeshLayer::selectVerticesByExpression( const QString &expressionSt
if ( !mNativeMesh )
return ret;
QgsExpression expression( expressionString );
QgsExpressionContext context = expressionContext;
std::unique_ptr<QgsExpressionContextScope> expScope( QgsExpressionContextUtils::meshExpressionScope() );
QgsExpressionContext context;
std::unique_ptr<QgsExpressionContextScope> expScope( QgsExpressionContextUtils::meshExpressionScope( QgsMesh::Vertex ) );
context.appendScope( expScope.release() );
context.lastScope()->setVariable( QStringLiteral( "_mesh_layer" ), QVariant::fromValue( this ) );
@ -1173,6 +1170,37 @@ QList<int> QgsMeshLayer::selectVerticesByExpression( const QString &expressionSt
return ret;
}
QList<int> QgsMeshLayer::selectFacesByExpression( QgsExpression expression )
{
if ( !mNativeMesh )
{
// lazy loading of mesh data
fillNativeMesh();
}
QList<int> ret;
if ( !mNativeMesh )
return ret;
QgsExpressionContext context;
std::unique_ptr<QgsExpressionContextScope> expScope( QgsExpressionContextUtils::meshExpressionScope( QgsMesh::Face ) );
context.appendScope( expScope.release() );
context.lastScope()->setVariable( QStringLiteral( "_mesh_layer" ), QVariant::fromValue( this ) );
expression.prepare( &context );
for ( int i = 0; i < mNativeMesh->faceCount(); ++i )
{
context.lastScope()->setVariable( QStringLiteral( "_mesh_face_index" ), i, false );
if ( expression.evaluate( &context ).toBool() )
ret.append( i );
}
return ret;
}
QgsMeshDatasetIndex QgsMeshLayer::staticScalarDatasetIndex() const
{
return QgsMeshDatasetIndex( mRendererSettings.activeScalarDatasetGroup(), mStaticScalarDatasetIndex );

View File

@ -678,7 +678,17 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer
*
* \since QGIS 3.22
*/
QList<int> selectVerticesByExpression( const QString &expression, const QgsExpressionContext &expressionContext = QgsExpressionContext() );
QList<int> selectVerticesByExpression( QgsExpression expression );
/**
* Returns a list of faces indexes that meet the condition defined by \a expression with the context \a expressionContext
*
* To express the relation with a face, the expression can be defined with function returning value
* linked to the current face, like " $face_area "
*
* \since QGIS 3.22
*/
QList<int> selectFacesByExpression( QgsExpression expression );
/**
* Returns the root items of the dataset group tree item

View File

@ -656,6 +656,11 @@ bool QgsExpressionBuilderWidget::parserError() const
return mExpressionPreviewWidget->parserError();
}
void QgsExpressionBuilderWidget::setExpressionPreviewVisible( bool isVisible )
{
mExpressionPreviewWidget->setVisible( isVisible );
}
bool QgsExpressionBuilderWidget::evalError() const
{
return mExpressionPreviewWidget->evalError();

View File

@ -259,6 +259,13 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp
*/
bool parserError() const;
/**
* Sets whether the expression preview is visible.
*
* \since QGIS 3.22
*/
void setExpressionPreviewVisible( bool isVisible );
public slots:
/**

View File

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsMeshSelectByExpressionDialogBase</class>
<widget class="QDialog" name="QgsMeshSelectByExpressionDialogBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>757</width>
<height>465</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="4">
<widget class="QToolButton" name="mButtonSelect">
<property name="text">
<string>...</string>
</property>
<property name="popupMode">
<enum>QToolButton::MenuButtonPopup</enum>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QPushButton" name="mButtonClose">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="6">
<widget class="QgsExpressionBuilderWidget" name="mExpressionBuilder" native="true"/>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="mButtonZoomToSelected">
<property name="text">
<string>Zoom to Selected</string>
</property>
</widget>
</item>
<item row="1" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Help</set>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="mComboBoxElementType"/>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsExpressionBuilderWidget</class>
<extends>QWidget</extends>
<header>qgsexpressionbuilderwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>QgsMeshSelectByExpressionDialogBase</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>QgsMeshSelectByExpressionDialogBase</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -266,18 +266,35 @@ class TestQgsExpression: public QObject
void evalMeshElement()
{
QgsExpressionContext context;
context.appendScope( QgsExpressionContextUtils::meshExpressionScope() );
context.appendScope( QgsExpressionContextUtils::meshExpressionScope( QgsMesh::Vertex ) );
context.lastScope()->setVariable( QStringLiteral( "_mesh_vertex_index" ), 2 );
context.lastScope()->setVariable( QStringLiteral( "_mesh_layer" ), QVariant::fromValue( mMeshLayer ) );
QgsExpression expression( QStringLiteral( "$vertex_z" ) );
QgsExpression expression( QStringLiteral( "$vertex_x" ) );
QCOMPARE( expression.evaluate( &context ).toDouble(), 2500.0 );
expression = QgsExpression( QStringLiteral( "$vertex_y" ) );
QCOMPARE( expression.evaluate( &context ).toDouble(), 2500.0 );
expression = QgsExpression( QStringLiteral( "$vertex_z" ) );
QCOMPARE( expression.evaluate( &context ).toDouble(), 800.0 );
expression = QgsExpression( QStringLiteral( "$vertex_index" ) );
QCOMPARE( expression.evaluate( &context ).toInt(), 2 );
expression = QgsExpression( QStringLiteral( "$vertex_as_point" ) );
QVariant out = expression.evaluate( &context );
QgsGeometry outGeom = out.value<QgsGeometry>();
QgsGeometry geom( new QgsPoint( 2500, 2500, 800 ) );
QCOMPARE( geom.equals( outGeom ), true );
context.appendScope( QgsExpressionContextUtils::meshExpressionScope( QgsMesh::Face ) );
context.lastScope()->setVariable( QStringLiteral( "_mesh_face_index" ), 2 );
expression = QgsExpression( QStringLiteral( "$face_area" ) );
QCOMPARE( expression.evaluate( &context ).toDouble(), 250000 );
expression = QgsExpression( QStringLiteral( "$face_index" ) );
QCOMPARE( expression.evaluate( &context ).toInt(), 2 );
}
void cleanupTestCase()

View File

@ -1712,14 +1712,25 @@ void TestQgsMeshLayer::testMdalProviderQuerySublayersFastScan()
void TestQgsMeshLayer::testSelectByExpression()
{
mMdalLayer->updateTriangularMesh();
QgsExpressionContext expressionContext;
QgsExpression expression( QStringLiteral( " $vertex_z > 30" ) );
QList<int> selectedVerticesIndexes = mMdalLayer->selectVerticesByExpression( QStringLiteral( " $vertex_z > 30" ) );
QList<int> selectedVerticesIndexes = mMdalLayer->selectVerticesByExpression( expression );
QCOMPARE( selectedVerticesIndexes, QList( {2, 3} ) );
selectedVerticesIndexes = mMdalLayer->selectVerticesByExpression( QStringLiteral( " x($vertex_as_point) > 1500" ) );
expression = QgsExpression( QStringLiteral( " x($vertex_as_point) > 1500" ) );
selectedVerticesIndexes = mMdalLayer->selectVerticesByExpression( expression );
QCOMPARE( selectedVerticesIndexes.count(), 3 );
QCOMPARE( selectedVerticesIndexes, QList( {1, 2, 3} ) );
expression = QgsExpression( QStringLiteral( " $face_area > 900000" ) );
QList<int> selectedFacesIndexes = mMdalLayer->selectFacesByExpression( expression );
QCOMPARE( selectedFacesIndexes.count(), 1 );
QCOMPARE( selectedFacesIndexes, QList( {0} ) );
expression = QgsExpression( QStringLiteral( " $face_area > 1100000" ) );
selectedFacesIndexes = mMdalLayer->selectFacesByExpression( expression );
QCOMPARE( selectedFacesIndexes.count(), 0 );
}
void TestQgsMeshLayer::test_temporal()