Skip to content

Commit

Permalink
[mesh] mesh frame editing part 3 - Advanced editing (#44169)
Browse files Browse the repository at this point in the history
[mesh] [feature] Delaunay triangulation and face refinement for advanced mesh editing tools
  • Loading branch information
vcloarec committed Jul 19, 2021
1 parent 4bb7f82 commit 8cd8bec
Show file tree
Hide file tree
Showing 22 changed files with 1,809 additions and 230 deletions.
30 changes: 30 additions & 0 deletions python/analysis/auto_generated/mesh/qgsmeshtriangulation.sip.in
Expand Up @@ -58,6 +58,13 @@ Adds break lines from a vector layer, return ``True`` if successful.
if the feature iterator contains only point geometries, the vertices will be added only without treating them as breaklines
%End

int addVertex( const QgsPoint &vertex );
%Docstring
Adds a new vertex in the triangulation and returns the index of the new vertex

.. versionadded:: 3.22
%End

QgsMesh triangulatedMesh( QgsFeedback *feedback = 0 ) const;
%Docstring
Returns the triangulated mesh
Expand Down Expand Up @@ -111,6 +118,29 @@ Constructor
QgsMeshZValueDatasetGroup( const QgsMeshZValueDatasetGroup &rhs );
};


class QgsMeshEditingDelaunayTriangulation : QgsMeshAdvancedEditing
{
%Docstring(signature="appended")

Class that can be used with :py:func:`QgsMeshEditor.advancedEdit()` to add triangle faces to a mesh created by
a Delaunay triangulation on provided existing vertex.

.. versionadded:: 3.22
%End

%TypeHeaderCode
#include "qgsmeshtriangulation.h"
%End
public:

QgsMeshEditingDelaunayTriangulation();
%Docstring
Constructor
%End

};

/************************************************************************
* This file has been generated automatically from *
* *
Expand Down
3 changes: 2 additions & 1 deletion python/core/auto_additions/qgis.py
Expand Up @@ -406,7 +406,8 @@
Qgis.MeshEditingErrorType.FlatFace.__doc__ = "A flat face is present"
Qgis.MeshEditingErrorType.UniqueSharedVertex.__doc__ = "A least two faces share only one vertices"
Qgis.MeshEditingErrorType.InvalidVertex.__doc__ = "An error occurs due to an invalid vertex (for example, vertex index is out of range the available vertex)"
Qgis.MeshEditingErrorType.__doc__ = 'Type of error that can occur during mesh frame editing.\n\n.. versionadded:: 3.22\n\n' + '* ``NoError``: ' + Qgis.MeshEditingErrorType.NoError.__doc__ + '\n' + '* ``InvalidFace``: ' + Qgis.MeshEditingErrorType.InvalidFace.__doc__ + '\n' + '* ``TooManyVerticesInFace``: ' + Qgis.MeshEditingErrorType.TooManyVerticesInFace.__doc__ + '\n' + '* ``FlatFace``: ' + Qgis.MeshEditingErrorType.FlatFace.__doc__ + '\n' + '* ``UniqueSharedVertex``: ' + Qgis.MeshEditingErrorType.UniqueSharedVertex.__doc__ + '\n' + '* ``InvalidVertex``: ' + Qgis.MeshEditingErrorType.InvalidVertex.__doc__
Qgis.MeshEditingErrorType.ManifoldFace.__doc__ = "ManifoldFace"
Qgis.MeshEditingErrorType.__doc__ = 'Type of error that can occur during mesh frame editing.\n\n.. versionadded:: 3.22\n\n' + '* ``NoError``: ' + Qgis.MeshEditingErrorType.NoError.__doc__ + '\n' + '* ``InvalidFace``: ' + Qgis.MeshEditingErrorType.InvalidFace.__doc__ + '\n' + '* ``TooManyVerticesInFace``: ' + Qgis.MeshEditingErrorType.TooManyVerticesInFace.__doc__ + '\n' + '* ``FlatFace``: ' + Qgis.MeshEditingErrorType.FlatFace.__doc__ + '\n' + '* ``UniqueSharedVertex``: ' + Qgis.MeshEditingErrorType.UniqueSharedVertex.__doc__ + '\n' + '* ``InvalidVertex``: ' + Qgis.MeshEditingErrorType.InvalidVertex.__doc__ + '\n' + '* ``ManifoldFace``: ' + Qgis.MeshEditingErrorType.ManifoldFace.__doc__
# --
Qgis.MeshEditingErrorType.baseClass = Qgis
# monkey patching scoped based enum
Expand Down
92 changes: 92 additions & 0 deletions python/core/auto_generated/mesh/qgsmeshadvancedediting.sip.in
@@ -0,0 +1,92 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/mesh/qgsmeshadvancedediting.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/




class QgsMeshAdvancedEditing : protected QgsTopologicalMesh::Changes /Abstract/
{
%Docstring(signature="appended")

Abstract class that can be derived to implement advanced editing on mesh

To apply the advanced editing, a pointer to an instance of a derived class is passed
in the method :py:func:`QgsMeshEditor.advancedEdit()`.

.. versionadded:: 3.22
%End

%TypeHeaderCode
#include "qgsmeshadvancedediting.h"
%End
public:

QgsMeshAdvancedEditing();
%Docstring
Constructor
%End
virtual ~QgsMeshAdvancedEditing();

void setInputVertices( const QList<int> verticesIndexes );
%Docstring
Sets the input vertices indexes that will be used for the editing
%End

void setInputFaces( const QList<int> faceIndexes );
%Docstring
Sets the input faces indexes that will be used for the editing
%End

QString message() const;
%Docstring
Returns a message that can be provided by the advanced editing when applying is done
%End

void clear();
%Docstring
Removes all data provided to the editing or created by the editing
%End

protected:

};

class QgsMeshEditRefineFaces : QgsMeshAdvancedEditing
{
%Docstring(signature="appended")

Class that can do a refinement of faces of a mesh.
This refinement is operated only on faces with 3 or 4 vertices (triangles or quads) by adding a vertex on the middle of each refined face.
For quad faces, a vertex is added on the centroid of the original face.
New vertices Z value are interpolated between original vertices.
Original triangle faces are replaced by four triangles, and original quad faces are replaced by four quads.
Neighboring faces are triangulated to take account of the new vertex in the shared edge.

.. versionadded:: 3.22
%End

%TypeHeaderCode
#include "qgsmeshadvancedediting.h"
%End
public:

QgsMeshEditRefineFaces();
%Docstring
Constructor
%End

};


/************************************************************************
* This file has been generated automatically from *
* *
* src/core/mesh/qgsmeshadvancedediting.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
18 changes: 18 additions & 0 deletions python/core/auto_generated/mesh/qgsmesheditor.sip.in
Expand Up @@ -8,6 +8,8 @@





class QgsMeshEditingError
{
%Docstring(signature="appended")
Expand Down Expand Up @@ -68,6 +70,12 @@ Initialize the mesh editor and return errors if the internal native mesh have to
bool faceCanBeAdded( const QgsMeshFace &face );
%Docstring
Returns ``True`` if a ``face`` can be added to the mesh
%End

bool isFaceGeometricallyCompatible( const QgsMeshFace &face );
%Docstring
Returns ``True`` if the face does not intersect or contains any other elements (faces or vertices)
The topological compatibility is not checked
%End

QgsMeshEditingError addFace( const QVector<int> &vertexIndexes );
Expand Down Expand Up @@ -140,6 +148,11 @@ Changes the Z values of the vertices with indexes in ``vertices`` indexes with t
%Docstring
Changes the (X,Y) coordinates values of the vertices with indexes in ``vertices`` indexes with the values in ``newValues``.
The caller has the responsibility to check if changing the vertices coordinates does not lead to topological errors
%End

void advancedEdit( QgsMeshAdvancedEditing *editing );
%Docstring
Applies an advance editing on the edited mesh, see :py:class:`QgsMeshAdvancedEditing`
%End

void stopEditing();
Expand Down Expand Up @@ -171,6 +184,11 @@ Returns whether the vertex with index ``vertexIndex`` is on a boundary
bool isVertexFree( int vertexIndex ) const;
%Docstring
Returns whether the vertex with index ``vertexIndex`` is a free vertex
%End

bool checkConsistency() const;
%Docstring
Return ``True`` if the edited mesh is consistent
%End

signals:
Expand Down
1 change: 1 addition & 0 deletions python/core/auto_generated/qgis.sip.in
Expand Up @@ -303,6 +303,7 @@ The development version
FlatFace,
UniqueSharedVertex,
InvalidVertex,
ManifoldFace,
};

enum class FilePathType
Expand Down
1 change: 1 addition & 0 deletions python/core/core_auto.sip
Expand Up @@ -425,6 +425,7 @@
%Include auto_generated/mesh/qgsmeshtimesettings.sip
%Include auto_generated/mesh/qgsmeshtracerenderer.sip
%Include auto_generated/mesh/qgsmeshcalculator.sip
%Include auto_generated/mesh/qgsmeshadvancedediting.sip
%Include auto_generated/pointcloud/qgspointcloudattribute.sip
%Include auto_generated/pointcloud/qgspointcloudattributebyramprenderer.sip
%Include auto_generated/pointcloud/qgspointcloudattributemodel.sip
Expand Down
110 changes: 110 additions & 0 deletions src/analysis/mesh/qgsmeshtriangulation.cpp
Expand Up @@ -25,6 +25,7 @@
#include "qgsmulticurve.h"
#include "qgsfeedback.h"
#include "qgslogger.h"
#include "qgsmesheditor.h"

QgsMeshTriangulation::QgsMeshTriangulation(): QObject()
{
Expand Down Expand Up @@ -94,6 +95,11 @@ bool QgsMeshTriangulation::addBreakLines( QgsFeatureIterator &lineFeatureIterato
return true;
}

int QgsMeshTriangulation::addVertex( const QgsPoint &vertex )
{
return mTriangulation->addPoint( vertex );
}

QgsMesh QgsMeshTriangulation::triangulatedMesh( QgsFeedback *feedback ) const
{
return mTriangulation->triangulationToMesh( feedback );
Expand Down Expand Up @@ -328,3 +334,107 @@ int QgsMeshZValueDataset::valuesCount() const
{
return mMesh.vertexCount();
}

QgsMeshEditingDelaunayTriangulation::QgsMeshEditingDelaunayTriangulation() = default;

QgsTopologicalMesh::Changes QgsMeshEditingDelaunayTriangulation::apply( QgsMeshEditor *meshEditor )
{
//use only vertices that are on boundary or free, if boundary
QList<int> vertexIndextoTriangulate;

QList<int> removedVerticesFromTriangulation;

for ( const int vertexIndex : std::as_const( mInputVertices ) )
{
if ( meshEditor->isVertexFree( vertexIndex ) || meshEditor->isVertexOnBoundary( vertexIndex ) )
vertexIndextoTriangulate.append( vertexIndex );
else
removedVerticesFromTriangulation.append( vertexIndex );
}

bool triangulationReady = false;
bool giveUp = false;
QgsTopologicalMesh::TopologicalFaces topologicFaces;

while ( !triangulationReady )
{
QgsMeshTriangulation triangulation;

QVector<int> triangulationVertexToMeshVertex( vertexIndextoTriangulate.count() );
const QgsMesh *destinationMesh = meshEditor->topologicalMesh().mesh();

for ( int i = 0; i < vertexIndextoTriangulate.count(); ++i )
{
triangulationVertexToMeshVertex[i] = vertexIndextoTriangulate.at( i );
triangulation.addVertex( destinationMesh->vertices.at( vertexIndextoTriangulate.at( i ) ) );
}

QgsMesh resultingTriangulation = triangulation.triangulatedMesh();

//Transform the new mesh triangulation to destination mesh faces
QVector<QgsMeshFace> rawDestinationFaces = resultingTriangulation.faces;

for ( QgsMeshFace &destinationFace : rawDestinationFaces )
{
for ( int &vertexIndex : destinationFace )
vertexIndex = triangulationVertexToMeshVertex[vertexIndex];
}

//The new triangulation may contains faces that intersect existing faces, we need to remove them
QVector<QgsMeshFace> destinationFaces;
for ( const QgsMeshFace &face : rawDestinationFaces )
{
if ( meshEditor->isFaceGeometricallyCompatible( face ) )
destinationFaces.append( face );
}

bool facesReady = false;
QgsMeshEditingError previousError;
while ( !facesReady && !giveUp )
{
QgsMeshEditingError error;
topologicFaces = meshEditor->topologicalMesh().createNewTopologicalFaces( destinationFaces, true, error );

if ( error == QgsMeshEditingError() )
error = meshEditor->topologicalMesh().canFacesBeAdded( topologicFaces );

switch ( error.errorType )
{
case Qgis::MeshEditingErrorType::NoError:
facesReady = true;
triangulationReady = true;
break;
case Qgis::MeshEditingErrorType::InvalidFace:
case Qgis::MeshEditingErrorType::FlatFace:
case Qgis::MeshEditingErrorType::TooManyVerticesInFace:
case Qgis::MeshEditingErrorType::ManifoldFace:
if ( error.elementIndex != -1 )
destinationFaces.remove( error.elementIndex );
else
giveUp = true; //we don't know what happens, better to give up
break;
case Qgis::MeshEditingErrorType::InvalidVertex:
case Qgis::MeshEditingErrorType::UniqueSharedVertex:
facesReady = true;
if ( error.elementIndex != -1 )
{
removedVerticesFromTriangulation.append( error.elementIndex );
vertexIndextoTriangulate.removeOne( error.elementIndex );
}
else
giveUp = true; //we don't know what happens, better to give up
break;
}
}
}

Q_ASSERT( meshEditor->topologicalMesh().checkConsistency() == QgsMeshEditingError() );

mMessage = QObject::tr( "%1 vertices have not been included in the triangulation" ).arg( removedVerticesFromTriangulation.count() );

if ( triangulationReady && !giveUp )
return meshEditor->topologicalMesh().addFaces( topologicFaces );
else
return QgsTopologicalMesh::Changes();
}

29 changes: 29 additions & 0 deletions src/analysis/mesh/qgsmeshtriangulation.h
Expand Up @@ -19,6 +19,7 @@

#include "qgscoordinatereferencesystem.h"
#include "qgsmeshdataprovider.h"
#include "qgsmeshadvancedediting.h"

#include "qgis_analysis.h"

Expand Down Expand Up @@ -72,6 +73,13 @@ class ANALYSIS_EXPORT QgsMeshTriangulation : public QObject
*/
bool addBreakLines( QgsFeatureIterator &lineFeatureIterator, int valueAttribute, const QgsCoordinateTransform &transformContext, QgsFeedback *feedback = nullptr, long featureCount = 1 );

/**
* Adds a new vertex in the triangulation and returns the index of the new vertex
*
* \since QGIS 3.22
*/
int addVertex( const QgsPoint &vertex );

//! Returns the triangulated mesh
QgsMesh triangulatedMesh( QgsFeedback *feedback = nullptr ) const;

Expand Down Expand Up @@ -156,4 +164,25 @@ class ANALYSIS_EXPORT QgsMeshZValueDatasetGroup: public QgsMeshDatasetGroup
std::unique_ptr<QgsMeshZValueDataset> mDataset;
};


/**
* \ingroup analysis
* \class QgsMeshEditingDelaunayTriangulation
*
* \brief Class that can be used with QgsMeshEditor::advancedEdit() to add triangle faces to a mesh created by
* a Delaunay triangulation on provided existing vertex.
*
* \since QGIS 3.22
*/
class ANALYSIS_EXPORT QgsMeshEditingDelaunayTriangulation : public QgsMeshAdvancedEditing
{
public:

//! Constructor
QgsMeshEditingDelaunayTriangulation();

private:
QgsTopologicalMesh::Changes apply( QgsMeshEditor *meshEditor ) override;
};

#endif // QGSMESHTRIANGULATION_H

0 comments on commit 8cd8bec

Please sign in to comment.