QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
qgsmesheditor.cpp
Go to the documentation of this file.
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 "qgsgeometryengine.h"
24#include "qgsgeometryutils.h"
25
26#include <poly2tri.h>
27
28#include <QSet>
29
30
32 : QObject( meshLayer )
33 , mMesh( meshLayer ? meshLayer->nativeMesh() : nullptr )
34 , mTriangularMesh( meshLayer ? meshLayer->triangularMeshByLodIndex( 0 ) : nullptr )
35 , mUndoStack( meshLayer ? meshLayer->undoStack() : nullptr )
36{
37 if ( meshLayer && meshLayer->dataProvider() )
38 mMaximumVerticesPerFace = meshLayer->dataProvider()->maximumVerticesCountPerFace();
39
40 if ( meshLayer )
41 connect( mUndoStack, &QUndoStack::indexChanged, this, &QgsMeshEditor::meshEdited );
42}
43
44QgsMeshEditor::QgsMeshEditor( QgsMesh *nativeMesh, QgsTriangularMesh *triangularMesh, QObject *parent )
45 : QObject( parent )
46 , mMesh( nativeMesh )
47 , mTriangularMesh( triangularMesh )
48{
49 mUndoStack = new QUndoStack( this );
50 connect( mUndoStack, &QUndoStack::indexChanged, this, &QgsMeshEditor::meshEdited );
51}
52
54{
55 std::unique_ptr<QgsMeshDatasetGroup> zValueDatasetGroup = std::make_unique<QgsMeshVerticesElevationDatasetGroup>( tr( "vertices Z value" ), mMesh );
56 mZValueDatasetGroup = zValueDatasetGroup.get();
57 return zValueDatasetGroup.release();
58}
59
61
63{
65 mTopologicalMesh = QgsTopologicalMesh::createTopologicalMesh( mMesh, mMaximumVerticesPerFace, error );
66
68 {
69 // we check for free vertices that could be included in face here
70 // because we need the spatial index of the triangular mesh
71 const QList<int> freeVertices = mTopologicalMesh.freeVerticesIndexes();
72 for ( int vi : freeVertices )
73 {
74 if ( mTriangularMesh->faceIndexForPoint_v2( mTriangularMesh->vertices().at( vi ) ) != -1 )
75 {
77 break;
78 }
79 }
80 }
81
82 mValidFacesCount = mMesh->faceCount();
83 mValidVerticesCount = mMesh->vertexCount();
84 return error;
85}
86
88{
89 QgsMeshEditingError lastError;
90
91 while ( true )
92 {
93 lastError = initialize();
95 break;
96
97 if ( !fixError( lastError ) )
98 break;
99
100 mTriangularMesh->update( mMesh );
101 };
102
103 return lastError;
104}
105
107{
108 switch ( error.errorType )
109 {
111 return true;
112 break;
117 if ( error.elementIndex != -1 && error.elementIndex < mMesh->faceCount() )
118 {
119 mMesh->faces.removeAt( error.elementIndex );
120 return true;
121 }
122 return false;
123 break;
126 {
127 auto faceIt = mMesh->faces.begin();
128 while ( faceIt != mMesh->faces.end() )
129 {
130 if ( faceIt->contains( error.elementIndex ) )
131 faceIt = mMesh->faces.erase( faceIt );
132 else
133 ++faceIt;
134 }
135
136 if ( error.elementIndex >= 0 && error.elementIndex < mMesh->vertexCount() )
137 {
138 mMesh->vertices[error.elementIndex] = QgsMeshVertex();
139 reindex( false );
140 }
141 return true;
142 }
143 break;
144 }
145
146 return false;
147}
148
150{
151 mTriangularMesh = triangularMesh;
152}
153
154
155bool QgsMeshEditor::isFaceGeometricallyCompatible( const QList<int> &vertexIndexes, const QList<QgsMeshVertex> &vertices ) const
156{
157 Q_ASSERT( vertexIndexes.count() == vertices.count() );
158
159 QVector<QgsPoint> ring;
160 for ( int i = 0; i < vertices.size(); ++i )
161 {
162 const QgsPoint &vertex = vertices[i];
163 ring.append( vertex );
164 }
165 std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >();
166 polygon->setExteriorRing( new QgsLineString( ring ) );
167 const QgsGeometry newFaceGeom( polygon.release() );
168 std::unique_ptr<QgsGeometryEngine> geomEngine( QgsGeometry::createGeometryEngine( newFaceGeom.constGet() ) );
169 geomEngine->prepareGeometry();
170
171 const QgsRectangle boundingBox = newFaceGeom.boundingBox();
172 int newFaceSize = vertexIndexes.count();
173 const QList<int> concernedFaceIndex = mTriangularMesh->nativeFaceIndexForRectangle( boundingBox );
174 if ( !concernedFaceIndex.isEmpty() )
175 {
176 // for each concerned face, we take edges and, if no common vertex with the new face,
177 // check is the edge intersects or is contained in the new face
178 for ( const int faceIndex : concernedFaceIndex )
179 {
180 const QgsMeshFace &existingFace = mMesh->faces.at( faceIndex );
181 int existingFaceSize = existingFace.count();
182 bool shareVertex = false;
183 for ( int i = 0; i < existingFaceSize; ++i )
184 {
185 if ( vertexIndexes.contains( existingFace.at( i ) ) )
186 {
187 shareVertex = true;
188 break;
189 }
190 }
191
192 if ( shareVertex )
193 {
194 for ( int i = 0; i < existingFaceSize; ++i )
195 {
196 int index1 = existingFace.at( i );
197 int index2 = existingFace.at( ( i + 1 ) % existingFaceSize );
198 const QgsMeshVertex &v1 = mTriangularMesh->vertices().at( index1 );
199 const QgsMeshVertex &v2 = mTriangularMesh->vertices().at( index2 );
200 QgsGeometry edgeGeom = QgsGeometry( new QgsLineString( v1, v2 ) );
201
202 if ( ! vertexIndexes.contains( index1 ) && !vertexIndexes.contains( index2 ) )
203 {
204 // test if the edge that not contains a shared vertex intersect the entire new face
205 if ( geomEngine->intersects( edgeGeom.constGet() ) )
206 return false;
207 }
208 else
209 {
210 for ( int vi = 0; vi < vertexIndexes.count(); ++vi )
211 {
212 int vertInNewFace1 = vertexIndexes.at( vi );
213 int vertInNewFace2 = vertexIndexes.at( ( vi + 1 ) % newFaceSize );
214 bool hasToBeTest = false;
215
216 if ( vertInNewFace1 != -1 && vertInNewFace2 != -1 )
217 {
218 hasToBeTest = vertInNewFace1 != index1 &&
219 vertInNewFace2 != index2 &&
220 vertInNewFace1 != index2 &&
221 vertInNewFace2 != index1;
222 }
223 else
224 {
225 if ( vertInNewFace1 == -1 )
226 hasToBeTest &= vertInNewFace2 != index1 && vertInNewFace2 != index2;
227
228
229 if ( vertInNewFace2 == -1 )
230 hasToBeTest &= vertInNewFace1 != index1 && vertInNewFace1 != index2;
231 }
232
233 if ( hasToBeTest )
234 {
235 const QgsMeshVertex &nv1 = vertices.at( vi );
236 const QgsMeshVertex &nv2 = vertices.at( ( vi + 1 ) % newFaceSize );
237 const QgsGeometry newEdgeGeom = QgsGeometry( new QgsLineString( nv1, nv2 ) );
238
239 if ( newEdgeGeom.intersects( edgeGeom ) )
240 return false;
241 }
242 }
243 }
244 }
245 }
246 else
247 {
248 const QgsGeometry existingFaceGeom = QgsMeshUtils::toGeometry( existingFace, mTriangularMesh->vertices() );
249 if ( geomEngine->intersects( existingFaceGeom.constGet() ) )
250 return false;
251 }
252 }
253 }
254
255 // Then search for free vertices included in the new face
256 const QList<int> &freeVertices = freeVerticesIndexes();
257 for ( const int freeVertexIndex : freeVertices )
258 {
259 if ( vertexIndexes.contains( freeVertexIndex ) )
260 continue;
261
262 const QgsMeshVertex &vertex = mTriangularMesh->vertices().at( freeVertexIndex );
263 if ( geomEngine->contains( &vertex ) )
264 return false;
265 }
266
267 return true;
268}
269
271{
272 const QList<int> newFaceVerticesIndexes( face.toList() );
273 QList<QgsMeshVertex> allVertices;
274 allVertices.reserve( face.count() );
275 for ( int i : face )
276 allVertices.append( mTriangularMesh->vertices().at( i ) );
277
278 return isFaceGeometricallyCompatible( newFaceVerticesIndexes, allVertices );
279
280}
281
282
284{
286
287 // Prepare and check the face
288 QVector<QgsMeshFace> facesToAdd = prepareFaces( {face}, error );
289
291 return false;
292
293 // Check if there is topological error with the mesh
295 error = mTopologicalMesh.facesCanBeAdded( topologicalFaces );
296
298 return false;
299
300 // Check geometry compatibility
301 // With the topological check, we know that the new face is not included in an existing one
302 // But maybe, the new face includes or intersects existing faces or free vertices, we need to check
303 // First search for faces intersecting the bounding box of the new face.
304
305 return isFaceGeometricallyCompatible( face );
306}
307
308bool QgsMeshEditor::faceCanBeAddedWithNewVertices( const QList<int> &verticesIndex, const QList<QgsMeshVertex> &newVertices ) const
309{
311 const QList<int> face = prepareFaceWithNewVertices( verticesIndex, newVertices, error );
312
313 if ( face.isEmpty() )
314 return false;
315
317 return false;
318
319 // if we are here, the face is convex and not flat
320
321 // Now we check the topology of the potential new face
322 int size = face.size();
323 QList<QgsMeshVertex> allVertices;
324 allVertices.reserve( verticesIndex.size() );
325 int newVertPos = 0;
326 for ( int i = 0; i < size; ++i )
327 {
328 int index = face.at( i );
329 if ( index == -1 )
330 {
331 if ( newVertPos >= newVertices.count() )
332 return false;
333 allVertices.append( newVertices.at( newVertPos++ ) );
334 continue;
335 }
336
337 allVertices.append( mTriangularMesh->vertices().at( index ) );
338
339 if ( isVertexFree( index ) )
340 continue;
341
342 int prevIndex = face.at( ( i - 1 + size ) % size );
343 int nextIndex = face.at( ( i + 1 ) % size );
344
345 QgsMeshVertexCirculator circulator = mTopologicalMesh.vertexCirculator( index );
346 if ( !circulator.goBoundaryClockwise() ) //vertex not on boundary
347 return false;
348
349 int prevOppVertex = circulator.oppositeVertexClockwise();
350 if ( prevOppVertex == nextIndex ) //manifold face
351 return false;
352
353 if ( !circulator.goBoundaryCounterClockwise() )
354 return false;
355
356 int nextOppVertex = circulator.oppositeVertexCounterClockwise();
357 if ( nextOppVertex == prevIndex ) //manifold face
358 return false;
359
360 if ( nextIndex != nextOppVertex && prevIndex != prevOppVertex ) //unique shared vertex
361 return false;
362 }
363
364 return isFaceGeometricallyCompatible( face, allVertices );
365}
366
367void QgsMeshEditor::applyEdit( QgsMeshEditor::Edit &edit )
368{
369 mTopologicalMesh.applyChanges( edit.topologicalChanges );
370 mTriangularMesh->applyChanges( edit.triangularMeshChanges );
371
372 if ( mZValueDatasetGroup &&
373 ( !edit.topologicalChanges.newVerticesZValues().isEmpty() ||
374 !edit.topologicalChanges.verticesToRemoveIndexes().isEmpty() ||
375 !edit.topologicalChanges.addedVertices().isEmpty() ) )
376 mZValueDatasetGroup->setStatisticObsolete();
377
378 updateElementsCount( edit.topologicalChanges );
379}
380
381void QgsMeshEditor::reverseEdit( QgsMeshEditor::Edit &edit )
382{
383 mTopologicalMesh.reverseChanges( edit.topologicalChanges );
384 mTriangularMesh->reverseChanges( edit.triangularMeshChanges, *mMesh );
385
386 if ( mZValueDatasetGroup &&
387 ( !edit.topologicalChanges.newVerticesZValues().isEmpty() ||
388 !edit.topologicalChanges.verticesToRemoveIndexes().isEmpty() ||
389 !edit.topologicalChanges.addedVertices().isEmpty() ) )
390 mZValueDatasetGroup->setStatisticObsolete();
391
392 updateElementsCount( edit.topologicalChanges, false );
393}
394
395void QgsMeshEditor::applyAddVertex( QgsMeshEditor::Edit &edit, const QgsMeshVertex &vertex, double tolerance )
396{
397 QgsMeshVertex vertexInTriangularCoordinate = mTriangularMesh->nativeToTriangularCoordinates( vertex );
398
399 //check if edges is closest than the tolerance from the vertex
400 int faceEdgeIntersect = -1;
401 int edgePosition = -1;
402
403 QgsTopologicalMesh::Changes topologicChanges;
404
405 if ( edgeIsClose( vertexInTriangularCoordinate, tolerance, faceEdgeIntersect, edgePosition ) )
406 {
407 topologicChanges = mTopologicalMesh.insertVertexInFacesEdge( faceEdgeIntersect, edgePosition, vertex );
408 }
409 else
410 {
411 int includingFaceIndex = mTriangularMesh->nativeFaceIndexForPoint( vertexInTriangularCoordinate );
412
413 if ( includingFaceIndex != -1 )
414 topologicChanges = mTopologicalMesh.addVertexInFace( includingFaceIndex, vertex );
415 else
416 topologicChanges = mTopologicalMesh.addFreeVertex( vertex );
417 }
418
419 applyEditOnTriangularMesh( edit, topologicChanges );
420
421 if ( mZValueDatasetGroup )
422 mZValueDatasetGroup->setStatisticObsolete();
423
424 updateElementsCount( edit.topologicalChanges );
425}
426
427bool QgsMeshEditor::applyRemoveVertexFillHole( QgsMeshEditor::Edit &edit, int vertexIndex )
428{
429 QgsTopologicalMesh::Changes changes = mTopologicalMesh.removeVertexFillHole( vertexIndex );
430
431 if ( !changes.isEmpty() )
432 {
433 applyEditOnTriangularMesh( edit, changes );
434
435 if ( mZValueDatasetGroup )
436 mZValueDatasetGroup->setStatisticObsolete();
437
438 updateElementsCount( edit.topologicalChanges );
439 return true;
440 }
441 else
442 return false;
443}
444
445void QgsMeshEditor::applyRemoveVerticesWithoutFillHole( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes )
446{
447 applyEditOnTriangularMesh( edit, mTopologicalMesh.removeVertices( verticesIndexes ) );
448
449 if ( mZValueDatasetGroup )
450 mZValueDatasetGroup->setStatisticObsolete();
451
452 updateElementsCount( edit.topologicalChanges );
453}
454
455void QgsMeshEditor::applyAddFaces( QgsMeshEditor::Edit &edit, const QgsTopologicalMesh::TopologicalFaces &faces )
456{
457 applyEditOnTriangularMesh( edit, mTopologicalMesh.addFaces( faces ) );
458
459 updateElementsCount( edit.topologicalChanges );
460}
461
462void QgsMeshEditor::applyRemoveFaces( QgsMeshEditor::Edit &edit, const QList<int> &faceToRemoveIndex )
463{
464 applyEditOnTriangularMesh( edit, mTopologicalMesh.removeFaces( faceToRemoveIndex ) );
465
466 updateElementsCount( edit.topologicalChanges );
467}
468
469void QgsMeshEditor::applyChangeZValue( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes, const QList<double> &newValues )
470{
471 applyEditOnTriangularMesh( edit, mTopologicalMesh.changeZValue( verticesIndexes, newValues ) );
472
473 if ( mZValueDatasetGroup )
474 mZValueDatasetGroup->setStatisticObsolete();
475}
476
477void QgsMeshEditor::applyChangeXYValue( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
478{
479 applyEditOnTriangularMesh( edit, mTopologicalMesh.changeXYValue( verticesIndexes, newValues ) );
480}
481
482void QgsMeshEditor::applyFlipEdge( QgsMeshEditor::Edit &edit, int vertexIndex1, int vertexIndex2 )
483{
484 applyEditOnTriangularMesh( edit, mTopologicalMesh.flipEdge( vertexIndex1, vertexIndex2 ) );
485
486 updateElementsCount( edit.topologicalChanges );
487}
488
489void QgsMeshEditor::applyMerge( QgsMeshEditor::Edit &edit, int vertexIndex1, int vertexIndex2 )
490{
491 applyEditOnTriangularMesh( edit, mTopologicalMesh.merge( vertexIndex1, vertexIndex2 ) );
492
493 updateElementsCount( edit.topologicalChanges );
494}
495
496void QgsMeshEditor::applySplit( QgsMeshEditor::Edit &edit, int faceIndex )
497{
498 applyEditOnTriangularMesh( edit, mTopologicalMesh.splitFace( faceIndex ) );
499
500 updateElementsCount( edit.topologicalChanges );
501}
502
503void QgsMeshEditor::applyAdvancedEdit( QgsMeshEditor::Edit &edit, QgsMeshAdvancedEditing *editing )
504{
505 applyEditOnTriangularMesh( edit, editing->apply( this ) );
506
507 updateElementsCount( edit.topologicalChanges );
508
509 if ( mZValueDatasetGroup )
510 mZValueDatasetGroup->setStatisticObsolete();
511}
512
513void QgsMeshEditor::applyEditOnTriangularMesh( QgsMeshEditor::Edit &edit, const QgsTopologicalMesh::Changes &topologicChanges )
514{
515 QgsTriangularMesh::Changes triangularChanges( topologicChanges, *mMesh );
516 mTriangularMesh->applyChanges( triangularChanges );
517
518 edit.topologicalChanges = topologicChanges;
519 edit.triangularMeshChanges = triangularChanges;
520}
521
522void QgsMeshEditor::updateElementsCount( const QgsTopologicalMesh::Changes &changes, bool apply )
523{
524 if ( apply )
525 {
526 mValidFacesCount += changes.addedFaces().count() - changes.removedFaces().count();
527 mValidVerticesCount += changes.addedVertices().count() - changes.verticesToRemoveIndexes().count();
528 }
529 else
530 {
531 //reverse
532 mValidFacesCount -= changes.addedFaces().count() - changes.removedFaces().count();
533 mValidVerticesCount -= changes.addedVertices().count() - changes.verticesToRemoveIndexes().count();
534 }
535}
536
538{
539 error = mTopologicalMesh.checkConsistency();
540 switch ( error.errorType )
541 {
543 break;
550 return false;
551 }
552
553 if ( mTriangularMesh->vertices().count() != mMesh->vertexCount() )
554 return false;
555
556 if ( mTriangularMesh->faceCentroids().count() != mMesh->faceCount() )
557 return false;
558
559 return true;
560}
561
562bool QgsMeshEditor::edgeIsClose( QgsPointXY point, double tolerance, int &faceIndex, int &edgePosition )
563{
564 QgsRectangle toleranceZone( point.x() - tolerance,
565 point.y() - tolerance,
566 point.x() + tolerance,
567 point.y() + tolerance );
568
569 edgePosition = -1;
570 double minDist = std::numeric_limits<double>::max();
571 const QList<int> &nativeFaces = mTriangularMesh->nativeFaceIndexForRectangle( toleranceZone );
572 double epsilon = std::numeric_limits<double>::epsilon() * tolerance;
573 for ( const int nativeFaceIndex : nativeFaces )
574 {
575 const QgsMeshFace &face = mMesh->face( nativeFaceIndex );
576 const int faceSize = face.size();
577 for ( int i = 0; i < faceSize; ++i )
578 {
579 const QgsMeshVertex &v1 = mTriangularMesh->vertices().at( face.at( i ) );
580 const QgsMeshVertex &v2 = mTriangularMesh->vertices().at( face.at( ( i + 1 ) % faceSize ) );
581
582 double mx, my;
583 double dist = sqrt( QgsGeometryUtils::sqrDistToLine( point.x(),
584 point.y(),
585 v1.x(),
586 v1.y(),
587 v2.x(),
588 v2.y(),
589 mx,
590 my,
591 epsilon ) );
592
593 if ( dist < tolerance && dist < minDist )
594 {
595 faceIndex = nativeFaceIndex;
596 edgePosition = i;
597 minDist = dist;
598 }
599 }
600 }
601
602 if ( edgePosition != -1 )
603 return true;
604
605 return false;
606
607}
608
610{
611 return mValidFacesCount;
612}
613
615{
616 return mValidVerticesCount;
617}
618
620{
621 return mMaximumVerticesPerFace;
622}
623
624QgsMeshEditingError QgsMeshEditor::removeFaces( const QList<int> &facesToRemove )
625{
626 QgsMeshEditingError error = mTopologicalMesh.facesCanBeRemoved( facesToRemove );
628 return error;
629
630 mUndoStack->push( new QgsMeshLayerUndoCommandRemoveFaces( this, facesToRemove ) );
631
632 return error;
633}
634
635bool QgsMeshEditor::edgeCanBeFlipped( int vertexIndex1, int vertexIndex2 ) const
636{
637 return mTopologicalMesh.edgeCanBeFlipped( vertexIndex1, vertexIndex2 );
638}
639
640void QgsMeshEditor::flipEdge( int vertexIndex1, int vertexIndex2 )
641{
642 if ( !edgeCanBeFlipped( vertexIndex1, vertexIndex2 ) )
643 return;
644
645 mUndoStack->push( new QgsMeshLayerUndoCommandFlipEdge( this, vertexIndex1, vertexIndex2 ) );
646}
647
648bool QgsMeshEditor::canBeMerged( int vertexIndex1, int vertexIndex2 ) const
649{
650 return mTopologicalMesh.canBeMerged( vertexIndex1, vertexIndex2 );
651}
652
653void QgsMeshEditor::merge( int vertexIndex1, int vertexIndex2 )
654{
655 if ( !canBeMerged( vertexIndex1, vertexIndex2 ) )
656 return;
657
658 mUndoStack->push( new QgsMeshLayerUndoCommandMerge( this, vertexIndex1, vertexIndex2 ) );
659}
660
661bool QgsMeshEditor::faceCanBeSplit( int faceIndex ) const
662{
663 return mTopologicalMesh.canBeSplit( faceIndex );
664}
665
666int QgsMeshEditor::splitFaces( const QList<int> &faceIndexes )
667{
668 QList<int> faceIndexesSplittable;
669
670 for ( const int faceIndex : faceIndexes )
671 if ( faceCanBeSplit( faceIndex ) )
672 faceIndexesSplittable.append( faceIndex );
673
674 if ( faceIndexesSplittable.isEmpty() )
675 return 0;
676
677 mUndoStack->push( new QgsMeshLayerUndoCommandSplitFaces( this, faceIndexesSplittable ) );
678
679 return faceIndexesSplittable.count();
680}
681
682QVector<QgsMeshFace> QgsMeshEditor::prepareFaces( const QVector<QgsMeshFace> &faces, QgsMeshEditingError &error ) const
683{
684 QVector<QgsMeshFace> treatedFaces = faces;
685
686 // here we could add later some filters, for example, removing faces intersecting with existing one
687
688 for ( int i = 0; i < treatedFaces.count(); ++i )
689 {
690 QgsMeshFace &face = treatedFaces[i];
691 if ( mMaximumVerticesPerFace != 0 && face.count() > mMaximumVerticesPerFace )
692 {
694 break;
695 }
696
697 error = QgsTopologicalMesh::counterClockwiseFaces( face, mMesh );
699 break;
700 }
701
702 return treatedFaces;
703}
704
705QList<int> QgsMeshEditor::prepareFaceWithNewVertices( const QList<int> &face, const QList<QgsMeshVertex> &newVertices, QgsMeshEditingError &error ) const
706{
707 if ( mMaximumVerticesPerFace != 0 && face.count() > mMaximumVerticesPerFace )
708 {
710 return face;
711 }
712
713 int faceSize = face.count();
714 QVector<QgsMeshVertex> vertices( faceSize );
715 int newVertexPos = 0;
716 for ( int i = 0; i < faceSize; ++i )
717 {
718 if ( face.at( i ) == -1 )
719 {
720 if ( newVertexPos >= newVertices.count() )
721 return QList<int>();
722 vertices[i] = newVertices.at( newVertexPos++ );
723 }
724 else if ( face.at( i ) >= 0 )
725 {
726 if ( face.at( i ) >= mTriangularMesh->vertices().count() )
727 {
729 break;
730 }
731 vertices[i] = mTriangularMesh->vertices().at( face.at( i ) );
732 }
733 else
734 {
736 break;
737 }
738 }
739
741 return face;
742
743 bool clockwise = false;
744 error = QgsTopologicalMesh::checkTopologyOfVerticesAsFace( vertices, clockwise );
745
746 if ( clockwise && error.errorType == Qgis::MeshEditingErrorType::NoError )
747 {
748
749 QList<int> newFace = face;
750 for ( int i = 0; i < faceSize / 2; ++i )
751 {
752 int temp = newFace[i];
753 newFace[i] = face.at( faceSize - i - 1 );
754 newFace[faceSize - i - 1] = temp;
755 }
756
757 return newFace;
758 }
759
760 return face;
761}
762
763QgsMeshEditingError QgsMeshEditor::addFaces( const QVector<QVector<int> > &faces )
764{
766 QVector<QgsMeshFace> facesToAdd = prepareFaces( faces, error );
767
769 return error;
770
772
773 error = mTopologicalMesh.facesCanBeAdded( topologicalFaces );
774
776 return error;
777
778 mUndoStack->push( new QgsMeshLayerUndoCommandAddFaces( this, topologicalFaces ) );
779
780 return error;
781}
782
783QgsMeshEditingError QgsMeshEditor::addFace( const QVector<int> &vertexIndexes )
784{
785 return addFaces( {vertexIndexes} );
786}
787
788QgsMeshEditingError QgsMeshEditor::addFaceWithNewVertices( const QList<int> &vertexIndexes, const QList<QgsMeshVertex> &newVertices )
789{
790 mUndoStack->beginMacro( tr( "Add a face with new %n vertices", nullptr, newVertices.count() ) );
791 int newVertexIndex = mMesh->vertexCount();
792 addVertices( newVertices.toVector(), 0 );
793 QgsMeshFace face( vertexIndexes.count() );
794 for ( int i = 0; i < vertexIndexes.count(); ++i )
795 {
796 int index = vertexIndexes.at( i );
797 if ( index == -1 )
798 face[i] = newVertexIndex++;
799 else
800 face[i] = index;
801 }
802
803 QgsMeshEditingError error = addFace( face );
804 mUndoStack->endMacro();
805
806 return error;
807}
808
809int QgsMeshEditor::addVertices( const QVector<QgsMeshVertex> &vertices, double tolerance )
810{
811 QVector<QgsMeshVertex> verticesInLayerCoordinate( vertices.count() );
812 int ignoredVertex = 0;
813 for ( int i = 0; i < vertices.count(); ++i )
814 {
815 const QgsPointXY &pointInTriangularMesh = vertices.at( i );
816 bool isTooClose = false;
817 int triangleIndex = mTriangularMesh->faceIndexForPoint_v2( pointInTriangularMesh );
818 if ( triangleIndex != -1 )
819 {
820 const QgsMeshFace face = mTriangularMesh->triangles().at( triangleIndex );
821 for ( int j = 0; j < 3; ++j )
822 {
823 const QgsPointXY &facePoint = mTriangularMesh->vertices().at( face.at( j ) );
824 double dist = pointInTriangularMesh.distance( facePoint );
825 if ( dist < tolerance )
826 {
827 isTooClose = true;
828 break;
829 }
830 }
831 }
832
833 if ( !isTooClose )
834 verticesInLayerCoordinate[i] = mTriangularMesh->triangularToNativeCoordinates( vertices.at( i ) );
835 else
836 verticesInLayerCoordinate[i] = QgsMeshVertex();
837
838 if ( verticesInLayerCoordinate.at( i ).isEmpty() )
839 ignoredVertex++;
840 }
841
842 if ( ignoredVertex < vertices.count() )
843 {
844 mUndoStack->push( new QgsMeshLayerUndoCommandAddVertices( this, verticesInLayerCoordinate, tolerance ) );
845 }
846
847 int effectivlyAddedVertex = vertices.count() - ignoredVertex;
848
849 return effectivlyAddedVertex;
850}
851
852int QgsMeshEditor::addPointsAsVertices( const QVector<QgsPoint> &point, double tolerance )
853{
854 return addVertices( point, tolerance );
855}
856
857QgsMeshEditingError QgsMeshEditor::removeVerticesWithoutFillHoles( const QList<int> &verticesToRemoveIndexes )
858{
860
861 QList<int> verticesIndexes = verticesToRemoveIndexes;
862
863 QSet<int> concernedNativeFaces;
864 for ( const int vi : std::as_const( verticesIndexes ) )
865 {
866 const QList<int> faces = mTopologicalMesh.facesAroundVertex( vi );
867 concernedNativeFaces.unite( QSet< int >( faces.begin(), faces.end() ) );
868 }
869
870 error = mTopologicalMesh.facesCanBeRemoved( concernedNativeFaces.values() );
871
873 return error;
874
875 mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles( this, verticesIndexes ) );
876 return error;
877}
878
879QList<int> QgsMeshEditor::removeVerticesFillHoles( const QList<int> &verticesToRemoveIndexes )
880{
881 QList<int> remainingVertices;
882 mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVerticesFillHoles( this, verticesToRemoveIndexes, &remainingVertices ) );
883
884 return remainingVertices;
885}
886
887
888void QgsMeshEditor::changeZValues( const QList<int> &verticesIndexes, const QList<double> &newZValues )
889{
890 mUndoStack->push( new QgsMeshLayerUndoCommandChangeZValue( this, verticesIndexes, newZValues ) );
891}
892
893bool QgsMeshEditor::canBeTransformed( const QList<int> &facesToCheck, const std::function<const QgsMeshVertex( int )> &transformFunction ) const
894{
895 for ( const int faceIndex : facesToCheck )
896 {
897 const QgsMeshFace &face = mMesh->face( faceIndex );
898 int faceSize = face.count();
899 QVector<QgsPointXY> pointsInTriangularMeshCoordinate( faceSize );
900 QVector<QgsPointXY> points( faceSize );
901 for ( int i = 0; i < faceSize; ++i )
902 {
903 int ip0 = face[i];
904 int ip1 = face[( i + 1 ) % faceSize];
905 int ip2 = face[( i + 2 ) % faceSize];
906
907 QgsMeshVertex p0 = transformFunction( ip0 );
908 QgsMeshVertex p1 = transformFunction( ip1 );
909 QgsMeshVertex p2 = transformFunction( ip2 );
910
911 double ux = p0.x() - p1.x();
912 double uy = p0.y() - p1.y();
913 double vx = p2.x() - p1.x();
914 double vy = p2.y() - p1.y();
915
916 double crossProduct = ux * vy - uy * vx;
917 if ( crossProduct >= 0 ) //if cross product>0, we have two edges clockwise
918 return false;
919 pointsInTriangularMeshCoordinate[i] = mTriangularMesh->nativeToTriangularCoordinates( p0 );
920 points[i] = p0;
921 }
922
923 const QgsGeometry &deformedFace = QgsGeometry::fromPolygonXY( {points} );
924
925 // now test if the deformed face contain something else
926 QList<int> otherFaceIndexes =
927 mTriangularMesh->nativeFaceIndexForRectangle( QgsGeometry::fromPolygonXY( {pointsInTriangularMeshCoordinate} ).boundingBox() );
928
929 for ( const int otherFaceIndex : otherFaceIndexes )
930 {
931 const QgsMeshFace &otherFace = mMesh->face( otherFaceIndex );
932 int existingFaceSize = otherFace.count();
933 bool shareVertex = false;
934 for ( int i = 0; i < existingFaceSize; ++i )
935 {
936 if ( face.contains( otherFace.at( i ) ) )
937 {
938 shareVertex = true;
939 break;
940 }
941 }
942 if ( shareVertex )
943 {
944 //only test the edge that not contains a shared vertex
945 for ( int i = 0; i < existingFaceSize; ++i )
946 {
947 int index1 = otherFace.at( i );
948 int index2 = otherFace.at( ( i + 1 ) % existingFaceSize );
949 if ( ! face.contains( index1 ) && !face.contains( index2 ) )
950 {
951 const QgsPointXY &v1 = transformFunction( index1 );
952 const QgsPointXY &v2 = transformFunction( index2 );
953 QgsGeometry edgeGeom = QgsGeometry::fromPolylineXY( { v1, v2} );
954 if ( deformedFace.intersects( edgeGeom ) )
955 return false;
956 }
957 }
958 }
959 else
960 {
961 QVector<QgsPointXY> otherPoints( existingFaceSize );
962 for ( int i = 0; i < existingFaceSize; ++i )
963 otherPoints[i] = transformFunction( otherFace.at( i ) );
964 const QgsGeometry existingFaceGeom = QgsGeometry::fromPolygonXY( {otherPoints } );
965 if ( deformedFace.intersects( existingFaceGeom ) )
966 return false;
967 }
968 }
969
970 const QList<int> freeVerticesIndex = freeVerticesIndexes();
971 for ( const int vertexIndex : freeVerticesIndex )
972 {
973 const QgsPointXY &mapPoint = transformFunction( vertexIndex ); //free vertices can be transformed
974 if ( deformedFace.contains( &mapPoint ) )
975 return false;
976 }
977 }
978
979 // free vertices
980 const QList<int> freeVerticesIndex = freeVerticesIndexes();
981 for ( const int vertexIndex : freeVerticesIndex )
982 {
983 const QgsMeshVertex &newFreeVertexPosition = transformFunction( vertexIndex ); // transformed free vertex
984 const QgsMeshVertex pointInTriangularCoord = mTriangularMesh->nativeToTriangularCoordinates( newFreeVertexPosition );
985 const int originalIncludingFace = mTriangularMesh->nativeFaceIndexForPoint( pointInTriangularCoord );
986
987 if ( originalIncludingFace != -1 )
988 {
989 // That means two things: the free vertex is moved AND is included in a face before transform
990 // Before returning false, we need to check if the vertex is still in the face after transform
991 const QgsMeshFace &face = mMesh->face( originalIncludingFace );
992 int faceSize = face.count();
993 QVector<QgsPointXY> points( faceSize );
994 for ( int i = 0; i < faceSize; ++i )
995 points[i] = transformFunction( face.at( i ) );
996
997 const QgsGeometry &deformedFace = QgsGeometry::fromPolygonXY( {points} );
998 const QgsPointXY ptXY( newFreeVertexPosition );
999 if ( deformedFace.contains( &ptXY ) )
1000 return false;
1001 }
1002 }
1003
1004 return true;
1005}
1006
1007void QgsMeshEditor::changeXYValues( const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
1008{
1009 // TODO : implement a check if it is possible to change the (x,y) values. For now, this check is made in the APP part
1010 mUndoStack->push( new QgsMeshLayerUndoCommandChangeXYValue( this, verticesIndexes, newValues ) );
1011}
1012
1013void QgsMeshEditor::changeCoordinates( const QList<int> &verticesIndexes, const QList<QgsPoint> &newCoordinates )
1014{
1015 mUndoStack->push( new QgsMeshLayerUndoCommandChangeCoordinates( this, verticesIndexes, newCoordinates ) );
1016}
1017
1019{
1020 mUndoStack->push( new QgsMeshLayerUndoCommandAdvancedEditing( this, editing ) );
1021}
1022
1024{
1025 mTopologicalMesh.reindex();
1026 mUndoStack->clear();
1027}
1028
1030 : mMeshEditor( meshEditor )
1031{
1032}
1033
1035{
1036 if ( mMeshEditor.isNull() )
1037 return;
1038
1039 for ( int i = mEdits.count() - 1; i >= 0; --i )
1040 mMeshEditor->reverseEdit( mEdits[i] );
1041}
1042
1044{
1045 if ( mMeshEditor.isNull() )
1046 return;
1047
1048 for ( QgsMeshEditor::Edit &edit : mEdits )
1049 mMeshEditor->applyEdit( edit );
1050}
1051
1052QgsMeshLayerUndoCommandAddVertices::QgsMeshLayerUndoCommandAddVertices( QgsMeshEditor *meshEditor, const QVector<QgsMeshVertex> &vertices, double tolerance )
1053 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1054 , mVertices( vertices )
1055 , mTolerance( tolerance )
1056{
1057 setText( QObject::tr( "Add %n vertices", nullptr, mVertices.count() ) );
1058}
1059
1061{
1062 if ( !mVertices.isEmpty() )
1063 {
1064 for ( int i = 0; i < mVertices.count(); ++i )
1065 {
1066 const QgsMeshVertex &vertex = mVertices.at( i );
1067 if ( vertex.isEmpty() )
1068 continue;
1069 QgsMeshEditor::Edit edit;
1070 mMeshEditor->applyAddVertex( edit, vertex, mTolerance );
1071 mEdits.append( edit );
1072 }
1073 mVertices.clear(); //not needed anymore, changes are store in mEdits
1074 }
1075 else
1076 {
1077 for ( QgsMeshEditor::Edit &edit : mEdits )
1078 mMeshEditor->applyEdit( edit );
1079 }
1080}
1081
1083 QgsMeshEditor *meshEditor,
1084 const QList<int> &verticesToRemoveIndexes,
1085 QList<int> *remainingVerticesPointer )
1086 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1087 , mVerticesToRemoveIndexes( verticesToRemoveIndexes )
1088 , mRemainingVerticesPointer( remainingVerticesPointer )
1089{
1090 setText( QObject::tr( "Remove %n vertices filling holes", nullptr, verticesToRemoveIndexes.count() ) );
1091}
1092
1094{
1095 int initialVertexCount = mVerticesToRemoveIndexes.count();
1096 if ( !mVerticesToRemoveIndexes.isEmpty() )
1097 {
1098 QgsMeshEditor::Edit edit;
1099 QList<int> vertexToRetry;
1100 while ( !mVerticesToRemoveIndexes.isEmpty() )
1101 {
1102 // try again and again until there is no vertices to remove anymore or nothing is removed.
1103 for ( const int &vertex : std::as_const( mVerticesToRemoveIndexes ) )
1104 {
1105 if ( mMeshEditor->applyRemoveVertexFillHole( edit, vertex ) )
1106 mEdits.append( edit );
1107 else
1108 vertexToRetry.append( vertex );
1109 }
1110
1111 if ( vertexToRetry.count() == mVerticesToRemoveIndexes.count() )
1112 break;
1113 else
1114 mVerticesToRemoveIndexes = vertexToRetry;
1115 }
1116
1117 if ( initialVertexCount == mVerticesToRemoveIndexes.count() )
1118 setObsolete( true );
1119
1120 if ( mRemainingVerticesPointer != nullptr )
1121 *mRemainingVerticesPointer = mVerticesToRemoveIndexes;
1122
1123 mRemainingVerticesPointer = nullptr;
1124
1125 mVerticesToRemoveIndexes.clear(); //not needed anymore, changes are store in mEdits
1126 }
1127 else
1128 {
1129 for ( QgsMeshEditor::Edit &edit : mEdits )
1130 mMeshEditor->applyEdit( edit );
1131 }
1132}
1133
1134
1136 QgsMeshEditor *meshEditor,
1137 const QList<int> &verticesToRemoveIndexes )
1138 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1139 , mVerticesToRemoveIndexes( verticesToRemoveIndexes )
1140{
1141 setText( QObject::tr( "Remove %n vertices without filling holes", nullptr, verticesToRemoveIndexes.count() ) ) ;
1142}
1143
1145{
1146 if ( !mVerticesToRemoveIndexes.isEmpty() )
1147 {
1148 QgsMeshEditor::Edit edit;
1149
1150 mMeshEditor->applyRemoveVerticesWithoutFillHole( edit, mVerticesToRemoveIndexes );
1151 mEdits.append( edit );
1152
1153 mVerticesToRemoveIndexes.clear(); //not needed anymore, changes are store in mEdits
1154 }
1155 else
1156 {
1157 for ( QgsMeshEditor::Edit &edit : mEdits )
1158 mMeshEditor->applyEdit( edit );
1159 }
1160}
1161
1163 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1164 , mFaces( faces )
1165{
1166 setText( QObject::tr( "Add %n face(s)", nullptr, faces.meshFaces().count() ) );
1167}
1168
1170{
1171 if ( !mFaces.meshFaces().isEmpty() )
1172 {
1173 QgsMeshEditor::Edit edit;
1174 mMeshEditor->applyAddFaces( edit, mFaces );
1175 mEdits.append( edit );
1176
1177 mFaces.clear(); //not needed anymore, now changes are store in edit
1178 }
1179 else
1180 {
1181 for ( QgsMeshEditor::Edit &edit : mEdits )
1182 mMeshEditor->applyEdit( edit );
1183 }
1184}
1185
1187 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1188 , mfacesToRemoveIndexes( facesToRemoveIndexes )
1189{
1190 setText( QObject::tr( "Remove %n face(s)", nullptr, facesToRemoveIndexes.count() ) );
1191}
1192
1194{
1195 if ( !mfacesToRemoveIndexes.isEmpty() )
1196 {
1197 QgsMeshEditor::Edit edit;
1198 mMeshEditor->applyRemoveFaces( edit, mfacesToRemoveIndexes );
1199 mEdits.append( edit );
1200
1201 mfacesToRemoveIndexes.clear(); //not needed anymore, now changes are store in edit
1202 }
1203 else
1204 {
1205 for ( QgsMeshEditor::Edit &edit : mEdits )
1206 mMeshEditor->applyEdit( edit );
1207 }
1208}
1209
1210QgsMeshEditingError::QgsMeshEditingError(): errorType( Qgis::MeshEditingErrorType::NoError ), elementIndex( -1 ) {}
1211
1212QgsMeshEditingError::QgsMeshEditingError( Qgis::MeshEditingErrorType type, int elementIndex ): errorType( type ), elementIndex( elementIndex ) {}
1213
1215{
1216 return mTriangularMesh->nativeExtent();
1217}
1218
1220{
1221 if ( mUndoStack )
1222 return !mUndoStack->isClean();
1223
1224 return false;
1225}
1226
1227bool QgsMeshEditor::reindex( bool renumbering )
1228{
1229 mTopologicalMesh.reindex();
1230 mUndoStack->clear();
1232 mValidFacesCount = mMesh->faceCount();
1233 mValidVerticesCount = mMesh->vertexCount();
1234
1236 return false;
1237
1238 if ( renumbering )
1239 {
1240 if ( !mTopologicalMesh.renumber() )
1241 return false;
1242
1243 QgsMeshEditingError error;
1244 mTopologicalMesh = QgsTopologicalMesh::createTopologicalMesh( mMesh, mMaximumVerticesPerFace, error );
1245 mValidFacesCount = mMesh->faceCount();
1246 mValidVerticesCount = mMesh->vertexCount();
1248 }
1249 else
1250 return true;
1251}
1252
1254{
1255 return mTopologicalMesh.freeVerticesIndexes();
1256}
1257
1258bool QgsMeshEditor::isVertexOnBoundary( int vertexIndex ) const
1259{
1260 return mTopologicalMesh.isVertexOnBoundary( vertexIndex );
1261}
1262
1263bool QgsMeshEditor::isVertexFree( int vertexIndex ) const
1264{
1265 return mTopologicalMesh.isVertexFree( vertexIndex );
1266}
1267
1269{
1270 return mTopologicalMesh.vertexCirculator( vertexIndex );
1271}
1272
1274{
1275 return mTopologicalMesh;
1276}
1277
1279{
1280 return mTriangularMesh;
1281}
1282
1283QgsMeshLayerUndoCommandChangeZValue::QgsMeshLayerUndoCommandChangeZValue( QgsMeshEditor *meshEditor, const QList<int> &verticesIndexes, const QList<double> &newValues )
1284 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1285 , mVerticesIndexes( verticesIndexes )
1286 , mNewValues( newValues )
1287{
1288 setText( QObject::tr( "Change %n vertices Z Value", nullptr, verticesIndexes.count() ) );
1289}
1290
1292{
1293 if ( !mVerticesIndexes.isEmpty() )
1294 {
1295 QgsMeshEditor::Edit edit;
1296 mMeshEditor->applyChangeZValue( edit, mVerticesIndexes, mNewValues );
1297 mEdits.append( edit );
1298 mVerticesIndexes.clear();
1299 mNewValues.clear();
1300 }
1301 else
1302 {
1303 for ( QgsMeshEditor::Edit &edit : mEdits )
1304 mMeshEditor->applyEdit( edit );
1305 }
1306}
1307
1308QgsMeshLayerUndoCommandChangeXYValue::QgsMeshLayerUndoCommandChangeXYValue( QgsMeshEditor *meshEditor, const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
1309 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1310 , mVerticesIndexes( verticesIndexes )
1311 , mNewValues( newValues )
1312{
1313 setText( QObject::tr( "Move %n vertices", nullptr, verticesIndexes.count() ) );
1314}
1315
1317{
1318 if ( !mVerticesIndexes.isEmpty() )
1319 {
1320 QgsMeshEditor::Edit edit;
1321 mMeshEditor->applyChangeXYValue( edit, mVerticesIndexes, mNewValues );
1322 mEdits.append( edit );
1323 mVerticesIndexes.clear();
1324 mNewValues.clear();
1325 }
1326 else
1327 {
1328 for ( QgsMeshEditor::Edit &edit : mEdits )
1329 mMeshEditor->applyEdit( edit );
1330 }
1331}
1332
1333
1334QgsMeshLayerUndoCommandChangeCoordinates::QgsMeshLayerUndoCommandChangeCoordinates( QgsMeshEditor *meshEditor, const QList<int> &verticesIndexes, const QList<QgsPoint> &newCoordinates )
1335 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1336 , mVerticesIndexes( verticesIndexes )
1337 , mNewCoordinates( newCoordinates )
1338{
1339 setText( QObject::tr( "Transform %n vertices coordinates", nullptr, verticesIndexes.count() ) );
1340}
1341
1343{
1344 if ( !mVerticesIndexes.isEmpty() )
1345 {
1346 QgsMeshEditor::Edit editXY;
1347 QList<QgsPointXY> newXY;
1348 newXY.reserve( mNewCoordinates.count() );
1349 QgsMeshEditor::Edit editZ;
1350 QList<double> newZ;
1351 newZ.reserve( mNewCoordinates.count() );
1352
1353 for ( const QgsPoint &pt : std::as_const( mNewCoordinates ) )
1354 {
1355 newXY.append( pt );
1356 newZ.append( pt.z() );
1357 }
1358
1359 mMeshEditor->applyChangeXYValue( editXY, mVerticesIndexes, newXY );
1360 mEdits.append( editXY );
1361 mMeshEditor->applyChangeZValue( editZ, mVerticesIndexes, newZ );
1362 mEdits.append( editZ );
1363 mVerticesIndexes.clear();
1364 mNewCoordinates.clear();
1365 }
1366 else
1367 {
1368 for ( QgsMeshEditor::Edit &edit : mEdits )
1369 mMeshEditor->applyEdit( edit );
1370 }
1371}
1372
1373
1374
1376 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1377 , mVertexIndex1( vertexIndex1 )
1378 , mVertexIndex2( vertexIndex2 )
1379{
1380 setText( QObject::tr( "Flip edge" ) );
1381}
1382
1384{
1385 if ( mVertexIndex1 >= 0 && mVertexIndex2 >= 0 )
1386 {
1387 QgsMeshEditor::Edit edit;
1388 mMeshEditor->applyFlipEdge( edit, mVertexIndex1, mVertexIndex2 );
1389 mEdits.append( edit );
1390 mVertexIndex1 = -1;
1391 mVertexIndex2 = -1;
1392 }
1393 else
1394 {
1395 for ( QgsMeshEditor::Edit &edit : mEdits )
1396 mMeshEditor->applyEdit( edit );
1397 }
1398}
1399
1400QgsMeshLayerUndoCommandMerge::QgsMeshLayerUndoCommandMerge( QgsMeshEditor *meshEditor, int vertexIndex1, int vertexIndex2 )
1401 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1402 , mVertexIndex1( vertexIndex1 )
1403 , mVertexIndex2( vertexIndex2 )
1404{
1405 setText( QObject::tr( "Merge faces" ) );
1406}
1407
1409{
1410 if ( mVertexIndex1 >= 0 && mVertexIndex2 >= 0 )
1411 {
1412 QgsMeshEditor::Edit edit;
1413 mMeshEditor->applyMerge( edit, mVertexIndex1, mVertexIndex2 );
1414 mEdits.append( edit );
1415 mVertexIndex1 = -1;
1416 mVertexIndex2 = -1;
1417 }
1418 else
1419 {
1420 for ( QgsMeshEditor::Edit &edit : mEdits )
1421 mMeshEditor->applyEdit( edit );
1422 }
1423}
1424
1426 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1427 , mFaceIndexes( faceIndexes )
1428{
1429 setText( QObject::tr( "Split %n face(s)", nullptr, faceIndexes.count() ) );
1430}
1431
1433{
1434 if ( !mFaceIndexes.isEmpty() )
1435 {
1436 for ( int faceIndex : std::as_const( mFaceIndexes ) )
1437 {
1438 QgsMeshEditor::Edit edit;
1439 mMeshEditor->applySplit( edit, faceIndex );
1440 mEdits.append( edit );
1441 }
1442 mFaceIndexes.clear();
1443 }
1444 else
1445 {
1446 for ( QgsMeshEditor::Edit &edit : mEdits )
1447 mMeshEditor->applyEdit( edit );
1448 }
1449}
1450
1452 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1453 , mAdvancedEditing( advancdEdit )
1454{
1455 setText( advancdEdit->text() );
1456}
1457
1459{
1460 if ( mAdvancedEditing )
1461 {
1462 QgsMeshEditor::Edit edit;
1463 while ( !mAdvancedEditing->isFinished() )
1464 {
1465 mMeshEditor->applyAdvancedEdit( edit, mAdvancedEditing );
1466 mEdits.append( edit );
1467 }
1468
1469 mAdvancedEditing = nullptr;
1470 }
1471 else
1472 {
1473 for ( QgsMeshEditor::Edit &edit : mEdits )
1474 mMeshEditor->applyEdit( edit );
1475 }
1476}
The Qgis class provides global constants for use throughout the application.
Definition: qgis.h:55
MeshEditingErrorType
Type of error that can occur during mesh frame editing.
Definition: qgis.h:966
@ TooManyVerticesInFace
A face has more vertices than the maximum number supported per face.
@ InvalidFace
An error occurs due to an invalid face (for example, vertex indexes are unordered)
@ UniqueSharedVertex
A least two faces share only one vertices.
@ ManifoldFace
ManifoldFace.
@ InvalidVertex
An error occurs due to an invalid vertex (for example, vertex index is out of range the available ver...
@ FlatFace
A flat face is present.
static double sqrDistToLine(double ptX, double ptY, double x1, double y1, double x2, double y2, double &minDistX, double &minDistY, double epsilon) SIP_HOLDGIL
Returns the squared distance between a point and a line.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static QgsGeometry fromPolylineXY(const QgsPolylineXY &polyline)
Creates a new LineString geometry from a list of QgsPointXY points.
bool contains(const QgsPointXY *p) const
Returns true if the geometry contains the point p.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine representing the specified geometry.
static QgsGeometry fromPolygonXY(const QgsPolygonXY &polygon)
Creates a new geometry from a QgsPolygonXY.
bool intersects(const QgsRectangle &rectangle) const
Returns true if this geometry exactly intersects with a rectangle.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:45
Abstract class that can be derived to implement advanced editing on mesh.
virtual QgsTopologicalMesh::Changes apply(QgsMeshEditor *meshEditor)=0
Apply a change to mesh Editor.
virtual bool isFinished() const
Returns whether the advanced edit is finished, if not, this edit has to be applied again with QgsMesh...
virtual QString text() const
Returns a short text string describing what this advanced edit does. Default implementation return a ...
virtual int maximumVerticesCountPerFace() const
Returns the maximum number of vertices per face supported by the current mesh, if returns 0,...
Abstract class that represents a dataset group.
void setStatisticObsolete() const
Sets statistic obsolete, that means statistic will be recalculated when requested.
Class that represents an error during mesh editing.
Definition: qgsmesheditor.h:43
Qgis::MeshEditingErrorType errorType
Definition: qgsmesheditor.h:52
QgsMeshEditingError()
Constructor of the default error, that is NoError.
Class that makes edit operation on a mesh.
Definition: qgsmesheditor.h:68
friend class QgsMeshLayerUndoCommandSplitFaces
QgsMeshEditingError initialize()
Initializes the mesh editor and returns first error if the internal native mesh has topological error...
friend class QgsMeshLayerUndoCommandMerge
void changeXYValues(const QList< int > &verticesIndexes, const QList< QgsPointXY > &newValues)
Changes the (X,Y) coordinates values of the vertices with indexes in verticesIndexes with the values ...
int validFacesCount() const
Returns the count of valid faces, that is non void faces in the mesh.
friend class QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles
QgsMeshEditingError removeFaces(const QList< int > &facesToRemove)
Removes faces faces to the mesh, returns topological errors if this operation fails (operation is not...
QgsMeshEditingError addFaces(const QVector< QgsMeshFace > &faces)
Adds faces faces to the mesh, returns topological errors if this operation fails (operation is not re...
bool checkConsistency(QgsMeshEditingError &error) const
Return true if the edited mesh is consistent.
QList< int > removeVerticesFillHoles(const QList< int > &verticesToRemoveIndexes)
Removes vertices with indexes in the list verticesToRemoveIndexes in the mesh the surrounding faces A...
void flipEdge(int vertexIndex1, int vertexIndex2)
Flips edge (vertexIndex1, vertexIndex2)
QgsRectangle extent() const
Returns the extent of the edited mesh.
friend class QgsMeshLayerUndoCommandAddVertices
bool faceCanBeSplit(int faceIndex) const
Returns true if face with index faceIndex can be split.
QgsMeshEditingError initializeWithErrorsFix()
Initializes the mesh editor.
int maximumVerticesPerFace() const
Returns the maximum count of vertices per face that the mesh can support.
QgsMeshEditingError addFace(const QVector< int > &vertexIndexes)
Adds a face face to the mesh with vertex indexes vertexIndexes, returns topological errors if this op...
QgsMeshEditor(QgsMeshLayer *meshLayer)
Constructor with a specified layer meshLayer.
void merge(int vertexIndex1, int vertexIndex2)
Merges faces separated by vertices with indexes vertexIndex1 and vertexIndex2.
bool edgeIsClose(QgsPointXY point, double tolerance, int &faceIndex, int &edgePosition)
Returns true if an edge of face is closest than the tolerance from the point in triangular mesh coord...
QgsMeshEditingError removeVerticesWithoutFillHoles(const QList< int > &verticesToRemoveIndexes)
Removes vertices with indexes in the list verticesToRemoveIndexes in the mesh removing the surroundin...
bool isFaceGeometricallyCompatible(const QgsMeshFace &face) const
Returns true if the face does not intersect or contains any other elements (faces or vertices) The to...
QList< int > freeVerticesIndexes() const
Returns all the free vertices indexes.
friend class QgsMeshLayerUndoCommandChangeXYValue
bool isVertexOnBoundary(int vertexIndex) const
Returns whether the vertex with index vertexIndex is on a boundary.
friend class QgsMeshLayerUndoCommandFlipEdge
bool faceCanBeAdded(const QgsMeshFace &face) const
Returns true if a face can be added to the mesh.
void changeCoordinates(const QList< int > &verticesIndexes, const QList< QgsPoint > &newCoordinates)
Changes the (X,Y,Z) coordinates values of the vertices with indexes in vertices indexes with the valu...
void stopEditing()
Stops editing.
friend class QgsMeshLayerUndoCommandAdvancedEditing
bool canBeMerged(int vertexIndex1, int vertexIndex2) const
Returns true if faces separated by vertices with indexes vertexIndex1 and vertexIndex2 can be merged.
friend class QgsMeshLayerUndoCommandAddFaces
QgsMeshEditingError addFaceWithNewVertices(const QList< int > &vertexIndexes, const QList< QgsMeshVertex > &newVertices)
Adds a face formed by some vertices vertexIndexes to the mesh, returns topological errors if this ope...
friend class QgsMeshLayerUndoCommandChangeCoordinates
bool edgeCanBeFlipped(int vertexIndex1, int vertexIndex2) const
Returns true if the edge can be flipped (only available for edge shared by two faces with 3 vertices)
int splitFaces(const QList< int > &faceIndexes)
Splits faces with index faceIndexes.
QgsMeshVertexCirculator vertexCirculator(int vertexIndex) const
Returns a vertex circulator linked to this mesh around the vertex with index vertexIndex.
bool faceCanBeAddedWithNewVertices(const QList< int > &verticesIndex, const QList< QgsMeshVertex > &newVertices) const
Returns true if a face formed by some vertices can be added to the mesh.
void meshEdited()
Emitted when the mesh is edited.
void changeZValues(const QList< int > &verticesIndexes, const QList< double > &newValues)
Changes the Z values of the vertices with indexes in vertices indexes with the values in newValues.
void resetTriangularMesh(QgsTriangularMesh *triangularMesh)
Resets the triangular mesh.
bool isModified() const
Returns whether the mesh has been modified.
void advancedEdit(QgsMeshAdvancedEditing *editing)
Applies an advance editing on the edited mesh, see QgsMeshAdvancedEditing.
bool canBeTransformed(const QList< int > &facesToCheck, const std::function< const QgsMeshVertex(int)> &transformFunction) const
Returns true if faces with index in transformedFaces can be transformed without obtaining topologic o...
int addVertices(const QVector< QgsMeshVertex > &vertices, double tolerance)
Adds vertices in triangular mesh coordinate in the mesh.
int validVerticesCount() const
Returns the count of valid vertices, that is non void vertices in the mesh.
friend class QgsMeshLayerUndoCommandRemoveVerticesFillHoles
bool isVertexFree(int vertexIndex) const
Returns whether the vertex with index vertexIndex is a free vertex.
bool reindex(bool renumbering)
Reindexes the mesh, that is remove unusued index of face and vertices, this operation void the undo/r...
friend class QgsMeshLayerUndoCommandChangeZValue
QgsTriangularMesh * triangularMesh()
Returns a pointer to the triangular mesh.
bool fixError(const QgsMeshEditingError &error)
Tries to fix the topological error in the mesh.
QgsMeshDatasetGroup * createZValueDatasetGroup()
Creates and returns a scalar dataset group with value on vertex that is can be used to access the Z v...
friend class QgsMeshLayerUndoCommandRemoveFaces
QgsTopologicalMesh & topologicalMesh()
Returns a reference to the topological mesh.
int addPointsAsVertices(const QVector< QgsPoint > &point, double tolerance)
Adds points as vertices in triangular mesh coordinate in the mesh.
QgsMeshLayerUndoCommandAddFaces(QgsMeshEditor *meshEditor, QgsTopologicalMesh::TopologicalFaces &faces)
Constructor with the associated meshEditor and faces that will be added.
QgsMeshLayerUndoCommandAddVertices(QgsMeshEditor *meshEditor, const QVector< QgsMeshVertex > &vertices, double tolerance)
Constructor with the associated meshEditor and vertices that will be added.
QgsMeshLayerUndoCommandAdvancedEditing(QgsMeshEditor *meshEditor, QgsMeshAdvancedEditing *advancdEdit)
Constructor with the associated meshEditor.
QgsMeshLayerUndoCommandChangeCoordinates(QgsMeshEditor *meshEditor, const QList< int > &verticesIndexes, const QList< QgsPoint > &newCoordinates)
Constructor with the associated meshEditor and indexes verticesIndexes of the vertices that will have...
QgsMeshLayerUndoCommandChangeXYValue(QgsMeshEditor *meshEditor, const QList< int > &verticesIndexes, const QList< QgsPointXY > &newValues)
Constructor with the associated meshEditor and indexes verticesIndexes of the vertices that will have...
QgsMeshLayerUndoCommandChangeZValue(QgsMeshEditor *meshEditor, const QList< int > &verticesIndexes, const QList< double > &newValues)
Constructor with the associated meshEditor and indexes verticesIndexes of the vertices that will have...
QgsMeshLayerUndoCommandFlipEdge(QgsMeshEditor *meshEditor, int vertexIndex1, int vertexIndex2)
Constructor with the associated meshEditor and the vertex indexes of the edge (vertexIndex1,...
QgsMeshLayerUndoCommandMerge(QgsMeshEditor *meshEditor, int vertexIndex1, int vertexIndex2)
Constructor with the associated meshEditor and the vertex indexes of the edge (vertexIndex1,...
Base class for undo/redo command for mesh editing.
QList< QgsMeshEditor::Edit > mEdits
QgsMeshLayerUndoCommandMeshEdit(QgsMeshEditor *meshEditor)
Constructor for the base class.
QPointer< QgsMeshEditor > mMeshEditor
QgsMeshLayerUndoCommandRemoveFaces(QgsMeshEditor *meshEditor, const QList< int > &facesToRemoveIndexes)
Constructor with the associated meshEditor and indexes facesToRemoveIndexes of the faces that will be...
QgsMeshLayerUndoCommandRemoveVerticesFillHoles(QgsMeshEditor *meshEditor, const QList< int > &verticesToRemoveIndexes, QList< int > *remainingVerticesPointer=nullptr)
Constructor with the associated meshEditor and vertices that will be removed.
QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles(QgsMeshEditor *meshEditor, const QList< int > &verticesToRemoveIndexes)
Constructor with the associated meshEditor and vertices that will be removed.
QgsMeshLayerUndoCommandSplitFaces(QgsMeshEditor *meshEditor, const QList< int > &faceIndexes)
Constructor with the associated meshEditor and indexes faceIndexes of the faces to split.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
Definition: qgsmeshlayer.h:100
QgsMeshDataProvider * dataProvider() override
Returns the layer's data provider, it may be nullptr.
Convenient class that turn around a vertex and provide information about faces and vertices.
bool goBoundaryCounterClockwise() const
Sets the circulator on the boundary face turning counter clockwise, return false is there isn't bound...
int oppositeVertexCounterClockwise() const
Returns the opposite vertex of the current face and on the edge on the side turning counter clockwise...
bool goBoundaryClockwise() const
Sets the circulator on the boundary face turning clockwise, return false is there isn't boundary face...
int oppositeVertexClockwise() const
Returns the opposite vertex of the current face and on the edge on the side turning clockwise.
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
double distance(double x, double y) const SIP_HOLDGIL
Returns the distance between this point and a specified x, y coordinate.
Definition: qgspointxy.h:211
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
bool isEmpty() const override SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgspoint.cpp:767
Q_GADGET double x
Definition: qgspoint.h:52
double y
Definition: qgspoint.h:53
A rectangle specified with double values.
Definition: qgsrectangle.h:42
Class that contains topological differences between two states of a topological mesh,...
QVector< QgsMeshFace > removedFaces() const
Returns the faces that are removed with this changes.
QVector< QgsMeshVertex > addedVertices() const
Returns the added vertices with this changes.
bool isEmpty() const
Returns whether changes are empty, that there is nothing to change.
QVector< QgsMeshFace > addedFaces() const
Returns the face that are added with this changes.
QList< int > verticesToRemoveIndexes() const
Returns the indexes of vertices to remove.
Class that contains independent faces an topological information about this faces.
void clear()
Clears all data contained in the instance.
QVector< QgsMeshFace > meshFaces() const
Returns faces.
Class that wraps a QgsMesh to ensure the consistency of the mesh during editing and help to access to...
static QgsMeshEditingError checkTopologyOfVerticesAsFace(const QVector< QgsMeshVertex > &vertices, bool &clockwise)
Checks the topology of the vertices as they are contained in a face and returns indication on directi...
Changes changeZValue(const QList< int > &verticesIndexes, const QList< double > &newValues)
Changes the Z values of the vertices with indexes in vertices indexes with the values in newValues.
static QgsTopologicalMesh createTopologicalMesh(QgsMesh *mesh, int maxVerticesPerFace, QgsMeshEditingError &error)
Creates a topologicaly consistent mesh with mesh, this static method modifies mesh to be topological ...
bool isVertexFree(int vertexIndex) const
Returns whether the vertex is a free vertex.
static QgsMeshEditingError counterClockwiseFaces(QgsMeshFace &face, QgsMesh *mesh)
Checks the topology of the face and sets it counter clockwise if necessary.
Changes removeVertexFillHole(int vertexIndex)
Removes the vertex with index vertexIndex.
void applyChanges(const Changes &changes)
Applies the changes.
QgsMeshEditingError checkConsistency() const
Checks the consistency of the topological mesh and return false if there is a consistency issue.
Changes removeVertices(const QList< int > &vertices)
Removes all the vertices with index in the list vertices If vertices in linked with faces,...
Changes changeXYValue(const QList< int > &verticesIndexes, const QList< QgsPointXY > &newValues)
Changes the (X,Y) values of the vertices with indexes in vertices indexes with the values in newValue...
void reindex()
Reindexes faces and vertices, after this operation, the topological mesh can't be edited anymore and ...
QgsMeshEditingError facesCanBeAdded(const TopologicalFaces &topologicalFaces) const
Returns whether the faces can be added to the mesh.
bool renumber()
Renumbers the indexes of vertices and faces using the Reverse CutHill McKee Algorithm.
Changes flipEdge(int vertexIndex1, int vertexIndex2)
Flips edge (vertexIndex1, vertexIndex2) The method returns a instance of the class QgsTopologicalMesh...
QgsMeshEditingError facesCanBeRemoved(const QList< int > &facesIndexes)
Returns whether faces with index in faceIndexes can be removed/ The method an error object with type ...
void reverseChanges(const Changes &changes)
Reverses the changes.
Changes addFaces(const TopologicalFaces &topologicFaces)
Adds faces topologicFaces to the topologic mesh.
Changes merge(int vertexIndex1, int vertexIndex2)
Merges faces separated by vertices with indexes vertexIndex1 and vertexIndex2 The method returns a in...
Changes removeFaces(const QList< int > &facesIndexes)
Removes faces with index in faceIndexes.
QList< int > freeVerticesIndexes() const
Returns a list of vertices are not linked to any faces.
bool edgeCanBeFlipped(int vertexIndex1, int vertexIndex2) const
Returns true if the edge can be flipped (only available for edge shared by two faces with 3 vertices)
Changes addVertexInFace(int faceIndex, const QgsMeshVertex &vertex)
Adds a vertex in the face with index faceIndex.
bool canBeMerged(int vertexIndex1, int vertexIndex2) const
Returns true if faces separated by vertices with indexes vertexIndex1 and vertexIndex2 can be merged.
QList< int > facesAroundVertex(int vertexIndex) const
Returns the indexes of faces that are around the vertex with index vertexIndex.
bool canBeSplit(int faceIndex) const
Returns true if face with index faceIndex can be split.
Changes addFreeVertex(const QgsMeshVertex &vertex)
Adds a free vertex in the face, that is a vertex tha tis not included or linked with any faces.
Changes insertVertexInFacesEdge(int faceIndex, int position, const QgsMeshVertex &vertex)
Inserts a vertex in the edge of face with index faceIndex at position .
bool isVertexOnBoundary(int vertexIndex) const
Returns whether the vertex is on a boundary.
Changes splitFace(int faceIndex)
Splits face with index faceIndex The method returns a instance of the class QgsTopologicalMesh::Chang...
static TopologicalFaces createNewTopologicalFaces(const QVector< QgsMeshFace > &faces, bool uniqueSharedVertexAllowed, QgsMeshEditingError &error)
Creates new topological faces that are not yet included in the mesh.
QgsMeshVertexCirculator vertexCirculator(int vertexIndex) const
Returns a vertex circulator linked to this mesh around the vertex with index vertexIndex.
The Changes class is used to make changes of the triangular and to keep traces of this changes If a C...
Triangular/Derived Mesh is mesh with vertices in map coordinates.
const QVector< QgsMeshFace > & triangles() const
Returns triangles.
QgsRectangle nativeExtent()
Returns the extent of the mesh in the native mesh coordinates system, returns empty extent if the tra...
int nativeFaceIndexForPoint(const QgsPointXY &point) const
Finds index of native face at given point It uses spatial indexing.
void reverseChanges(const Changes &changes, const QgsMesh &nativeMesh)
Reverses the changes on the triangular mesh (see Changes)
void applyChanges(const Changes &changes)
Applies the changes on the triangular mesh (see Changes)
const QVector< QgsMeshVertex > & vertices() const
Returns vertices in map coordinate system.
QgsMeshVertex triangularToNativeCoordinates(const QgsMeshVertex &vertex) const
Transforms the vertex from triangular mesh coordinates system to native coordinates system.
QgsMeshVertex nativeToTriangularCoordinates(const QgsMeshVertex &vertex) const
Transforms the vertex from native coordinates system to triangular mesh coordinates system.
bool update(QgsMesh *nativeMesh, const QgsCoordinateTransform &transform)
Constructs triangular mesh from layer's native mesh and transform to destination CRS.
const QVector< QgsMeshVertex > & faceCentroids() const
Returns centroids of the native faces in map CRS.
QList< int > nativeFaceIndexForRectangle(const QgsRectangle &rectangle) const
Finds indexes of native faces which bounding boxes intersect given bounding box It uses spatial index...
int faceIndexForPoint_v2(const QgsPointXY &point) const
Finds index of triangle at given point It uses spatial indexing and don't use geos to be faster.
CORE_EXPORT QgsGeometry toGeometry(const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Returns face as polygon geometry.
QVector< int > QgsMeshFace
List of vertex indexes.
QgsPoint QgsMeshVertex
xyz coords of vertex
Mesh - vertices, edges and faces.
int vertexCount() const
Returns number of vertices.
QVector< QgsMeshVertex > vertices
QgsMeshFace face(int index) const
Returns a face at the index.
QVector< QgsMeshFace > faces
int faceCount() const
Returns number of faces.