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