mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-15 00:07:25 -05:00
Select mesh elements by expression UI (#44835)
[mesh] [feature] Select mesh elements by expression
This commit is contained in:
parent
b90b86b221
commit
45e07dd72f
@ -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>
|
||||
|
||||
1
images/themes/default/mActionMeshSelectExpression.svg
Normal file
1
images/themes/default/mActionMeshSelectExpression.svg
Normal 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 |
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
7
resources/function_help/json/$face_area
Normal file
7
resources/function_help/json/$face_area
Normal 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"}]
|
||||
}
|
||||
7
resources/function_help/json/$face_index
Normal file
7
resources/function_help/json/$face_index
Normal 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"}]
|
||||
}
|
||||
8
resources/function_help/json/$vertex_index
Normal file
8
resources/function_help/json/$vertex_index
Normal 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"}
|
||||
]
|
||||
}
|
||||
8
resources/function_help/json/$vertex_x
Normal file
8
resources/function_help/json/$vertex_x
Normal 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"}
|
||||
]
|
||||
}
|
||||
8
resources/function_help/json/$vertex_y
Normal file
8
resources/function_help/json/$vertex_y
Normal 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"}
|
||||
]
|
||||
}
|
||||
@ -251,6 +251,7 @@ set(QGIS_APP_SRCS
|
||||
mesh/qgsnewmeshlayerdialog.cpp
|
||||
mesh/qgsmaptooleditmeshframe.cpp
|
||||
mesh/qgsmeshtransformcoordinatesdockwidget.cpp
|
||||
mesh/qgsmeshselectbyexpressiondialog.cpp
|
||||
)
|
||||
|
||||
if (WITH_SPATIALITE)
|
||||
|
||||
@ -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 );
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
113
src/app/mesh/qgsmeshselectbyexpressiondialog.cpp
Normal file
113
src/app/mesh/qgsmeshselectbyexpressiondialog.cpp
Normal 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() );
|
||||
}
|
||||
64
src/app/mesh/qgsmeshselectbyexpressiondialog.h
Normal file
64
src/app/mesh/qgsmeshselectbyexpressiondialog.h
Normal 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
|
||||
@ -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 )
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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:
|
||||
|
||||
|
||||
@ -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 ) );
|
||||
|
||||
@ -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 );
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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:
|
||||
|
||||
/**
|
||||
|
||||
140
src/ui/mesh/qgsmeshselectbyexpressiondialogbase.ui
Normal file
140
src/ui/mesh/qgsmeshselectbyexpressiondialogbase.ui
Normal 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>
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user