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