Skip to content

Commit 697f2bc

Browse files
authoredOct 15, 2021
Improve robustness of mesh editing (#45497)
* renaming and typo * QgsMeshEditor:checkConsistency returns error * change approach for remove vertex filling hole on boundary * split QgsMeshEditor::removeVertex (with/without filling holes) * check edited mesh before saving * send start/save editing message to message bar
1 parent 527b111 commit 697f2bc

File tree

10 files changed

+817
-217
lines changed

10 files changed

+817
-217
lines changed
 

‎python/core/auto_generated/mesh/qgsmesheditor.sip.in

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,20 @@ The method returns the number of vertices effectivly added.
137137
if the mesh hasn't topological error before this operation, the toological operation always succeed
138138
%End
139139

140-
QgsMeshEditingError removeVertices( const QList<int> &verticesToRemoveIndexes, bool fillHoles = false );
140+
QgsMeshEditingError removeVerticesWithoutFillHoles( const QList<int> &verticesToRemoveIndexes );
141141
%Docstring
142-
Removes vertices with indexes in the list ``verticesToRemoveIndexes`` in the mesh
143-
if ``fillHoles`` is set to ``True``, this operation will fill holes created in the mesh, if not remove the surrounding faces
142+
Removes vertices with indexes in the list ``verticesToRemoveIndexes`` in the mesh removing the surrounding faces without filling the freed space.
144143

145-
If removing these vertices leads to a topological errors, the method will return the corresponding error and the operatio is canceled
144+
If removing these vertices leads to a topological errors, the method will return the corresponding error and the operation is canceled
145+
%End
146+
147+
QList<int> removeVerticesFillHoles( const QList<int> &verticesToRemoveIndexes );
148+
%Docstring
149+
Removes vertices with indexes in the list ``verticesToRemoveIndexes`` in the mesh the surrounding faces AND fills the freed space.
150+
151+
This operation fills holes by a Delaunay triangulation using the surrounding vertices.
152+
Some vertices could no be deleted to avoid topological error even with hole filling (can not be detected before execution).
153+
A list of the remaining vertex indexes is returned.
146154
%End
147155

148156
void changeZValues( const QList<int> &verticesIndexes, const QList<double> &newValues );
@@ -152,7 +160,7 @@ Changes the Z values of the vertices with indexes in ``vertices`` indexes with t
152160

153161
void changeXYValues( const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues );
154162
%Docstring
155-
Changes the (X,Y) coordinates values of the vertices with indexes in ``vertices`` indexes with the values in ``newValues``.
163+
Changes the (X,Y) coordinates values of the vertices with indexes in ``verticesIndexes`` with the values in ``newValues``.
156164
The caller has the responsibility to check if changing the vertices coordinates does not lead to topological errors.
157165
New values are in layer CRS.
158166
%End
@@ -209,7 +217,7 @@ Returns whether the vertex with index ``vertexIndex`` is on a boundary
209217
Returns whether the vertex with index ``vertexIndex`` is a free vertex
210218
%End
211219

212-
bool checkConsistency() const;
220+
bool checkConsistency( QgsMeshEditingError &error ) const;
213221
%Docstring
214222
Return ``True`` if the edited mesh is consistent
215223
%End
@@ -228,6 +236,11 @@ Returns the count of valid faces, that is non void faces in the mesh
228236
int validVerticesCount() const;
229237
%Docstring
230238
Returns the count of valid vertices, that is non void vertices in the mesh
239+
%End
240+
241+
int maximumVerticesPerFace() const;
242+
%Docstring
243+
Returns the maximum count of vertices per face that the mesh can support
231244
%End
232245

233246
signals:

‎src/analysis/mesh/qgsmeshtriangulation.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ QgsTopologicalMesh::Changes QgsMeshEditingDelaunayTriangulation::apply( QgsMeshE
401401
topologicFaces = meshEditor->topologicalMesh().createNewTopologicalFaces( destinationFaces, true, error );
402402

403403
if ( error == QgsMeshEditingError() )
404-
error = meshEditor->topologicalMesh().canFacesBeAdded( topologicFaces );
404+
error = meshEditor->topologicalMesh().facesCanBeAdded( topologicFaces );
405405

406406
switch ( error.errorType )
407407
{

‎src/app/mesh/qgsmaptooleditmeshframe.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,12 +1709,27 @@ void QgsMapToolEditMeshFrame::setSelectedFaces( const QList<int> newSelectedFace
17091709

17101710
void QgsMapToolEditMeshFrame::removeSelectedVerticesFromMesh( bool fillHole )
17111711
{
1712-
QgsMeshEditingError error = mCurrentEditor->removeVertices( mSelectedVertices.keys(), fillHole );
1713-
if ( error != QgsMeshEditingError() )
1712+
if ( fillHole )
17141713
{
1715-
QgisApp::instance()->messageBar()->pushWarning(
1716-
tr( "Mesh editing" ),
1717-
tr( "removing the vertex %1 leads to a topological error, operation canceled." ).arg( error.elementIndex ) );
1714+
1715+
QList<int> remainingVertex = mCurrentEditor->removeVerticesFillHoles( mSelectedVertices.keys() );
1716+
1717+
if ( !remainingVertex.isEmpty() )
1718+
{
1719+
QgisApp::instance()->messageBar()->pushWarning(
1720+
tr( "Mesh editing" ),
1721+
tr( "%n vertices were not removed", nullptr, remainingVertex.count() ) );
1722+
}
1723+
}
1724+
else
1725+
{
1726+
QgsMeshEditingError error = mCurrentEditor->removeVerticesWithoutFillHoles( mSelectedVertices.keys() );
1727+
if ( error != QgsMeshEditingError() )
1728+
{
1729+
QgisApp::instance()->messageBar()->pushWarning(
1730+
tr( "Mesh editing" ),
1731+
tr( "removing the vertex %1 leads to a topological error, operation canceled." ).arg( error.elementIndex ) );
1732+
}
17181733
}
17191734
}
17201735

‎src/app/qgisapp.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11482,7 +11482,11 @@ bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel )
1148211482
mActionToggleEditing->setChecked( res );
1148311483

1148411484
if ( !res )
11485-
QgsMessageLog::logMessage( tr( "Unable to start mesh editing for layer \"%1\"" ).arg( mlayer->name() ) );
11485+
{
11486+
visibleMessageBar()->pushWarning(
11487+
tr( "Mesh editing" ),
11488+
tr( "Unable to start mesh editing for layer \"%1\"" ).arg( mlayer->name() ) );
11489+
}
1148611490
}
1148711491
else if ( mlayer->isModified() )
1148811492
{
@@ -11504,6 +11508,9 @@ bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel )
1150411508
QgsCanvasRefreshBlocker refreshBlocker;
1150511509
if ( !mlayer->commitFrameEditing( transform, false ) )
1150611510
{
11511+
visibleMessageBar()->pushWarning(
11512+
tr( "Mesh editing" ),
11513+
tr( "Unable to save editing for layer \"%1\"" ).arg( mlayer->name() ) );
1150711514
res = false;
1150811515
}
1150911516

@@ -11606,7 +11613,11 @@ void QgisApp::saveMeshLayerEdits( QgsMapLayer *layer, bool leaveEditable, bool t
1160611613

1160711614
QgsCanvasRefreshBlocker refreshBlocker;
1160811615
QgsCoordinateTransform transform( mlayer->crs(), mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance() );
11609-
mlayer->commitFrameEditing( transform, leaveEditable );
11616+
11617+
if ( !mlayer->commitFrameEditing( transform, leaveEditable ) )
11618+
visibleMessageBar()->pushWarning(
11619+
tr( "Mesh editing" ),
11620+
tr( "Unable to save editing for layer \"%1\"" ).arg( mlayer->name() ) );
1161011621

1161111622
if ( triggerRepaint )
1161211623
{

‎src/core/mesh/qgsmesheditor.cpp

Lines changed: 98 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ bool QgsMeshEditor::faceCanBeAdded( const QgsMeshFace &face )
176176

177177
// Check if there is topological error with the mesh
178178
QgsTopologicalMesh::TopologicalFaces topologicalFaces = mTopologicalMesh.createNewTopologicalFaces( facesToAdd, true, error );
179-
error = mTopologicalMesh.canFacesBeAdded( topologicalFaces );
179+
error = mTopologicalMesh.facesCanBeAdded( topologicalFaces );
180180

181181
if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
182182
return false;
@@ -249,14 +249,22 @@ void QgsMeshEditor::applyAddVertex( QgsMeshEditor::Edit &edit, const QgsMeshVert
249249
updateElementsCount( edit.topologicalChanges );
250250
}
251251

252-
void QgsMeshEditor::applyRemoveVertexFillHole( QgsMeshEditor::Edit &edit, int vertexIndex )
252+
bool QgsMeshEditor::applyRemoveVertexFillHole( QgsMeshEditor::Edit &edit, int vertexIndex )
253253
{
254-
applyEditOnTriangularMesh( edit, mTopologicalMesh.removeVertexFillHole( vertexIndex ) );
254+
QgsTopologicalMesh::Changes changes = mTopologicalMesh.removeVertexFillHole( vertexIndex );
255255

256-
if ( mZValueDatasetGroup )
257-
mZValueDatasetGroup->setStatisticObsolete();
256+
if ( !changes.isEmpty() )
257+
{
258+
applyEditOnTriangularMesh( edit, changes );
258259

259-
updateElementsCount( edit.topologicalChanges );
260+
if ( mZValueDatasetGroup )
261+
mZValueDatasetGroup->setStatisticObsolete();
262+
263+
updateElementsCount( edit.topologicalChanges );
264+
return true;
265+
}
266+
else
267+
return false;
260268
}
261269

262270
void QgsMeshEditor::applyRemoveVerticesWithoutFillHole( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes )
@@ -348,9 +356,10 @@ void QgsMeshEditor::updateElementsCount( const QgsTopologicalMesh::Changes &chan
348356
}
349357
}
350358

351-
bool QgsMeshEditor::checkConsistency() const
359+
bool QgsMeshEditor::checkConsistency( QgsMeshEditingError &error ) const
352360
{
353-
switch ( mTopologicalMesh.checkConsistency().errorType )
361+
error = mTopologicalMesh.checkConsistency();
362+
switch ( error.errorType )
354363
{
355364
case Qgis::MeshEditingErrorType::NoError:
356365
break;
@@ -429,9 +438,14 @@ int QgsMeshEditor::validVerticesCount() const
429438
return mValidVerticesCount;
430439
}
431440

441+
int QgsMeshEditor::maximumVerticesPerFace() const
442+
{
443+
return mMaximumVerticesPerFace;
444+
}
445+
432446
QgsMeshEditingError QgsMeshEditor::removeFaces( const QList<int> &facesToRemove )
433447
{
434-
QgsMeshEditingError error = mTopologicalMesh.canFacesBeRemoved( facesToRemove );
448+
QgsMeshEditingError error = mTopologicalMesh.facesCanBeRemoved( facesToRemove );
435449
if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
436450
return error;
437451

@@ -468,7 +482,7 @@ void QgsMeshEditor::merge( int vertexIndex1, int vertexIndex2 )
468482

469483
bool QgsMeshEditor::faceCanBeSplit( int faceIndex ) const
470484
{
471-
return mTopologicalMesh.faceCanBeSplit( faceIndex );
485+
return mTopologicalMesh.canBeSplit( faceIndex );
472486
}
473487

474488
int QgsMeshEditor::splitFaces( const QList<int> &faceIndexes )
@@ -520,7 +534,7 @@ QgsMeshEditingError QgsMeshEditor::addFaces( const QVector<QVector<int> > &faces
520534

521535
QgsTopologicalMesh::TopologicalFaces topologicalFaces = mTopologicalMesh.createNewTopologicalFaces( facesToAdd, true, error );
522536

523-
error = mTopologicalMesh.canFacesBeAdded( topologicalFaces );
537+
error = mTopologicalMesh.facesCanBeAdded( topologicalFaces );
524538

525539
if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
526540
return error;
@@ -583,31 +597,34 @@ int QgsMeshEditor::addPointsAsVertices( const QVector<QgsPoint> &point, double t
583597
return addVertices( point, tolerance );
584598
}
585599

586-
QgsMeshEditingError QgsMeshEditor::removeVertices( const QList<int> &verticesToRemoveIndexes, bool fillHoles )
600+
QgsMeshEditingError QgsMeshEditor::removeVerticesWithoutFillHoles( const QList<int> &verticesToRemoveIndexes )
587601
{
588602
QgsMeshEditingError error;
589603

590604
QList<int> verticesIndexes = verticesToRemoveIndexes;
591605

592-
if ( !fillHoles )
593-
{
594-
QSet<int> concernedNativeFaces;
595-
for ( const int vi : std::as_const( verticesIndexes ) )
596-
concernedNativeFaces.unite( qgis::listToSet( mTopologicalMesh.facesAroundVertex( vi ) ) );
606+
QSet<int> concernedNativeFaces;
607+
for ( const int vi : std::as_const( verticesIndexes ) )
608+
concernedNativeFaces.unite( qgis::listToSet( mTopologicalMesh.facesAroundVertex( vi ) ) );
597609

598-
error = mTopologicalMesh.canFacesBeRemoved( concernedNativeFaces.values() );
599-
if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
600-
return error;
601-
}
610+
error = mTopologicalMesh.facesCanBeRemoved( concernedNativeFaces.values() );
602611

603-
if ( error.errorType == Qgis::MeshEditingErrorType::NoError )
604-
{
605-
mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVertices( this, verticesIndexes, fillHoles ) );
606-
}
612+
if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
613+
return error;
607614

615+
mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles( this, verticesIndexes ) );
608616
return error;
609617
}
610618

619+
QList<int> QgsMeshEditor::removeVerticesFillHoles( const QList<int> &verticesToRemoveIndexes )
620+
{
621+
QList<int> remainingVertices;
622+
mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVerticesFillHoles( this, verticesToRemoveIndexes, &remainingVertices ) );
623+
624+
return remainingVertices;
625+
}
626+
627+
611628
void QgsMeshEditor::changeZValues( const QList<int> &verticesIndexes, const QList<double> &newZValues )
612629
{
613630
mUndoStack->push( new QgsMeshLayerUndoCommandChangeZValue( this, verticesIndexes, newZValues ) );
@@ -802,32 +819,76 @@ void QgsMeshLayerUndoCommandAddVertices::redo()
802819
}
803820
}
804821

805-
QgsMeshLayerUndoCommandRemoveVertices::QgsMeshLayerUndoCommandRemoveVertices( QgsMeshEditor *meshEditor, const QList<int> &verticesToRemoveIndexes, bool fillHole )
822+
QgsMeshLayerUndoCommandRemoveVerticesFillHoles::QgsMeshLayerUndoCommandRemoveVerticesFillHoles(
823+
QgsMeshEditor *meshEditor,
824+
const QList<int> &verticesToRemoveIndexes,
825+
QList<int> *remainingVerticesPointer )
806826
: QgsMeshLayerUndoCommandMeshEdit( meshEditor )
807827
, mVerticesToRemoveIndexes( verticesToRemoveIndexes )
808-
, mFillHole( fillHole )
828+
, mRemainingVerticesPointer( remainingVerticesPointer )
809829
{
810-
setText( QObject::tr( "Remove %n vertices", nullptr, verticesToRemoveIndexes.count() ) ) ;
830+
setText( QObject::tr( "Remove %n vertices filling holes", nullptr, verticesToRemoveIndexes.count() ) );
811831
}
812832

813-
void QgsMeshLayerUndoCommandRemoveVertices::redo()
833+
void QgsMeshLayerUndoCommandRemoveVerticesFillHoles::redo()
814834
{
835+
int initialVertexCount = mVerticesToRemoveIndexes.count();
815836
if ( !mVerticesToRemoveIndexes.isEmpty() )
816837
{
817838
QgsMeshEditor::Edit edit;
818-
if ( mFillHole )
839+
QList<int> vertexToRetry;
840+
while ( !mVerticesToRemoveIndexes.isEmpty() )
819841
{
842+
// try again and again until there is no vertices to remove anymore or nothing is removed.
820843
for ( const int &vertex : std::as_const( mVerticesToRemoveIndexes ) )
821844
{
822-
mMeshEditor->applyRemoveVertexFillHole( edit, vertex );
823-
mEdits.append( edit );
845+
if ( mMeshEditor->applyRemoveVertexFillHole( edit, vertex ) )
846+
mEdits.append( edit );
847+
else
848+
vertexToRetry.append( vertex );
824849
}
850+
851+
if ( vertexToRetry.count() == mVerticesToRemoveIndexes.count() )
852+
break;
853+
else
854+
mVerticesToRemoveIndexes = vertexToRetry;
825855
}
826-
else
827-
{
828-
mMeshEditor->applyRemoveVerticesWithoutFillHole( edit, mVerticesToRemoveIndexes );
829-
mEdits.append( edit );
830-
}
856+
857+
if ( initialVertexCount == mVerticesToRemoveIndexes.count() )
858+
setObsolete( true );
859+
860+
if ( mRemainingVerticesPointer != nullptr )
861+
*mRemainingVerticesPointer = mVerticesToRemoveIndexes;
862+
863+
mRemainingVerticesPointer = nullptr;
864+
865+
mVerticesToRemoveIndexes.clear(); //not needed anymore, changes are store in mEdits
866+
}
867+
else
868+
{
869+
for ( QgsMeshEditor::Edit &edit : mEdits )
870+
mMeshEditor->applyEdit( edit );
871+
}
872+
}
873+
874+
875+
QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles::QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles(
876+
QgsMeshEditor *meshEditor,
877+
const QList<int> &verticesToRemoveIndexes )
878+
: QgsMeshLayerUndoCommandMeshEdit( meshEditor )
879+
, mVerticesToRemoveIndexes( verticesToRemoveIndexes )
880+
{
881+
setText( QObject::tr( "Remove %n vertices without filling holes", nullptr, verticesToRemoveIndexes.count() ) ) ;
882+
}
883+
884+
void QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles::redo()
885+
{
886+
if ( !mVerticesToRemoveIndexes.isEmpty() )
887+
{
888+
QgsMeshEditor::Edit edit;
889+
890+
mMeshEditor->applyRemoveVerticesWithoutFillHole( edit, mVerticesToRemoveIndexes );
891+
mEdits.append( edit );
831892

832893
mVerticesToRemoveIndexes.clear(); //not needed anymore, changes are store in mEdits
833894
}

0 commit comments

Comments
 (0)
Please sign in to comment.