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