Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vcloarec committed Oct 15, 2021
1 parent 527b111 commit 697f2bc
Show file tree
Hide file tree
Showing 10 changed files with 817 additions and 217 deletions.
25 changes: 19 additions & 6 deletions python/core/auto_generated/mesh/qgsmesheditor.sip.in
Expand Up @@ -137,12 +137,20 @@ The method returns the number of vertices effectivly added.
if the mesh hasn't topological error before this operation, the toological operation always succeed
%End

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

If removing these vertices leads to a topological errors, the method will return the corresponding error and the operatio is canceled
If removing these vertices leads to a topological errors, the method will return the corresponding error and the operation is canceled
%End

QList<int> removeVerticesFillHoles( const QList<int> &verticesToRemoveIndexes );
%Docstring
Removes vertices with indexes in the list ``verticesToRemoveIndexes`` in the mesh the surrounding faces AND fills the freed space.

This operation fills holes by a Delaunay triangulation using the surrounding vertices.
Some vertices could no be deleted to avoid topological error even with hole filling (can not be detected before execution).
A list of the remaining vertex indexes is returned.
%End

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

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

bool checkConsistency() const;
bool checkConsistency( QgsMeshEditingError &error ) const;
%Docstring
Return ``True`` if the edited mesh is consistent
%End
Expand All @@ -228,6 +236,11 @@ Returns the count of valid faces, that is non void faces in the mesh
int validVerticesCount() const;
%Docstring
Returns the count of valid vertices, that is non void vertices in the mesh
%End

int maximumVerticesPerFace() const;
%Docstring
Returns the maximum count of vertices per face that the mesh can support
%End

signals:
Expand Down
2 changes: 1 addition & 1 deletion src/analysis/mesh/qgsmeshtriangulation.cpp
Expand Up @@ -401,7 +401,7 @@ QgsTopologicalMesh::Changes QgsMeshEditingDelaunayTriangulation::apply( QgsMeshE
topologicFaces = meshEditor->topologicalMesh().createNewTopologicalFaces( destinationFaces, true, error );

if ( error == QgsMeshEditingError() )
error = meshEditor->topologicalMesh().canFacesBeAdded( topologicFaces );
error = meshEditor->topologicalMesh().facesCanBeAdded( topologicFaces );

switch ( error.errorType )
{
Expand Down
25 changes: 20 additions & 5 deletions src/app/mesh/qgsmaptooleditmeshframe.cpp
Expand Up @@ -1709,12 +1709,27 @@ void QgsMapToolEditMeshFrame::setSelectedFaces( const QList<int> newSelectedFace

void QgsMapToolEditMeshFrame::removeSelectedVerticesFromMesh( bool fillHole )
{
QgsMeshEditingError error = mCurrentEditor->removeVertices( mSelectedVertices.keys(), fillHole );
if ( error != QgsMeshEditingError() )
if ( fillHole )
{
QgisApp::instance()->messageBar()->pushWarning(
tr( "Mesh editing" ),
tr( "removing the vertex %1 leads to a topological error, operation canceled." ).arg( error.elementIndex ) );

QList<int> remainingVertex = mCurrentEditor->removeVerticesFillHoles( mSelectedVertices.keys() );

if ( !remainingVertex.isEmpty() )
{
QgisApp::instance()->messageBar()->pushWarning(
tr( "Mesh editing" ),
tr( "%n vertices were not removed", nullptr, remainingVertex.count() ) );
}
}
else
{
QgsMeshEditingError error = mCurrentEditor->removeVerticesWithoutFillHoles( mSelectedVertices.keys() );
if ( error != QgsMeshEditingError() )
{
QgisApp::instance()->messageBar()->pushWarning(
tr( "Mesh editing" ),
tr( "removing the vertex %1 leads to a topological error, operation canceled." ).arg( error.elementIndex ) );
}
}
}

Expand Down
15 changes: 13 additions & 2 deletions src/app/qgisapp.cpp
Expand Up @@ -11482,7 +11482,11 @@ bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel )
mActionToggleEditing->setChecked( res );

if ( !res )
QgsMessageLog::logMessage( tr( "Unable to start mesh editing for layer \"%1\"" ).arg( mlayer->name() ) );
{
visibleMessageBar()->pushWarning(
tr( "Mesh editing" ),
tr( "Unable to start mesh editing for layer \"%1\"" ).arg( mlayer->name() ) );
}
}
else if ( mlayer->isModified() )
{
Expand All @@ -11504,6 +11508,9 @@ bool QgisApp::toggleEditingMeshLayer( QgsMeshLayer *mlayer, bool allowCancel )
QgsCanvasRefreshBlocker refreshBlocker;
if ( !mlayer->commitFrameEditing( transform, false ) )
{
visibleMessageBar()->pushWarning(
tr( "Mesh editing" ),
tr( "Unable to save editing for layer \"%1\"" ).arg( mlayer->name() ) );
res = false;
}

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

QgsCanvasRefreshBlocker refreshBlocker;
QgsCoordinateTransform transform( mlayer->crs(), mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance() );
mlayer->commitFrameEditing( transform, leaveEditable );

if ( !mlayer->commitFrameEditing( transform, leaveEditable ) )
visibleMessageBar()->pushWarning(
tr( "Mesh editing" ),
tr( "Unable to save editing for layer \"%1\"" ).arg( mlayer->name() ) );

if ( triggerRepaint )
{
Expand Down
135 changes: 98 additions & 37 deletions src/core/mesh/qgsmesheditor.cpp
Expand Up @@ -176,7 +176,7 @@ bool QgsMeshEditor::faceCanBeAdded( const QgsMeshFace &face )

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

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

void QgsMeshEditor::applyRemoveVertexFillHole( QgsMeshEditor::Edit &edit, int vertexIndex )
bool QgsMeshEditor::applyRemoveVertexFillHole( QgsMeshEditor::Edit &edit, int vertexIndex )
{
applyEditOnTriangularMesh( edit, mTopologicalMesh.removeVertexFillHole( vertexIndex ) );
QgsTopologicalMesh::Changes changes = mTopologicalMesh.removeVertexFillHole( vertexIndex );

if ( mZValueDatasetGroup )
mZValueDatasetGroup->setStatisticObsolete();
if ( !changes.isEmpty() )
{
applyEditOnTriangularMesh( edit, changes );

updateElementsCount( edit.topologicalChanges );
if ( mZValueDatasetGroup )
mZValueDatasetGroup->setStatisticObsolete();

updateElementsCount( edit.topologicalChanges );
return true;
}
else
return false;
}

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

bool QgsMeshEditor::checkConsistency() const
bool QgsMeshEditor::checkConsistency( QgsMeshEditingError &error ) const
{
switch ( mTopologicalMesh.checkConsistency().errorType )
error = mTopologicalMesh.checkConsistency();
switch ( error.errorType )
{
case Qgis::MeshEditingErrorType::NoError:
break;
Expand Down Expand Up @@ -429,9 +438,14 @@ int QgsMeshEditor::validVerticesCount() const
return mValidVerticesCount;
}

int QgsMeshEditor::maximumVerticesPerFace() const
{
return mMaximumVerticesPerFace;
}

QgsMeshEditingError QgsMeshEditor::removeFaces( const QList<int> &facesToRemove )
{
QgsMeshEditingError error = mTopologicalMesh.canFacesBeRemoved( facesToRemove );
QgsMeshEditingError error = mTopologicalMesh.facesCanBeRemoved( facesToRemove );
if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
return error;

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

bool QgsMeshEditor::faceCanBeSplit( int faceIndex ) const
{
return mTopologicalMesh.faceCanBeSplit( faceIndex );
return mTopologicalMesh.canBeSplit( faceIndex );
}

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

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

error = mTopologicalMesh.canFacesBeAdded( topologicalFaces );
error = mTopologicalMesh.facesCanBeAdded( topologicalFaces );

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

QgsMeshEditingError QgsMeshEditor::removeVertices( const QList<int> &verticesToRemoveIndexes, bool fillHoles )
QgsMeshEditingError QgsMeshEditor::removeVerticesWithoutFillHoles( const QList<int> &verticesToRemoveIndexes )
{
QgsMeshEditingError error;

QList<int> verticesIndexes = verticesToRemoveIndexes;

if ( !fillHoles )
{
QSet<int> concernedNativeFaces;
for ( const int vi : std::as_const( verticesIndexes ) )
concernedNativeFaces.unite( qgis::listToSet( mTopologicalMesh.facesAroundVertex( vi ) ) );
QSet<int> concernedNativeFaces;
for ( const int vi : std::as_const( verticesIndexes ) )
concernedNativeFaces.unite( qgis::listToSet( mTopologicalMesh.facesAroundVertex( vi ) ) );

error = mTopologicalMesh.canFacesBeRemoved( concernedNativeFaces.values() );
if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
return error;
}
error = mTopologicalMesh.facesCanBeRemoved( concernedNativeFaces.values() );

if ( error.errorType == Qgis::MeshEditingErrorType::NoError )
{
mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVertices( this, verticesIndexes, fillHoles ) );
}
if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
return error;

mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles( this, verticesIndexes ) );
return error;
}

QList<int> QgsMeshEditor::removeVerticesFillHoles( const QList<int> &verticesToRemoveIndexes )
{
QList<int> remainingVertices;
mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVerticesFillHoles( this, verticesToRemoveIndexes, &remainingVertices ) );

return remainingVertices;
}


void QgsMeshEditor::changeZValues( const QList<int> &verticesIndexes, const QList<double> &newZValues )
{
mUndoStack->push( new QgsMeshLayerUndoCommandChangeZValue( this, verticesIndexes, newZValues ) );
Expand Down Expand Up @@ -802,32 +819,76 @@ void QgsMeshLayerUndoCommandAddVertices::redo()
}
}

QgsMeshLayerUndoCommandRemoveVertices::QgsMeshLayerUndoCommandRemoveVertices( QgsMeshEditor *meshEditor, const QList<int> &verticesToRemoveIndexes, bool fillHole )
QgsMeshLayerUndoCommandRemoveVerticesFillHoles::QgsMeshLayerUndoCommandRemoveVerticesFillHoles(
QgsMeshEditor *meshEditor,
const QList<int> &verticesToRemoveIndexes,
QList<int> *remainingVerticesPointer )
: QgsMeshLayerUndoCommandMeshEdit( meshEditor )
, mVerticesToRemoveIndexes( verticesToRemoveIndexes )
, mFillHole( fillHole )
, mRemainingVerticesPointer( remainingVerticesPointer )
{
setText( QObject::tr( "Remove %n vertices", nullptr, verticesToRemoveIndexes.count() ) ) ;
setText( QObject::tr( "Remove %n vertices filling holes", nullptr, verticesToRemoveIndexes.count() ) );
}

void QgsMeshLayerUndoCommandRemoveVertices::redo()
void QgsMeshLayerUndoCommandRemoveVerticesFillHoles::redo()
{
int initialVertexCount = mVerticesToRemoveIndexes.count();
if ( !mVerticesToRemoveIndexes.isEmpty() )
{
QgsMeshEditor::Edit edit;
if ( mFillHole )
QList<int> vertexToRetry;
while ( !mVerticesToRemoveIndexes.isEmpty() )
{
// try again and again until there is no vertices to remove anymore or nothing is removed.
for ( const int &vertex : std::as_const( mVerticesToRemoveIndexes ) )
{
mMeshEditor->applyRemoveVertexFillHole( edit, vertex );
mEdits.append( edit );
if ( mMeshEditor->applyRemoveVertexFillHole( edit, vertex ) )
mEdits.append( edit );
else
vertexToRetry.append( vertex );
}

if ( vertexToRetry.count() == mVerticesToRemoveIndexes.count() )
break;
else
mVerticesToRemoveIndexes = vertexToRetry;
}
else
{
mMeshEditor->applyRemoveVerticesWithoutFillHole( edit, mVerticesToRemoveIndexes );
mEdits.append( edit );
}

if ( initialVertexCount == mVerticesToRemoveIndexes.count() )
setObsolete( true );

if ( mRemainingVerticesPointer != nullptr )
*mRemainingVerticesPointer = mVerticesToRemoveIndexes;

mRemainingVerticesPointer = nullptr;

mVerticesToRemoveIndexes.clear(); //not needed anymore, changes are store in mEdits
}
else
{
for ( QgsMeshEditor::Edit &edit : mEdits )
mMeshEditor->applyEdit( edit );
}
}


QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles::QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles(
QgsMeshEditor *meshEditor,
const QList<int> &verticesToRemoveIndexes )
: QgsMeshLayerUndoCommandMeshEdit( meshEditor )
, mVerticesToRemoveIndexes( verticesToRemoveIndexes )
{
setText( QObject::tr( "Remove %n vertices without filling holes", nullptr, verticesToRemoveIndexes.count() ) ) ;
}

void QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles::redo()
{
if ( !mVerticesToRemoveIndexes.isEmpty() )
{
QgsMeshEditor::Edit edit;

mMeshEditor->applyRemoveVerticesWithoutFillHole( edit, mVerticesToRemoveIndexes );
mEdits.append( edit );

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

0 comments on commit 697f2bc

Please sign in to comment.