QGIS API Documentation 3.41.0-Master (d2aaa9c6e02)
Loading...
Searching...
No Matches
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 "moc_qgsmesheditor.cpp"
20#include "qgsmeshdataprovider.h"
21#include "qgstriangularmesh.h"
22#include "qgsmeshlayer.h"
23#include "qgsgeometryengine.h"
25#include "qgsgeometryutils.h"
26#include "qgspolygon.h"
27
28#include <poly2tri.h>
29
30#include <QSet>
31
32
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
46QgsMeshEditor::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
56{
57 std::unique_ptr<QgsMeshDatasetGroup> zValueDatasetGroup = std::make_unique<QgsMeshVerticesElevationDatasetGroup>( tr( "vertices Z value" ), mMesh );
58
59 // this DOES look very dangerous!
60 // TODO rework to avoid this danger
61
62 // cppcheck-suppress danglingLifetime
63 mZValueDatasetGroup = zValueDatasetGroup.get();
64
65 return zValueDatasetGroup.release();
66}
67
69
71{
73 mTopologicalMesh = QgsTopologicalMesh::createTopologicalMesh( mMesh, mMaximumVerticesPerFace, error );
74
76 {
77 // we check for free vertices that could be included in face here
78 // because we need the spatial index of the triangular mesh
79 const QList<int> freeVertices = mTopologicalMesh.freeVerticesIndexes();
80 for ( int vi : freeVertices )
81 {
82 if ( mTriangularMesh->faceIndexForPoint_v2( mTriangularMesh->vertices().at( vi ) ) != -1 )
83 {
85 break;
86 }
87 }
88 }
89
90 mValidFacesCount = mMesh->faceCount();
91 mValidVerticesCount = mMesh->vertexCount();
92 return error;
93}
94
96{
97 QgsMeshEditingError lastError;
98
99 while ( true )
100 {
101 lastError = initialize();
103 break;
104
105 if ( !fixError( lastError ) )
106 break;
107
108 mTriangularMesh->update( mMesh );
109 };
110
111 return lastError;
112}
113
115{
116 switch ( error.errorType )
117 {
119 return true;
120 break;
125 if ( error.elementIndex != -1 && error.elementIndex < mMesh->faceCount() )
126 {
127 mMesh->faces.removeAt( error.elementIndex );
128 return true;
129 }
130 return false;
131 break;
134 {
135 auto faceIt = mMesh->faces.begin();
136 while ( faceIt != mMesh->faces.end() )
137 {
138 if ( faceIt->contains( error.elementIndex ) )
139 faceIt = mMesh->faces.erase( faceIt );
140 else
141 ++faceIt;
142 }
143
144 if ( error.elementIndex >= 0 && error.elementIndex < mMesh->vertexCount() )
145 {
146 mMesh->vertices[error.elementIndex] = QgsMeshVertex();
147 reindex( false );
148 }
149 return true;
150 }
151 break;
152 }
153
154 return false;
155}
156
158{
159 mTriangularMesh = triangularMesh;
160}
161
162
163bool QgsMeshEditor::isFaceGeometricallyCompatible( const QList<int> &vertexIndexes, const QList<QgsMeshVertex> &vertices ) const
164{
165 Q_ASSERT( vertexIndexes.count() == vertices.count() );
166
167 QVector<QgsPoint> ring;
168 for ( int i = 0; i < vertices.size(); ++i )
169 {
170 const QgsPoint &vertex = vertices[i];
171 ring.append( vertex );
172 }
173 std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >();
174 polygon->setExteriorRing( new QgsLineString( ring ) );
175 const QgsGeometry newFaceGeom( polygon.release() );
176 std::unique_ptr<QgsGeometryEngine> geomEngine( QgsGeometry::createGeometryEngine( newFaceGeom.constGet() ) );
177 geomEngine->prepareGeometry();
178
179 const QgsRectangle boundingBox = newFaceGeom.boundingBox();
180 int newFaceSize = vertexIndexes.count();
181 const QList<int> concernedFaceIndex = mTriangularMesh->nativeFaceIndexForRectangle( boundingBox );
182 if ( !concernedFaceIndex.isEmpty() )
183 {
184 // for each concerned face, we take edges and, if no common vertex with the new face,
185 // check is the edge intersects or is contained in the new face
186 for ( const int faceIndex : concernedFaceIndex )
187 {
188 const QgsMeshFace &existingFace = mMesh->faces.at( faceIndex );
189 int existingFaceSize = existingFace.count();
190 bool shareVertex = false;
191 for ( int i = 0; i < existingFaceSize; ++i )
192 {
193 if ( vertexIndexes.contains( existingFace.at( i ) ) )
194 {
195 shareVertex = true;
196 break;
197 }
198 }
199
200 if ( shareVertex )
201 {
202 for ( int i = 0; i < existingFaceSize; ++i )
203 {
204 int index1 = existingFace.at( i );
205 int index2 = existingFace.at( ( i + 1 ) % existingFaceSize );
206 const QgsMeshVertex &v1 = mTriangularMesh->vertices().at( index1 );
207 const QgsMeshVertex &v2 = mTriangularMesh->vertices().at( index2 );
208 QgsGeometry edgeGeom = QgsGeometry( new QgsLineString( v1, v2 ) );
209
210 if ( ! vertexIndexes.contains( index1 ) && !vertexIndexes.contains( index2 ) )
211 {
212 // test if the edge that not contains a shared vertex intersect the entire new face
213 if ( geomEngine->intersects( edgeGeom.constGet() ) )
214 return false;
215 }
216 else
217 {
218 for ( int vi = 0; vi < vertexIndexes.count(); ++vi )
219 {
220 int vertInNewFace1 = vertexIndexes.at( vi );
221 int vertInNewFace2 = vertexIndexes.at( ( vi + 1 ) % newFaceSize );
222 bool hasToBeTest = false;
223
224 if ( vertInNewFace1 != -1 && vertInNewFace2 != -1 )
225 {
226 hasToBeTest = vertInNewFace1 != index1 &&
227 vertInNewFace2 != index2 &&
228 vertInNewFace1 != index2 &&
229 vertInNewFace2 != index1;
230 }
231 else
232 {
233 if ( vertInNewFace1 == -1 )
234 hasToBeTest &= vertInNewFace2 != index1 && vertInNewFace2 != index2;
235
236
237 if ( vertInNewFace2 == -1 )
238 hasToBeTest &= vertInNewFace1 != index1 && vertInNewFace1 != index2;
239 }
240
241 if ( hasToBeTest )
242 {
243 const QgsMeshVertex &nv1 = vertices.at( vi );
244 const QgsMeshVertex &nv2 = vertices.at( ( vi + 1 ) % newFaceSize );
245 const QgsGeometry newEdgeGeom = QgsGeometry( new QgsLineString( nv1, nv2 ) );
246
247 if ( newEdgeGeom.intersects( edgeGeom ) )
248 return false;
249 }
250 }
251 }
252 }
253 }
254 else
255 {
256 const QgsGeometry existingFaceGeom = QgsMeshUtils::toGeometry( existingFace, mTriangularMesh->vertices() );
257 if ( geomEngine->intersects( existingFaceGeom.constGet() ) )
258 return false;
259 }
260 }
261 }
262
263 // Then search for free vertices included in the new face
264 const QList<int> &freeVertices = freeVerticesIndexes();
265 for ( const int freeVertexIndex : freeVertices )
266 {
267 if ( vertexIndexes.contains( freeVertexIndex ) )
268 continue;
269
270 const QgsMeshVertex &vertex = mTriangularMesh->vertices().at( freeVertexIndex );
271 if ( geomEngine->contains( &vertex ) )
272 return false;
273 }
274
275 return true;
276}
277
279{
280 const QList<int> newFaceVerticesIndexes( face.toList() );
281 QList<QgsMeshVertex> allVertices;
282 allVertices.reserve( face.count() );
283 for ( int i : face )
284 allVertices.append( mTriangularMesh->vertices().at( i ) );
285
286 return isFaceGeometricallyCompatible( newFaceVerticesIndexes, allVertices );
287
288}
289
290
292{
294
295 // Prepare and check the face
296 QVector<QgsMeshFace> facesToAdd = prepareFaces( {face}, error );
297
299 return false;
300
301 // Check if there is topological error with the mesh
303 error = mTopologicalMesh.facesCanBeAdded( topologicalFaces );
304
306 return false;
307
308 // Check geometry compatibility
309 // With the topological check, we know that the new face is not included in an existing one
310 // But maybe, the new face includes or intersects existing faces or free vertices, we need to check
311 // First search for faces intersecting the bounding box of the new face.
312
313 return isFaceGeometricallyCompatible( face );
314}
315
316bool QgsMeshEditor::faceCanBeAddedWithNewVertices( const QList<int> &verticesIndex, const QList<QgsMeshVertex> &newVertices ) const
317{
319 const QList<int> face = prepareFaceWithNewVertices( verticesIndex, newVertices, error );
320
321 if ( face.isEmpty() )
322 return false;
323
325 return false;
326
327 // if we are here, the face is convex and not flat
328
329 // Now we check the topology of the potential new face
330 int size = face.size();
331 QList<QgsMeshVertex> allVertices;
332 allVertices.reserve( verticesIndex.size() );
333 int newVertPos = 0;
334 for ( int i = 0; i < size; ++i )
335 {
336 int index = face.at( i );
337 if ( index == -1 )
338 {
339 if ( newVertPos >= newVertices.count() )
340 return false;
341 allVertices.append( newVertices.at( newVertPos++ ) );
342 continue;
343 }
344
345 allVertices.append( mTriangularMesh->vertices().at( index ) );
346
347 if ( isVertexFree( index ) )
348 continue;
349
350 int prevIndex = face.at( ( i - 1 + size ) % size );
351 int nextIndex = face.at( ( i + 1 ) % size );
352
353 QgsMeshVertexCirculator circulator = mTopologicalMesh.vertexCirculator( index );
354 if ( !circulator.goBoundaryClockwise() ) //vertex not on boundary
355 return false;
356
357 int prevOppVertex = circulator.oppositeVertexClockwise();
358 if ( prevOppVertex == nextIndex ) //manifold face
359 return false;
360
361 if ( !circulator.goBoundaryCounterClockwise() )
362 return false;
363
364 int nextOppVertex = circulator.oppositeVertexCounterClockwise();
365 if ( nextOppVertex == prevIndex ) //manifold face
366 return false;
367
368 if ( nextIndex != nextOppVertex && prevIndex != prevOppVertex ) //unique shared vertex
369 return false;
370 }
371
372 return isFaceGeometricallyCompatible( face, allVertices );
373}
374
375void QgsMeshEditor::applyEdit( QgsMeshEditor::Edit &edit )
376{
377 mTopologicalMesh.applyChanges( edit.topologicalChanges );
378 mTriangularMesh->applyChanges( edit.triangularMeshChanges );
379
380 if ( mZValueDatasetGroup &&
381 ( !edit.topologicalChanges.newVerticesZValues().isEmpty() ||
382 !edit.topologicalChanges.verticesToRemoveIndexes().isEmpty() ||
383 !edit.topologicalChanges.addedVertices().isEmpty() ) )
384 mZValueDatasetGroup->setStatisticObsolete();
385
386 updateElementsCount( edit.topologicalChanges );
387}
388
389void QgsMeshEditor::reverseEdit( QgsMeshEditor::Edit &edit )
390{
391 mTopologicalMesh.reverseChanges( edit.topologicalChanges );
392 mTriangularMesh->reverseChanges( edit.triangularMeshChanges, *mMesh );
393
394 if ( mZValueDatasetGroup &&
395 ( !edit.topologicalChanges.newVerticesZValues().isEmpty() ||
396 !edit.topologicalChanges.verticesToRemoveIndexes().isEmpty() ||
397 !edit.topologicalChanges.addedVertices().isEmpty() ) )
398 mZValueDatasetGroup->setStatisticObsolete();
399
400 updateElementsCount( edit.topologicalChanges, false );
401}
402
403void QgsMeshEditor::applyAddVertex( QgsMeshEditor::Edit &edit, const QgsMeshVertex &vertex, double tolerance )
404{
405 QgsMeshVertex vertexInTriangularCoordinate = mTriangularMesh->nativeToTriangularCoordinates( vertex );
406
407 //check if edges is closest than the tolerance from the vertex
408 int faceEdgeIntersect = -1;
409 int edgePosition = -1;
410
411 QgsTopologicalMesh::Changes topologicChanges;
412
413 if ( edgeIsClose( vertexInTriangularCoordinate, tolerance, faceEdgeIntersect, edgePosition ) )
414 {
415 topologicChanges = mTopologicalMesh.insertVertexInFacesEdge( faceEdgeIntersect, edgePosition, vertex );
416 }
417 else
418 {
419 int includingFaceIndex = mTriangularMesh->nativeFaceIndexForPoint( vertexInTriangularCoordinate );
420
421 if ( includingFaceIndex != -1 )
422 topologicChanges = mTopologicalMesh.addVertexInFace( includingFaceIndex, vertex );
423 else
424 topologicChanges = mTopologicalMesh.addFreeVertex( vertex );
425 }
426
427 applyEditOnTriangularMesh( edit, topologicChanges );
428
429 if ( mZValueDatasetGroup )
430 mZValueDatasetGroup->setStatisticObsolete();
431
432 updateElementsCount( edit.topologicalChanges );
433}
434
435bool QgsMeshEditor::applyRemoveVertexFillHole( QgsMeshEditor::Edit &edit, int vertexIndex )
436{
437 QgsTopologicalMesh::Changes changes = mTopologicalMesh.removeVertexFillHole( vertexIndex );
438
439 if ( !changes.isEmpty() )
440 {
441 applyEditOnTriangularMesh( edit, changes );
442
443 if ( mZValueDatasetGroup )
444 mZValueDatasetGroup->setStatisticObsolete();
445
446 updateElementsCount( edit.topologicalChanges );
447 return true;
448 }
449 else
450 return false;
451}
452
453void QgsMeshEditor::applyRemoveVerticesWithoutFillHole( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes )
454{
455 applyEditOnTriangularMesh( edit, mTopologicalMesh.removeVertices( verticesIndexes ) );
456
457 if ( mZValueDatasetGroup )
458 mZValueDatasetGroup->setStatisticObsolete();
459
460 updateElementsCount( edit.topologicalChanges );
461}
462
463void QgsMeshEditor::applyAddFaces( QgsMeshEditor::Edit &edit, const QgsTopologicalMesh::TopologicalFaces &faces )
464{
465 applyEditOnTriangularMesh( edit, mTopologicalMesh.addFaces( faces ) );
466
467 updateElementsCount( edit.topologicalChanges );
468}
469
470void QgsMeshEditor::applyRemoveFaces( QgsMeshEditor::Edit &edit, const QList<int> &faceToRemoveIndex )
471{
472 applyEditOnTriangularMesh( edit, mTopologicalMesh.removeFaces( faceToRemoveIndex ) );
473
474 updateElementsCount( edit.topologicalChanges );
475}
476
477void QgsMeshEditor::applyChangeZValue( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes, const QList<double> &newValues )
478{
479 applyEditOnTriangularMesh( edit, mTopologicalMesh.changeZValue( verticesIndexes, newValues ) );
480
481 if ( mZValueDatasetGroup )
482 mZValueDatasetGroup->setStatisticObsolete();
483}
484
485void QgsMeshEditor::applyChangeXYValue( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
486{
487 applyEditOnTriangularMesh( edit, mTopologicalMesh.changeXYValue( verticesIndexes, newValues ) );
488}
489
490void QgsMeshEditor::applyFlipEdge( QgsMeshEditor::Edit &edit, int vertexIndex1, int vertexIndex2 )
491{
492 applyEditOnTriangularMesh( edit, mTopologicalMesh.flipEdge( vertexIndex1, vertexIndex2 ) );
493
494 updateElementsCount( edit.topologicalChanges );
495}
496
497void QgsMeshEditor::applyMerge( QgsMeshEditor::Edit &edit, int vertexIndex1, int vertexIndex2 )
498{
499 applyEditOnTriangularMesh( edit, mTopologicalMesh.merge( vertexIndex1, vertexIndex2 ) );
500
501 updateElementsCount( edit.topologicalChanges );
502}
503
504void QgsMeshEditor::applySplit( QgsMeshEditor::Edit &edit, int faceIndex )
505{
506 applyEditOnTriangularMesh( edit, mTopologicalMesh.splitFace( faceIndex ) );
507
508 updateElementsCount( edit.topologicalChanges );
509}
510
511void QgsMeshEditor::applyAdvancedEdit( QgsMeshEditor::Edit &edit, QgsMeshAdvancedEditing *editing )
512{
513 applyEditOnTriangularMesh( edit, editing->apply( this ) );
514
515 updateElementsCount( edit.topologicalChanges );
516
517 if ( mZValueDatasetGroup )
518 mZValueDatasetGroup->setStatisticObsolete();
519}
520
521void QgsMeshEditor::applyEditOnTriangularMesh( QgsMeshEditor::Edit &edit, const QgsTopologicalMesh::Changes &topologicChanges )
522{
523 QgsTriangularMesh::Changes triangularChanges( topologicChanges, *mMesh );
524 mTriangularMesh->applyChanges( triangularChanges );
525
526 edit.topologicalChanges = topologicChanges;
527 edit.triangularMeshChanges = triangularChanges;
528}
529
530void QgsMeshEditor::updateElementsCount( const QgsTopologicalMesh::Changes &changes, bool apply )
531{
532 if ( apply )
533 {
534 mValidFacesCount += changes.addedFaces().count() - changes.removedFaces().count();
535 mValidVerticesCount += changes.addedVertices().count() - changes.verticesToRemoveIndexes().count();
536 }
537 else
538 {
539 //reverse
540 mValidFacesCount -= changes.addedFaces().count() - changes.removedFaces().count();
541 mValidVerticesCount -= changes.addedVertices().count() - changes.verticesToRemoveIndexes().count();
542 }
543}
544
546{
547 error = mTopologicalMesh.checkConsistency();
548 switch ( error.errorType )
549 {
551 break;
558 return false;
559 }
560
561 if ( mTriangularMesh->vertices().count() != mMesh->vertexCount() )
562 return false;
563
564 if ( mTriangularMesh->faceCentroids().count() != mMesh->faceCount() )
565 return false;
566
567 return true;
568}
569
570bool QgsMeshEditor::edgeIsClose( QgsPointXY point, double tolerance, int &faceIndex, int &edgePosition )
571{
572 QgsRectangle toleranceZone( point.x() - tolerance,
573 point.y() - tolerance,
574 point.x() + tolerance,
575 point.y() + tolerance );
576
577 edgePosition = -1;
578 double minDist = std::numeric_limits<double>::max();
579 const QList<int> &nativeFaces = mTriangularMesh->nativeFaceIndexForRectangle( toleranceZone );
580 double epsilon = std::numeric_limits<double>::epsilon() * tolerance;
581 for ( const int nativeFaceIndex : nativeFaces )
582 {
583 const QgsMeshFace &face = mMesh->face( nativeFaceIndex );
584 const int faceSize = face.size();
585 for ( int i = 0; i < faceSize; ++i )
586 {
587 const QgsMeshVertex &v1 = mTriangularMesh->vertices().at( face.at( i ) );
588 const QgsMeshVertex &v2 = mTriangularMesh->vertices().at( face.at( ( i + 1 ) % faceSize ) );
589
590 double mx, my;
591 double dist = sqrt( QgsGeometryUtilsBase::sqrDistToLine( point.x(),
592 point.y(),
593 v1.x(),
594 v1.y(),
595 v2.x(),
596 v2.y(),
597 mx,
598 my,
599 epsilon ) );
600
601 if ( dist < tolerance && dist < minDist )
602 {
603 faceIndex = nativeFaceIndex;
604 edgePosition = i;
605 minDist = dist;
606 }
607 }
608 }
609
610 if ( edgePosition != -1 )
611 return true;
612
613 return false;
614
615}
616
618{
619 return mValidFacesCount;
620}
621
623{
624 return mValidVerticesCount;
625}
626
628{
629 return mMaximumVerticesPerFace;
630}
631
632QgsMeshEditingError QgsMeshEditor::removeFaces( const QList<int> &facesToRemove )
633{
634 QgsMeshEditingError error = mTopologicalMesh.facesCanBeRemoved( facesToRemove );
636 return error;
637
638 mUndoStack->push( new QgsMeshLayerUndoCommandRemoveFaces( this, facesToRemove ) );
639
640 return error;
641}
642
643void QgsMeshEditor::addVertexWithDelaunayRefinement( const QgsMeshVertex &vertex, const double tolerance )
644{
645 int triangleIndex = mTriangularMesh->faceIndexForPoint_v2( vertex );
646 if ( triangleIndex == -1 )
647 return;
648
649 mUndoStack->push( new QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement( this, vertex, tolerance ) );
650}
651
652bool QgsMeshEditor::edgeCanBeFlipped( int vertexIndex1, int vertexIndex2 ) const
653{
654 return mTopologicalMesh.edgeCanBeFlipped( vertexIndex1, vertexIndex2 );
655}
656
657void QgsMeshEditor::flipEdge( int vertexIndex1, int vertexIndex2 )
658{
659 if ( !edgeCanBeFlipped( vertexIndex1, vertexIndex2 ) )
660 return;
661
662 mUndoStack->push( new QgsMeshLayerUndoCommandFlipEdge( this, vertexIndex1, vertexIndex2 ) );
663}
664
665bool QgsMeshEditor::canBeMerged( int vertexIndex1, int vertexIndex2 ) const
666{
667 return mTopologicalMesh.canBeMerged( vertexIndex1, vertexIndex2 );
668}
669
670void QgsMeshEditor::merge( int vertexIndex1, int vertexIndex2 )
671{
672 if ( !canBeMerged( vertexIndex1, vertexIndex2 ) )
673 return;
674
675 mUndoStack->push( new QgsMeshLayerUndoCommandMerge( this, vertexIndex1, vertexIndex2 ) );
676}
677
678bool QgsMeshEditor::faceCanBeSplit( int faceIndex ) const
679{
680 return mTopologicalMesh.canBeSplit( faceIndex );
681}
682
683int QgsMeshEditor::splitFaces( const QList<int> &faceIndexes )
684{
685 QList<int> faceIndexesSplittable;
686
687 for ( const int faceIndex : faceIndexes )
688 if ( faceCanBeSplit( faceIndex ) )
689 faceIndexesSplittable.append( faceIndex );
690
691 if ( faceIndexesSplittable.isEmpty() )
692 return 0;
693
694 mUndoStack->push( new QgsMeshLayerUndoCommandSplitFaces( this, faceIndexesSplittable ) );
695
696 return faceIndexesSplittable.count();
697}
698
699QVector<QgsMeshFace> QgsMeshEditor::prepareFaces( const QVector<QgsMeshFace> &faces, QgsMeshEditingError &error ) const
700{
701 QVector<QgsMeshFace> treatedFaces = faces;
702
703 // here we could add later some filters, for example, removing faces intersecting with existing one
704
705 for ( int i = 0; i < treatedFaces.count(); ++i )
706 {
707 QgsMeshFace &face = treatedFaces[i];
708 if ( mMaximumVerticesPerFace != 0 && face.count() > mMaximumVerticesPerFace )
709 {
711 break;
712 }
713
714 error = QgsTopologicalMesh::counterClockwiseFaces( face, mMesh );
716 break;
717 }
718
719 return treatedFaces;
720}
721
722QList<int> QgsMeshEditor::prepareFaceWithNewVertices( const QList<int> &face, const QList<QgsMeshVertex> &newVertices, QgsMeshEditingError &error ) const
723{
724 if ( mMaximumVerticesPerFace != 0 && face.count() > mMaximumVerticesPerFace )
725 {
727 return face;
728 }
729
730 int faceSize = face.count();
731 QVector<QgsMeshVertex> vertices( faceSize );
732 int newVertexPos = 0;
733 for ( int i = 0; i < faceSize; ++i )
734 {
735 if ( face.at( i ) == -1 )
736 {
737 if ( newVertexPos >= newVertices.count() )
738 return QList<int>();
739 vertices[i] = newVertices.at( newVertexPos++ );
740 }
741 else if ( face.at( i ) >= 0 )
742 {
743 if ( face.at( i ) >= mTriangularMesh->vertices().count() )
744 {
746 break;
747 }
748 vertices[i] = mTriangularMesh->vertices().at( face.at( i ) );
749 }
750 else
751 {
753 break;
754 }
755 }
756
758 return face;
759
760 bool clockwise = false;
761 error = QgsTopologicalMesh::checkTopologyOfVerticesAsFace( vertices, clockwise );
762
763 if ( clockwise && error.errorType == Qgis::MeshEditingErrorType::NoError )
764 {
765
766 QList<int> newFace = face;
767 for ( int i = 0; i < faceSize / 2; ++i )
768 {
769 int temp = newFace[i];
770 newFace[i] = face.at( faceSize - i - 1 );
771 newFace[faceSize - i - 1] = temp;
772 }
773
774 return newFace;
775 }
776
777 return face;
778}
779
780QgsMeshEditingError QgsMeshEditor::addFaces( const QVector<QVector<int> > &faces )
781{
783 QVector<QgsMeshFace> facesToAdd = prepareFaces( faces, error );
784
786 return error;
787
789
790 error = mTopologicalMesh.facesCanBeAdded( topologicalFaces );
791
793 return error;
794
795 mUndoStack->push( new QgsMeshLayerUndoCommandAddFaces( this, topologicalFaces ) );
796
797 return error;
798}
799
800QgsMeshEditingError QgsMeshEditor::addFace( const QVector<int> &vertexIndexes )
801{
802 return addFaces( {vertexIndexes} );
803}
804
805QgsMeshEditingError QgsMeshEditor::addFaceWithNewVertices( const QList<int> &vertexIndexes, const QList<QgsMeshVertex> &newVertices )
806{
807 mUndoStack->beginMacro( tr( "Add a face with new %n vertices", nullptr, newVertices.count() ) );
808 int newVertexIndex = mMesh->vertexCount();
809 addVertices( newVertices.toVector(), 0 );
810 QgsMeshFace face( vertexIndexes.count() );
811 for ( int i = 0; i < vertexIndexes.count(); ++i )
812 {
813 int index = vertexIndexes.at( i );
814 if ( index == -1 )
815 face[i] = newVertexIndex++;
816 else
817 face[i] = index;
818 }
819
820 QgsMeshEditingError error = addFace( face );
821 mUndoStack->endMacro();
822
823 return error;
824}
825
826int QgsMeshEditor::addVertices( const QVector<QgsMeshVertex> &vertices, double tolerance )
827{
828 QVector<QgsMeshVertex> verticesInLayerCoordinate( vertices.count() );
829 int ignoredVertex = 0;
830 for ( int i = 0; i < vertices.count(); ++i )
831 {
832 const QgsPointXY &pointInTriangularMesh = vertices.at( i );
833 bool isTooClose = false;
834 int triangleIndex = mTriangularMesh->faceIndexForPoint_v2( pointInTriangularMesh );
835 if ( triangleIndex != -1 )
836 {
837 const QgsMeshFace face = mTriangularMesh->triangles().at( triangleIndex );
838 for ( int j = 0; j < 3; ++j )
839 {
840 const QgsPointXY &facePoint = mTriangularMesh->vertices().at( face.at( j ) );
841 double dist = pointInTriangularMesh.distance( facePoint );
842 if ( dist < tolerance )
843 {
844 isTooClose = true;
845 break;
846 }
847 }
848 }
849
850 if ( !isTooClose )
851 verticesInLayerCoordinate[i] = mTriangularMesh->triangularToNativeCoordinates( vertices.at( i ) );
852 else
853 verticesInLayerCoordinate[i] = QgsMeshVertex();
854
855 if ( verticesInLayerCoordinate.at( i ).isEmpty() )
856 ignoredVertex++;
857 }
858
859 if ( ignoredVertex < vertices.count() )
860 {
861 mUndoStack->push( new QgsMeshLayerUndoCommandAddVertices( this, verticesInLayerCoordinate, tolerance ) );
862 }
863
864 int effectivlyAddedVertex = vertices.count() - ignoredVertex;
865
866 return effectivlyAddedVertex;
867}
868
869int QgsMeshEditor::addPointsAsVertices( const QVector<QgsPoint> &point, double tolerance )
870{
871 return addVertices( point, tolerance );
872}
873
874QgsMeshEditingError QgsMeshEditor::removeVerticesWithoutFillHoles( const QList<int> &verticesToRemoveIndexes )
875{
877
878 QList<int> verticesIndexes = verticesToRemoveIndexes;
879
880 QSet<int> concernedNativeFaces;
881 for ( const int vi : std::as_const( verticesIndexes ) )
882 {
883 const QList<int> faces = mTopologicalMesh.facesAroundVertex( vi );
884 concernedNativeFaces.unite( QSet< int >( faces.begin(), faces.end() ) );
885 }
886
887 error = mTopologicalMesh.facesCanBeRemoved( concernedNativeFaces.values() );
888
890 return error;
891
892 mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles( this, verticesIndexes ) );
893 return error;
894}
895
896QList<int> QgsMeshEditor::removeVerticesFillHoles( const QList<int> &verticesToRemoveIndexes )
897{
898 QList<int> remainingVertices;
899 mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVerticesFillHoles( this, verticesToRemoveIndexes, &remainingVertices ) );
900
901 return remainingVertices;
902}
903
904
905void QgsMeshEditor::changeZValues( const QList<int> &verticesIndexes, const QList<double> &newZValues )
906{
907 mUndoStack->push( new QgsMeshLayerUndoCommandChangeZValue( this, verticesIndexes, newZValues ) );
908}
909
910bool QgsMeshEditor::canBeTransformed( const QList<int> &facesToCheck, const std::function<const QgsMeshVertex( int )> &transformFunction ) const
911{
912 for ( const int faceIndex : facesToCheck )
913 {
914 const QgsMeshFace &face = mMesh->face( faceIndex );
915 int faceSize = face.count();
916 QVector<QgsPointXY> pointsInTriangularMeshCoordinate( faceSize );
917 QVector<QgsPointXY> points( faceSize );
918 for ( int i = 0; i < faceSize; ++i )
919 {
920 int ip0 = face[i];
921 int ip1 = face[( i + 1 ) % faceSize];
922 int ip2 = face[( i + 2 ) % faceSize];
923
924 QgsMeshVertex p0 = transformFunction( ip0 );
925 QgsMeshVertex p1 = transformFunction( ip1 );
926 QgsMeshVertex p2 = transformFunction( ip2 );
927
928 double ux = p0.x() - p1.x();
929 double uy = p0.y() - p1.y();
930 double vx = p2.x() - p1.x();
931 double vy = p2.y() - p1.y();
932
933 double crossProduct = ux * vy - uy * vx;
934 if ( crossProduct >= 0 ) //if cross product>0, we have two edges clockwise
935 return false;
936 pointsInTriangularMeshCoordinate[i] = mTriangularMesh->nativeToTriangularCoordinates( p0 );
937 points[i] = p0;
938 }
939
940 const QgsGeometry &deformedFace = QgsGeometry::fromPolygonXY( {points} );
941
942 // now test if the deformed face contain something else
943 QList<int> otherFaceIndexes =
944 mTriangularMesh->nativeFaceIndexForRectangle( QgsGeometry::fromPolygonXY( {pointsInTriangularMeshCoordinate} ).boundingBox() );
945
946 for ( const int otherFaceIndex : otherFaceIndexes )
947 {
948 const QgsMeshFace &otherFace = mMesh->face( otherFaceIndex );
949 int existingFaceSize = otherFace.count();
950 bool shareVertex = false;
951 for ( int i = 0; i < existingFaceSize; ++i )
952 {
953 if ( face.contains( otherFace.at( i ) ) )
954 {
955 shareVertex = true;
956 break;
957 }
958 }
959 if ( shareVertex )
960 {
961 //only test the edge that not contains a shared vertex
962 for ( int i = 0; i < existingFaceSize; ++i )
963 {
964 int index1 = otherFace.at( i );
965 int index2 = otherFace.at( ( i + 1 ) % existingFaceSize );
966 if ( ! face.contains( index1 ) && !face.contains( index2 ) )
967 {
968 const QgsPointXY &v1 = transformFunction( index1 );
969 const QgsPointXY &v2 = transformFunction( index2 );
970 QgsGeometry edgeGeom = QgsGeometry::fromPolylineXY( { v1, v2} );
971 if ( deformedFace.intersects( edgeGeom ) )
972 return false;
973 }
974 }
975 }
976 else
977 {
978 QVector<QgsPointXY> otherPoints( existingFaceSize );
979 for ( int i = 0; i < existingFaceSize; ++i )
980 otherPoints[i] = transformFunction( otherFace.at( i ) );
981 const QgsGeometry existingFaceGeom = QgsGeometry::fromPolygonXY( {otherPoints } );
982 if ( deformedFace.intersects( existingFaceGeom ) )
983 return false;
984 }
985 }
986
987 const QList<int> freeVerticesIndex = freeVerticesIndexes();
988 for ( const int vertexIndex : freeVerticesIndex )
989 {
990 const QgsPointXY &mapPoint = transformFunction( vertexIndex ); //free vertices can be transformed
991 if ( deformedFace.contains( &mapPoint ) )
992 return false;
993 }
994 }
995
996 // free vertices
997 const QList<int> freeVerticesIndex = freeVerticesIndexes();
998 for ( const int vertexIndex : freeVerticesIndex )
999 {
1000 const QgsMeshVertex &newFreeVertexPosition = transformFunction( vertexIndex ); // transformed free vertex
1001 const QgsMeshVertex pointInTriangularCoord = mTriangularMesh->nativeToTriangularCoordinates( newFreeVertexPosition );
1002 const int originalIncludingFace = mTriangularMesh->nativeFaceIndexForPoint( pointInTriangularCoord );
1003
1004 if ( originalIncludingFace != -1 )
1005 {
1006 // That means two things: the free vertex is moved AND is included in a face before transform
1007 // Before returning false, we need to check if the vertex is still in the face after transform
1008 const QgsMeshFace &face = mMesh->face( originalIncludingFace );
1009 int faceSize = face.count();
1010 QVector<QgsPointXY> points( faceSize );
1011 for ( int i = 0; i < faceSize; ++i )
1012 points[i] = transformFunction( face.at( i ) );
1013
1014 const QgsGeometry &deformedFace = QgsGeometry::fromPolygonXY( {points} );
1015 const QgsPointXY ptXY( newFreeVertexPosition );
1016 if ( deformedFace.contains( &ptXY ) )
1017 return false;
1018 }
1019 }
1020
1021 return true;
1022}
1023
1024void QgsMeshEditor::changeXYValues( const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
1025{
1026 // TODO : implement a check if it is possible to change the (x,y) values. For now, this check is made in the APP part
1027 mUndoStack->push( new QgsMeshLayerUndoCommandChangeXYValue( this, verticesIndexes, newValues ) );
1028}
1029
1030void QgsMeshEditor::changeCoordinates( const QList<int> &verticesIndexes, const QList<QgsPoint> &newCoordinates )
1031{
1032 mUndoStack->push( new QgsMeshLayerUndoCommandChangeCoordinates( this, verticesIndexes, newCoordinates ) );
1033}
1034
1036{
1037 mUndoStack->push( new QgsMeshLayerUndoCommandAdvancedEditing( this, editing ) );
1038}
1039
1041{
1042 mTopologicalMesh.reindex();
1043 mUndoStack->clear();
1044}
1045
1047 : mMeshEditor( meshEditor )
1048{
1049}
1050
1052{
1053 if ( mMeshEditor.isNull() )
1054 return;
1055
1056 for ( int i = mEdits.count() - 1; i >= 0; --i )
1057 mMeshEditor->reverseEdit( mEdits[i] );
1058}
1059
1061{
1062 if ( mMeshEditor.isNull() )
1063 return;
1064
1065 for ( QgsMeshEditor::Edit &edit : mEdits )
1066 mMeshEditor->applyEdit( edit );
1067}
1068
1069QgsMeshLayerUndoCommandAddVertices::QgsMeshLayerUndoCommandAddVertices( QgsMeshEditor *meshEditor, const QVector<QgsMeshVertex> &vertices, double tolerance )
1070 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1071 , mVertices( vertices )
1072 , mTolerance( tolerance )
1073{
1074 setText( QObject::tr( "Add %n vertices", nullptr, mVertices.count() ) );
1075}
1076
1078{
1079 if ( !mVertices.isEmpty() )
1080 {
1081 for ( int i = 0; i < mVertices.count(); ++i )
1082 {
1083 const QgsMeshVertex &vertex = mVertices.at( i );
1084 if ( vertex.isEmpty() )
1085 continue;
1086 QgsMeshEditor::Edit edit;
1087 mMeshEditor->applyAddVertex( edit, vertex, mTolerance );
1088 mEdits.append( edit );
1089 }
1090 mVertices.clear(); //not needed anymore, changes are store in mEdits
1091 }
1092 else
1093 {
1094 for ( QgsMeshEditor::Edit &edit : mEdits )
1095 mMeshEditor->applyEdit( edit );
1096 }
1097}
1098
1100 QgsMeshEditor *meshEditor,
1101 const QList<int> &verticesToRemoveIndexes,
1102 QList<int> *remainingVerticesPointer )
1103 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1104 , mVerticesToRemoveIndexes( verticesToRemoveIndexes )
1105 , mRemainingVerticesPointer( remainingVerticesPointer )
1106{
1107 setText( QObject::tr( "Remove %n vertices filling holes", nullptr, verticesToRemoveIndexes.count() ) );
1108}
1109
1111{
1112 int initialVertexCount = mVerticesToRemoveIndexes.count();
1113 if ( !mVerticesToRemoveIndexes.isEmpty() )
1114 {
1115 QgsMeshEditor::Edit edit;
1116 QList<int> vertexToRetry;
1117 while ( !mVerticesToRemoveIndexes.isEmpty() )
1118 {
1119 // try again and again until there is no vertices to remove anymore or nothing is removed.
1120 for ( const int &vertex : std::as_const( mVerticesToRemoveIndexes ) )
1121 {
1122 if ( mMeshEditor->applyRemoveVertexFillHole( edit, vertex ) )
1123 mEdits.append( edit );
1124 else
1125 vertexToRetry.append( vertex );
1126 }
1127
1128 if ( vertexToRetry.count() == mVerticesToRemoveIndexes.count() )
1129 break;
1130 else
1131 mVerticesToRemoveIndexes = vertexToRetry;
1132 }
1133
1134 if ( initialVertexCount == mVerticesToRemoveIndexes.count() )
1135 setObsolete( true );
1136
1137 if ( mRemainingVerticesPointer )
1138 *mRemainingVerticesPointer = mVerticesToRemoveIndexes;
1139
1140 mRemainingVerticesPointer = nullptr;
1141
1142 mVerticesToRemoveIndexes.clear(); //not needed anymore, changes are store in mEdits
1143 }
1144 else
1145 {
1146 for ( QgsMeshEditor::Edit &edit : mEdits )
1147 mMeshEditor->applyEdit( edit );
1148 }
1149}
1150
1151
1153 QgsMeshEditor *meshEditor,
1154 const QList<int> &verticesToRemoveIndexes )
1155 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1156 , mVerticesToRemoveIndexes( verticesToRemoveIndexes )
1157{
1158 setText( QObject::tr( "Remove %n vertices without filling holes", nullptr, verticesToRemoveIndexes.count() ) ) ;
1159}
1160
1162{
1163 if ( !mVerticesToRemoveIndexes.isEmpty() )
1164 {
1165 QgsMeshEditor::Edit edit;
1166
1167 mMeshEditor->applyRemoveVerticesWithoutFillHole( edit, mVerticesToRemoveIndexes );
1168 mEdits.append( edit );
1169
1170 mVerticesToRemoveIndexes.clear(); //not needed anymore, changes are store in mEdits
1171 }
1172 else
1173 {
1174 for ( QgsMeshEditor::Edit &edit : mEdits )
1175 mMeshEditor->applyEdit( edit );
1176 }
1177}
1178
1180 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1181 , mFaces( faces )
1182{
1183 setText( QObject::tr( "Add %n face(s)", nullptr, faces.meshFaces().count() ) );
1184}
1185
1187{
1188 if ( !mFaces.meshFaces().isEmpty() )
1189 {
1190 QgsMeshEditor::Edit edit;
1191 mMeshEditor->applyAddFaces( edit, mFaces );
1192 mEdits.append( edit );
1193
1194 mFaces.clear(); //not needed anymore, now changes are store in edit
1195 }
1196 else
1197 {
1198 for ( QgsMeshEditor::Edit &edit : mEdits )
1199 mMeshEditor->applyEdit( edit );
1200 }
1201}
1202
1204 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1205 , mfacesToRemoveIndexes( facesToRemoveIndexes )
1206{
1207 setText( QObject::tr( "Remove %n face(s)", nullptr, facesToRemoveIndexes.count() ) );
1208}
1209
1211{
1212 if ( !mfacesToRemoveIndexes.isEmpty() )
1213 {
1214 QgsMeshEditor::Edit edit;
1215 mMeshEditor->applyRemoveFaces( edit, mfacesToRemoveIndexes );
1216 mEdits.append( edit );
1217
1218 mfacesToRemoveIndexes.clear(); //not needed anymore, now changes are store in edit
1219 }
1220 else
1221 {
1222 for ( QgsMeshEditor::Edit &edit : mEdits )
1223 mMeshEditor->applyEdit( edit );
1224 }
1225}
1226
1227QgsMeshEditingError::QgsMeshEditingError(): errorType( Qgis::MeshEditingErrorType::NoError ), elementIndex( -1 ) {}
1228
1229QgsMeshEditingError::QgsMeshEditingError( Qgis::MeshEditingErrorType type, int elementIndex ): errorType( type ), elementIndex( elementIndex ) {}
1230
1232{
1233 return mTriangularMesh->nativeExtent();
1234}
1235
1237{
1238 if ( mUndoStack )
1239 return !mUndoStack->isClean();
1240
1241 return false;
1242}
1243
1244bool QgsMeshEditor::reindex( bool renumbering )
1245{
1246 mTopologicalMesh.reindex();
1247 mUndoStack->clear();
1249 mValidFacesCount = mMesh->faceCount();
1250 mValidVerticesCount = mMesh->vertexCount();
1251
1253 return false;
1254
1255 if ( renumbering )
1256 {
1257 if ( !mTopologicalMesh.renumber() )
1258 return false;
1259
1260 QgsMeshEditingError error;
1261 mTopologicalMesh = QgsTopologicalMesh::createTopologicalMesh( mMesh, mMaximumVerticesPerFace, error );
1262 mValidFacesCount = mMesh->faceCount();
1263 mValidVerticesCount = mMesh->vertexCount();
1265 }
1266 else
1267 return true;
1268}
1269
1271{
1272 return mTopologicalMesh.freeVerticesIndexes();
1273}
1274
1275bool QgsMeshEditor::isVertexOnBoundary( int vertexIndex ) const
1276{
1277 return mTopologicalMesh.isVertexOnBoundary( vertexIndex );
1278}
1279
1280bool QgsMeshEditor::isVertexFree( int vertexIndex ) const
1281{
1282 return mTopologicalMesh.isVertexFree( vertexIndex );
1283}
1284
1286{
1287 return mTopologicalMesh.vertexCirculator( vertexIndex );
1288}
1289
1291{
1292 return mTopologicalMesh;
1293}
1294
1296{
1297 return mTriangularMesh;
1298}
1299
1300QgsMeshLayerUndoCommandChangeZValue::QgsMeshLayerUndoCommandChangeZValue( QgsMeshEditor *meshEditor, const QList<int> &verticesIndexes, const QList<double> &newValues )
1301 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1302 , mVerticesIndexes( verticesIndexes )
1303 , mNewValues( newValues )
1304{
1305 setText( QObject::tr( "Change %n vertices Z Value", nullptr, verticesIndexes.count() ) );
1306}
1307
1309{
1310 if ( !mVerticesIndexes.isEmpty() )
1311 {
1312 QgsMeshEditor::Edit edit;
1313 mMeshEditor->applyChangeZValue( edit, mVerticesIndexes, mNewValues );
1314 mEdits.append( edit );
1315 mVerticesIndexes.clear();
1316 mNewValues.clear();
1317 }
1318 else
1319 {
1320 for ( QgsMeshEditor::Edit &edit : mEdits )
1321 mMeshEditor->applyEdit( edit );
1322 }
1323}
1324
1325QgsMeshLayerUndoCommandChangeXYValue::QgsMeshLayerUndoCommandChangeXYValue( QgsMeshEditor *meshEditor, const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
1326 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1327 , mVerticesIndexes( verticesIndexes )
1328 , mNewValues( newValues )
1329{
1330 setText( QObject::tr( "Move %n vertices", nullptr, verticesIndexes.count() ) );
1331}
1332
1334{
1335 if ( !mVerticesIndexes.isEmpty() )
1336 {
1337 QgsMeshEditor::Edit edit;
1338 mMeshEditor->applyChangeXYValue( edit, mVerticesIndexes, mNewValues );
1339 mEdits.append( edit );
1340 mVerticesIndexes.clear();
1341 mNewValues.clear();
1342 }
1343 else
1344 {
1345 for ( QgsMeshEditor::Edit &edit : mEdits )
1346 mMeshEditor->applyEdit( edit );
1347 }
1348}
1349
1350
1351QgsMeshLayerUndoCommandChangeCoordinates::QgsMeshLayerUndoCommandChangeCoordinates( QgsMeshEditor *meshEditor, const QList<int> &verticesIndexes, const QList<QgsPoint> &newCoordinates )
1352 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1353 , mVerticesIndexes( verticesIndexes )
1354 , mNewCoordinates( newCoordinates )
1355{
1356 setText( QObject::tr( "Transform %n vertices coordinates", nullptr, verticesIndexes.count() ) );
1357}
1358
1360{
1361 if ( !mVerticesIndexes.isEmpty() )
1362 {
1363 QgsMeshEditor::Edit editXY;
1364 QList<QgsPointXY> newXY;
1365 newXY.reserve( mNewCoordinates.count() );
1366 QgsMeshEditor::Edit editZ;
1367 QList<double> newZ;
1368 newZ.reserve( mNewCoordinates.count() );
1369
1370 for ( const QgsPoint &pt : std::as_const( mNewCoordinates ) )
1371 {
1372 newXY.append( pt );
1373 newZ.append( pt.z() );
1374 }
1375
1376 mMeshEditor->applyChangeXYValue( editXY, mVerticesIndexes, newXY );
1377 mEdits.append( editXY );
1378 mMeshEditor->applyChangeZValue( editZ, mVerticesIndexes, newZ );
1379 mEdits.append( editZ );
1380 mVerticesIndexes.clear();
1381 mNewCoordinates.clear();
1382 }
1383 else
1384 {
1385 for ( QgsMeshEditor::Edit &edit : mEdits )
1386 mMeshEditor->applyEdit( edit );
1387 }
1388}
1389
1390
1391
1393 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1394 , mVertexIndex1( vertexIndex1 )
1395 , mVertexIndex2( vertexIndex2 )
1396{
1397 setText( QObject::tr( "Flip edge" ) );
1398}
1399
1401{
1402 if ( mVertexIndex1 >= 0 && mVertexIndex2 >= 0 )
1403 {
1404 QgsMeshEditor::Edit edit;
1405 mMeshEditor->applyFlipEdge( edit, mVertexIndex1, mVertexIndex2 );
1406 mEdits.append( edit );
1407 mVertexIndex1 = -1;
1408 mVertexIndex2 = -1;
1409 }
1410 else
1411 {
1412 for ( QgsMeshEditor::Edit &edit : mEdits )
1413 mMeshEditor->applyEdit( edit );
1414 }
1415}
1416
1417QgsMeshLayerUndoCommandMerge::QgsMeshLayerUndoCommandMerge( QgsMeshEditor *meshEditor, int vertexIndex1, int vertexIndex2 )
1418 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1419 , mVertexIndex1( vertexIndex1 )
1420 , mVertexIndex2( vertexIndex2 )
1421{
1422 setText( QObject::tr( "Merge faces" ) );
1423}
1424
1426{
1427 if ( mVertexIndex1 >= 0 && mVertexIndex2 >= 0 )
1428 {
1429 QgsMeshEditor::Edit edit;
1430 mMeshEditor->applyMerge( edit, mVertexIndex1, mVertexIndex2 );
1431 mEdits.append( edit );
1432 mVertexIndex1 = -1;
1433 mVertexIndex2 = -1;
1434 }
1435 else
1436 {
1437 for ( QgsMeshEditor::Edit &edit : mEdits )
1438 mMeshEditor->applyEdit( edit );
1439 }
1440}
1441
1443 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1444 , mFaceIndexes( faceIndexes )
1445{
1446 setText( QObject::tr( "Split %n face(s)", nullptr, faceIndexes.count() ) );
1447}
1448
1450{
1451 if ( !mFaceIndexes.isEmpty() )
1452 {
1453 for ( int faceIndex : std::as_const( mFaceIndexes ) )
1454 {
1455 QgsMeshEditor::Edit edit;
1456 mMeshEditor->applySplit( edit, faceIndex );
1457 mEdits.append( edit );
1458 }
1459 mFaceIndexes.clear();
1460 }
1461 else
1462 {
1463 for ( QgsMeshEditor::Edit &edit : mEdits )
1464 mMeshEditor->applyEdit( edit );
1465 }
1466}
1467
1469 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1470 , mAdvancedEditing( advancdEdit )
1471{
1472 setText( advancdEdit->text() );
1473}
1474
1476{
1477 if ( mAdvancedEditing )
1478 {
1479 QgsMeshEditor::Edit edit;
1480 while ( !mAdvancedEditing->isFinished() )
1481 {
1482 mMeshEditor->applyAdvancedEdit( edit, mAdvancedEditing );
1483 mEdits.append( edit );
1484 }
1485
1486 mAdvancedEditing = nullptr;
1487 }
1488 else
1489 {
1490 for ( QgsMeshEditor::Edit &edit : mEdits )
1491 mMeshEditor->applyEdit( edit );
1492 }
1493}
1494
1496 QgsMeshEditor *meshEditor,
1497 const QgsMeshVertex &vertex,
1498 double tolerance )
1499 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1500 , mVertex( vertex )
1501 , mTolerance( tolerance )
1502{
1503 setText( QObject::tr( "Add vertex inside face with Delaunay refinement" ) );
1504}
1505
1507{
1508 if ( !mVertex.isEmpty() )
1509 {
1510 QgsMeshEditor::Edit edit;
1511
1512 mMeshEditor->applyAddVertex( edit, mVertex, mTolerance );
1513 mEdits.append( edit );
1514
1515 QList<std::pair<int, int>> sharedEdges = innerEdges( secondNeighboringTriangularFaces() );
1516
1517 for ( std::pair<int, int> edge : sharedEdges )
1518 {
1519 if ( mMeshEditor->edgeCanBeFlipped( edge.first, edge.second ) && !mMeshEditor->topologicalMesh().delaunayConditionForEdge( edge.first, edge.second ) )
1520 {
1521 mMeshEditor->applyFlipEdge( edit, edge.first, edge.second );
1522 mEdits.append( edit );
1523 }
1524 }
1525
1526 mVertex = QgsMeshVertex();
1527 }
1528 else
1529 {
1530 for ( QgsMeshEditor::Edit &edit : mEdits )
1531 mMeshEditor->applyEdit( edit );
1532 }
1533}
1534
1535QSet<int> QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement::secondNeighboringTriangularFaces()
1536{
1537 const int vIndex = mMeshEditor->topologicalMesh().mesh()->vertexCount() - 1;
1538 const QList<int> firstNeighborFaces = mMeshEditor->topologicalMesh().facesAroundVertex( vIndex );
1539 QSet<int> firstNeighborVertices;
1540 for ( int face : firstNeighborFaces )
1541 {
1542 const QgsMeshFace meshFace = mMeshEditor->topologicalMesh().mesh()->face( face );
1543 for ( int vertex : meshFace )
1544 {
1545 firstNeighborVertices.insert( vertex );
1546 }
1547 }
1548
1549 QSet<int> secondNeighboringFaces;
1550 for ( int vertex : firstNeighborVertices )
1551 {
1552 const QList<int> faces = mMeshEditor->topologicalMesh().facesAroundVertex( vertex );
1553 for ( int face : faces )
1554 {
1555 if ( mMeshEditor->topologicalMesh().mesh()->face( face ).count() == 3 )
1556 secondNeighboringFaces.insert( face );
1557 }
1558 }
1559 return secondNeighboringFaces;
1560}
1561
1562QList<std::pair<int, int>> QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement::innerEdges( const QSet<int> &faces )
1563{
1564 // edges and number of their occurrence in triangular faces
1565 QMap<std::pair<int, int>, int> edges;
1566
1567 for ( int faceIndex : faces )
1568 {
1569 const QgsMeshFace face = mMeshEditor->topologicalMesh().mesh()->face( faceIndex );
1570
1571 for ( int i = 0; i < face.size(); i++ )
1572 {
1573 int next = i + 1;
1574 if ( next == face.size() )
1575 {
1576 next = 0;
1577 }
1578
1579 int minIndex = std::min( face.at( i ), face.at( next ) );
1580 int maxIndex = std::max( face.at( i ), face.at( next ) );
1581 std::pair<int, int> edge = std::pair<int, int>( minIndex, maxIndex );
1582
1583 int count = 1;
1584 if ( edges.contains( edge ) )
1585 {
1586 count = edges.take( edge );
1587 count++;
1588 }
1589
1590 edges.insert( edge, count );
1591 }
1592 }
1593
1594 QList<std::pair<int, int>> sharedEdges;
1595
1596 for ( auto it = edges.begin(); it != edges.end(); it++ )
1597 {
1598 if ( it.value() == 2 )
1599 {
1600 sharedEdges.push_back( it.key() );
1601 }
1602 }
1603
1604 return sharedEdges;
1605}
The Qgis class provides global constants for use throughout the application.
Definition qgis.h:54
MeshEditingErrorType
Type of error that can occur during mesh frame editing.
Definition qgis.h:1569
@ 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)
Returns the squared distance between a point and a line.
A geometry is the spatial representation of a feature.
static QgsGeometry fromPolylineXY(const QgsPolylineXY &polyline)
Creates a new LineString geometry from a list of QgsPointXY points.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool contains(const QgsPointXY *p) const
Returns true if the geometry contains the point p.
static QgsGeometry fromPolygonXY(const QgsPolygonXY &polygon)
Creates a new geometry from a QgsPolygonXY.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry, double precision=0.0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlag::SkipEmptyInteriorRings)
Creates and returns a new geometry engine representing the specified geometry using precision on a gr...
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.
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.
Qgis::MeshEditingErrorType errorType
QgsMeshEditingError()
Constructor of the default error, that is NoError.
Class that makes edit operation on a mesh.
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
void addVertexWithDelaunayRefinement(const QgsMeshVertex &vertex, const double tolerance)
Add a vertex in a face with Delaunay refinement of neighboring faces All neighboring faces sharing a ...
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.
friend class QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement
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.
QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement(QgsMeshEditor *meshEditor, const QgsMeshVertex &vertex, double tolerance)
Constructor with the associated meshEditor and indexes vertex and tolerance.
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.
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:60
double distance(double x, double y) const
Returns the distance between this point and a specified x, y coordinate.
Definition qgspointxy.h:206
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double x
Definition qgspoint.h:52
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:738
double y
Definition qgspoint.h:53
A rectangle specified with double values.
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 that is 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.
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.