QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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 "qgsmesheditor.h"
18
19#include "poly2tri.h"
20#include "qgis.h"
21#include "qgsgeometryengine.h"
22#include "qgsgeometryutils.h"
24#include "qgsmeshdataprovider.h"
25#include "qgsmeshlayer.h"
26#include "qgsmeshutils.h"
27#include "qgspolygon.h"
28#include "qgstriangularmesh.h"
29
30#include <QSet>
31
32#include "moc_qgsmesheditor.cpp"
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
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
56std::unique_ptr< QgsMeshDatasetGroup > QgsMeshEditor::createZValueDatasetGroup()
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;
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
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 && vertInNewFace2 != index2 && vertInNewFace1 != index2 && vertInNewFace2 != index1;
228 }
229 else
230 {
231 if ( vertInNewFace1 == -1 )
232 hasToBeTest &= vertInNewFace2 != index1 && vertInNewFace2 != index2;
233
234
235 if ( vertInNewFace2 == -1 )
236 hasToBeTest &= vertInNewFace1 != index1 && vertInNewFace1 != index2;
237 }
238
239 if ( hasToBeTest )
240 {
241 const QgsMeshVertex &nv1 = vertices.at( vi );
242 const QgsMeshVertex &nv2 = vertices.at( ( vi + 1 ) % newFaceSize );
243 const QgsGeometry newEdgeGeom = QgsGeometry( new QgsLineString( nv1, nv2 ) );
244
245 if ( newEdgeGeom.intersects( edgeGeom ) )
246 return false;
247 }
248 }
249 }
250 }
251 }
252 else
253 {
254 const QgsGeometry existingFaceGeom = QgsMeshUtils::toGeometry( existingFace, mTriangularMesh->vertices() );
255 if ( geomEngine->intersects( existingFaceGeom.constGet() ) )
256 return false;
257 }
258 }
259 }
260
261 // Then search for free vertices included in the new face
262 const QList<int> &freeVertices = freeVerticesIndexes();
263 for ( const int freeVertexIndex : freeVertices )
264 {
265 if ( vertexIndexes.contains( freeVertexIndex ) )
266 continue;
267
268 const QgsMeshVertex &vertex = mTriangularMesh->vertices().at( freeVertexIndex );
269 if ( geomEngine->contains( &vertex ) )
270 return false;
271 }
272
273 return true;
274}
275
277{
278 const QList<int> newFaceVerticesIndexes( face.toList() );
279 QList<QgsMeshVertex> allVertices;
280 allVertices.reserve( face.count() );
281 for ( int i : face )
282 allVertices.append( mTriangularMesh->vertices().at( i ) );
283
284 return isFaceGeometricallyCompatible( newFaceVerticesIndexes, allVertices );
285}
286
287
289{
291
292 // Prepare and check the face
293 QVector<QgsMeshFace> facesToAdd = prepareFaces( { face }, error );
294
296 return false;
297
298 // Check if there is topological error with the mesh
300 error = mTopologicalMesh.facesCanBeAdded( topologicalFaces );
301
303 return false;
304
305 // Check geometry compatibility
306 // With the topological check, we know that the new face is not included in an existing one
307 // But maybe, the new face includes or intersects existing faces or free vertices, we need to check
308 // First search for faces intersecting the bounding box of the new face.
309
310 return isFaceGeometricallyCompatible( face );
311}
312
313bool QgsMeshEditor::faceCanBeAddedWithNewVertices( const QList<int> &verticesIndex, const QList<QgsMeshVertex> &newVertices ) const
314{
316 const QList<int> face = prepareFaceWithNewVertices( verticesIndex, newVertices, error );
317
318 if ( face.isEmpty() )
319 return false;
320
322 return false;
323
324 // if we are here, the face is convex and not flat
325
326 // Now we check the topology of the potential new face
327 int size = face.size();
328 QList<QgsMeshVertex> allVertices;
329 allVertices.reserve( verticesIndex.size() );
330 int newVertPos = 0;
331 for ( int i = 0; i < size; ++i )
332 {
333 int index = face.at( i );
334 if ( index == -1 )
335 {
336 if ( newVertPos >= newVertices.count() )
337 return false;
338 allVertices.append( newVertices.at( newVertPos++ ) );
339 continue;
340 }
341
342 allVertices.append( mTriangularMesh->vertices().at( index ) );
343
344 if ( isVertexFree( index ) )
345 continue;
346
347 int prevIndex = face.at( ( i - 1 + size ) % size );
348 int nextIndex = face.at( ( i + 1 ) % size );
349
350 QgsMeshVertexCirculator circulator = mTopologicalMesh.vertexCirculator( index );
351 if ( !circulator.goBoundaryClockwise() ) //vertex not on boundary
352 return false;
353
354 int prevOppVertex = circulator.oppositeVertexClockwise();
355 if ( prevOppVertex == nextIndex ) //manifold face
356 return false;
357
358 if ( !circulator.goBoundaryCounterClockwise() )
359 return false;
360
361 int nextOppVertex = circulator.oppositeVertexCounterClockwise();
362 if ( nextOppVertex == prevIndex ) //manifold face
363 return false;
364
365 if ( nextIndex != nextOppVertex && prevIndex != prevOppVertex ) //unique shared vertex
366 return false;
367 }
368
369 return isFaceGeometricallyCompatible( face, allVertices );
370}
371
372void QgsMeshEditor::applyEdit( QgsMeshEditor::Edit &edit )
373{
374 mTopologicalMesh.applyChanges( edit.topologicalChanges );
375 mTriangularMesh->applyChanges( edit.triangularMeshChanges );
376
377 if ( mZValueDatasetGroup
378 && ( !edit.topologicalChanges.newVerticesZValues().isEmpty() || !edit.topologicalChanges.verticesToRemoveIndexes().isEmpty() || !edit.topologicalChanges.addedVertices().isEmpty() ) )
379 mZValueDatasetGroup->setStatisticObsolete();
380
381 updateElementsCount( edit.topologicalChanges );
382}
383
384void QgsMeshEditor::reverseEdit( QgsMeshEditor::Edit &edit )
385{
386 mTopologicalMesh.reverseChanges( edit.topologicalChanges );
387 mTriangularMesh->reverseChanges( edit.triangularMeshChanges, *mMesh );
388
389 if ( mZValueDatasetGroup
390 && ( !edit.topologicalChanges.newVerticesZValues().isEmpty() || !edit.topologicalChanges.verticesToRemoveIndexes().isEmpty() || !edit.topologicalChanges.addedVertices().isEmpty() ) )
391 mZValueDatasetGroup->setStatisticObsolete();
392
393 updateElementsCount( edit.topologicalChanges, false );
394}
395
396void QgsMeshEditor::applyAddVertex( QgsMeshEditor::Edit &edit, const QgsMeshVertex &vertex, double tolerance )
397{
398 QgsMeshVertex vertexInTriangularCoordinate = mTriangularMesh->nativeToTriangularCoordinates( vertex );
399
400 //check if edges is closest than the tolerance from the vertex
401 int faceEdgeIntersect = -1;
402 int edgePosition = -1;
403
404 QgsTopologicalMesh::Changes topologicChanges;
405
406 if ( edgeIsClose( vertexInTriangularCoordinate, tolerance, faceEdgeIntersect, edgePosition ) )
407 {
408 topologicChanges = mTopologicalMesh.insertVertexInFacesEdge( faceEdgeIntersect, edgePosition, vertex );
409 }
410 else
411 {
412 int includingFaceIndex = mTriangularMesh->nativeFaceIndexForPoint( vertexInTriangularCoordinate );
413
414 if ( includingFaceIndex != -1 )
415 topologicChanges = mTopologicalMesh.addVertexInFace( includingFaceIndex, vertex );
416 else
417 topologicChanges = mTopologicalMesh.addFreeVertex( vertex );
418 }
419
420 applyEditOnTriangularMesh( edit, topologicChanges );
421
422 if ( mZValueDatasetGroup )
423 mZValueDatasetGroup->setStatisticObsolete();
424
425 updateElementsCount( edit.topologicalChanges );
426}
427
428bool QgsMeshEditor::applyRemoveVertexFillHole( QgsMeshEditor::Edit &edit, int vertexIndex )
429{
430 QgsTopologicalMesh::Changes changes = mTopologicalMesh.removeVertexFillHole( vertexIndex );
431
432 if ( !changes.isEmpty() )
433 {
434 applyEditOnTriangularMesh( edit, changes );
435
436 if ( mZValueDatasetGroup )
437 mZValueDatasetGroup->setStatisticObsolete();
438
439 updateElementsCount( edit.topologicalChanges );
440 return true;
441 }
442 else
443 return false;
444}
445
446void QgsMeshEditor::applyRemoveVerticesWithoutFillHole( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes )
447{
448 applyEditOnTriangularMesh( edit, mTopologicalMesh.removeVertices( verticesIndexes ) );
449
450 if ( mZValueDatasetGroup )
451 mZValueDatasetGroup->setStatisticObsolete();
452
453 updateElementsCount( edit.topologicalChanges );
454}
455
456void QgsMeshEditor::applyAddFaces( QgsMeshEditor::Edit &edit, const QgsTopologicalMesh::TopologicalFaces &faces )
457{
458 applyEditOnTriangularMesh( edit, mTopologicalMesh.addFaces( faces ) );
459
460 updateElementsCount( edit.topologicalChanges );
461}
462
463void QgsMeshEditor::applyRemoveFaces( QgsMeshEditor::Edit &edit, const QList<int> &faceToRemoveIndex )
464{
465 applyEditOnTriangularMesh( edit, mTopologicalMesh.removeFaces( faceToRemoveIndex ) );
466
467 updateElementsCount( edit.topologicalChanges );
468}
469
470void QgsMeshEditor::applyChangeZValue( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes, const QList<double> &newValues )
471{
472 applyEditOnTriangularMesh( edit, mTopologicalMesh.changeZValue( verticesIndexes, newValues ) );
473
474 if ( mZValueDatasetGroup )
475 mZValueDatasetGroup->setStatisticObsolete();
476}
477
478void QgsMeshEditor::applyChangeXYValue( QgsMeshEditor::Edit &edit, const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
479{
480 applyEditOnTriangularMesh( edit, mTopologicalMesh.changeXYValue( verticesIndexes, newValues ) );
481}
482
483void QgsMeshEditor::applyFlipEdge( QgsMeshEditor::Edit &edit, int vertexIndex1, int vertexIndex2 )
484{
485 applyEditOnTriangularMesh( edit, mTopologicalMesh.flipEdge( vertexIndex1, vertexIndex2 ) );
486
487 updateElementsCount( edit.topologicalChanges );
488}
489
490void QgsMeshEditor::applyMerge( QgsMeshEditor::Edit &edit, int vertexIndex1, int vertexIndex2 )
491{
492 applyEditOnTriangularMesh( edit, mTopologicalMesh.merge( vertexIndex1, vertexIndex2 ) );
493
494 updateElementsCount( edit.topologicalChanges );
495}
496
497void QgsMeshEditor::applySplit( QgsMeshEditor::Edit &edit, int faceIndex )
498{
499 applyEditOnTriangularMesh( edit, mTopologicalMesh.splitFace( faceIndex ) );
500
501 updateElementsCount( edit.topologicalChanges );
502}
503
504void QgsMeshEditor::applyAdvancedEdit( QgsMeshEditor::Edit &edit, QgsMeshAdvancedEditing *editing )
505{
506 applyEditOnTriangularMesh( edit, editing->apply( this ) );
507
508 updateElementsCount( edit.topologicalChanges );
509
510 if ( mZValueDatasetGroup )
511 mZValueDatasetGroup->setStatisticObsolete();
512}
513
514void QgsMeshEditor::applyEditOnTriangularMesh( QgsMeshEditor::Edit &edit, const QgsTopologicalMesh::Changes &topologicChanges )
515{
516 QgsTriangularMesh::Changes triangularChanges( topologicChanges, *mMesh );
517 mTriangularMesh->applyChanges( triangularChanges );
518
519 edit.topologicalChanges = topologicChanges;
520 edit.triangularMeshChanges = triangularChanges;
521}
522
523void QgsMeshEditor::updateElementsCount( const QgsTopologicalMesh::Changes &changes, bool apply )
524{
525 if ( apply )
526 {
527 mValidFacesCount += changes.addedFaces().count() - changes.removedFaces().count();
528 mValidVerticesCount += changes.addedVertices().count() - changes.verticesToRemoveIndexes().count();
529 }
530 else
531 {
532 //reverse
533 mValidFacesCount -= changes.addedFaces().count() - changes.removedFaces().count();
534 mValidVerticesCount -= changes.addedVertices().count() - changes.verticesToRemoveIndexes().count();
535 }
536}
537
539{
540 error = mTopologicalMesh.checkConsistency();
541 switch ( error.errorType )
542 {
544 break;
551 return false;
552 }
553
554 if ( mTriangularMesh->vertices().count() != mMesh->vertexCount() )
555 return false;
556
557 if ( mTriangularMesh->faceCentroids().count() != mMesh->faceCount() )
558 return false;
559
560 return true;
561}
562
563bool QgsMeshEditor::edgeIsClose( QgsPointXY point, double tolerance, int &faceIndex, int &edgePosition )
564{
565 QgsRectangle toleranceZone( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
566
567 edgePosition = -1;
568 double minDist = std::numeric_limits<double>::max();
569 const QList<int> &nativeFaces = mTriangularMesh->nativeFaceIndexForRectangle( toleranceZone );
570 double epsilon = std::numeric_limits<double>::epsilon() * tolerance;
571 for ( const int nativeFaceIndex : nativeFaces )
572 {
573 const QgsMeshFace &face = mMesh->face( nativeFaceIndex );
574 const int faceSize = face.size();
575 for ( int i = 0; i < faceSize; ++i )
576 {
577 const QgsMeshVertex &v1 = mTriangularMesh->vertices().at( face.at( i ) );
578 const QgsMeshVertex &v2 = mTriangularMesh->vertices().at( face.at( ( i + 1 ) % faceSize ) );
579
580 double mx, my;
581 double dist = sqrt( QgsGeometryUtilsBase::sqrDistToLine( point.x(), point.y(), v1.x(), v1.y(), v2.x(), v2.y(), mx, my, epsilon ) );
582
583 if ( dist < tolerance && dist < minDist )
584 {
585 faceIndex = nativeFaceIndex;
586 edgePosition = i;
587 minDist = dist;
588 }
589 }
590 }
591
592 if ( edgePosition != -1 )
593 return true;
594
595 return false;
596}
597
599{
600 return mValidFacesCount;
601}
602
604{
605 return mValidVerticesCount;
606}
607
609{
610 return mMaximumVerticesPerFace;
611}
612
613QgsMeshEditingError QgsMeshEditor::removeFaces( const QList<int> &facesToRemove )
614{
615 QgsMeshEditingError error = mTopologicalMesh.facesCanBeRemoved( facesToRemove );
617 return error;
618
619 mUndoStack->push( new QgsMeshLayerUndoCommandRemoveFaces( this, facesToRemove ) );
620
621 return error;
622}
623
624void QgsMeshEditor::addVertexWithDelaunayRefinement( const QgsMeshVertex &vertex, const double tolerance )
625{
626 int triangleIndex = mTriangularMesh->faceIndexForPoint_v2( vertex );
627 if ( triangleIndex == -1 )
628 return;
629
630 mUndoStack->push( new QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement( this, vertex, tolerance ) );
631}
632
633bool QgsMeshEditor::edgeCanBeFlipped( int vertexIndex1, int vertexIndex2 ) const
634{
635 return mTopologicalMesh.edgeCanBeFlipped( vertexIndex1, vertexIndex2 );
636}
637
638void QgsMeshEditor::flipEdge( int vertexIndex1, int vertexIndex2 )
639{
640 if ( !edgeCanBeFlipped( vertexIndex1, vertexIndex2 ) )
641 return;
642
643 mUndoStack->push( new QgsMeshLayerUndoCommandFlipEdge( this, vertexIndex1, vertexIndex2 ) );
644}
645
646bool QgsMeshEditor::canBeMerged( int vertexIndex1, int vertexIndex2 ) const
647{
648 return mTopologicalMesh.canBeMerged( vertexIndex1, vertexIndex2 );
649}
650
651void QgsMeshEditor::merge( int vertexIndex1, int vertexIndex2 )
652{
653 if ( !canBeMerged( vertexIndex1, vertexIndex2 ) )
654 return;
655
656 mUndoStack->push( new QgsMeshLayerUndoCommandMerge( this, vertexIndex1, vertexIndex2 ) );
657}
658
659bool QgsMeshEditor::faceCanBeSplit( int faceIndex ) const
660{
661 return mTopologicalMesh.canBeSplit( faceIndex );
662}
663
664int QgsMeshEditor::splitFaces( const QList<int> &faceIndexes )
665{
666 QList<int> faceIndexesSplittable;
667
668 for ( const int faceIndex : faceIndexes )
669 if ( faceCanBeSplit( faceIndex ) )
670 faceIndexesSplittable.append( faceIndex );
671
672 if ( faceIndexesSplittable.isEmpty() )
673 return 0;
674
675 mUndoStack->push( new QgsMeshLayerUndoCommandSplitFaces( this, faceIndexesSplittable ) );
676
677 return faceIndexesSplittable.count();
678}
679
680QVector<QgsMeshFace> QgsMeshEditor::prepareFaces( const QVector<QgsMeshFace> &faces, QgsMeshEditingError &error ) const
681{
682 QVector<QgsMeshFace> treatedFaces = faces;
683
684 // here we could add later some filters, for example, removing faces intersecting with existing one
685
686 for ( int i = 0; i < treatedFaces.count(); ++i )
687 {
688 QgsMeshFace &face = treatedFaces[i];
689 if ( mMaximumVerticesPerFace != 0 && face.count() > mMaximumVerticesPerFace )
690 {
692 break;
693 }
694
695 error = QgsTopologicalMesh::counterClockwiseFaces( face, mMesh );
697 break;
698 }
699
700 return treatedFaces;
701}
702
703QList<int> QgsMeshEditor::prepareFaceWithNewVertices( const QList<int> &face, const QList<QgsMeshVertex> &newVertices, QgsMeshEditingError &error ) const
704{
705 if ( mMaximumVerticesPerFace != 0 && face.count() > mMaximumVerticesPerFace )
706 {
707 error = QgsMeshEditingError( Qgis::MeshEditingErrorType::InvalidFace, 0 );
708 return face;
709 }
710
711 int faceSize = face.count();
712 QVector<QgsMeshVertex> vertices( faceSize );
713 int newVertexPos = 0;
714 for ( int i = 0; i < faceSize; ++i )
715 {
716 if ( face.at( i ) == -1 )
717 {
718 if ( newVertexPos >= newVertices.count() )
719 return QList<int>();
720 vertices[i] = newVertices.at( newVertexPos++ );
721 }
722 else if ( face.at( i ) >= 0 )
723 {
724 if ( face.at( i ) >= mTriangularMesh->vertices().count() )
725 {
726 error = QgsMeshEditingError( Qgis::MeshEditingErrorType::InvalidFace, 11 );
727 break;
728 }
729 vertices[i] = mTriangularMesh->vertices().at( face.at( i ) );
730 }
731 else
732 {
733 error = QgsMeshEditingError( Qgis::MeshEditingErrorType::InvalidFace, 12 );
734 break;
735 }
736 }
737
739 return face;
740
741 bool clockwise = false;
742 error = QgsTopologicalMesh::checkTopologyOfVerticesAsFace( vertices, clockwise );
743
744 if ( clockwise && error.errorType == Qgis::MeshEditingErrorType::NoError )
745 {
746 QList<int> newFace = face;
747 for ( int i = 0; i < faceSize / 2; ++i )
748 {
749 int temp = newFace[i];
750 newFace[i] = face.at( faceSize - i - 1 );
751 newFace[faceSize - i - 1] = temp;
752 }
753
754 return newFace;
755 }
756
757 return face;
758}
759
760QgsMeshEditingError QgsMeshEditor::addFaces( const QVector<QVector<int> > &faces )
761{
763 QVector<QgsMeshFace> facesToAdd = prepareFaces( faces, error );
764
766 return error;
767
769
770 error = mTopologicalMesh.facesCanBeAdded( topologicalFaces );
771
773 return error;
774
775 mUndoStack->push( new QgsMeshLayerUndoCommandAddFaces( this, topologicalFaces ) );
776
777 return error;
778}
779
780QgsMeshEditingError QgsMeshEditor::addFace( const QVector<int> &vertexIndexes )
781{
782 return addFaces( { vertexIndexes } );
783}
784
785QgsMeshEditingError QgsMeshEditor::addFaceWithNewVertices( const QList<int> &vertexIndexes, const QList<QgsMeshVertex> &newVertices )
786{
787 mUndoStack->beginMacro( tr( "Add a face with new %n vertices", nullptr, newVertices.count() ) );
788 int newVertexIndex = mMesh->vertexCount();
789 addVertices( newVertices.toVector(), 0 );
790 QgsMeshFace face( vertexIndexes.count() );
791 for ( int i = 0; i < vertexIndexes.count(); ++i )
792 {
793 int index = vertexIndexes.at( i );
794 if ( index == -1 )
795 face[i] = newVertexIndex++;
796 else
797 face[i] = index;
798 }
799
800 QgsMeshEditingError error = addFace( face );
801 mUndoStack->endMacro();
802
803 return error;
804}
805
806int QgsMeshEditor::addVertices( const QVector<QgsMeshVertex> &vertices, double tolerance )
807{
808 QVector<QgsMeshVertex> verticesInLayerCoordinate( vertices.count() );
809 int ignoredVertex = 0;
810 for ( int i = 0; i < vertices.count(); ++i )
811 {
812 const QgsPointXY &pointInTriangularMesh = vertices.at( i );
813 bool isTooClose = false;
814 int triangleIndex = mTriangularMesh->faceIndexForPoint_v2( pointInTriangularMesh );
815 if ( triangleIndex != -1 )
816 {
817 const QgsMeshFace face = mTriangularMesh->triangles().at( triangleIndex );
818 for ( int j = 0; j < 3; ++j )
819 {
820 const QgsPointXY &facePoint = mTriangularMesh->vertices().at( face.at( j ) );
821 double dist = pointInTriangularMesh.distance( facePoint );
822 if ( dist < tolerance )
823 {
824 isTooClose = true;
825 break;
826 }
827 }
828 }
829
830 if ( !isTooClose )
831 verticesInLayerCoordinate[i] = mTriangularMesh->triangularToNativeCoordinates( vertices.at( i ) );
832 else
833 verticesInLayerCoordinate[i] = QgsMeshVertex();
834
835 if ( verticesInLayerCoordinate.at( i ).isEmpty() )
836 ignoredVertex++;
837 }
838
839 if ( ignoredVertex < vertices.count() )
840 {
841 mUndoStack->push( new QgsMeshLayerUndoCommandAddVertices( this, verticesInLayerCoordinate, tolerance ) );
842 }
843
844 int effectivlyAddedVertex = vertices.count() - ignoredVertex;
845
846 return effectivlyAddedVertex;
847}
848
849int QgsMeshEditor::addPointsAsVertices( const QVector<QgsPoint> &point, double tolerance )
850{
851 return addVertices( point, tolerance );
852}
853
854QgsMeshEditingError QgsMeshEditor::removeVerticesWithoutFillHoles( const QList<int> &verticesToRemoveIndexes )
855{
857
858 QList<int> verticesIndexes = verticesToRemoveIndexes;
859
860 QSet<int> concernedNativeFaces;
861 for ( const int vi : std::as_const( verticesIndexes ) )
862 {
863 const QList<int> faces = mTopologicalMesh.facesAroundVertex( vi );
864 concernedNativeFaces.unite( QSet< int >( faces.begin(), faces.end() ) );
865 }
866
867 error = mTopologicalMesh.facesCanBeRemoved( concernedNativeFaces.values() );
868
870 return error;
871
872 mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVerticesWithoutFillHoles( this, verticesIndexes ) );
873 return error;
874}
875
876QList<int> QgsMeshEditor::removeVerticesFillHoles( const QList<int> &verticesToRemoveIndexes )
877{
878 QList<int> remainingVertices;
879 mUndoStack->push( new QgsMeshLayerUndoCommandRemoveVerticesFillHoles( this, verticesToRemoveIndexes, &remainingVertices ) );
880
881 return remainingVertices;
882}
883
884
885void QgsMeshEditor::changeZValues( const QList<int> &verticesIndexes, const QList<double> &newZValues )
886{
887 mUndoStack->push( new QgsMeshLayerUndoCommandChangeZValue( this, verticesIndexes, newZValues ) );
888}
889
890bool QgsMeshEditor::canBeTransformed( const QList<int> &facesToCheck, const std::function<const QgsMeshVertex( int )> &transformFunction ) const
891{
892 for ( const int faceIndex : facesToCheck )
893 {
894 const QgsMeshFace &face = mMesh->face( faceIndex );
895 int faceSize = face.count();
896 QVector<QgsPointXY> pointsInTriangularMeshCoordinate( faceSize );
897 QVector<QgsPointXY> points( faceSize );
898 for ( int i = 0; i < faceSize; ++i )
899 {
900 int ip0 = face[i];
901 int ip1 = face[( i + 1 ) % faceSize];
902 int ip2 = face[( i + 2 ) % faceSize];
903
904 QgsMeshVertex p0 = transformFunction( ip0 );
905 QgsMeshVertex p1 = transformFunction( ip1 );
906 QgsMeshVertex p2 = transformFunction( ip2 );
907
908 double ux = p0.x() - p1.x();
909 double uy = p0.y() - p1.y();
910 double vx = p2.x() - p1.x();
911 double vy = p2.y() - p1.y();
912
913 double crossProduct = ux * vy - uy * vx;
914 if ( crossProduct >= 0 ) //if cross product>0, we have two edges clockwise
915 return false;
916 pointsInTriangularMeshCoordinate[i] = mTriangularMesh->nativeToTriangularCoordinates( p0 );
917 points[i] = p0;
918 }
919
920 const QgsGeometry &deformedFace = QgsGeometry::fromPolygonXY( { points } );
921
922 // now test if the deformed face contain something else
923 QList<int> otherFaceIndexes = mTriangularMesh->nativeFaceIndexForRectangle( QgsGeometry::fromPolygonXY( { pointsInTriangularMeshCoordinate } ).boundingBox() );
924
925 for ( const int otherFaceIndex : otherFaceIndexes )
926 {
927 const QgsMeshFace &otherFace = mMesh->face( otherFaceIndex );
928 int existingFaceSize = otherFace.count();
929 bool shareVertex = false;
930 for ( int i = 0; i < existingFaceSize; ++i )
931 {
932 if ( face.contains( otherFace.at( i ) ) )
933 {
934 shareVertex = true;
935 break;
936 }
937 }
938 if ( shareVertex )
939 {
940 //only test the edge that not contains a shared vertex
941 for ( int i = 0; i < existingFaceSize; ++i )
942 {
943 int index1 = otherFace.at( i );
944 int index2 = otherFace.at( ( i + 1 ) % existingFaceSize );
945 if ( !face.contains( index1 ) && !face.contains( index2 ) )
946 {
947 const QgsPointXY &v1 = transformFunction( index1 );
948 const QgsPointXY &v2 = transformFunction( index2 );
949 QgsGeometry edgeGeom = QgsGeometry::fromPolylineXY( { v1, v2 } );
950 if ( deformedFace.intersects( edgeGeom ) )
951 return false;
952 }
953 }
954 }
955 else
956 {
957 QVector<QgsPointXY> otherPoints( existingFaceSize );
958 for ( int i = 0; i < existingFaceSize; ++i )
959 otherPoints[i] = transformFunction( otherFace.at( i ) );
960 const QgsGeometry existingFaceGeom = QgsGeometry::fromPolygonXY( { otherPoints } );
961 if ( deformedFace.intersects( existingFaceGeom ) )
962 return false;
963 }
964 }
965
966 const QList<int> freeVerticesIndex = freeVerticesIndexes();
967 for ( const int vertexIndex : freeVerticesIndex )
968 {
969 const QgsPointXY &mapPoint = transformFunction( vertexIndex ); //free vertices can be transformed
970 if ( deformedFace.contains( &mapPoint ) )
971 return false;
972 }
973 }
974
975 // free vertices
976 const QList<int> freeVerticesIndex = freeVerticesIndexes();
977 for ( const int vertexIndex : freeVerticesIndex )
978 {
979 const QgsMeshVertex &newFreeVertexPosition = transformFunction( vertexIndex ); // transformed free vertex
980 const QgsMeshVertex pointInTriangularCoord = mTriangularMesh->nativeToTriangularCoordinates( newFreeVertexPosition );
981 const int originalIncludingFace = mTriangularMesh->nativeFaceIndexForPoint( pointInTriangularCoord );
982
983 if ( originalIncludingFace != -1 )
984 {
985 // That means two things: the free vertex is moved AND is included in a face before transform
986 // Before returning false, we need to check if the vertex is still in the face after transform
987 const QgsMeshFace &face = mMesh->face( originalIncludingFace );
988 int faceSize = face.count();
989 QVector<QgsPointXY> points( faceSize );
990 for ( int i = 0; i < faceSize; ++i )
991 points[i] = transformFunction( face.at( i ) );
992
993 const QgsGeometry &deformedFace = QgsGeometry::fromPolygonXY( { points } );
994 const QgsPointXY ptXY( newFreeVertexPosition );
995 if ( deformedFace.contains( &ptXY ) )
996 return false;
997 }
998 }
999
1000 return true;
1001}
1002
1003void QgsMeshEditor::changeXYValues( const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
1004{
1005 // TODO : implement a check if it is possible to change the (x,y) values. For now, this check is made in the APP part
1006 mUndoStack->push( new QgsMeshLayerUndoCommandChangeXYValue( this, verticesIndexes, newValues ) );
1007}
1008
1009void QgsMeshEditor::changeCoordinates( const QList<int> &verticesIndexes, const QList<QgsPoint> &newCoordinates )
1010{
1011 mUndoStack->push( new QgsMeshLayerUndoCommandChangeCoordinates( this, verticesIndexes, newCoordinates ) );
1012}
1013
1015{
1016 mUndoStack->push( new QgsMeshLayerUndoCommandAdvancedEditing( this, editing ) );
1017}
1018
1020{
1021 mTopologicalMesh.reindex();
1022 mUndoStack->clear();
1023}
1024
1028
1030{
1031 if ( mMeshEditor.isNull() )
1032 return;
1033
1034 for ( int i = mEdits.count() - 1; i >= 0; --i )
1035 mMeshEditor->reverseEdit( mEdits[i] );
1036}
1037
1039{
1040 if ( mMeshEditor.isNull() )
1041 return;
1042
1043 for ( QgsMeshEditor::Edit &edit : mEdits )
1044 mMeshEditor->applyEdit( edit );
1045}
1046
1047QgsMeshLayerUndoCommandAddVertices::QgsMeshLayerUndoCommandAddVertices( QgsMeshEditor *meshEditor, const QVector<QgsMeshVertex> &vertices, double tolerance )
1048 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1049 , mVertices( vertices )
1050 , mTolerance( tolerance )
1051{
1052 setText( QObject::tr( "Add %n vertices", nullptr, mVertices.count() ) );
1053}
1054
1056{
1057 if ( !mVertices.isEmpty() )
1058 {
1059 for ( int i = 0; i < mVertices.count(); ++i )
1060 {
1061 const QgsMeshVertex &vertex = mVertices.at( i );
1062 if ( vertex.isEmpty() )
1063 continue;
1064 QgsMeshEditor::Edit edit;
1065 mMeshEditor->applyAddVertex( edit, vertex, mTolerance );
1066 mEdits.append( edit );
1067 }
1068 mVertices.clear(); //not needed anymore, changes are store in mEdits
1069 }
1070 else
1071 {
1072 for ( QgsMeshEditor::Edit &edit : mEdits )
1073 mMeshEditor->applyEdit( edit );
1074 }
1075}
1076
1077QgsMeshLayerUndoCommandRemoveVerticesFillHoles::QgsMeshLayerUndoCommandRemoveVerticesFillHoles( QgsMeshEditor *meshEditor, const QList<int> &verticesToRemoveIndexes, QList<int> *remainingVerticesPointer )
1078 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1079 , mVerticesToRemoveIndexes( verticesToRemoveIndexes )
1080 , mRemainingVerticesPointer( remainingVerticesPointer )
1081{
1082 setText( QObject::tr( "Remove %n vertices filling holes", nullptr, verticesToRemoveIndexes.count() ) );
1083}
1084
1086{
1087 int initialVertexCount = mVerticesToRemoveIndexes.count();
1088 if ( !mVerticesToRemoveIndexes.isEmpty() )
1089 {
1090 QgsMeshEditor::Edit edit;
1091 QList<int> vertexToRetry;
1092 while ( !mVerticesToRemoveIndexes.isEmpty() )
1093 {
1094 // try again and again until there is no vertices to remove anymore or nothing is removed.
1095 for ( const int &vertex : std::as_const( mVerticesToRemoveIndexes ) )
1096 {
1097 if ( mMeshEditor->applyRemoveVertexFillHole( edit, vertex ) )
1098 mEdits.append( edit );
1099 else
1100 vertexToRetry.append( vertex );
1101 }
1102
1103 if ( vertexToRetry.count() == mVerticesToRemoveIndexes.count() )
1104 break;
1105 else
1106 mVerticesToRemoveIndexes = vertexToRetry;
1107 }
1108
1109 if ( initialVertexCount == mVerticesToRemoveIndexes.count() )
1110 setObsolete( true );
1111
1112 if ( mRemainingVerticesPointer )
1113 *mRemainingVerticesPointer = mVerticesToRemoveIndexes;
1114
1115 mRemainingVerticesPointer = nullptr;
1116
1117 mVerticesToRemoveIndexes.clear(); //not needed anymore, changes are store in mEdits
1118 }
1119 else
1120 {
1121 for ( QgsMeshEditor::Edit &edit : mEdits )
1122 mMeshEditor->applyEdit( edit );
1123 }
1124}
1125
1126
1128 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1129 , mVerticesToRemoveIndexes( verticesToRemoveIndexes )
1130{
1131 setText( QObject::tr( "Remove %n vertices without filling holes", nullptr, verticesToRemoveIndexes.count() ) );
1132}
1133
1135{
1136 if ( !mVerticesToRemoveIndexes.isEmpty() )
1137 {
1138 QgsMeshEditor::Edit edit;
1139
1140 mMeshEditor->applyRemoveVerticesWithoutFillHole( edit, mVerticesToRemoveIndexes );
1141 mEdits.append( edit );
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
1153 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1154 , mFaces( faces )
1155{
1156 setText( QObject::tr( "Add %n face(s)", nullptr, faces.meshFaces().count() ) );
1157}
1158
1160{
1161 if ( !mFaces.meshFaces().isEmpty() )
1162 {
1163 QgsMeshEditor::Edit edit;
1164 mMeshEditor->applyAddFaces( edit, mFaces );
1165 mEdits.append( edit );
1166
1167 mFaces.clear(); //not needed anymore, now changes are store in edit
1168 }
1169 else
1170 {
1171 for ( QgsMeshEditor::Edit &edit : mEdits )
1172 mMeshEditor->applyEdit( edit );
1173 }
1174}
1175
1177 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1178 , mfacesToRemoveIndexes( facesToRemoveIndexes )
1179{
1180 setText( QObject::tr( "Remove %n face(s)", nullptr, facesToRemoveIndexes.count() ) );
1181}
1182
1184{
1185 if ( !mfacesToRemoveIndexes.isEmpty() )
1186 {
1187 QgsMeshEditor::Edit edit;
1188 mMeshEditor->applyRemoveFaces( edit, mfacesToRemoveIndexes );
1189 mEdits.append( edit );
1190
1191 mfacesToRemoveIndexes.clear(); //not needed anymore, now changes are store in edit
1192 }
1193 else
1194 {
1195 for ( QgsMeshEditor::Edit &edit : mEdits )
1196 mMeshEditor->applyEdit( edit );
1197 }
1198}
1199
1202
1207
1209{
1210 return mTriangularMesh->nativeExtent();
1211}
1212
1214{
1215 if ( mUndoStack )
1216 return !mUndoStack->isClean();
1217
1218 return false;
1219}
1220
1221bool QgsMeshEditor::reindex( bool renumbering )
1222{
1223 mTopologicalMesh.reindex();
1224 mUndoStack->clear();
1226 mValidFacesCount = mMesh->faceCount();
1227 mValidVerticesCount = mMesh->vertexCount();
1228
1230 return false;
1231
1232 if ( renumbering )
1233 {
1234 if ( !mTopologicalMesh.renumber() )
1235 return false;
1236
1237 QgsMeshEditingError error;
1238 mTopologicalMesh = QgsTopologicalMesh::createTopologicalMesh( mMesh, mMaximumVerticesPerFace, error );
1239 mValidFacesCount = mMesh->faceCount();
1240 mValidVerticesCount = mMesh->vertexCount();
1242 }
1243 else
1244 return true;
1245}
1246
1248{
1249 return mTopologicalMesh.freeVerticesIndexes();
1250}
1251
1252bool QgsMeshEditor::isVertexOnBoundary( int vertexIndex ) const
1253{
1254 return mTopologicalMesh.isVertexOnBoundary( vertexIndex );
1255}
1256
1257bool QgsMeshEditor::isVertexFree( int vertexIndex ) const
1258{
1259 return mTopologicalMesh.isVertexFree( vertexIndex );
1260}
1261
1263{
1264 return mTopologicalMesh.vertexCirculator( vertexIndex );
1265}
1266
1268{
1269 return mTopologicalMesh;
1270}
1271
1273{
1274 return mTriangularMesh;
1275}
1276
1277QgsMeshLayerUndoCommandChangeZValue::QgsMeshLayerUndoCommandChangeZValue( QgsMeshEditor *meshEditor, const QList<int> &verticesIndexes, const QList<double> &newValues )
1278 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1279 , mVerticesIndexes( verticesIndexes )
1280 , mNewValues( newValues )
1281{
1282 setText( QObject::tr( "Change %n vertices Z Value", nullptr, verticesIndexes.count() ) );
1283}
1284
1286{
1287 if ( !mVerticesIndexes.isEmpty() )
1288 {
1289 QgsMeshEditor::Edit edit;
1290 mMeshEditor->applyChangeZValue( edit, mVerticesIndexes, mNewValues );
1291 mEdits.append( edit );
1292 mVerticesIndexes.clear();
1293 mNewValues.clear();
1294 }
1295 else
1296 {
1297 for ( QgsMeshEditor::Edit &edit : mEdits )
1298 mMeshEditor->applyEdit( edit );
1299 }
1300}
1301
1302QgsMeshLayerUndoCommandChangeXYValue::QgsMeshLayerUndoCommandChangeXYValue( QgsMeshEditor *meshEditor, const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
1303 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1304 , mVerticesIndexes( verticesIndexes )
1305 , mNewValues( newValues )
1306{
1307 setText( QObject::tr( "Move %n vertices", nullptr, verticesIndexes.count() ) );
1308}
1309
1311{
1312 if ( !mVerticesIndexes.isEmpty() )
1313 {
1314 QgsMeshEditor::Edit edit;
1315 mMeshEditor->applyChangeXYValue( edit, mVerticesIndexes, mNewValues );
1316 mEdits.append( edit );
1317 mVerticesIndexes.clear();
1318 mNewValues.clear();
1319 }
1320 else
1321 {
1322 for ( QgsMeshEditor::Edit &edit : mEdits )
1323 mMeshEditor->applyEdit( edit );
1324 }
1325}
1326
1327
1328QgsMeshLayerUndoCommandChangeCoordinates::QgsMeshLayerUndoCommandChangeCoordinates( QgsMeshEditor *meshEditor, const QList<int> &verticesIndexes, const QList<QgsPoint> &newCoordinates )
1329 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1330 , mVerticesIndexes( verticesIndexes )
1331 , mNewCoordinates( newCoordinates )
1332{
1333 setText( QObject::tr( "Transform %n vertices coordinates", nullptr, verticesIndexes.count() ) );
1334}
1335
1337{
1338 if ( !mVerticesIndexes.isEmpty() )
1339 {
1340 QgsMeshEditor::Edit editXY;
1341 QList<QgsPointXY> newXY;
1342 newXY.reserve( mNewCoordinates.count() );
1343 QgsMeshEditor::Edit editZ;
1344 QList<double> newZ;
1345 newZ.reserve( mNewCoordinates.count() );
1346
1347 for ( const QgsPoint &pt : std::as_const( mNewCoordinates ) )
1348 {
1349 newXY.append( pt );
1350 newZ.append( pt.z() );
1351 }
1352
1353 mMeshEditor->applyChangeXYValue( editXY, mVerticesIndexes, newXY );
1354 mEdits.append( editXY );
1355 mMeshEditor->applyChangeZValue( editZ, mVerticesIndexes, newZ );
1356 mEdits.append( editZ );
1357 mVerticesIndexes.clear();
1358 mNewCoordinates.clear();
1359 }
1360 else
1361 {
1362 for ( QgsMeshEditor::Edit &edit : mEdits )
1363 mMeshEditor->applyEdit( edit );
1364 }
1365}
1366
1367
1369 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1370 , mVertexIndex1( vertexIndex1 )
1371 , mVertexIndex2( vertexIndex2 )
1372{
1373 setText( QObject::tr( "Flip edge" ) );
1374}
1375
1377{
1378 if ( mVertexIndex1 >= 0 && mVertexIndex2 >= 0 )
1379 {
1380 QgsMeshEditor::Edit edit;
1381 mMeshEditor->applyFlipEdge( edit, mVertexIndex1, mVertexIndex2 );
1382 mEdits.append( edit );
1383 mVertexIndex1 = -1;
1384 mVertexIndex2 = -1;
1385 }
1386 else
1387 {
1388 for ( QgsMeshEditor::Edit &edit : mEdits )
1389 mMeshEditor->applyEdit( edit );
1390 }
1391}
1392
1393QgsMeshLayerUndoCommandMerge::QgsMeshLayerUndoCommandMerge( QgsMeshEditor *meshEditor, int vertexIndex1, int vertexIndex2 )
1394 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1395 , mVertexIndex1( vertexIndex1 )
1396 , mVertexIndex2( vertexIndex2 )
1397{
1398 setText( QObject::tr( "Merge faces" ) );
1399}
1400
1402{
1403 if ( mVertexIndex1 >= 0 && mVertexIndex2 >= 0 )
1404 {
1405 QgsMeshEditor::Edit edit;
1406 mMeshEditor->applyMerge( 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
1419 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1420 , mFaceIndexes( faceIndexes )
1421{
1422 setText( QObject::tr( "Split %n face(s)", nullptr, faceIndexes.count() ) );
1423}
1424
1426{
1427 if ( !mFaceIndexes.isEmpty() )
1428 {
1429 for ( int faceIndex : std::as_const( mFaceIndexes ) )
1430 {
1431 QgsMeshEditor::Edit edit;
1432 mMeshEditor->applySplit( edit, faceIndex );
1433 mEdits.append( edit );
1434 }
1435 mFaceIndexes.clear();
1436 }
1437 else
1438 {
1439 for ( QgsMeshEditor::Edit &edit : mEdits )
1440 mMeshEditor->applyEdit( edit );
1441 }
1442}
1443
1445 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1446 , mAdvancedEditing( advancdEdit )
1447{
1448 setText( advancdEdit->text() );
1449}
1450
1452{
1453 if ( mAdvancedEditing )
1454 {
1455 QgsMeshEditor::Edit edit;
1456 while ( !mAdvancedEditing->isFinished() )
1457 {
1458 mMeshEditor->applyAdvancedEdit( edit, mAdvancedEditing );
1459 mEdits.append( edit );
1460 }
1461
1462 mAdvancedEditing = nullptr;
1463 }
1464 else
1465 {
1466 for ( QgsMeshEditor::Edit &edit : mEdits )
1467 mMeshEditor->applyEdit( edit );
1468 }
1469}
1470
1472 : QgsMeshLayerUndoCommandMeshEdit( meshEditor )
1473 , mVertex( vertex )
1474 , mTolerance( tolerance )
1475{
1476 setText( QObject::tr( "Add vertex inside face with Delaunay refinement" ) );
1477}
1478
1480{
1481 if ( !mVertex.isEmpty() )
1482 {
1483 QgsMeshEditor::Edit edit;
1484
1485 mMeshEditor->applyAddVertex( edit, mVertex, mTolerance );
1486 mEdits.append( edit );
1487
1488 QList<std::pair<int, int>> sharedEdges = innerEdges( secondNeighboringTriangularFaces() );
1489
1490 for ( std::pair<int, int> edge : sharedEdges )
1491 {
1492 if ( mMeshEditor->edgeCanBeFlipped( edge.first, edge.second ) && !mMeshEditor->topologicalMesh().delaunayConditionForEdge( edge.first, edge.second ) )
1493 {
1494 mMeshEditor->applyFlipEdge( edit, edge.first, edge.second );
1495 mEdits.append( edit );
1496 }
1497 }
1498
1499 mVertex = QgsMeshVertex();
1500 }
1501 else
1502 {
1503 for ( QgsMeshEditor::Edit &edit : mEdits )
1504 mMeshEditor->applyEdit( edit );
1505 }
1506}
1507
1508QSet<int> QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement::secondNeighboringTriangularFaces()
1509{
1510 const int vIndex = mMeshEditor->topologicalMesh().mesh()->vertexCount() - 1;
1511 const QList<int> firstNeighborFaces = mMeshEditor->topologicalMesh().facesAroundVertex( vIndex );
1512 QSet<int> firstNeighborVertices;
1513 for ( int face : firstNeighborFaces )
1514 {
1515 const QgsMeshFace meshFace = mMeshEditor->topologicalMesh().mesh()->face( face );
1516 for ( int vertex : meshFace )
1517 {
1518 firstNeighborVertices.insert( vertex );
1519 }
1520 }
1521
1522 QSet<int> secondNeighboringFaces;
1523 for ( int vertex : firstNeighborVertices )
1524 {
1525 const QList<int> faces = mMeshEditor->topologicalMesh().facesAroundVertex( vertex );
1526 for ( int face : faces )
1527 {
1528 if ( mMeshEditor->topologicalMesh().mesh()->face( face ).count() == 3 )
1529 secondNeighboringFaces.insert( face );
1530 }
1531 }
1532 return secondNeighboringFaces;
1533}
1534
1535QList<std::pair<int, int>> QgsMeshLayerUndoCommandAddVertexInFaceWithDelaunayRefinement::innerEdges( const QSet<int> &faces )
1536{
1537 // edges and number of their occurrence in triangular faces
1538 QMap<std::pair<int, int>, int> edges;
1539
1540 for ( int faceIndex : faces )
1541 {
1542 const QgsMeshFace face = mMeshEditor->topologicalMesh().mesh()->face( faceIndex );
1543
1544 for ( int i = 0; i < face.size(); i++ )
1545 {
1546 int next = i + 1;
1547 if ( next == face.size() )
1548 {
1549 next = 0;
1550 }
1551
1552 int minIndex = std::min( face.at( i ), face.at( next ) );
1553 int maxIndex = std::max( face.at( i ), face.at( next ) );
1554 std::pair<int, int> edge = std::pair<int, int>( minIndex, maxIndex );
1555
1556 int count = 1;
1557 if ( edges.contains( edge ) )
1558 {
1559 count = edges.take( edge );
1560 count++;
1561 }
1562
1563 edges.insert( edge, count );
1564 }
1565 }
1566
1567 QList<std::pair<int, int>> sharedEdges;
1568
1569 for ( auto it = edges.begin(); it != edges.end(); it++ )
1570 {
1571 if ( it.value() == 2 )
1572 {
1573 sharedEdges.push_back( it.key() );
1574 }
1575 }
1576
1577 return sharedEdges;
1578}
MeshEditingErrorType
Type of error that can occur during mesh frame editing.
Definition qgis.h:1738
@ TooManyVerticesInFace
A face has more vertices than the maximum number supported per face.
Definition qgis.h:1741
@ InvalidFace
An error occurs due to an invalid face (for example, vertex indexes are unordered).
Definition qgis.h:1740
@ UniqueSharedVertex
A least two faces share only one vertices.
Definition qgis.h:1743
@ ManifoldFace
ManifoldFace.
Definition qgis.h:1745
@ InvalidVertex
An error occurs due to an invalid vertex (for example, vertex index is out of range the available ver...
Definition qgis.h:1744
@ FlatFace
A flat face is present.
Definition qgis.h:1742
QgsVertexIterator vertices() const
Returns a read-only, Java-style iterator for traversal of vertices of all the geometry,...
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.
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 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,...
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...
std::unique_ptr< 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 QgsMeshLayerUndoCommandChangeZValue
QgsTriangularMesh * triangularMesh()
Returns a pointer to the triangular mesh.
bool fixError(const QgsMeshEditingError &error)
Tries to fix the topological error in the mesh.
friend class QgsMeshLayerUndoCommandRemoveFaces
QgsTopologicalMesh & topologicalMesh()
Returns a reference to the topological mesh.
~QgsMeshEditor() override
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,...
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:62
double distance(double x, double y) const
Returns the distance between this point and a specified x, y coordinate.
Definition qgspointxy.h:209
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
double x
Definition qgspoint.h:56
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:766
double y
Definition qgspoint.h:57
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.
QList< double > newVerticesZValues() const
Returns the new Z values of vertices that have changed their coordinates.
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.
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...
static QgsTopologicalMesh createTopologicalMesh(QgsMesh *mesh, int maxVerticesPerFace, QgsMeshEditingError &error)
Creates a topologicaly consistent mesh with mesh, this static method modifies mesh to be topological ...
static QgsMeshEditingError counterClockwiseFaces(QgsMeshFace &face, QgsMesh *mesh)
Checks the topology of the face and sets it counter clockwise if necessary.
void applyChanges(const Changes &changes)
Applies the changes.
static TopologicalFaces createNewTopologicalFaces(const QVector< QgsMeshFace > &faces, bool uniqueSharedVertexAllowed, QgsMeshEditingError &error)
Creates new topological faces that are not yet included in the mesh.
A triangular/derived mesh with vertices in map coordinates.
void applyChanges(const Changes &changes)
Applies the changes on the triangular mesh (see Changes).
QVector< int > QgsMeshFace
List of vertex indexes.
QgsPoint QgsMeshVertex
xyz coords of vertex
Mesh - vertices, edges and faces.