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