1 /***************************************************************************
2   qgsmesheditor.cpp - QgsMeshEditor
3 
4  ---------------------
5  begin                : 8.6.2021
6  copyright            : (C) 2021 by Vincent Cloarec
7  email                : vcloarec at gmail dot com
8  ***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 
17 #include "qgis.h"
18 #include "qgsmesheditor.h"
19 #include "qgsmeshdataprovider.h"
20 #include "qgstriangularmesh.h"
21 #include "qgsmeshlayer.h"
22 #include "qgsmeshlayerutils.h"
23 #include "qgslogger.h"
24 #include "qgsgeometryengine.h"
25 #include "qgsmeshadvancedediting.h"
26 #include "qgsgeometryutils.h"
27 
28 #include <poly2tri.h>
29 
30 #include <QSet>
31 
32 
QgsMeshEditor(QgsMeshLayer * meshLayer)33 QgsMeshEditor::QgsMeshEditor( QgsMeshLayer *meshLayer )
34   : QObject( meshLayer )
35   , mMesh( meshLayer ? meshLayer->nativeMesh() : nullptr )
36   , mTriangularMesh( meshLayer ? meshLayer->triangularMeshByLodIndex( 0 ) : nullptr )
37   , mUndoStack( meshLayer ? meshLayer->undoStack() : nullptr )
38 {
39   if ( meshLayer && meshLayer->dataProvider() )
40     mMaximumVerticesPerFace = meshLayer->dataProvider()->maximumVerticesCountPerFace();
41 
42   if ( meshLayer )
43     connect( mUndoStack, &QUndoStack::indexChanged, this, &QgsMeshEditor::meshEdited );
44 }
45 
QgsMeshEditor(QgsMesh * nativeMesh,QgsTriangularMesh * triangularMesh,QObject * parent)46 QgsMeshEditor::QgsMeshEditor( QgsMesh *nativeMesh, QgsTriangularMesh *triangularMesh, QObject *parent )
47   : QObject( parent )
48   , mMesh( nativeMesh )
49   , mTriangularMesh( triangularMesh )
50 {
51   mUndoStack = new QUndoStack( this );
52   connect( mUndoStack, &QUndoStack::indexChanged, this, &QgsMeshEditor::meshEdited );
53 }
54 
createZValueDatasetGroup()55 QgsMeshDatasetGroup *QgsMeshEditor::createZValueDatasetGroup()
56 {
57   std::unique_ptr<QgsMeshDatasetGroup> zValueDatasetGroup = std::make_unique<QgsMeshVerticesElevationDatasetGroup>( tr( "vertices Z value" ), mMesh );
58   mZValueDatasetGroup = zValueDatasetGroup.get();
59   return zValueDatasetGroup.release();
60 }
61 
62 QgsMeshEditor::~QgsMeshEditor() = default;
63 
initialize()64 QgsMeshEditingError QgsMeshEditor::initialize()
65 {
66   QgsMeshEditingError error;
67   mTopologicalMesh = QgsTopologicalMesh::createTopologicalMesh( mMesh, mMaximumVerticesPerFace, error );
68   mValidFacesCount = mMesh->faceCount();
69   mValidVerticesCount = mMesh->vertexCount();
70   return error;
71 }
72 
resetTriangularMesh(QgsTriangularMesh * triangularMesh)73 void QgsMeshEditor::resetTriangularMesh( QgsTriangularMesh *triangularMesh )
74 {
75   mTriangularMesh = triangularMesh;
76 }
77 
isFaceGeometricallyCompatible(const QgsMeshFace & face)78 bool QgsMeshEditor::isFaceGeometricallyCompatible( const QgsMeshFace &face )
79 {
80   const QgsGeometry newFaceGeom = QgsMeshUtils::toGeometry( face, mTriangularMesh->vertices() );
81   std::unique_ptr<QgsGeometryEngine> geomEngine( QgsGeometry::createGeometryEngine( newFaceGeom.constGet() ) );
82   geomEngine->prepareGeometry();
83 
84   QgsRectangle boundingBox = newFaceGeom.boundingBox();
85   QList<int> newFaceVerticesIndexes( face.toList() );
86   int newFaceSize = face.count();
87   QList<int> concernedFaceIndex = mTriangularMesh->nativeFaceIndexForRectangle( boundingBox );
88   if ( !concernedFaceIndex.isEmpty() )
89   {
90     // for each concerned face, we take edges and, if no common vertex with the new face,
91     // check is the edge intersects or is contained in the new face
92     for ( const int faceIndex : concernedFaceIndex )
93     {
94       const QgsMeshFace &existingFace = mMesh->faces.at( faceIndex );
95       int existingFaceSize = existingFace.count();
96       bool shareVertex = false;
97       for ( int i = 0; i < existingFaceSize; ++i )
98       {
99         if ( newFaceVerticesIndexes.contains( existingFace.at( i ) ) )
100         {
101           shareVertex = true;
102           break;
103         }
104       }
105 
106       if ( shareVertex )
107       {
108         for ( int i = 0; i < existingFaceSize; ++i )
109         {
110           int index1 = existingFace.at( i );
111           int index2 = existingFace.at( ( i + 1 ) % existingFaceSize );
112           const QgsMeshVertex &v1 = mTriangularMesh->vertices().at( index1 );
113           const QgsMeshVertex &v2 = mTriangularMesh->vertices().at( index2 );
114           QgsGeometry edgeGeom = QgsGeometry( new QgsLineString( v1, v2 ) );
115 
116           if ( ! newFaceVerticesIndexes.contains( index1 )  && !newFaceVerticesIndexes.contains( index2 ) )
117           {
118             // test if the edge that not contains a shared vertex intersect the entire new face
119             if ( geomEngine->intersects( edgeGeom.constGet() ) )
120               return false;
121           }
122           else
123           {
124             for ( int vi = 0; vi < newFaceVerticesIndexes.count(); ++vi )
125             {
126               int vertInNewFace1 = newFaceVerticesIndexes.at( vi );
127               int vertInNewFace2 = newFaceVerticesIndexes.at( ( vi + 1 ) % newFaceSize );
128               if ( vertInNewFace1 != index1 && vertInNewFace2 != index2 && vertInNewFace1 != index2 && vertInNewFace2 != index1 )
129               {
130                 const QgsMeshVertex &nv1 = mTriangularMesh->vertices().at( vertInNewFace1 );
131                 const QgsMeshVertex &nv2 = mTriangularMesh->vertices().at( vertInNewFace2 );
132                 QgsGeometry newEdgeGeom = QgsGeometry( new QgsLineString( nv1, nv2 ) );
133 
134                 if ( newEdgeGeom.intersects( edgeGeom ) )
135                   return false;
136               }
137             }
138           }
139         }
140       }
141       else
142       {
143         const QgsGeometry existingFaceGeom = QgsMeshUtils::toGeometry( existingFace, mTriangularMesh->vertices() );
144         if ( geomEngine->intersects( existingFaceGeom.constGet() ) )
145           return false;
146       }
147     }
148   }
149 
150   // Then search for free vertices included in the new face
151   const QList<int> &freeVertices = freeVerticesIndexes();
152 
153   for ( const int freeVertexIndex : freeVertices )
154   {
155     if ( newFaceVerticesIndexes.contains( freeVertexIndex ) )
156       continue;
157 
158     const QgsMeshVertex &vertex = mTriangularMesh->vertices().at( freeVertexIndex );
159     if ( geomEngine->contains( &vertex ) )
160       return false;
161   }
162 
163   return true;
164 }
165 
166 
faceCanBeAdded(const QgsMeshFace & face)167 bool QgsMeshEditor::faceCanBeAdded( const QgsMeshFace &face )
168 {
169   QgsMeshEditingError error;
170 
171   // Prepare and check the face
172   QVector<QgsMeshFace> facesToAdd = prepareFaces( {face}, error );
173 
174   if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
175     return false;
176 
177   // Check if there is topological error with the mesh
178   QgsTopologicalMesh::TopologicalFaces topologicalFaces = mTopologicalMesh.createNewTopologicalFaces( facesToAdd, true, error );
179   error = mTopologicalMesh.facesCanBeAdded( topologicalFaces );
180 
181   if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
182     return false;
183 
184   // Check geometry compatibility
185   // With the topological check, we know that the new face is not included in an existing one
186   // But maybe, the new face includes or intersects existing faces or free vertices, we need to check
187   // First search for faces intersecting the bounding box of the new face.
188 
189   return isFaceGeometricallyCompatible( face );
190 }
191 
applyEdit(QgsMeshEditor::Edit & edit)192 void QgsMeshEditor::applyEdit( QgsMeshEditor::Edit &edit )
193 {
194   mTopologicalMesh.applyChanges( edit.topologicalChanges );
195   mTriangularMesh->applyChanges( edit.triangularMeshChanges );
196 
197   if ( mZValueDatasetGroup &&
198        ( !edit.topologicalChanges.newVerticesZValues().isEmpty() ||
199          !edit.topologicalChanges.verticesToRemoveIndexes().isEmpty() ||
200          !edit.topologicalChanges.addedVertices().isEmpty() ) )
201     mZValueDatasetGroup->setStatisticObsolete();
202 
203   updateElementsCount( edit.topologicalChanges );
204 }
205 
reverseEdit(QgsMeshEditor::Edit & edit)206 void QgsMeshEditor::reverseEdit( QgsMeshEditor::Edit &edit )
207 {
208   mTopologicalMesh.reverseChanges( edit.topologicalChanges );
209   mTriangularMesh->reverseChanges( edit.triangularMeshChanges, *mMesh );
210 
211   if ( mZValueDatasetGroup &&
212        ( !edit.topologicalChanges.newVerticesZValues().isEmpty() ||
213          !edit.topologicalChanges.verticesToRemoveIndexes().isEmpty() ||
214          !edit.topologicalChanges.addedVertices().isEmpty() ) )
215     mZValueDatasetGroup->setStatisticObsolete();
216 
217   updateElementsCount( edit.topologicalChanges, false );
218 }
219 
applyAddVertex(QgsMeshEditor::Edit & edit,const QgsMeshVertex & vertex,double tolerance)220 void QgsMeshEditor::applyAddVertex( QgsMeshEditor::Edit &edit, const QgsMeshVertex &vertex, double tolerance )
221 {
222   QgsMeshVertex vertexInTriangularCoordinate =  mTriangularMesh->nativeToTriangularCoordinates( vertex );
223 
224   //check if edges is closest than the tolerance from the vertex
225   int faceEdgeIntersect = -1;
226   int edgePosition = -1;
227 
228   QgsTopologicalMesh::Changes topologicChanges;
229 
230   if ( edgeIsClose( vertexInTriangularCoordinate, tolerance, faceEdgeIntersect, edgePosition ) )
231   {
232     topologicChanges = mTopologicalMesh.insertVertexInFacesEdge( faceEdgeIntersect, edgePosition, vertex );
233   }
234   else
235   {
236     int includingFaceIndex = mTriangularMesh->nativeFaceIndexForPoint( vertexInTriangularCoordinate );
237 
238     if ( includingFaceIndex != -1 )
239       topologicChanges = mTopologicalMesh.addVertexInFace( includingFaceIndex, vertex );
240     else
241       topologicChanges = mTopologicalMesh.addFreeVertex( vertex );
242   }
243 
244   applyEditOnTriangularMesh( edit, topologicChanges );
245 
246   if ( mZValueDatasetGroup )
247     mZValueDatasetGroup->setStatisticObsolete();
248 
249   updateElementsCount( edit.topologicalChanges );
250 }
251 
applyRemoveVertexFillHole(QgsMeshEditor::Edit & edit,int vertexIndex)252 bool QgsMeshEditor::applyRemoveVertexFillHole( QgsMeshEditor::Edit &edit, int vertexIndex )
253 {
254   QgsTopologicalMesh::Changes changes = mTopologicalMesh.removeVertexFillHole( vertexIndex );
255 
256   if ( !changes.isEmpty() )
257   {
258     applyEditOnTriangularMesh( edit, changes );
259 
260     if ( mZValueDatasetGroup )
261       mZValueDatasetGroup->setStatisticObsolete();
262 
263     updateElementsCount( edit.topologicalChanges );
264     return true;
265   }
266   else
267     return false;
268 }
269 
applyRemoveVerticesWithoutFillHole(QgsMeshEditor::Edit & edit,const QList<int> & verticesIndexes)270 void QgsMeshEditor::applyRemoveVerticesWithoutFillHole( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes )
271 {
272   applyEditOnTriangularMesh( edit, mTopologicalMesh.removeVertices( verticesIndexes ) );
273 
274   if ( mZValueDatasetGroup )
275     mZValueDatasetGroup->setStatisticObsolete();
276 
277   updateElementsCount( edit.topologicalChanges );
278 }
279 
applyAddFaces(QgsMeshEditor::Edit & edit,const QgsTopologicalMesh::TopologicalFaces & faces)280 void QgsMeshEditor::applyAddFaces( QgsMeshEditor::Edit &edit, const QgsTopologicalMesh::TopologicalFaces &faces )
281 {
282   applyEditOnTriangularMesh( edit,  mTopologicalMesh.addFaces( faces ) );
283 
284   updateElementsCount( edit.topologicalChanges );
285 }
286 
applyRemoveFaces(QgsMeshEditor::Edit & edit,const QList<int> & faceToRemoveIndex)287 void QgsMeshEditor::applyRemoveFaces( QgsMeshEditor::Edit &edit, const QList<int> &faceToRemoveIndex )
288 {
289   applyEditOnTriangularMesh( edit, mTopologicalMesh.removeFaces( faceToRemoveIndex ) );
290 
291   updateElementsCount( edit.topologicalChanges );
292 }
293 
applyChangeZValue(QgsMeshEditor::Edit & edit,const QList<int> & verticesIndexes,const QList<double> & newValues)294 void QgsMeshEditor::applyChangeZValue( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes, const QList<double> &newValues )
295 {
296   applyEditOnTriangularMesh( edit, mTopologicalMesh.changeZValue( verticesIndexes, newValues ) );
297 
298   if ( mZValueDatasetGroup )
299     mZValueDatasetGroup->setStatisticObsolete();
300 }
301 
applyChangeXYValue(QgsMeshEditor::Edit & edit,const QList<int> & verticesIndexes,const QList<QgsPointXY> & newValues)302 void QgsMeshEditor::applyChangeXYValue( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
303 {
304   applyEditOnTriangularMesh( edit, mTopologicalMesh.changeXYValue( verticesIndexes, newValues ) );
305 }
306 
applyFlipEdge(QgsMeshEditor::Edit & edit,int vertexIndex1,int vertexIndex2)307 void QgsMeshEditor::applyFlipEdge( QgsMeshEditor::Edit &edit, int vertexIndex1, int vertexIndex2 )
308 {
309   applyEditOnTriangularMesh( edit, mTopologicalMesh.flipEdge( vertexIndex1, vertexIndex2 ) );
310 
311   updateElementsCount( edit.topologicalChanges );
312 }
313 
applyMerge(QgsMeshEditor::Edit & edit,int vertexIndex1,int vertexIndex2)314 void QgsMeshEditor::applyMerge( QgsMeshEditor::Edit &edit, int vertexIndex1, int vertexIndex2 )
315 {
316   applyEditOnTriangularMesh( edit, mTopologicalMesh.merge( vertexIndex1, vertexIndex2 ) );
317 
318   updateElementsCount( edit.topologicalChanges );
319 }
320 
applySplit(QgsMeshEditor::Edit & edit,int faceIndex)321 void QgsMeshEditor::applySplit( QgsMeshEditor::Edit &edit, int faceIndex )
322 {
323   applyEditOnTriangularMesh( edit, mTopologicalMesh.splitFace( faceIndex ) );
324 
325   updateElementsCount( edit.topologicalChanges );
326 }
327 
applyAdvancedEdit(QgsMeshEditor::Edit & edit,QgsMeshAdvancedEditing * editing)328 void QgsMeshEditor::applyAdvancedEdit( QgsMeshEditor::Edit &edit, QgsMeshAdvancedEditing *editing )
329 {
330   applyEditOnTriangularMesh( edit, editing->apply( this ) );
331 
332   updateElementsCount( edit.topologicalChanges );
333 
334   if ( mZValueDatasetGroup )
335     mZValueDatasetGroup->setStatisticObsolete();
336 }
337 
applyEditOnTriangularMesh(QgsMeshEditor::Edit & edit,const QgsTopologicalMesh::Changes & topologicChanges)338 void QgsMeshEditor::applyEditOnTriangularMesh( QgsMeshEditor::Edit &edit, const QgsTopologicalMesh::Changes &topologicChanges )
339 {
340   QgsTriangularMesh::Changes triangularChanges( topologicChanges, *mMesh );
341   mTriangularMesh->applyChanges( triangularChanges );
342 
343   edit.topologicalChanges = topologicChanges;
344   edit.triangularMeshChanges = triangularChanges;
345 }
346 
updateElementsCount(const QgsTopologicalMesh::Changes & changes,bool apply)347 void QgsMeshEditor::updateElementsCount( const QgsTopologicalMesh::Changes &changes, bool apply )
348 {
349   if ( apply )
350   {
351     mValidFacesCount += changes.addedFaces().count() - changes.removedFaces().count();
352     mValidVerticesCount += changes.addedVertices().count() - changes.verticesToRemoveIndexes().count();
353   }
354   else
355   {
356     //reverse
357     mValidFacesCount -= changes.addedFaces().count() - changes.removedFaces().count();
358     mValidVerticesCount -= changes.addedVertices().count() - changes.verticesToRemoveIndexes().count();
359   }
360 }
361 
checkConsistency(QgsMeshEditingError & error) const362 bool QgsMeshEditor::checkConsistency( QgsMeshEditingError &error ) const
363 {
364   error = mTopologicalMesh.checkConsistency();
365   switch ( error.errorType )
366   {
367     case Qgis::MeshEditingErrorType::NoError:
368       break;
369     case Qgis::MeshEditingErrorType::InvalidFace:
370     case Qgis::MeshEditingErrorType::TooManyVerticesInFace:
371     case Qgis::MeshEditingErrorType::FlatFace:
372     case Qgis::MeshEditingErrorType::UniqueSharedVertex:
373     case Qgis::MeshEditingErrorType::InvalidVertex:
374     case Qgis::MeshEditingErrorType::ManifoldFace:
375       return false;
376   }
377 
378   if ( mTriangularMesh->vertices().count() != mMesh->vertexCount() )
379     return false;
380 
381   if ( mTriangularMesh->faceCentroids().count() != mMesh->faceCount() )
382     return false;
383 
384   return true;
385 }
386 
edgeIsClose(QgsPointXY point,double tolerance,int & faceIndex,int & edgePosition)387 bool QgsMeshEditor::edgeIsClose( QgsPointXY point, double tolerance, int &faceIndex, int &edgePosition )
388 {
389   QgsRectangle toleranceZone( point.x() - tolerance,
390                               point.y() - tolerance,
391                               point.x() + tolerance,
392                               point.y() + tolerance );
393 
394   edgePosition = -1;
395   double minDist = std::numeric_limits<double>::max();
396   const QList<int> &nativeFaces = mTriangularMesh->nativeFaceIndexForRectangle( toleranceZone );
397   double epsilon = std::numeric_limits<double>::epsilon() * tolerance;
398   for ( const int nativeFaceIndex : nativeFaces )
399   {
400     const QgsMeshFace &face = mMesh->face( nativeFaceIndex );
401     const int faceSize = face.size();
402     for ( int i = 0; i < faceSize; ++i )
403     {
404       const QgsMeshVertex &v1 = mTriangularMesh->vertices().at( face.at( i ) );
405       const QgsMeshVertex &v2 = mTriangularMesh->vertices().at( face.at( ( i + 1 ) % faceSize ) );
406 
407       double mx, my;
408       double dist = sqrt( QgsGeometryUtils::sqrDistToLine( point.x(),
409                           point.y(),
410                           v1.x(),
411                           v1.y(),
412                           v2.x(),
413                           v2.y(),
414                           mx,
415                           my,
416                           epsilon ) );
417 
418       if ( dist < tolerance && dist < minDist )
419       {
420         faceIndex = nativeFaceIndex;
421         edgePosition = i;
422         minDist = dist;
423       }
424     }
425   }
426 
427   if ( edgePosition != -1 )
428     return true;
429 
430   return false;
431 
432 }
433 
validFacesCount() const434 int QgsMeshEditor::validFacesCount() const
435 {
436   return mValidFacesCount;
437 }
438 
validVerticesCount() const439 int QgsMeshEditor::validVerticesCount() const
440 {
441   return mValidVerticesCount;
442 }
443 
maximumVerticesPerFace() const444 int QgsMeshEditor::maximumVerticesPerFace() const
445 {
446   return mMaximumVerticesPerFace;
447 }
448 
removeFaces(const QList<int> & facesToRemove)449 QgsMeshEditingError QgsMeshEditor::removeFaces( const QList<int> &facesToRemove )
450 {
451   QgsMeshEditingError error = mTopologicalMesh.facesCanBeRemoved( facesToRemove );
452   if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
453     return error;
454 
455   mUndoStack->push( new QgsMeshLayerUndoCommandRemoveFaces( this, facesToRemove ) );
456 
457   return error;
458 }
459 
edgeCanBeFlipped(int vertexIndex1,int vertexIndex2) const460 bool QgsMeshEditor::edgeCanBeFlipped( int vertexIndex1, int vertexIndex2 ) const
461 {
462   return mTopologicalMesh.edgeCanBeFlipped( vertexIndex1, vertexIndex2 );
463 }
464 
flipEdge(int vertexIndex1,int vertexIndex2)465 void QgsMeshEditor::flipEdge( int vertexIndex1, int vertexIndex2 )
466 {
467   if ( !edgeCanBeFlipped( vertexIndex1, vertexIndex2 ) )
468     return;
469 
470   mUndoStack->push( new QgsMeshLayerUndoCommandFlipEdge( this, vertexIndex1, vertexIndex2 ) );
471 }
472 
canBeMerged(int vertexIndex1,int vertexIndex2) const473 bool QgsMeshEditor::canBeMerged( int vertexIndex1, int vertexIndex2 ) const
474 {
475   return mTopologicalMesh.canBeMerged( vertexIndex1, vertexIndex2 );
476 }
477 
merge(int vertexIndex1,int vertexIndex2)478 void QgsMeshEditor::merge( int vertexIndex1, int vertexIndex2 )
479 {
480   if ( !canBeMerged( vertexIndex1, vertexIndex2 ) )
481     return;
482 
483   mUndoStack->push( new QgsMeshLayerUndoCommandMerge( this, vertexIndex1, vertexIndex2 ) );
484 }
485 
faceCanBeSplit(int faceIndex) const486 bool QgsMeshEditor::faceCanBeSplit( int faceIndex ) const
487 {
488   return mTopologicalMesh.canBeSplit( faceIndex );
489 }
490 
splitFaces(const QList<int> & faceIndexes)491 int QgsMeshEditor::splitFaces( const QList<int> &faceIndexes )
492 {
493   QList<int> faceIndexesSplittable;
494 
495   for ( const int faceIndex : faceIndexes )
496     if ( faceCanBeSplit( faceIndex ) )
497       faceIndexesSplittable.append( faceIndex );
498 
499   if ( faceIndexesSplittable.isEmpty() )
500     return 0;
501 
502   mUndoStack->push( new QgsMeshLayerUndoCommandSplitFaces( this, faceIndexesSplittable ) );
503 
504   return faceIndexesSplittable.count();
505 }
506 
prepareFaces(const QVector<QgsMeshFace> & faces,QgsMeshEditingError & error)507 QVector<QgsMeshFace> QgsMeshEditor::prepareFaces( const QVector<QgsMeshFace> &faces, QgsMeshEditingError &error )
508 {
509   QVector<QgsMeshFace> treatedFaces = faces;
510 
511   // here we could add later some filters, for example, removing faces intersecting with existing one
512 
513   for ( int i = 0; i < treatedFaces.count(); ++i )
514   {
515     QgsMeshFace &face = treatedFaces[i];
516     if ( mMaximumVerticesPerFace != 0 && face.count() > mMaximumVerticesPerFace )
517     {
518       error = QgsMeshEditingError( Qgis::MeshEditingErrorType::InvalidFace, i );
519       break;
520     }
521 
522     error = mTopologicalMesh.counterClockwiseFaces( face, mMesh );
523     if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
524       break;
525   }
526 
527   return treatedFaces;
528 }
529 
addFaces(const QVector<QVector<int>> & faces)530 QgsMeshEditingError QgsMeshEditor::addFaces( const QVector<QVector<int> > &faces )
531 {
532   QgsMeshEditingError error;
533   QVector<QgsMeshFace> facesToAdd = prepareFaces( faces, error );
534 
535   if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
536     return error;
537 
538   QgsTopologicalMesh::TopologicalFaces topologicalFaces = mTopologicalMesh.createNewTopologicalFaces( facesToAdd, true, error );
539 
540   error = mTopologicalMesh.facesCanBeAdded( topologicalFaces );
541 
542   if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
543     return error;
544 
545   mUndoStack->push( new QgsMeshLayerUndoCommandAddFaces( this, topologicalFaces ) );
546 
547   return error;
548 }
549 
addFace(const QVector<int> & vertexIndexes)550 QgsMeshEditingError QgsMeshEditor::addFace( const QVector<int> &vertexIndexes )
551 {
552   return addFaces( {vertexIndexes} );
553 }
554 
addVertices(const QVector<QgsMeshVertex> & vertices,double tolerance)555 int QgsMeshEditor::addVertices( const QVector<QgsMeshVertex> &vertices, double tolerance )
556 {
557   QVector<QgsMeshVertex> verticesInLayerCoordinate( vertices.count() );
558   int ignoredVertex = 0;
559   for ( int i = 0; i < vertices.count(); ++i )
560   {
561     const QgsPointXY &pointInTriangularMesh = vertices.at( i );
562     bool isTooClose = false;
563     int triangleIndex = mTriangularMesh->faceIndexForPoint_v2( pointInTriangularMesh );
564     if ( triangleIndex != -1 )
565     {
566       const QgsMeshFace face = mTriangularMesh->triangles().at( triangleIndex );
567       for ( int j = 0; j < 3; ++j )
568       {
569         const QgsPointXY &facePoint = mTriangularMesh->vertices().at( face.at( j ) );
570         double dist = pointInTriangularMesh.distance( facePoint );
571         if ( dist < tolerance )
572         {
573           isTooClose = true;
574           break;
575         }
576       }
577     }
578 
579     if ( !isTooClose )
580       verticesInLayerCoordinate[i] = mTriangularMesh->triangularToNativeCoordinates( vertices.at( i ) );
581     else
582       verticesInLayerCoordinate[i] = QgsMeshVertex();
583 
584     if ( verticesInLayerCoordinate.at( i ).isEmpty() )
585       ignoredVertex++;
586   }
587 
588   if ( ignoredVertex < vertices.count() )
589   {
590     mUndoStack->push( new QgsMeshLayerUndoCommandAddVertices( this, verticesInLayerCoordinate, tolerance ) );
591   }
592 
593   int effectivlyAddedVertex = vertices.count() - ignoredVertex;
594 
595   return effectivlyAddedVertex;
596 }
597 
addPointsAsVertices(const QVector<QgsPoint> & point,double tolerance)598 int QgsMeshEditor::addPointsAsVertices( const QVector<QgsPoint> &point, double tolerance )
599 {
600   return addVertices( point, tolerance );
601 }
602 
removeVerticesWithoutFillHoles(const QList<int> & verticesToRemoveIndexes)603 QgsMeshEditingError QgsMeshEditor::removeVerticesWithoutFillHoles( const QList<int> &verticesToRemoveIndexes )
604 {
605   QgsMeshEditingError error;
606 
607   QList<int> verticesIndexes = verticesToRemoveIndexes;
608 
609   QSet<int> concernedNativeFaces;
610   for ( const int vi : std::as_const( verticesIndexes ) )
611     concernedNativeFaces.unite( qgis::listToSet( mTopologicalMesh.facesAroundVertex( vi ) ) );
612 
613   error = mTopologicalMesh.facesCanBeRemoved( concernedNativeFaces.values() );
614 
615   if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
616     return error;
617 
618   mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles( this, verticesIndexes ) );
619   return error;
620 }
621 
removeVerticesFillHoles(const QList<int> & verticesToRemoveIndexes)622 QList<int> QgsMeshEditor::removeVerticesFillHoles( const QList<int> &verticesToRemoveIndexes )
623 {
624   QList<int> remainingVertices;
625   mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVerticesFillHoles( this, verticesToRemoveIndexes, &remainingVertices ) );
626 
627   return remainingVertices;
628 }
629 
630 
changeZValues(const QList<int> & verticesIndexes,const QList<double> & newZValues)631 void QgsMeshEditor::changeZValues( const QList<int> &verticesIndexes, const QList<double> &newZValues )
632 {
633   mUndoStack->push( new QgsMeshLayerUndoCommandChangeZValue( this, verticesIndexes, newZValues ) );
634 }
635 
canBeTransformed(const QList<int> & facesToCheck,const std::function<const QgsMeshVertex (int)> & transformFunction) const636 bool QgsMeshEditor::canBeTransformed( const QList<int> &facesToCheck, const std::function<const QgsMeshVertex( int )> &transformFunction ) const
637 {
638   for ( const int faceIndex : facesToCheck )
639   {
640     const QgsMeshFace &face = mMesh->face( faceIndex );
641     int faceSize = face.count();
642     QVector<QgsPointXY> pointsInTriangularMeshCoordinate( faceSize );
643     QVector<QgsPointXY> points( faceSize );
644     for ( int i = 0; i < faceSize; ++i )
645     {
646       int ip0 =  face[i];
647       int ip1 = face[( i + 1 ) % faceSize];
648       int ip2 = face[( i + 2 ) % faceSize];
649 
650       QgsMeshVertex p0 = transformFunction( ip0 );
651       QgsMeshVertex p1 = transformFunction( ip1 );
652       QgsMeshVertex p2 = transformFunction( ip2 );
653 
654       double ux = p0.x() - p1.x();
655       double uy = p0.y() - p1.y();
656       double vx = p2.x() - p1.x();
657       double vy = p2.y() - p1.y();
658 
659       double crossProduct = ux * vy - uy * vx;
660       if ( crossProduct >= 0 ) //if cross product>0, we have two edges clockwise
661         return false;
662       pointsInTriangularMeshCoordinate[i] = mTriangularMesh->nativeToTriangularCoordinates( p0 );
663       points[i] = p0;
664     }
665 
666     const QgsGeometry &deformedFace = QgsGeometry::fromPolygonXY( {points} );
667 
668     // now test if the deformed face contain something else
669     QList<int> otherFaceIndexes =
670       mTriangularMesh->nativeFaceIndexForRectangle( QgsGeometry::fromPolygonXY( {pointsInTriangularMeshCoordinate} ).boundingBox() );
671 
672     for ( const int otherFaceIndex : otherFaceIndexes )
673     {
674       const QgsMeshFace &otherFace = mMesh->face( otherFaceIndex );
675       int existingFaceSize = otherFace.count();
676       bool shareVertex = false;
677       for ( int i = 0; i < existingFaceSize; ++i )
678       {
679         if ( face.contains( otherFace.at( i ) ) )
680         {
681           shareVertex = true;
682           break;
683         }
684       }
685       if ( shareVertex )
686       {
687         //only test the edge that not contains a shared vertex
688         for ( int i = 0; i < existingFaceSize; ++i )
689         {
690           int index1 = otherFace.at( i );
691           int index2 = otherFace.at( ( i + 1 ) % existingFaceSize );
692           if ( ! face.contains( index1 )  && !face.contains( index2 ) )
693           {
694             const QgsPointXY &v1 = transformFunction( index1 );
695             const QgsPointXY &v2 =  transformFunction( index2 );
696             QgsGeometry edgeGeom = QgsGeometry::fromPolylineXY( { v1, v2} );
697             if ( deformedFace.intersects( edgeGeom ) )
698               return false;
699           }
700         }
701       }
702       else
703       {
704         QVector<QgsPointXY> otherPoints( existingFaceSize );
705         for ( int i = 0; i < existingFaceSize; ++i )
706           otherPoints[i] = transformFunction( otherFace.at( i ) );
707         const QgsGeometry existingFaceGeom = QgsGeometry::fromPolygonXY( {otherPoints } );
708         if ( deformedFace.intersects( existingFaceGeom ) )
709           return false;
710       }
711     }
712 
713     const QList<int> freeVerticesIndex = freeVerticesIndexes();
714     for ( const int vertexIndex : freeVerticesIndex )
715     {
716       const QgsPointXY &mapPoint = transformFunction( vertexIndex ); //free vertices can be transformed
717       if ( deformedFace.contains( &mapPoint ) )
718         return false;
719     }
720   }
721 
722   // free vertices
723   const QList<int> freeVerticesIndex = freeVerticesIndexes();
724   for ( const int vertexIndex : freeVerticesIndex )
725   {
726     const QgsMeshVertex &newFreeVertexPosition = transformFunction( vertexIndex ); // transformed free vertex
727     const QgsMeshVertex pointInTriangularCoord = mTriangularMesh->nativeToTriangularCoordinates( newFreeVertexPosition );
728     const int originalIncludingFace = mTriangularMesh->nativeFaceIndexForPoint( pointInTriangularCoord );
729 
730     if ( originalIncludingFace != -1 )
731     {
732       // That means two things: the free vertex is moved AND is included in a face before transform
733       // Before returning false, we need to check if the vertex is still in the face after transform
734       const QgsMeshFace &face = mMesh->face( originalIncludingFace );
735       int faceSize = face.count();
736       QVector<QgsPointXY> points( faceSize );
737       for ( int i = 0; i < faceSize; ++i )
738         points[i] = transformFunction( face.at( i ) );
739 
740       const QgsGeometry &deformedFace = QgsGeometry::fromPolygonXY( {points} );
741       const QgsPointXY ptXY( newFreeVertexPosition );
742       if ( deformedFace.contains( &ptXY ) )
743         return false;
744     }
745   }
746 
747   return true;
748 }
749 
changeXYValues(const QList<int> & verticesIndexes,const QList<QgsPointXY> & newValues)750 void QgsMeshEditor::changeXYValues( const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
751 {
752   // TODO : implement a check if it is possible to change the (x,y) values. For now, this check is made in the APP part
753   mUndoStack->push( new QgsMeshLayerUndoCommandChangeXYValue( this, verticesIndexes, newValues ) );
754 }
755 
changeCoordinates(const QList<int> & verticesIndexes,const QList<QgsPoint> & newCoordinates)756 void QgsMeshEditor::changeCoordinates( const QList<int> &verticesIndexes, const QList<QgsPoint> &newCoordinates )
757 {
758   mUndoStack->push( new QgsMeshLayerUndoCommandChangeCoordinates( this, verticesIndexes, newCoordinates ) );
759 }
760 
advancedEdit(QgsMeshAdvancedEditing * editing)761 void QgsMeshEditor::advancedEdit( QgsMeshAdvancedEditing *editing )
762 {
763   mUndoStack->push( new QgsMeshLayerUndoCommandAdvancedEditing( this, editing ) );
764 }
765 
stopEditing()766 void QgsMeshEditor::stopEditing()
767 {
768   mTopologicalMesh.reindex();
769   mUndoStack->clear();
770 }
771 
QgsMeshLayerUndoCommandMeshEdit(QgsMeshEditor * meshEditor)772 QgsMeshLayerUndoCommandMeshEdit::QgsMeshLayerUndoCommandMeshEdit( QgsMeshEditor *meshEditor )
773   : mMeshEditor( meshEditor )
774 {
775 }
776 
undo()777 void QgsMeshLayerUndoCommandMeshEdit::undo()
778 {
779   if ( mMeshEditor.isNull() )
780     return;
781 
782   for ( int i = mEdits.count() - 1; i >= 0; --i )
783     mMeshEditor->reverseEdit( mEdits[i] );
784 }
785 
redo()786 void QgsMeshLayerUndoCommandMeshEdit::redo()
787 {
788   if ( mMeshEditor.isNull() )
789     return;
790 
791   for ( QgsMeshEditor::Edit &edit : mEdits )
792     mMeshEditor->applyEdit( edit );
793 }
794 
QgsMeshLayerUndoCommandAddVertices(QgsMeshEditor * meshEditor,const QVector<QgsMeshVertex> & vertices,double tolerance)795 QgsMeshLayerUndoCommandAddVertices::QgsMeshLayerUndoCommandAddVertices( QgsMeshEditor *meshEditor, const QVector<QgsMeshVertex> &vertices, double tolerance )
796   : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
797   , mVertices( vertices )
798   , mTolerance( tolerance )
799 {
800   setText( QObject::tr( "Add %n vertices", nullptr, mVertices.count() ) );
801 }
802 
redo()803 void QgsMeshLayerUndoCommandAddVertices::redo()
804 {
805   if ( !mVertices.isEmpty() )
806   {
807     for ( int i = 0; i < mVertices.count(); ++i )
808     {
809       const QgsMeshVertex &vertex = mVertices.at( i );
810       if ( vertex.isEmpty() )
811         continue;
812       QgsMeshEditor::Edit edit;
813       mMeshEditor->applyAddVertex( edit, vertex, mTolerance );
814       mEdits.append( edit );
815     }
816     mVertices.clear(); //not needed anymore, changes are store in mEdits
817   }
818   else
819   {
820     for ( QgsMeshEditor::Edit &edit : mEdits )
821       mMeshEditor->applyEdit( edit );
822   }
823 }
824 
QgsMeshLayerUndoCommandRemoveVerticesFillHoles(QgsMeshEditor * meshEditor,const QList<int> & verticesToRemoveIndexes,QList<int> * remainingVerticesPointer)825 QgsMeshLayerUndoCommandRemoveVerticesFillHoles::QgsMeshLayerUndoCommandRemoveVerticesFillHoles(
826   QgsMeshEditor *meshEditor,
827   const QList<int> &verticesToRemoveIndexes,
828   QList<int> *remainingVerticesPointer )
829   : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
830   , mVerticesToRemoveIndexes( verticesToRemoveIndexes )
831   , mRemainingVerticesPointer( remainingVerticesPointer )
832 {
833   setText( QObject::tr( "Remove %n vertices filling holes", nullptr, verticesToRemoveIndexes.count() ) );
834 }
835 
redo()836 void QgsMeshLayerUndoCommandRemoveVerticesFillHoles::redo()
837 {
838   int initialVertexCount = mVerticesToRemoveIndexes.count();
839   if ( !mVerticesToRemoveIndexes.isEmpty() )
840   {
841     QgsMeshEditor::Edit edit;
842     QList<int> vertexToRetry;
843     while ( !mVerticesToRemoveIndexes.isEmpty() )
844     {
845       // try again and again until there is no vertices to remove anymore or nothing is removed.
846       for ( const int &vertex : std::as_const( mVerticesToRemoveIndexes ) )
847       {
848         if ( mMeshEditor->applyRemoveVertexFillHole( edit, vertex ) )
849           mEdits.append( edit );
850         else
851           vertexToRetry.append( vertex );
852       }
853 
854       if ( vertexToRetry.count() == mVerticesToRemoveIndexes.count() )
855         break;
856       else
857         mVerticesToRemoveIndexes = vertexToRetry;
858     }
859 
860     if ( initialVertexCount == mVerticesToRemoveIndexes.count() )
861       setObsolete( true );
862 
863     if ( mRemainingVerticesPointer != nullptr )
864       *mRemainingVerticesPointer = mVerticesToRemoveIndexes;
865 
866     mRemainingVerticesPointer = nullptr;
867 
868     mVerticesToRemoveIndexes.clear(); //not needed anymore, changes are store in mEdits
869   }
870   else
871   {
872     for ( QgsMeshEditor::Edit &edit : mEdits )
873       mMeshEditor->applyEdit( edit );
874   }
875 }
876 
877 
QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles(QgsMeshEditor * meshEditor,const QList<int> & verticesToRemoveIndexes)878 QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles::QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles(
879   QgsMeshEditor *meshEditor,
880   const QList<int> &verticesToRemoveIndexes )
881   : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
882   , mVerticesToRemoveIndexes( verticesToRemoveIndexes )
883 {
884   setText( QObject::tr( "Remove %n vertices without filling holes", nullptr, verticesToRemoveIndexes.count() ) ) ;
885 }
886 
redo()887 void QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles::redo()
888 {
889   if ( !mVerticesToRemoveIndexes.isEmpty() )
890   {
891     QgsMeshEditor::Edit edit;
892 
893     mMeshEditor->applyRemoveVerticesWithoutFillHole( edit, mVerticesToRemoveIndexes );
894     mEdits.append( edit );
895 
896     mVerticesToRemoveIndexes.clear(); //not needed anymore, changes are store in mEdits
897   }
898   else
899   {
900     for ( QgsMeshEditor::Edit &edit : mEdits )
901       mMeshEditor->applyEdit( edit );
902   }
903 }
904 
QgsMeshLayerUndoCommandAddFaces(QgsMeshEditor * meshEditor,QgsTopologicalMesh::TopologicalFaces & faces)905 QgsMeshLayerUndoCommandAddFaces::QgsMeshLayerUndoCommandAddFaces( QgsMeshEditor *meshEditor, QgsTopologicalMesh::TopologicalFaces &faces )
906   : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
907   , mFaces( faces )
908 {
909   setText( QObject::tr( "Add %n faces", nullptr, faces.meshFaces().count() ) );
910 }
911 
redo()912 void QgsMeshLayerUndoCommandAddFaces::redo()
913 {
914   if ( !mFaces.meshFaces().isEmpty() )
915   {
916     QgsMeshEditor::Edit edit;
917     mMeshEditor->applyAddFaces( edit, mFaces );
918     mEdits.append( edit );
919 
920     mFaces.clear(); //not needed anymore, now changes are store in edit
921   }
922   else
923   {
924     for ( QgsMeshEditor::Edit &edit : mEdits )
925       mMeshEditor->applyEdit( edit );
926   }
927 }
928 
QgsMeshLayerUndoCommandRemoveFaces(QgsMeshEditor * meshEditor,const QList<int> & facesToRemoveIndexes)929 QgsMeshLayerUndoCommandRemoveFaces::QgsMeshLayerUndoCommandRemoveFaces( QgsMeshEditor *meshEditor, const QList<int> &facesToRemoveIndexes )
930   : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
931   , mfacesToRemoveIndexes( facesToRemoveIndexes )
932 {
933   setText( QObject::tr( "Remove %n faces", nullptr, facesToRemoveIndexes.count() ) );
934 }
935 
redo()936 void QgsMeshLayerUndoCommandRemoveFaces::redo()
937 {
938   if ( !mfacesToRemoveIndexes.isEmpty() )
939   {
940     QgsMeshEditor::Edit edit;
941     mMeshEditor->applyRemoveFaces( edit, mfacesToRemoveIndexes );
942     mEdits.append( edit );
943 
944     mfacesToRemoveIndexes.clear(); //not needed anymore, now changes are store in edit
945   }
946   else
947   {
948     for ( QgsMeshEditor::Edit &edit : mEdits )
949       mMeshEditor->applyEdit( edit );
950   }
951 }
952 
QgsMeshEditingError()953 QgsMeshEditingError::QgsMeshEditingError(): errorType( Qgis::MeshEditingErrorType::NoError ), elementIndex( -1 ) {}
954 
QgsMeshEditingError(Qgis::MeshEditingErrorType type,int elementIndex)955 QgsMeshEditingError::QgsMeshEditingError( Qgis::MeshEditingErrorType type, int elementIndex ): errorType( type ), elementIndex( elementIndex ) {}
956 
extent() const957 QgsRectangle QgsMeshEditor::extent() const
958 {
959   return mTriangularMesh->nativeExtent();
960 }
961 
isModified() const962 bool QgsMeshEditor::isModified() const
963 {
964   if ( mUndoStack )
965     return !mUndoStack->isClean();
966 
967   return false;
968 }
969 
reindex(bool renumbering)970 bool QgsMeshEditor::reindex( bool renumbering )
971 {
972   mTopologicalMesh.reindex();
973   mUndoStack->clear();
974   QgsMeshEditingError error = initialize();
975   mValidFacesCount = mMesh->faceCount();
976   mValidVerticesCount = mMesh->vertexCount();
977 
978   if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
979     return false;
980 
981   if ( renumbering )
982   {
983     if ( !mTopologicalMesh.renumber() )
984       return false;
985 
986     QgsMeshEditingError error = initialize();
987     return error.errorType == Qgis::MeshEditingErrorType::NoError;
988   }
989 
990   else
991     return true;
992 }
993 
freeVerticesIndexes() const994 QList<int> QgsMeshEditor::freeVerticesIndexes() const
995 {
996   return mTopologicalMesh.freeVerticesIndexes();
997 }
998 
isVertexOnBoundary(int vertexIndex) const999 bool QgsMeshEditor::isVertexOnBoundary( int vertexIndex ) const
1000 {
1001   return mTopologicalMesh.isVertexOnBoundary( vertexIndex );
1002 }
1003 
isVertexFree(int vertexIndex) const1004 bool QgsMeshEditor::isVertexFree( int vertexIndex ) const
1005 {
1006   return mTopologicalMesh.isVertexFree( vertexIndex );
1007 }
1008 
vertexCirculator(int vertexIndex) const1009 QgsMeshVertexCirculator QgsMeshEditor::vertexCirculator( int vertexIndex ) const
1010 {
1011   return mTopologicalMesh.vertexCirculator( vertexIndex );
1012 }
1013 
topologicalMesh()1014 QgsTopologicalMesh &QgsMeshEditor::topologicalMesh()
1015 {
1016   return mTopologicalMesh;
1017 }
1018 
triangularMesh()1019 QgsTriangularMesh *QgsMeshEditor::triangularMesh()
1020 {
1021   return mTriangularMesh;
1022 }
1023 
QgsMeshLayerUndoCommandChangeZValue(QgsMeshEditor * meshEditor,const QList<int> & verticesIndexes,const QList<double> & newValues)1024 QgsMeshLayerUndoCommandChangeZValue::QgsMeshLayerUndoCommandChangeZValue( QgsMeshEditor *meshEditor, const QList<int> &verticesIndexes, const QList<double> &newValues )
1025   : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1026   , mVerticesIndexes( verticesIndexes )
1027   , mNewValues( newValues )
1028 {
1029   setText( QObject::tr( "Change %n vertices Z Value", nullptr, verticesIndexes.count() ) );
1030 }
1031 
redo()1032 void QgsMeshLayerUndoCommandChangeZValue::redo()
1033 {
1034   if ( !mVerticesIndexes.isEmpty() )
1035   {
1036     QgsMeshEditor::Edit edit;
1037     mMeshEditor->applyChangeZValue( edit, mVerticesIndexes, mNewValues );
1038     mEdits.append( edit );
1039     mVerticesIndexes.clear();
1040     mNewValues.clear();
1041   }
1042   else
1043   {
1044     for ( QgsMeshEditor::Edit &edit : mEdits )
1045       mMeshEditor->applyEdit( edit );
1046   }
1047 }
1048 
QgsMeshLayerUndoCommandChangeXYValue(QgsMeshEditor * meshEditor,const QList<int> & verticesIndexes,const QList<QgsPointXY> & newValues)1049 QgsMeshLayerUndoCommandChangeXYValue::QgsMeshLayerUndoCommandChangeXYValue( QgsMeshEditor *meshEditor, const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
1050   : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1051   , mVerticesIndexes( verticesIndexes )
1052   , mNewValues( newValues )
1053 {
1054   setText( QObject::tr( "Move %n vertices", nullptr, verticesIndexes.count() ) );
1055 }
1056 
redo()1057 void QgsMeshLayerUndoCommandChangeXYValue::redo()
1058 {
1059   if ( !mVerticesIndexes.isEmpty() )
1060   {
1061     QgsMeshEditor::Edit edit;
1062     mMeshEditor->applyChangeXYValue( edit, mVerticesIndexes, mNewValues );
1063     mEdits.append( edit );
1064     mVerticesIndexes.clear();
1065     mNewValues.clear();
1066   }
1067   else
1068   {
1069     for ( QgsMeshEditor::Edit &edit : mEdits )
1070       mMeshEditor->applyEdit( edit );
1071   }
1072 }
1073 
1074 
QgsMeshLayerUndoCommandChangeCoordinates(QgsMeshEditor * meshEditor,const QList<int> & verticesIndexes,const QList<QgsPoint> & newCoordinates)1075 QgsMeshLayerUndoCommandChangeCoordinates::QgsMeshLayerUndoCommandChangeCoordinates( QgsMeshEditor *meshEditor, const QList<int> &verticesIndexes, const QList<QgsPoint> &newCoordinates )
1076   : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1077   , mVerticesIndexes( verticesIndexes )
1078   , mNewCoordinates( newCoordinates )
1079 {
1080   setText( QObject::tr( "Transform %n vertices coordinates", nullptr, verticesIndexes.count() ) );
1081 }
1082 
redo()1083 void QgsMeshLayerUndoCommandChangeCoordinates::redo()
1084 {
1085   if ( !mVerticesIndexes.isEmpty() )
1086   {
1087     QgsMeshEditor::Edit editXY;
1088     QList<QgsPointXY> newXY;
1089     newXY.reserve( mNewCoordinates.count() );
1090     QgsMeshEditor::Edit editZ;
1091     QList<double> newZ;
1092     newZ.reserve( mNewCoordinates.count() );
1093 
1094     for ( const QgsPoint &pt : std::as_const( mNewCoordinates ) )
1095     {
1096       newXY.append( pt );
1097       newZ.append( pt.z() );
1098     }
1099 
1100     mMeshEditor->applyChangeXYValue( editXY, mVerticesIndexes, newXY );
1101     mEdits.append( editXY );
1102     mMeshEditor->applyChangeZValue( editZ, mVerticesIndexes, newZ );
1103     mEdits.append( editZ );
1104     mVerticesIndexes.clear();
1105     mNewCoordinates.clear();
1106   }
1107   else
1108   {
1109     for ( QgsMeshEditor::Edit &edit : mEdits )
1110       mMeshEditor->applyEdit( edit );
1111   }
1112 }
1113 
1114 
1115 
QgsMeshLayerUndoCommandFlipEdge(QgsMeshEditor * meshEditor,int vertexIndex1,int vertexIndex2)1116 QgsMeshLayerUndoCommandFlipEdge::QgsMeshLayerUndoCommandFlipEdge( QgsMeshEditor *meshEditor, int vertexIndex1, int vertexIndex2 )
1117   : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1118   , mVertexIndex1( vertexIndex1 )
1119   , mVertexIndex2( vertexIndex2 )
1120 {
1121   setText( QObject::tr( "Flip edge" ) );
1122 }
1123 
redo()1124 void QgsMeshLayerUndoCommandFlipEdge::redo()
1125 {
1126   if ( mVertexIndex1 >= 0 && mVertexIndex2 >= 0 )
1127   {
1128     QgsMeshEditor::Edit edit;
1129     mMeshEditor->applyFlipEdge( edit, mVertexIndex1, mVertexIndex2 );
1130     mEdits.append( edit );
1131     mVertexIndex1 = -1;
1132     mVertexIndex2 = -1;
1133   }
1134   else
1135   {
1136     for ( QgsMeshEditor::Edit &edit : mEdits )
1137       mMeshEditor->applyEdit( edit );
1138   }
1139 }
1140 
QgsMeshLayerUndoCommandMerge(QgsMeshEditor * meshEditor,int vertexIndex1,int vertexIndex2)1141 QgsMeshLayerUndoCommandMerge::QgsMeshLayerUndoCommandMerge( QgsMeshEditor *meshEditor, int vertexIndex1, int vertexIndex2 )
1142   : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1143   , mVertexIndex1( vertexIndex1 )
1144   , mVertexIndex2( vertexIndex2 )
1145 {
1146   setText( QObject::tr( "Merge faces" ) );
1147 }
1148 
redo()1149 void QgsMeshLayerUndoCommandMerge::redo()
1150 {
1151   if ( mVertexIndex1 >= 0 && mVertexIndex2 >= 0 )
1152   {
1153     QgsMeshEditor::Edit edit;
1154     mMeshEditor->applyMerge( edit, mVertexIndex1, mVertexIndex2 );
1155     mEdits.append( edit );
1156     mVertexIndex1 = -1;
1157     mVertexIndex2 = -1;
1158   }
1159   else
1160   {
1161     for ( QgsMeshEditor::Edit &edit : mEdits )
1162       mMeshEditor->applyEdit( edit );
1163   }
1164 }
1165 
QgsMeshLayerUndoCommandSplitFaces(QgsMeshEditor * meshEditor,const QList<int> & faceIndexes)1166 QgsMeshLayerUndoCommandSplitFaces::QgsMeshLayerUndoCommandSplitFaces( QgsMeshEditor *meshEditor, const QList<int> &faceIndexes )
1167   : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1168   , mFaceIndexes( faceIndexes )
1169 {
1170   setText( QObject::tr( "Split %n faces", nullptr, faceIndexes.count() ) );
1171 }
1172 
redo()1173 void QgsMeshLayerUndoCommandSplitFaces::redo()
1174 {
1175   if ( !mFaceIndexes.isEmpty() )
1176   {
1177     for ( int faceIndex : std::as_const( mFaceIndexes ) )
1178     {
1179       QgsMeshEditor::Edit edit;
1180       mMeshEditor->applySplit( edit, faceIndex );
1181       mEdits.append( edit );
1182     }
1183     mFaceIndexes.clear();
1184   }
1185   else
1186   {
1187     for ( QgsMeshEditor::Edit &edit : mEdits )
1188       mMeshEditor->applyEdit( edit );
1189   }
1190 }
1191 
QgsMeshLayerUndoCommandAdvancedEditing(QgsMeshEditor * meshEditor,QgsMeshAdvancedEditing * advancdEdit)1192 QgsMeshLayerUndoCommandAdvancedEditing::QgsMeshLayerUndoCommandAdvancedEditing( QgsMeshEditor *meshEditor, QgsMeshAdvancedEditing *advancdEdit )
1193   : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1194   , mAdvancedEditing( advancdEdit )
1195 {
1196   setText( advancdEdit->text() );
1197 }
1198 
redo()1199 void QgsMeshLayerUndoCommandAdvancedEditing::redo()
1200 {
1201   if ( mAdvancedEditing )
1202   {
1203     QgsMeshEditor::Edit edit;
1204     while ( !mAdvancedEditing->isFinished() )
1205     {
1206       mMeshEditor->applyAdvancedEdit( edit, mAdvancedEditing );
1207       mEdits.append( edit );
1208     }
1209 
1210     mAdvancedEditing = nullptr;
1211   }
1212   else
1213   {
1214     for ( QgsMeshEditor::Edit &edit : mEdits )
1215       mMeshEditor->applyEdit( edit );
1216   }
1217 }
1218