mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-04 00:04:03 -04:00
Mesh transform by expression (#44873)
[feature] [mesh] allows the user to make geom transformation of mesh frame by changing the vertices coordinates by expression. Each coordinates (X,Y,Z) of selected vertices can be calculated with an expression allowing transformation of the mesh while the mesh is still valid. Co-authored-by: Harrissou Sant-anna <delazj@gmail.com>
This commit is contained in:
parent
8b18399704
commit
9ce295ebbd
@ -925,6 +925,7 @@
|
||||
<file>themes/default/mActionMeshDigitizing.svg</file>
|
||||
<file>themes/default/mActionMeshSelectPolygon.svg</file>
|
||||
<file>themes/default/mActionNewMeshLayer.svg</file>
|
||||
<file>themes/default/mActionMeshTransformByExpression.svg</file>
|
||||
<file>themes/default/mIconGeometryCollectionLayer.svg</file>
|
||||
<file>themes/default/mIconGps.svg</file>
|
||||
<file>themes/default/mActionNewGpx.svg</file>
|
||||
|
@ -0,0 +1 @@
|
||||
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#5b6775" stroke-linecap="round" stroke-width="1.001834"><path d="m21.881899 5.6699901-9.359749 1.0247509m4.044093-5.1770872 1.271563 9.3294242m-1.329362-9.7534888 5.798899 4.5298213-4.411738 5.6477305-5.798898-4.5298209z"/><path d="m10.499931 14.707888-7.887379 7.548378m0-7.548378 7.887379 7.548378m-8.2458959-7.891486h8.6044139v8.234593h-8.6044139z"/><path d="m9.2712536 2.6065281-6.2013467 6.2974513m0-6.2974513 6.2013467 6.2974513m-6.4832259-6.5836989h6.7651061v6.8699459h-6.7651061z"/></g><g transform="translate(5.318556 -3.59995)"><path d="m-12.901812 19.194933c-1.92383-.683586-2.885744-1.718741-2.885742-3.105469-.000002-1.093739.50781-1.967761 1.523438-2.62207 1.025386-.654284 2.294916-.981432 3.808593-.981445 1.3769451.000013 2.470694.229505 3.2812505.688476.8105357.449232 1.2158087.991223 1.2158203 1.625977-.0000116.332042-.1269646.625011-.3808594.878906-.2539172.244151-.5468856.366221-.8789062.366211-.5468848.00001-.9961031-.380849-1.3476563-1.142578-.4882897-1.054675-1.2207109-1.582019-2.1972659-1.582031-.77149.000012-1.406255.253918-1.904297.761718-.498051.507824-.747074 1.215832-.74707 2.124024-.000004 1.787117.922847 2.680671 2.768555 2.680664.195305.000007.419914-.01952.6738279-.05859.4394451-.05859.7812417-.08788 1.0253906-.08789.595694.000008.8935452.170906.8935547.512696-.0000095.380866-.3027435.571295-.9082031.571289-.2148521.000006-.5371174-.03417-.9667969-.102539-.3222732-.05859-.5712962-.08788-.7470702-.08789-1.95313.000007-2.929692.996099-2.929688 2.988281-.000004.9668.258785 1.748049.776368 2.34375.517572.585939 1.240228.878907 2.167968.878907 1.1621017 0 1.9335853-.600585 2.3144535-1.801758.1953031-.634763.4101466-1.074216.6445312-1.31836.2441306-.244136.5663959-.366207.9667969-.366211.3320201.000004.6298714.122075.8935547.366211.2734255.234379.4101441.537113.4101562.908204-.0000121.888673-.4980585 1.625977-1.4941406 2.211914-.9961034.576171-2.2021568.864257-3.6181639.864257-1.55274 0-2.934575-.371093-4.145508-1.113281-1.201173-.742186-1.801759-1.733396-1.801758-2.973633-.000001-1.552729 1.196287-2.695306 3.588867-3.427734" fill="#5c3566" transform="translate(22.960894 .402232)"/><g fill="#fff"><path d="m8.8453703 15.335963c-1.8135886 1.95573-1.401671-.322256-.3145916-1.106748 1.1264312-.81289 1.7573183-.79349 1.7490763-.54418-.0096.290414-.8636237 1.035325-1.4344847 1.650928z" opacity=".5"/><path d="m8.2348719 21.645433c-1.8135886 1.95573-1.5022297.147018-.4151503-.637474 1.1264312-.81289 1.7573183-.79349 1.7490763-.54418-.0096.290414-.763065.566051-1.333926 1.181654z" opacity=".5"/><path d="m16.204463 23.831019c-1.74795 1.952651-1.613829.09846-.49057-.620714.858053-.549374 1.112902-.0745.49057.620714z" opacity=".5"/></g></g></svg>
|
After Width: | Height: | Size: 2.7 KiB |
@ -83,6 +83,69 @@ Constructor
|
||||
};
|
||||
|
||||
|
||||
class QgsMeshTransformVerticesByExpression : QgsMeshAdvancedEditing
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
|
||||
Class that can transform vertices of a mesh by expression
|
||||
|
||||
Each coordinates are associated with an expression that can be defined with function
|
||||
returning the current coordinates (see :py:func:`~QgsMeshEditRefineFaces.setExpressions`):
|
||||
|
||||
- $vertex_x
|
||||
- $vertex_y
|
||||
- $vertex_z
|
||||
|
||||
Example:
|
||||
Transposing a mesh and translate following axe X with a distance of 50 and increase the level of the mesh
|
||||
with an height of 80 when previous X coordinate is under 100 and de crease the level of 150 when X is under 100:
|
||||
|
||||
expressionX: "$vertex_y + 50"
|
||||
expressionY: "$vertex_x"
|
||||
expressionZ: "if( $vertex_x <= 100 , $vertex_z + 80 , $vertex_z - 150)"
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsmeshadvancedediting.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsMeshTransformVerticesByExpression();
|
||||
%Docstring
|
||||
Constructor
|
||||
%End
|
||||
|
||||
void setExpressions( const QString &expressionX, const QString &expressionY, const QString &expressionZ );
|
||||
%Docstring
|
||||
Sets the expressions for the coordinates transformation.
|
||||
|
||||
.. note::
|
||||
|
||||
Expressions are optional for each coordinate, the coordinate will not be transformed if the string is void.
|
||||
%End
|
||||
|
||||
bool calculate( QgsMeshLayer *layer );
|
||||
%Docstring
|
||||
Calculates the transformed vertices of the mesh ``layer``, returns ``False`` if this leads to topological or geometrical errors.
|
||||
The mesh layer must be in edit mode.
|
||||
|
||||
.. note::
|
||||
|
||||
this method not apply new vertices to the mesh layer but only store the calculated transformation
|
||||
that can be apply later with :py:func:`QgsMeshEditor.advancedEdit()`
|
||||
%End
|
||||
|
||||
QgsMeshVertex transformedVertex( QgsMeshLayer *layer, int vertexIndex ) const;
|
||||
%Docstring
|
||||
Returns the transformed vertex from its index ``vertexIndex`` for the mesh ``layer``
|
||||
|
||||
If ``layer`` is not the same than the one used to make the calculation, this will create an undefined behavior
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
|
@ -171,7 +171,7 @@ Returns whether the mesh has been modified
|
||||
%End
|
||||
|
||||
|
||||
QList<int> freeVerticesIndexes();
|
||||
QList<int> freeVerticesIndexes() const;
|
||||
%Docstring
|
||||
Returns all the free vertices indexes
|
||||
%End
|
||||
|
@ -248,6 +248,7 @@ set(QGIS_APP_SRCS
|
||||
mesh/qgsmeshcalculatordialog.cpp
|
||||
mesh/qgsnewmeshlayerdialog.cpp
|
||||
mesh/qgsmaptooleditmeshframe.cpp
|
||||
mesh/qgsmeshtransformcoordinatesdockwidget.cpp
|
||||
)
|
||||
|
||||
if (WITH_SPATIALITE)
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include "qgsvertexmarker.h"
|
||||
#include "qgsguiutils.h"
|
||||
#include "qgsmeshtriangulation.h"
|
||||
#include "qgsmeshtransformcoordinatesdockwidget.h"
|
||||
|
||||
|
||||
QgsZValueWidget::QgsZValueWidget( const QString &label, QWidget *parent ): QWidget( parent )
|
||||
@ -90,11 +91,14 @@ QgsMapToolEditMeshFrame::QgsMapToolEditMeshFrame( QgsMapCanvas *canvas )
|
||||
: QgsMapToolAdvancedDigitizing( canvas, QgisApp::instance()->cadDockWidget() )
|
||||
, mSnapIndicator( new QgsSnapIndicator( canvas ) )
|
||||
{
|
||||
mActionDigitizing = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshDigitizing.svg" ) ), tr( "Digitize mesh elements" ) );
|
||||
mActionDigitizing = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshDigitizing.svg" ) ), tr( "Digitize mesh elements" ), this );
|
||||
mActionDigitizing->setCheckable( true );
|
||||
mActionSelectByPolygon = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshSelectPolygon.svg" ) ), tr( "Select mesh element by polygon" ) );
|
||||
mActionSelectByPolygon = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshSelectPolygon.svg" ) ), tr( "Select mesh element by polygon" ), this );
|
||||
mActionSelectByPolygon->setCheckable( true );
|
||||
|
||||
mActionTransformCoordinates = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshTransformByExpression.svg" ) ), tr( "Transform vertices coordinates" ), this );
|
||||
mActionTransformCoordinates->setCheckable( true );
|
||||
|
||||
mActionRemoveVerticesFillingHole = new QAction( this );
|
||||
mActionDelaunayTriangulation = new QAction( tr( "Delaunay triangulation with selected vertices" ), this );
|
||||
mActionFacesRefinement = new QAction( tr( "Refine current face" ), this );
|
||||
@ -149,6 +153,8 @@ QgsMapToolEditMeshFrame::QgsMapToolEditMeshFrame( QgsMapCanvas *canvas )
|
||||
}
|
||||
} );
|
||||
|
||||
connect( mActionTransformCoordinates, &QAction::triggered, this, &QgsMapToolEditMeshFrame::triggerTransformCoordinatesDockWidget );
|
||||
|
||||
setAutoSnapEnabled( true );
|
||||
}
|
||||
|
||||
@ -177,7 +183,8 @@ QList<QAction *> QgsMapToolEditMeshFrame::actions() const
|
||||
{
|
||||
return QList<QAction *>()
|
||||
<< mActionDigitizing
|
||||
<< mActionSelectByPolygon;
|
||||
<< mActionSelectByPolygon
|
||||
<< mActionTransformCoordinates;
|
||||
}
|
||||
|
||||
QList<QAction *> QgsMapToolEditMeshFrame::mapToolActions()
|
||||
@ -275,14 +282,6 @@ void QgsMapToolEditMeshFrame::initialize()
|
||||
if ( !mMovingFacesRubberband )
|
||||
mMovingFacesRubberband = createRubberBand( QgsWkbTypes::PolygonGeometry );
|
||||
|
||||
if ( !mMovingVerticesRubberband )
|
||||
mMovingVerticesRubberband = createRubberBand( QgsWkbTypes::PointGeometry );
|
||||
mMovingEdgesRubberband->setWidth( QgsGuiUtils::scaleIconSize( 2 ) );
|
||||
mMovingEdgesRubberband->setBrushStyle( Qt::NoBrush );
|
||||
mMovingEdgesRubberband->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
|
||||
mMovingEdgesRubberband->setVisible( false );
|
||||
mMovingEdgesRubberband->setZValue( 5 );
|
||||
|
||||
if ( !mFlipEdgeMarker )
|
||||
mFlipEdgeMarker = new QgsVertexMarker( canvas() );
|
||||
mFlipEdgeMarker->setIconType( QgsVertexMarker::ICON_CIRCLE );
|
||||
@ -552,13 +551,13 @@ void QgsMapToolEditMeshFrame::cadCanvasMoveEvent( QgsMapMouseEvent *e )
|
||||
{
|
||||
const QgsVector &translation = mapPoint - mStartMovingPoint;
|
||||
mMovingEdgesRubberband->reset( QgsWkbTypes::LineGeometry );
|
||||
mMovingVerticesRubberband->reset( QgsWkbTypes::PointGeometry );
|
||||
mMovingFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
|
||||
QgsGeometry faceGeom = mSelectedFacesRubberband->asGeometry();
|
||||
faceGeom.translate( translation.x(), translation.y() );
|
||||
mMovingFacesRubberband->setToGeometry( faceGeom );
|
||||
QgsGeometry movingFacesGeometry = mSelectedFacesRubberband->asGeometry();
|
||||
movingFacesGeometry.translate( translation.x(), translation.y() );
|
||||
mMovingFacesRubberband->setToGeometry( movingFacesGeometry );
|
||||
|
||||
QSet<int> borderMovingFace;
|
||||
|
||||
mIsMovingAllowed = true;
|
||||
for ( QMap<int, SelectedVertexData>::const_iterator it = mSelectedVertices.constBegin(); it != mSelectedVertices.constEnd(); ++it )
|
||||
{
|
||||
const QgsPointXY &point1 = mapVertexXY( it.key() ) + translation;
|
||||
@ -568,32 +567,46 @@ void QgsMapToolEditMeshFrame::cadCanvasMoveEvent( QgsMapMouseEvent *e )
|
||||
const QgsPointXY point2 = mapVertexXY( vertexData.meshFixedEdges.at( i ).second );
|
||||
QgsGeometry edge( new QgsLineString( {point1, point2} ) );
|
||||
mMovingEdgesRubberband->addGeometry( edge );
|
||||
if ( mIsMovingAllowed )
|
||||
mIsMovingAllowed &= testBorderMovingFace( nativeFace( vertexData.meshFixedEdges.at( i ).first ), translation );
|
||||
int associateFace = vertexData.meshFixedEdges.at( i ).first;
|
||||
if ( associateFace != -1 )
|
||||
borderMovingFace.insert( associateFace );
|
||||
}
|
||||
|
||||
for ( int i = 0; i < vertexData.selectedEdges.count(); ++i )
|
||||
for ( int i = 0; i < vertexData.borderEdges.count(); ++i )
|
||||
{
|
||||
const QgsPointXY point2 = mapVertexXY( vertexData.selectedEdges.at( i ).second ) + translation;
|
||||
const QgsPointXY middlePoint( ( point1.x() + point2.x() ) / 2, ( point1.y() + point2.y() ) / 2 );
|
||||
if ( !faceGeom.contains( &middlePoint ) )
|
||||
{
|
||||
QgsGeometry edge( new QgsLineString( {point1, point2} ) );
|
||||
mMovingEdgesRubberband->addGeometry( edge );
|
||||
}
|
||||
const QgsPointXY point2 = mapVertexXY( vertexData.borderEdges.at( i ).second ) + translation;
|
||||
const QgsGeometry edge( new QgsLineString( {point1, point2} ) );
|
||||
mMovingEdgesRubberband->addGeometry( edge );
|
||||
}
|
||||
}
|
||||
|
||||
const QgsMeshVertex &mapPointInNativeCoordinate =
|
||||
mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( QgsMeshVertex( mapPoint.x(), mapPoint.y() ) );
|
||||
const QgsMeshVertex &startingPointInNativeCoordinate =
|
||||
mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( QgsMeshVertex( mStartMovingPoint.x(), mStartMovingPoint.y() ) );
|
||||
const QgsVector &translationInLayerCoordinate = mapPointInNativeCoordinate - startingPointInNativeCoordinate;
|
||||
|
||||
auto transformFunction = [translationInLayerCoordinate, this ]( int vi )-> const QgsMeshVertex
|
||||
{
|
||||
if ( mSelectedVertices.contains( vi ) )
|
||||
return mCurrentLayer->nativeMesh()->vertex( vi ) + translationInLayerCoordinate;
|
||||
else
|
||||
return mCurrentLayer->nativeMesh()->vertex( vi );
|
||||
};
|
||||
|
||||
// we test only the faces that are deformed on the border, moving and not deformed faces are tested later
|
||||
mIsMovingAllowed = mCurrentEditor->canBeTransformed( qgis::setToList( borderMovingFace ), transformFunction );
|
||||
|
||||
if ( mIsMovingAllowed )
|
||||
{
|
||||
//to finish test if the polygons formed by the moving faces contains something else
|
||||
const QList<int> &faceIndexesIntersect = mCurrentLayer->triangularMesh()->nativeFaceIndexForRectangle( faceGeom.boundingBox() );
|
||||
const QList<int> &faceIndexesIntersect = mCurrentLayer->triangularMesh()->nativeFaceIndexForRectangle( movingFacesGeometry.boundingBox() );
|
||||
for ( const int faceIndex : faceIndexesIntersect )
|
||||
{
|
||||
if ( mConcernedFaceBySelection.contains( faceIndex ) )
|
||||
continue;
|
||||
const QgsGeometry otherFaceGeom( new QgsPolygon( new QgsLineString( nativeFaceGeometry( faceIndex ) ) ) );
|
||||
mIsMovingAllowed &= !faceGeom.intersects( otherFaceGeom );
|
||||
mIsMovingAllowed &= !movingFacesGeometry.intersects( otherFaceGeom );
|
||||
if ( !mIsMovingAllowed )
|
||||
break;
|
||||
}
|
||||
@ -604,23 +617,14 @@ void QgsMapToolEditMeshFrame::cadCanvasMoveEvent( QgsMapMouseEvent *e )
|
||||
for ( const int vertexIndex : freeVerticesIndexes )
|
||||
{
|
||||
const QgsPointXY &pointInMap = mapVertexXY( vertexIndex );
|
||||
mIsMovingAllowed &= !faceGeom.contains( &pointInMap );
|
||||
mIsMovingAllowed &= !movingFacesGeometry.contains( &pointInMap );
|
||||
if ( !mIsMovingAllowed )
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( mIsMovingAllowed )
|
||||
{
|
||||
mMovingFacesRubberband->setFillColor( QColor( 0, 200, 0, 100 ) );
|
||||
mMovingEdgesRubberband->setColor( QColor( 0, 200, 0, 100 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
mMovingFacesRubberband->setFillColor( QColor( 200, 0, 0, 100 ) );
|
||||
mMovingEdgesRubberband->setColor( QColor( 200, 0, 0, 100 ) );
|
||||
}
|
||||
setMovingRubberBandValidity( mIsMovingAllowed );
|
||||
}
|
||||
break;
|
||||
case SelectingByPolygon:
|
||||
@ -751,11 +755,17 @@ void QgsMapToolEditMeshFrame::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
|
||||
const QList<int> verticesIndexes = mSelectedVertices.keys();
|
||||
QList<QgsPointXY> newPosition;
|
||||
newPosition.reserve( verticesIndexes.count() );
|
||||
QgsVector translation = e->mapPoint() - mStartMovingPoint;
|
||||
|
||||
const QgsMeshVertex &mapPointInNativeCoordinate =
|
||||
mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( QgsMeshVertex( mapPoint.x(), mapPoint.y() ) );
|
||||
const QgsMeshVertex &startingPointInNativeCoordinate =
|
||||
mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( QgsMeshVertex( mStartMovingPoint.x(), mStartMovingPoint.y() ) );
|
||||
const QgsVector &translationInLayerCoordinate = mapPointInNativeCoordinate - startingPointInNativeCoordinate;
|
||||
|
||||
const QgsMesh &mesh = *mCurrentLayer->nativeMesh();
|
||||
for ( int i = 0; i < verticesIndexes.count(); ++i )
|
||||
{
|
||||
newPosition.append( mapVertexXY( verticesIndexes.at( i ) ) + translation );
|
||||
}
|
||||
newPosition.append( QgsPointXY( mesh.vertex( verticesIndexes.at( i ) ) ) + translationInLayerCoordinate );
|
||||
|
||||
mKeepSelectionOnEdit = true;
|
||||
mCurrentEditor->changeXYValues( mSelectedVertices.keys(), newPosition );
|
||||
}
|
||||
@ -764,7 +774,6 @@ void QgsMapToolEditMeshFrame::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
|
||||
clearCanvasHelpers();
|
||||
mMovingEdgesRubberband->reset();
|
||||
mMovingFacesRubberband->reset();
|
||||
mMovingVerticesRubberband->reset();
|
||||
mCurrentState = Digitizing;
|
||||
break;
|
||||
case SelectingByPolygon:
|
||||
@ -815,92 +824,6 @@ void QgsMapToolEditMeshFrame::select( const QgsPointXY &mapPoint, Qt::KeyboardMo
|
||||
setSelectedVertices( QList<int>(), modifiers );
|
||||
}
|
||||
|
||||
bool QgsMapToolEditMeshFrame::testBorderMovingFace( const QgsMeshFace &borderMovingfaces, const QgsVector &translation ) const
|
||||
{
|
||||
int faceSize = borderMovingfaces.count();
|
||||
QgsPolygonXY polygon;
|
||||
QVector<QgsPointXY> points( faceSize );
|
||||
for ( int i = 0; i < faceSize; ++i )
|
||||
{
|
||||
int ip0 = borderMovingfaces[i];
|
||||
int ip1 = borderMovingfaces[( i + 1 ) % faceSize];
|
||||
int ip2 = borderMovingfaces[( i + 2 ) % faceSize];
|
||||
|
||||
QgsPointXY p0 = mCurrentLayer->nativeMesh()->vertices.at( ip0 ) + ( mSelectedVertices.contains( ip0 ) ? translation : QgsVector( 0, 0 ) );
|
||||
QgsPointXY p1 = mCurrentLayer->nativeMesh()->vertices.at( ip1 ) + ( mSelectedVertices.contains( ip1 ) ? translation : QgsVector( 0, 0 ) );
|
||||
QgsPointXY p2 = mCurrentLayer->nativeMesh()->vertices.at( ip2 ) + ( mSelectedVertices.contains( ip2 ) ? translation : QgsVector( 0, 0 ) );
|
||||
|
||||
double ux = p0.x() - p1.x();
|
||||
double uy = p0.y() - p1.y();
|
||||
double vx = p2.x() - p1.x();
|
||||
double vy = p2.y() - p1.y();
|
||||
|
||||
double crossProduct = ux * vy - uy * vx;
|
||||
if ( crossProduct >= 0 ) //if cross product>0, we have two edges clockwise
|
||||
return false;
|
||||
points[i] = p0;
|
||||
}
|
||||
polygon.append( points );
|
||||
|
||||
const QgsGeometry &deformedFace = QgsGeometry::fromPolygonXY( polygon );
|
||||
|
||||
// now test if the deformedface contain something else
|
||||
QList<int> otherFaceIndexes = mCurrentLayer->triangularMesh()->nativeFaceIndexForRectangle( deformedFace.boundingBox() );
|
||||
{
|
||||
for ( const int otherFaceIndex : otherFaceIndexes )
|
||||
{
|
||||
if ( mConcernedFaceBySelection.contains( otherFaceIndex ) )
|
||||
continue;
|
||||
|
||||
const QgsMeshFace &otherFace = nativeFace( otherFaceIndex );
|
||||
int existingFaceSize = otherFace.count();
|
||||
bool shareVertex = false;
|
||||
for ( int i = 0; i < existingFaceSize; ++i )
|
||||
{
|
||||
if ( borderMovingfaces.contains( otherFace.at( i ) ) )
|
||||
{
|
||||
shareVertex = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( shareVertex )
|
||||
{
|
||||
//only test the edge that not contain a shared vertex
|
||||
for ( int i = 0; i < existingFaceSize; ++i )
|
||||
{
|
||||
int index1 = otherFace.at( i );
|
||||
int index2 = otherFace.at( ( i + 1 ) % existingFaceSize );
|
||||
if ( ! borderMovingfaces.contains( index1 ) && !borderMovingfaces.contains( index2 ) )
|
||||
{
|
||||
const QgsPointXY &v1 = mapVertexXY( index1 );
|
||||
const QgsPointXY &v2 = mapVertexXY( index2 );
|
||||
QgsGeometry edgeGeom = QgsGeometry::fromPolylineXY( { v1, v2} );
|
||||
if ( deformedFace.intersects( edgeGeom ) )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const QgsGeometry existingFaceGeom( new QgsPolygon( new QgsLineString( nativeFaceGeometry( otherFaceIndex ) ) ) );
|
||||
if ( deformedFace.intersects( existingFaceGeom ) )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//finish with free vertices...
|
||||
const QList<int> freeVerticesIndex = mCurrentEditor->freeVerticesIndexes();
|
||||
for ( const int vertexIndex : freeVerticesIndex )
|
||||
{
|
||||
const QgsPointXY &mapPoint = mapVertexXY( vertexIndex );
|
||||
if ( deformedFace.contains( &mapPoint ) )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QgsMapToolEditMeshFrame::keyPressEvent( QKeyEvent *e )
|
||||
{
|
||||
bool consumned = false;
|
||||
@ -1062,6 +985,9 @@ void QgsMapToolEditMeshFrame::setCurrentLayer( QgsMapLayer *layer )
|
||||
if ( mCurrentLayer == meshLayer && mCurrentEditor != nullptr )
|
||||
return;
|
||||
|
||||
if ( mIsInitialized )
|
||||
clearSelection(); //TODO later: implement a mechanism to retrieve selection if the layer is again selected
|
||||
|
||||
if ( mCurrentLayer )
|
||||
{
|
||||
disconnect( mCurrentLayer, &QgsMeshLayer::editingStarted, this, &QgsMapToolEditMeshFrame::onEditingStarted );
|
||||
@ -1101,6 +1027,7 @@ void QgsMapToolEditMeshFrame::setCurrentLayer( QgsMapLayer *layer )
|
||||
else
|
||||
deactivate();
|
||||
|
||||
emit selectionChange( mCurrentLayer, mSelectedVertices.keys() );
|
||||
}
|
||||
|
||||
void QgsMapToolEditMeshFrame::onEdit()
|
||||
@ -1291,6 +1218,146 @@ void QgsMapToolEditMeshFrame::splitSelectedFaces()
|
||||
mCurrentEditor->splitFaces( {mCurrentFaceIndex} );
|
||||
}
|
||||
|
||||
void QgsMapToolEditMeshFrame::triggerTransformCoordinatesDockWidget( bool checked )
|
||||
{
|
||||
if ( !checked && mTransformDockWidget )
|
||||
{
|
||||
mTransformDockWidget->deleteLater();
|
||||
mTransformDockWidget = nullptr;
|
||||
return;
|
||||
}
|
||||
else if ( mTransformDockWidget )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
onEditingStarted();
|
||||
|
||||
mTransformDockWidget = new QgsMeshTransformCoordinatesDockWidget( QgisApp::instance() );
|
||||
mTransformDockWidget->setWindowTitle( tr( "Transform Mesh Vertices" ) );
|
||||
mTransformDockWidget->setObjectName( QStringLiteral( "TransformMeshVerticesDockWidget" ) );
|
||||
mTransformDockWidget->setAttribute( Qt::WA_DeleteOnClose );
|
||||
mTransformDockWidget->setInput( mCurrentLayer, mSelectedVertices.keys() );
|
||||
|
||||
if ( !QgisApp::instance()->restoreDockWidget( mTransformDockWidget ) )
|
||||
QgisApp::instance()->addDockWidget( Qt::LeftDockWidgetArea, mTransformDockWidget );
|
||||
else
|
||||
QgisApp::instance()->panelMenu()->addAction( mTransformDockWidget->toggleViewAction() );
|
||||
|
||||
mTransformDockWidget->show();
|
||||
|
||||
connect( this, &QgsMapToolEditMeshFrame::selectionChange, mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::setInput );
|
||||
|
||||
connect( mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::calculationUpdated, this, [this]
|
||||
{
|
||||
mMovingFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
|
||||
mMovingEdgesRubberband->reset( QgsWkbTypes::LineGeometry );
|
||||
setMovingRubberBandValidity( mTransformDockWidget->isResultValid() );
|
||||
|
||||
if ( !mCurrentLayer || !mCurrentEditor )
|
||||
return;
|
||||
|
||||
QList<int> faceList = qgis::setToList( mSelectedFaces );
|
||||
QgsGeometry faceGeometrie;
|
||||
if ( faceList.count() == 1 )
|
||||
{
|
||||
const QgsMeshFace &face = mCurrentLayer->nativeMesh()->face( faceList.at( 0 ) );
|
||||
const int faceSize = face.size();
|
||||
QVector<QgsPointXY> faceVertices( faceSize );
|
||||
for ( int j = 0; j < faceSize; ++j )
|
||||
faceVertices[j] = mTransformDockWidget->transformedVertex( face.at( j ) );
|
||||
|
||||
faceGeometrie = QgsGeometry::fromPolygonXY( {faceVertices} );
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unique_ptr<QgsGeometryEngine> geomEngine( QgsGeometry::createGeometryEngine( faceGeometrie.constGet() ) );
|
||||
geomEngine->prepareGeometry();
|
||||
QVector<QgsGeometry> faces( mSelectedFaces.count() );
|
||||
for ( int i = 0; i < faceList.count(); ++i )
|
||||
{
|
||||
const QgsMeshFace &face = mCurrentLayer->nativeMesh()->face( faceList.at( i ) );
|
||||
const int faceSize = face.size();
|
||||
QVector<QgsPointXY> faceVertices( faceSize );
|
||||
for ( int j = 0; j < faceSize; ++j )
|
||||
faceVertices[j] = mTransformDockWidget->transformedVertex( face.at( j ) );
|
||||
|
||||
faces[i] = QgsGeometry::fromPolygonXY( {faceVertices} );
|
||||
}
|
||||
QString error;
|
||||
faceGeometrie = QgsGeometry( geomEngine->combine( faces, &error ) );
|
||||
}
|
||||
|
||||
QSet<int> borderMovingFace;
|
||||
QgsGeometry edgesGeom = QgsGeometry::fromMultiPolylineXY( QgsMultiPolylineXY() );
|
||||
for ( QMap<int, SelectedVertexData>::const_iterator it = mSelectedVertices.constBegin(); it != mSelectedVertices.constEnd(); ++it )
|
||||
{
|
||||
const QgsPointXY &point1 = mTransformDockWidget->transformedVertex( it.key() ) ;
|
||||
const SelectedVertexData &vertexData = it.value();
|
||||
for ( int i = 0; i < vertexData.meshFixedEdges.count(); ++i )
|
||||
{
|
||||
const QgsPointXY point2 = mTransformDockWidget->transformedVertex( vertexData.meshFixedEdges.at( i ).second );
|
||||
QgsGeometry edge( new QgsLineString( {point1, point2} ) );
|
||||
edgesGeom.addPart( edge );
|
||||
int associateFace = vertexData.meshFixedEdges.at( i ).first;
|
||||
if ( associateFace != -1 )
|
||||
borderMovingFace.insert( associateFace );
|
||||
}
|
||||
|
||||
for ( int i = 0; i < vertexData.borderEdges.count(); ++i )
|
||||
{
|
||||
const QgsPointXY point2 = mTransformDockWidget->transformedVertex( vertexData.borderEdges.at( i ).second );
|
||||
const QgsGeometry edge( new QgsLineString( {point1, point2} ) );
|
||||
edgesGeom.addPart( edge );
|
||||
}
|
||||
}
|
||||
|
||||
QgsCoordinateTransform coordinateTransform( mCurrentLayer->crs(), canvas()->mapSettings().destinationCrs(), QgsProject::instance() );
|
||||
|
||||
try
|
||||
{
|
||||
faceGeometrie.transform( coordinateTransform );
|
||||
}
|
||||
catch ( QgsCsException & )
|
||||
{}
|
||||
|
||||
try
|
||||
{
|
||||
edgesGeom.transform( coordinateTransform );
|
||||
}
|
||||
catch ( QgsCsException & )
|
||||
{}
|
||||
|
||||
mMovingFacesRubberband->setToGeometry( faceGeometrie );
|
||||
mMovingEdgesRubberband->setToGeometry( edgesGeom );
|
||||
setMovingRubberBandValidity( mTransformDockWidget->isResultValid() );
|
||||
} );
|
||||
|
||||
connect( mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::aboutToBeApplied, this, [this]
|
||||
{
|
||||
mKeepSelectionOnEdit = true;
|
||||
} );
|
||||
|
||||
connect( mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::applied, this, [this]
|
||||
{
|
||||
mTransformDockWidget->setInput( mCurrentLayer, mSelectedVertices.keys() );
|
||||
updateSelectecVerticesMarker();
|
||||
prepareSelection();
|
||||
} );
|
||||
|
||||
connect( mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::destroyed, this, [this]
|
||||
{
|
||||
mTransformDockWidget = nullptr;
|
||||
mActionTransformCoordinates->setChecked( false );
|
||||
if ( !mIsInitialized )
|
||||
return;
|
||||
mMovingFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
|
||||
mMovingEdgesRubberband->reset( QgsWkbTypes::LineGeometry );
|
||||
setMovingRubberBandValidity( false );
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
void QgsMapToolEditMeshFrame::selectInGeometry( const QgsGeometry &geometry, Qt::KeyboardModifiers modifiers )
|
||||
{
|
||||
if ( mCurrentLayer.isNull() || !mCurrentLayer->triangularMesh() || mCurrentEditor.isNull() )
|
||||
@ -1355,7 +1422,6 @@ void QgsMapToolEditMeshFrame::applyZValueOnSelectedVertices()
|
||||
void QgsMapToolEditMeshFrame::prepareSelection()
|
||||
{
|
||||
mConcernedFaceBySelection.clear();
|
||||
QSet<int> borderSelectionVertices;
|
||||
QMap<int, SelectedVertexData> movingVertices;
|
||||
|
||||
// search for moving edges and mesh fixed edges
|
||||
@ -1364,25 +1430,22 @@ void QgsMapToolEditMeshFrame::prepareSelection()
|
||||
SelectedVertexData &vertexData = it.value();
|
||||
int vertexIndex = it.key();
|
||||
|
||||
vertexData.selectedEdges.clear();
|
||||
vertexData.meshFixedEdges.clear();
|
||||
QgsMeshVertexCirculator circulator = mCurrentEditor->vertexCirculator( vertexIndex );
|
||||
|
||||
if ( !circulator.isValid() )
|
||||
continue;
|
||||
|
||||
|
||||
circulator.goBoundaryClockwise();
|
||||
int firstface = circulator.currentFaceIndex();
|
||||
do
|
||||
{
|
||||
int oppositeVertex = circulator.oppositeVertexClockwise();
|
||||
if ( mSelectedVertices.contains( oppositeVertex ) )
|
||||
vertexData.selectedEdges.append( {circulator.currentFaceIndex(), oppositeVertex} );
|
||||
vertexData.borderEdges.append( {circulator.currentFaceIndex(), oppositeVertex} );
|
||||
else
|
||||
{
|
||||
vertexData.meshFixedEdges.append( {circulator.currentFaceIndex(), oppositeVertex} );
|
||||
borderSelectionVertices.insert( vertexIndex );
|
||||
}
|
||||
|
||||
mConcernedFaceBySelection.insert( circulator.currentFaceIndex() );
|
||||
}
|
||||
@ -1393,11 +1456,9 @@ void QgsMapToolEditMeshFrame::prepareSelection()
|
||||
circulator.turnClockwise();
|
||||
int oppositeVertex = circulator.oppositeVertexCounterClockwise();
|
||||
if ( mSelectedVertices.contains( oppositeVertex ) )
|
||||
vertexData.selectedEdges.append( {-1, oppositeVertex} );
|
||||
vertexData.borderEdges.append( {-1, oppositeVertex} );
|
||||
else
|
||||
vertexData.meshFixedEdges.append( {-1, oppositeVertex} );
|
||||
|
||||
borderSelectionVertices.insert( vertexIndex );
|
||||
}
|
||||
}
|
||||
|
||||
@ -1414,9 +1475,24 @@ void QgsMapToolEditMeshFrame::prepareSelection()
|
||||
}
|
||||
}
|
||||
|
||||
// here, we search for border edges that have associate face in the selection and remove it
|
||||
for ( QMap<int, SelectedVertexData>::iterator it = mSelectedVertices.begin(); it != mSelectedVertices.end(); ++it )
|
||||
{
|
||||
SelectedVertexData &vertexData = it.value();
|
||||
int i = 0;
|
||||
while ( i < vertexData.borderEdges.count() )
|
||||
{
|
||||
int associateFace = vertexData.borderEdges.at( i ).first;
|
||||
if ( associateFace == -1 || mSelectedFaces.contains( associateFace ) )
|
||||
vertexData.borderEdges.removeAt( i );
|
||||
else
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !mSelectedFaces.isEmpty() )
|
||||
{
|
||||
QList<int> faceList = mSelectedFaces.values();
|
||||
const QList<int> faceList = qgis::setToList( mSelectedFaces );
|
||||
QgsGeometry faceGeometrie( new QgsPolygon( new QgsLineString( nativeFaceGeometry( faceList.at( 0 ) ) ) ) );
|
||||
if ( mSelectedFaces.count() == 1 )
|
||||
{
|
||||
@ -1428,10 +1504,10 @@ void QgsMapToolEditMeshFrame::prepareSelection()
|
||||
geomEngine->prepareGeometry();
|
||||
|
||||
QVector<QgsGeometry> otherFaces( mSelectedFaces.count() );
|
||||
for ( int i = 0; i < mSelectedFaces.count(); ++i )
|
||||
for ( int i = 0; i < faceList.count(); ++i )
|
||||
otherFaces[i] = QgsGeometry( new QgsPolygon( new QgsLineString( nativeFaceGeometry( faceList.at( i ) ) ) ) );
|
||||
QString error;
|
||||
QgsGeometry allFaces( geomEngine->combine( otherFaces, &error ) );
|
||||
const QgsGeometry allFaces( geomEngine->combine( otherFaces, &error ) );
|
||||
mSelectedFacesRubberband->setToGeometry( allFaces );
|
||||
}
|
||||
|
||||
@ -1485,6 +1561,8 @@ void QgsMapToolEditMeshFrame::prepareSelection()
|
||||
mActionSplitFaces->setText( tr( "Split %1 selected faces" ).arg( mSplittableFaceCount ) );
|
||||
else
|
||||
mActionSplitFaces->setText( tr( "Split current face" ) );
|
||||
|
||||
emit selectionChange( mCurrentLayer, mSelectedVertices.keys() );
|
||||
}
|
||||
|
||||
void QgsMapToolEditMeshFrame::updateSelectecVerticesMarker()
|
||||
@ -1493,7 +1571,6 @@ void QgsMapToolEditMeshFrame::updateSelectecVerticesMarker()
|
||||
mSelectedVerticesMarker.clear();
|
||||
for ( const int vertexIndex : mSelectedVertices.keys() )
|
||||
{
|
||||
mSelectedVertices.insert( vertexIndex, SelectedVertexData() );
|
||||
QgsVertexMarker *marker = new QgsVertexMarker( canvas() );
|
||||
marker->setIconType( QgsVertexMarker::ICON_CIRCLE );
|
||||
marker->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
|
||||
@ -1505,6 +1582,22 @@ void QgsMapToolEditMeshFrame::updateSelectecVerticesMarker()
|
||||
}
|
||||
}
|
||||
|
||||
void QgsMapToolEditMeshFrame::setMovingRubberBandValidity( bool valid )
|
||||
{
|
||||
if ( valid )
|
||||
{
|
||||
mMovingFacesRubberband->setFillColor( QColor( 0, 200, 0, 100 ) );
|
||||
mMovingFacesRubberband->setStrokeColor( QColor( 0, 200, 0 ) );
|
||||
mMovingEdgesRubberband->setColor( QColor( 0, 200, 0 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
mMovingFacesRubberband->setFillColor( QColor( 200, 0, 0, 100 ) );
|
||||
mMovingFacesRubberband->setStrokeColor( QColor( 200, 0, 0 ) );
|
||||
mMovingEdgesRubberband->setColor( QColor( 200, 0, 0 ) );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsMapToolEditMeshFrame::highlightCurrentHoveredFace( const QgsPointXY &mapPoint )
|
||||
{
|
||||
int faceIndex = -1;
|
||||
|
@ -31,6 +31,7 @@ class QgsRubberBand;
|
||||
class QgsVertexMarker;
|
||||
class QgsDoubleSpinBox;
|
||||
class QgsSnapIndicator;
|
||||
class QgsMeshTransformCoordinatesDockWidget;
|
||||
|
||||
|
||||
class APP_EXPORT QgsZValueWidget : public QWidget
|
||||
@ -76,6 +77,9 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
|
||||
bool populateContextMenuWithEvent( QMenu *menu, QgsMapMouseEvent *event ) override;
|
||||
Flags flags() const override;
|
||||
|
||||
signals:
|
||||
void selectionChange( QgsMeshLayer *meshLayer, const QList<int> verticesIndex );
|
||||
|
||||
protected:
|
||||
void cadCanvasPressEvent( QgsMapMouseEvent *e ) override;
|
||||
void cadCanvasReleaseEvent( QgsMapMouseEvent *e ) override;
|
||||
@ -95,6 +99,8 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
|
||||
void removeFacesFromMesh();
|
||||
void splitSelectedFaces();
|
||||
|
||||
void triggerTransformCoordinatesDockWidget( bool checked );
|
||||
|
||||
private:
|
||||
|
||||
enum State
|
||||
@ -156,14 +162,14 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
|
||||
void prepareSelection();
|
||||
void updateSelectecVerticesMarker();
|
||||
|
||||
bool testBorderMovingFace( const QgsMeshFace &borderMovingfaces, const QgsVector &translation ) const;
|
||||
void setMovingRubberBandValidity( bool valid );
|
||||
|
||||
// members
|
||||
struct SelectedVertexData
|
||||
{
|
||||
//Here edges are the indexes of the face where the following vertices (ccw) is the other extremity of the edge
|
||||
QList<Edge> selectedEdges;
|
||||
QList<Edge> meshFixedEdges;
|
||||
QList<Edge> meshFixedEdges; // that have one extremity not on the selection
|
||||
QList<Edge> borderEdges; // that are on the border of the selection
|
||||
};
|
||||
|
||||
bool mIsInitialized = false;
|
||||
@ -223,7 +229,6 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
|
||||
bool mCanMovingStart = false;
|
||||
QgsRubberBand *mMovingEdgesRubberband = nullptr; //own by map canvas
|
||||
QgsRubberBand *mMovingFacesRubberband = nullptr; //own by map canvas
|
||||
QgsRubberBand *mMovingVerticesRubberband = nullptr; //own by map canvas
|
||||
bool mIsMovingAllowed = false;
|
||||
|
||||
//! members for edge flip
|
||||
@ -237,6 +242,8 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
|
||||
|
||||
QgsZValueWidget *mZValueWidget = nullptr; //own by QgsUserInputWidget instance
|
||||
|
||||
QgsMeshTransformCoordinatesDockWidget *mTransformDockWidget = nullptr; //own by the application
|
||||
|
||||
QAction *mActionRemoveVerticesFillingHole = nullptr;
|
||||
QAction *mActionRemoveVerticesWithoutFillingHole = nullptr;
|
||||
QAction *mActionRemoveFaces = nullptr;
|
||||
@ -248,6 +255,8 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing
|
||||
QAction *mActionDigitizing = nullptr;
|
||||
QAction *mActionSelectByPolygon = nullptr;
|
||||
|
||||
QAction *mActionTransformCoordinates = nullptr;
|
||||
|
||||
friend class TestQgsMapToolEditMesh;
|
||||
};
|
||||
|
||||
|
159
src/app/mesh/qgsmeshtransformcoordinatesdockwidget.cpp
Normal file
159
src/app/mesh/qgsmeshtransformcoordinatesdockwidget.cpp
Normal file
@ -0,0 +1,159 @@
|
||||
/***************************************************************************
|
||||
qgsmeshtransformcoordinatesdockwidget.cpp - QgsMeshTransformCoordinatesDockWidget
|
||||
|
||||
---------------------
|
||||
begin : 26.8.2021
|
||||
copyright : (C) 2021 by Vincent Cloarec
|
||||
email : vcloarec at gmail dot com
|
||||
***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgsmeshtransformcoordinatesdockwidget.h"
|
||||
|
||||
#include "qgsgui.h"
|
||||
#include "qgsexpressioncontextutils.h"
|
||||
#include "qgsmesheditor.h"
|
||||
#include "qgsmeshlayer.h"
|
||||
#include "qgsmeshadvancedediting.h"
|
||||
#include "qgsproject.h"
|
||||
#include "qgsguiutils.h"
|
||||
#include "qgshelp.h"
|
||||
|
||||
QgsMeshTransformCoordinatesDockWidget::QgsMeshTransformCoordinatesDockWidget( QWidget *parent ):
|
||||
QgsDockWidget( parent )
|
||||
{
|
||||
setupUi( this );
|
||||
|
||||
QgsGui::enableAutoGeometryRestore( this );
|
||||
|
||||
setWindowTitle( tr( "Transform Mesh Vertices by Expression" ) );
|
||||
|
||||
mExpressionLineEdits << mExpressionEditX << mExpressionEditY << mExpressionEditZ;
|
||||
mCheckBoxes << mCheckBoxX << mCheckBoxY << mCheckBoxZ;
|
||||
|
||||
Q_ASSERT( mExpressionLineEdits.count() == mCheckBoxes.count() );
|
||||
|
||||
for ( int i = 0; i < mExpressionLineEdits.count(); ++i )
|
||||
{
|
||||
mExpressionLineEdits.at( i )->registerExpressionContextGenerator( this );
|
||||
mExpressionLineEdits.at( i )->setEnabled( mCheckBoxes.at( i )->isChecked() );
|
||||
connect( mCheckBoxes.at( i ), &QCheckBox::toggled, mExpressionLineEdits.at( i ), &QWidget::setEnabled );
|
||||
|
||||
connect( mExpressionLineEdits.at( i ), &QgsExpressionLineEdit::expressionChanged, this, &QgsMeshTransformCoordinatesDockWidget::updateButton );
|
||||
connect( mCheckBoxes.at( i ), &QCheckBox::toggled, this, &QgsMeshTransformCoordinatesDockWidget::updateButton );
|
||||
}
|
||||
|
||||
connect( mButtonPreview, &QToolButton::clicked, this, &QgsMeshTransformCoordinatesDockWidget::calculate );
|
||||
connect( mButtonApply, &QPushButton::clicked, this, &QgsMeshTransformCoordinatesDockWidget::apply );
|
||||
}
|
||||
|
||||
QgsExpressionContext QgsMeshTransformCoordinatesDockWidget::createExpressionContext() const
|
||||
{
|
||||
return QgsExpressionContext( {QgsExpressionContextUtils::meshExpressionScope()} );
|
||||
}
|
||||
|
||||
QgsMeshVertex QgsMeshTransformCoordinatesDockWidget::transformedVertex( int i )
|
||||
{
|
||||
if ( ! mInputLayer || !mIsCalculated )
|
||||
return QgsMeshVertex();
|
||||
|
||||
return mTransformVertices.transformedVertex( mInputLayer, i );
|
||||
}
|
||||
|
||||
bool QgsMeshTransformCoordinatesDockWidget::isResultValid() const
|
||||
{
|
||||
return mIsResultValid;
|
||||
}
|
||||
|
||||
bool QgsMeshTransformCoordinatesDockWidget::isCalculated() const
|
||||
{
|
||||
return mIsCalculated;
|
||||
}
|
||||
|
||||
void QgsMeshTransformCoordinatesDockWidget::setInput( QgsMeshLayer *layer, const QList<int> &vertexIndexes )
|
||||
{
|
||||
mInputLayer = layer;
|
||||
mInputVertices = vertexIndexes;
|
||||
mIsCalculated = false;
|
||||
mIsResultValid = false;
|
||||
if ( !mInputLayer )
|
||||
mLabelInformation->setText( tr( "No active mesh layer" ) );
|
||||
else
|
||||
{
|
||||
if ( !mInputLayer->isEditable() )
|
||||
mLabelInformation->setText( tr( "Mesh layer \"%1\" not in edit mode" ).arg( mInputLayer->name() ) );
|
||||
else
|
||||
{
|
||||
if ( mInputVertices.count() == 0 )
|
||||
mLabelInformation->setText( tr( "No vertex selected for mesh \"%1\"" ).arg( mInputLayer->name() ) );
|
||||
else if ( mInputVertices.count() == 1 )
|
||||
mLabelInformation->setText( tr( "1 vertex of mesh layer \"%1\" to transform" ).arg( mInputLayer->name() ) );
|
||||
else
|
||||
mLabelInformation->setText( tr( "%1 vertices of mesh layer \"%2\" to transform" ).
|
||||
arg( QString::number( mInputVertices.count() ), mInputLayer->name() ) );
|
||||
}
|
||||
}
|
||||
updateButton();
|
||||
emit calculationUpdated();
|
||||
}
|
||||
|
||||
void QgsMeshTransformCoordinatesDockWidget::calculate()
|
||||
{
|
||||
if ( !mInputLayer || mInputVertices.isEmpty() )
|
||||
return;
|
||||
|
||||
QgsTemporaryCursorOverride busyCursor( Qt::WaitCursor );
|
||||
mTransformVertices.clear();
|
||||
mTransformVertices.setInputVertices( mInputVertices );
|
||||
mTransformVertices.setExpressions( mCheckBoxX->isChecked() ? mExpressionEditX->expression() : QString(),
|
||||
mCheckBoxY->isChecked() ? mExpressionEditY->expression() : QString(),
|
||||
mCheckBoxZ->isChecked() ? mExpressionEditZ->expression() : QString() );
|
||||
QgsExpressionContext context;
|
||||
context.appendScope( QgsExpressionContextUtils::projectScope( QgsProject::instance() ) );
|
||||
|
||||
mIsResultValid = mTransformVertices.calculate( mInputLayer );
|
||||
|
||||
mIsCalculated = true;
|
||||
mButtonApply->setEnabled( mIsResultValid );
|
||||
|
||||
emit calculationUpdated();
|
||||
}
|
||||
|
||||
void QgsMeshTransformCoordinatesDockWidget::updateButton()
|
||||
{
|
||||
mButtonApply->setEnabled( false );
|
||||
bool isCalculable = mInputLayer && !mInputVertices.isEmpty();
|
||||
if ( isCalculable )
|
||||
{
|
||||
isCalculable = false;
|
||||
for ( const QCheckBox *cb : std::as_const( mCheckBoxes ) )
|
||||
isCalculable |= cb->isChecked();
|
||||
|
||||
if ( isCalculable )
|
||||
{
|
||||
for ( int i = 0; i < mCheckBoxes.count(); ++i )
|
||||
{
|
||||
bool checked = mCheckBoxes.at( i )->isChecked();
|
||||
isCalculable &= !checked || mExpressionLineEdits.at( i )->isValidExpression();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mButtonPreview->setEnabled( isCalculable );
|
||||
}
|
||||
|
||||
void QgsMeshTransformCoordinatesDockWidget::apply()
|
||||
{
|
||||
emit aboutToBeApplied();
|
||||
QgsTemporaryCursorOverride busyCursor( Qt::WaitCursor );
|
||||
if ( mIsResultValid && mInputLayer && mInputLayer->meshEditor() )
|
||||
mInputLayer->meshEditor()->advancedEdit( & mTransformVertices );
|
||||
emit applied();
|
||||
}
|
||||
|
82
src/app/mesh/qgsmeshtransformcoordinatesdockwidget.h
Normal file
82
src/app/mesh/qgsmeshtransformcoordinatesdockwidget.h
Normal file
@ -0,0 +1,82 @@
|
||||
/***************************************************************************
|
||||
qgsmeshtransformcoordinatesdockwidget.h - QgsMeshTransformCoordinatesDockWidget
|
||||
|
||||
---------------------
|
||||
begin : 26.8.2021
|
||||
copyright : (C) 2021 by Vincent Cloarec
|
||||
email : vcloarec at gmail dot com
|
||||
***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef QGSMESHTRANSFORMCOORDINATESDOCKWIDGET_H
|
||||
#define QGSMESHTRANSFORMCOORDINATESDOCKWIDGET_H
|
||||
|
||||
#include "ui_qgsmeshtransformcoordinatesdockwidgetbase.h"
|
||||
|
||||
#include "qgsexpressioncontextgenerator.h"
|
||||
#include "qgsmeshadvancedediting.h"
|
||||
#include "qgisapp.h"
|
||||
|
||||
class QgsMeshLayer;
|
||||
|
||||
/**
|
||||
* \brief A dock widget that is used to make some geometrical transformations of mesh vertices by expression
|
||||
*
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
class APP_EXPORT QgsMeshTransformCoordinatesDockWidget: public QgsDockWidget, public QgsExpressionContextGenerator, private Ui::QgsMeshTransformCoordinatesDockWidgetBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
//! Constructor
|
||||
QgsMeshTransformCoordinatesDockWidget( QWidget *parent );
|
||||
|
||||
virtual QgsExpressionContext createExpressionContext() const override;
|
||||
|
||||
//! Returns the vertex with index \a vertexIndex after calculation
|
||||
QgsMeshVertex transformedVertex( int vertexIndex );
|
||||
|
||||
//! Returns whether the result of transformation is a valid mesh
|
||||
bool isResultValid() const;
|
||||
|
||||
//! Returns whether the calculation has been done
|
||||
bool isCalculated() const;
|
||||
|
||||
signals:
|
||||
//! Emitted when the calculation of the transform is done
|
||||
void calculationUpdated();
|
||||
|
||||
//! Emitted just before the transform is applied
|
||||
void aboutToBeApplied();
|
||||
|
||||
//! Emitted just after the transform is applied
|
||||
void applied();
|
||||
|
||||
public slots:
|
||||
//! Set the vertices indexes to transform \vertexIndexes for the mesh \a layer
|
||||
void setInput( QgsMeshLayer *layer, const QList<int> &vertexIndexes );
|
||||
|
||||
private slots:
|
||||
void calculate();
|
||||
void updateButton();
|
||||
void apply();
|
||||
|
||||
private:
|
||||
QgsMeshTransformVerticesByExpression mTransformVertices;
|
||||
QgsMeshLayer *mInputLayer;
|
||||
QList<int> mInputVertices;
|
||||
bool mIsCalculated = false;
|
||||
bool mIsResultValid = false;
|
||||
QList<QgsExpressionLineEdit *> mExpressionLineEdits;
|
||||
QList<QCheckBox *> mCheckBoxes;
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSMESHTRANSFORMCOORDINATESDOCKWIDGET_H
|
@ -1019,6 +1019,82 @@ class CurrentVertexZValueExpressionFunction: public QgsScopedExpressionFunction
|
||||
}
|
||||
};
|
||||
|
||||
class CurrentVertexXValueExpressionFunction: public QgsScopedExpressionFunction
|
||||
{
|
||||
public:
|
||||
CurrentVertexXValueExpressionFunction():
|
||||
QgsScopedExpressionFunction( "$vertex_x",
|
||||
0,
|
||||
QStringLiteral( "Meshes" ) )
|
||||
{}
|
||||
|
||||
QgsScopedExpressionFunction *clone() const override {return new CurrentVertexXValueExpressionFunction();}
|
||||
|
||||
QVariant func( const QVariantList &, const QgsExpressionContext *context, QgsExpression *, const QgsExpressionNodeFunction * ) override
|
||||
{
|
||||
if ( !context )
|
||||
return QVariant();
|
||||
|
||||
if ( !context->hasVariable( QStringLiteral( "_mesh_vertex_index" ) ) || !context->hasVariable( QStringLiteral( "_mesh_layer" ) ) )
|
||||
return QVariant();
|
||||
|
||||
int vertexIndex = context->variable( QStringLiteral( "_mesh_vertex_index" ) ).toInt();
|
||||
|
||||
QgsMeshLayer *layer = qobject_cast<QgsMeshLayer *>( qvariant_cast<QgsMapLayer *>( context->variable( QStringLiteral( "_mesh_layer" ) ) ) );
|
||||
if ( !layer || !layer->nativeMesh() || layer->nativeMesh()->vertexCount() <= vertexIndex )
|
||||
return QVariant();
|
||||
|
||||
const QgsMeshVertex &vertex = layer->nativeMesh()->vertex( vertexIndex );
|
||||
if ( !vertex.isEmpty() )
|
||||
return vertex.x();
|
||||
else
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool isStatic( const QgsExpressionNodeFunction *, QgsExpression *, const QgsExpressionContext * ) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class CurrentVertexYValueExpressionFunction: public QgsScopedExpressionFunction
|
||||
{
|
||||
public:
|
||||
CurrentVertexYValueExpressionFunction():
|
||||
QgsScopedExpressionFunction( "$vertex_y",
|
||||
0,
|
||||
QStringLiteral( "Meshes" ) )
|
||||
{}
|
||||
|
||||
QgsScopedExpressionFunction *clone() const override {return new CurrentVertexYValueExpressionFunction();}
|
||||
|
||||
QVariant func( const QVariantList &, const QgsExpressionContext *context, QgsExpression *, const QgsExpressionNodeFunction * ) override
|
||||
{
|
||||
if ( !context )
|
||||
return QVariant();
|
||||
|
||||
if ( !context->hasVariable( QStringLiteral( "_mesh_vertex_index" ) ) || !context->hasVariable( QStringLiteral( "_mesh_layer" ) ) )
|
||||
return QVariant();
|
||||
|
||||
int vertexIndex = context->variable( QStringLiteral( "_mesh_vertex_index" ) ).toInt();
|
||||
|
||||
QgsMeshLayer *layer = qobject_cast<QgsMeshLayer *>( qvariant_cast<QgsMapLayer *>( context->variable( QStringLiteral( "_mesh_layer" ) ) ) );
|
||||
if ( !layer || !layer->nativeMesh() || layer->nativeMesh()->vertexCount() <= vertexIndex )
|
||||
return QVariant();
|
||||
|
||||
const QgsMeshVertex &vertex = layer->nativeMesh()->vertex( vertexIndex );
|
||||
if ( !vertex.isEmpty() )
|
||||
return vertex.y();
|
||||
else
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool isStatic( const QgsExpressionNodeFunction *, QgsExpression *, const QgsExpressionContext * ) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class CurrentVertexExpressionFunction: public QgsScopedExpressionFunction
|
||||
{
|
||||
public:
|
||||
@ -1060,10 +1136,14 @@ class CurrentVertexExpressionFunction: public QgsScopedExpressionFunction
|
||||
QgsExpressionContextScope *QgsExpressionContextUtils::meshExpressionScope()
|
||||
{
|
||||
QgsExpression::registerFunction( new CurrentVertexExpressionFunction, true );
|
||||
QgsExpression::registerFunction( new CurrentVertexXValueExpressionFunction, true );
|
||||
QgsExpression::registerFunction( new CurrentVertexYValueExpressionFunction, true );
|
||||
QgsExpression::registerFunction( new CurrentVertexZValueExpressionFunction, true );
|
||||
|
||||
std::unique_ptr<QgsExpressionContextScope> scope = std::make_unique<QgsExpressionContextScope>();
|
||||
scope->addFunction( "$vertex_as_point", new CurrentVertexExpressionFunction );
|
||||
scope->addFunction( "$vertex_x", new CurrentVertexXValueExpressionFunction );
|
||||
scope->addFunction( "$vertex_y", new CurrentVertexYValueExpressionFunction );
|
||||
scope->addFunction( "$vertex_z", new CurrentVertexZValueExpressionFunction );
|
||||
|
||||
return scope.release();
|
||||
|
@ -19,6 +19,10 @@
|
||||
#include "qgsmesheditor.h"
|
||||
#include "poly2tri.h"
|
||||
|
||||
#include "qgsmeshlayer.h"
|
||||
#include "qgsexpression.h"
|
||||
#include "qgsexpressioncontextutils.h"
|
||||
|
||||
QgsMeshAdvancedEditing::QgsMeshAdvancedEditing() = default;
|
||||
|
||||
QgsMeshAdvancedEditing::~QgsMeshAdvancedEditing() = default;
|
||||
@ -591,3 +595,181 @@ bool QgsMeshEditRefineFaces::createNewBorderFaces( QgsMeshEditor *meshEditor,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QgsMeshTransformVerticesByExpression::calculate( QgsMeshLayer *layer )
|
||||
{
|
||||
if ( !layer || !layer->meshEditor() || !layer->nativeMesh() )
|
||||
return false;
|
||||
|
||||
if ( mInputVertices.isEmpty() )
|
||||
return false;
|
||||
|
||||
const QgsMesh mesh = *layer->nativeMesh();
|
||||
QSet<int> concernedFaces;
|
||||
mChangingVertexMap = QHash<int, int>();
|
||||
|
||||
std::unique_ptr<QgsExpressionContextScope> expScope( QgsExpressionContextUtils::meshExpressionScope() );
|
||||
QgsExpressionContext context;
|
||||
context.appendScope( expScope.release() );
|
||||
context.lastScope()->setVariable( QStringLiteral( "_mesh_layer" ), QVariant::fromValue( layer ) );
|
||||
|
||||
QVector<QgsMeshVertex> newVertices;
|
||||
newVertices.reserve( mInputVertices.count() );
|
||||
|
||||
int inputCount = mInputVertices.count();
|
||||
mChangeCoordinateVerticesIndexes = mInputVertices;
|
||||
|
||||
bool calcX = !mExpressionX.isEmpty();
|
||||
bool calcY = !mExpressionY.isEmpty();
|
||||
bool calcZ = !mExpressionZ.isEmpty();
|
||||
QgsExpression expressionX;
|
||||
if ( calcX )
|
||||
{
|
||||
expressionX = QgsExpression( mExpressionX );
|
||||
expressionX.prepare( &context );
|
||||
}
|
||||
|
||||
QgsExpression expressionY;
|
||||
if ( calcY )
|
||||
{
|
||||
expressionY = QgsExpression( mExpressionY );
|
||||
expressionY.prepare( &context );
|
||||
}
|
||||
|
||||
if ( calcX || calcY )
|
||||
{
|
||||
mNewXYValues.reserve( inputCount );
|
||||
mOldXYValues.reserve( inputCount );
|
||||
}
|
||||
|
||||
QgsExpression expressionZ;
|
||||
if ( calcZ )
|
||||
{
|
||||
expressionZ = QgsExpression( mExpressionZ );
|
||||
expressionZ.prepare( &context );
|
||||
mNewZValues.reserve( inputCount );
|
||||
mOldZValues.reserve( inputCount );
|
||||
}
|
||||
|
||||
for ( int i = 0; i < mInputVertices.count(); ++i )
|
||||
{
|
||||
const int vertexIndex = mInputVertices.at( i );
|
||||
context.lastScope()->setVariable( QStringLiteral( "_mesh_vertex_index" ), vertexIndex, false );
|
||||
|
||||
mChangingVertexMap[vertexIndex] = i;
|
||||
const QVariant xvar = expressionX.evaluate( &context );
|
||||
const QVariant yvar = expressionY.evaluate( &context );
|
||||
const QVariant zvar = expressionZ.evaluate( &context );
|
||||
|
||||
const QgsMeshVertex &vert = mesh.vertex( vertexIndex );
|
||||
|
||||
if ( calcX || calcY )
|
||||
{
|
||||
mOldXYValues.append( QgsPointXY( vert ) );
|
||||
mNewXYValues.append( QgsPointXY( vert ) );
|
||||
|
||||
const QList<int> facesAround = layer->meshEditor()->topologicalMesh().facesAroundVertex( vertexIndex );
|
||||
concernedFaces.unite( qgis::listToSet( facesAround ) );
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
if ( calcX )
|
||||
{
|
||||
if ( xvar.isValid() )
|
||||
{
|
||||
double x = xvar.toDouble( &ok );
|
||||
if ( ok )
|
||||
{
|
||||
mNewXYValues.last().setX( x );
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( calcY )
|
||||
{
|
||||
if ( yvar.isValid() )
|
||||
{
|
||||
double y = yvar.toDouble( &ok );
|
||||
if ( ok )
|
||||
{
|
||||
mNewXYValues.last().setY( y );
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( calcZ )
|
||||
{
|
||||
if ( zvar.isValid() )
|
||||
{
|
||||
double z = zvar.toDouble( &ok );
|
||||
if ( ok )
|
||||
{
|
||||
mNewZValues.append( z );
|
||||
mOldZValues.append( vert.z() );
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto transformFunction = [this, layer ]( int vi )-> const QgsMeshVertex
|
||||
{
|
||||
return transformedVertex( layer, vi );
|
||||
};
|
||||
|
||||
mNativeFacesIndexesGeometryChanged = qgis::setToList( concernedFaces );
|
||||
return layer->meshEditor()->canBeTransformed( mNativeFacesIndexesGeometryChanged, transformFunction );
|
||||
}
|
||||
|
||||
void QgsMeshTransformVerticesByExpression::setExpressions( const QString &expressionX, const QString &expressionY, const QString &expressionZ )
|
||||
{
|
||||
mExpressionX = expressionX;
|
||||
mExpressionY = expressionY;
|
||||
mExpressionZ = expressionZ;
|
||||
|
||||
mChangingVertexMap.clear();
|
||||
}
|
||||
|
||||
QgsTopologicalMesh::Changes QgsMeshTransformVerticesByExpression::apply( QgsMeshEditor *meshEditor )
|
||||
{
|
||||
meshEditor->topologicalMesh().applyChanges( *this );
|
||||
return *this;
|
||||
}
|
||||
|
||||
QgsMeshVertex QgsMeshTransformVerticesByExpression::transformedVertex( QgsMeshLayer *layer, int vertexIndex ) const
|
||||
{
|
||||
int pos = mChangingVertexMap.value( vertexIndex, -1 );
|
||||
if ( pos > -1 )
|
||||
{
|
||||
QgsPointXY pointXY;
|
||||
double z;
|
||||
|
||||
if ( mNewXYValues.isEmpty() )
|
||||
pointXY = layer->nativeMesh()->vertex( vertexIndex );
|
||||
else
|
||||
pointXY = mNewXYValues.at( pos );
|
||||
|
||||
if ( mNewZValues.isEmpty() )
|
||||
z = layer->nativeMesh()->vertex( vertexIndex ).z();
|
||||
else
|
||||
z = mNewZValues.at( pos );
|
||||
|
||||
return QgsMeshVertex( pointXY.x(), pointXY.y(), z );
|
||||
}
|
||||
else
|
||||
return layer->nativeMesh()->vertex( vertexIndex );
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
class QgsMeshEditor;
|
||||
class QgsProcessingFeedback;
|
||||
class QgsExpressionContext;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -124,4 +125,67 @@ class CORE_EXPORT QgsMeshEditRefineFaces : public QgsMeshAdvancedEditing
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
*
|
||||
* \brief Class that can transform vertices of a mesh by expression
|
||||
*
|
||||
* Each coordinates are associated with an expression that can be defined with function
|
||||
* returning the current coordinates (see setExpressions()):
|
||||
*
|
||||
* - $vertex_x
|
||||
* - $vertex_y
|
||||
* - $vertex_z
|
||||
*
|
||||
* Example:
|
||||
* Transposing a mesh and translate following axe X with a distance of 50 and increase the level of the mesh
|
||||
* with an height of 80 when previous X coordinate is under 100 and de crease the level of 150 when X is under 100:
|
||||
*
|
||||
* expressionX: "$vertex_y + 50"
|
||||
* expressionY: "$vertex_x"
|
||||
* expressionZ: "if( $vertex_x <= 100 , $vertex_z + 80 , $vertex_z - 150)"
|
||||
*
|
||||
* \since QGIS 3.22
|
||||
*/
|
||||
class CORE_EXPORT QgsMeshTransformVerticesByExpression : public QgsMeshAdvancedEditing
|
||||
{
|
||||
public:
|
||||
|
||||
//! Constructor
|
||||
QgsMeshTransformVerticesByExpression() = default;
|
||||
|
||||
/**
|
||||
* Sets the expressions for the coordinates transformation.
|
||||
*
|
||||
* \note Expressions are optional for each coordinate, the coordinate will not be transformed if the string is void.
|
||||
*/
|
||||
void setExpressions( const QString &expressionX, const QString &expressionY, const QString &expressionZ );
|
||||
|
||||
/**
|
||||
* Calculates the transformed vertices of the mesh \a layer, returns FALSE if this leads to topological or geometrical errors.
|
||||
* The mesh layer must be in edit mode.
|
||||
*
|
||||
* \note this method not apply new vertices to the mesh layer but only store the calculated transformation
|
||||
* that can be apply later with QgsMeshEditor::advancedEdit()
|
||||
*/
|
||||
bool calculate( QgsMeshLayer *layer );
|
||||
|
||||
/**
|
||||
* Returns the transformed vertex from its index \a vertexIndex for the mesh \a layer
|
||||
*
|
||||
* If \a layer is not the same than the one used to make the calculation, this will create an undefined behavior
|
||||
*/
|
||||
QgsMeshVertex transformedVertex( QgsMeshLayer *layer, int vertexIndex ) const;
|
||||
|
||||
private:
|
||||
QString mExpressionX;
|
||||
QString mExpressionY;
|
||||
QString mExpressionZ;
|
||||
QHash<int, int> mChangingVertexMap;
|
||||
|
||||
QgsTopologicalMesh::Changes apply( QgsMeshEditor *meshEditor ) override;
|
||||
|
||||
friend class TestQgsMeshEditor;
|
||||
};
|
||||
|
||||
#endif // QGSMESHADVANCEDEDITING_H
|
||||
|
@ -257,8 +257,6 @@ void QgsMeshEditor::applyEditOnTriangularMesh( QgsMeshEditor::Edit &edit, const
|
||||
void QgsMeshEditor::applyAdvancedEdit( QgsMeshEditor::Edit &edit, QgsMeshAdvancedEditing *editing )
|
||||
{
|
||||
applyEditOnTriangularMesh( edit, editing->apply( this ) );
|
||||
|
||||
emit meshEdited();
|
||||
}
|
||||
|
||||
bool QgsMeshEditor::checkConsistency() const
|
||||
@ -358,7 +356,7 @@ QVector<QgsMeshFace> QgsMeshEditor::prepareFaces( const QVector<QgsMeshFace> &fa
|
||||
break;
|
||||
}
|
||||
|
||||
error = mTopologicalMesh.counterClockWiseFaces( face, mMesh );
|
||||
error = mTopologicalMesh.counterClockwiseFaces( face, mMesh );
|
||||
if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
|
||||
break;
|
||||
}
|
||||
@ -464,6 +462,96 @@ void QgsMeshEditor::changeZValues( const QList<int> &verticesIndexes, const QLis
|
||||
mUndoStack->push( new QgsMeshLayerUndoCommandChangeZValue( this, verticesIndexes, newZValues ) );
|
||||
}
|
||||
|
||||
bool QgsMeshEditor::canBeTransformed( const QList<int> &transformedFaces, const std::function<const QgsMeshVertex( int )> &transformFunction ) const
|
||||
{
|
||||
for ( const int faceIndex : transformedFaces )
|
||||
{
|
||||
const QgsMeshFace &face = mMesh->face( faceIndex );
|
||||
int faceSize = face.count();
|
||||
QVector<QgsPointXY> pointsInTriangularMeshCoordinate( faceSize );
|
||||
QVector<QgsPointXY> points( faceSize );
|
||||
for ( int i = 0; i < faceSize; ++i )
|
||||
{
|
||||
int ip0 = face[i];
|
||||
int ip1 = face[( i + 1 ) % faceSize];
|
||||
int ip2 = face[( i + 2 ) % faceSize];
|
||||
|
||||
QgsMeshVertex p0 = transformFunction( ip0 );
|
||||
QgsMeshVertex p1 = transformFunction( ip1 );
|
||||
QgsMeshVertex p2 = transformFunction( ip2 );
|
||||
|
||||
double ux = p0.x() - p1.x();
|
||||
double uy = p0.y() - p1.y();
|
||||
double vx = p2.x() - p1.x();
|
||||
double vy = p2.y() - p1.y();
|
||||
|
||||
double crossProduct = ux * vy - uy * vx;
|
||||
if ( crossProduct >= 0 ) //if cross product>0, we have two edges clockwise
|
||||
return false;
|
||||
pointsInTriangularMeshCoordinate[i] = mTriangularMesh->nativeToTriangularCoordinates( p0 );
|
||||
points[i] = p0;
|
||||
}
|
||||
|
||||
const QgsGeometry &deformedFace = QgsGeometry::fromPolygonXY( {points} );
|
||||
|
||||
// now test if the deformed face contain something else (search canditate <ith the
|
||||
QList<int> otherFaceIndexes =
|
||||
mTriangularMesh->nativeFaceIndexForRectangle( QgsGeometry::fromPolygonXY( {pointsInTriangularMeshCoordinate} ).boundingBox() );
|
||||
|
||||
for ( const int otherFaceIndex : otherFaceIndexes )
|
||||
{
|
||||
const QgsMeshFace &otherFace = mMesh->face( otherFaceIndex );
|
||||
int existingFaceSize = otherFace.count();
|
||||
bool shareVertex = false;
|
||||
for ( int i = 0; i < existingFaceSize; ++i )
|
||||
{
|
||||
if ( face.contains( otherFace.at( i ) ) )
|
||||
{
|
||||
shareVertex = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( shareVertex )
|
||||
{
|
||||
//only test the edge that not contain a shared vertex
|
||||
for ( int i = 0; i < existingFaceSize; ++i )
|
||||
{
|
||||
int index1 = otherFace.at( i );
|
||||
int index2 = otherFace.at( ( i + 1 ) % existingFaceSize );
|
||||
if ( ! face.contains( index1 ) && !face.contains( index2 ) )
|
||||
{
|
||||
const QgsPointXY &v1 = transformFunction( index1 );
|
||||
const QgsPointXY &v2 = transformFunction( index2 );
|
||||
QgsGeometry edgeGeom = QgsGeometry::fromPolylineXY( { v1, v2} );
|
||||
if ( deformedFace.intersects( edgeGeom ) )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QVector<QgsPointXY> otherPoints( existingFaceSize );
|
||||
for ( int i = 0; i < existingFaceSize; ++i )
|
||||
otherPoints[i] = transformFunction( otherFace.at( i ) );
|
||||
const QgsGeometry existingFaceGeom = QgsGeometry::fromPolygonXY( {otherPoints } );
|
||||
if ( deformedFace.intersects( existingFaceGeom ) )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//finish with free vertices...
|
||||
const QList<int> freeVerticesIndex = freeVerticesIndexes();
|
||||
for ( const int vertexIndex : freeVerticesIndex )
|
||||
{
|
||||
const QgsPointXY &mapPoint = transformFunction( vertexIndex ); //free vertices can be transformed
|
||||
if ( deformedFace.contains( &mapPoint ) )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QgsMeshEditor::changeXYValues( const QList<int> &verticesIndexes, const QList<QgsPointXY> &newValues )
|
||||
{
|
||||
// TODO : implement a check if it is possible to change the (x,y) values. For now, this check is made in the APP part
|
||||
@ -626,7 +714,7 @@ bool QgsMeshEditor::isModified() const
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<int> QgsMeshEditor::freeVerticesIndexes()
|
||||
QList<int> QgsMeshEditor::freeVerticesIndexes() const
|
||||
{
|
||||
return mTopologicalMesh.freeVerticesIndexes();
|
||||
}
|
||||
|
@ -152,6 +152,15 @@ class CORE_EXPORT QgsMeshEditor : public QObject
|
||||
*/
|
||||
void changeZValues( const QList<int> &verticesIndexes, const QList<double> &newValues );
|
||||
|
||||
/**
|
||||
* Returns TRUE if faces with index in \a transformedFaces can be transformed without obtaining topologic or geometrical errors
|
||||
* condidering the transform function \a transformFunction
|
||||
*
|
||||
* The transform function takes a vertex index in parameter and return a QgsMeshVertex object with transformed coordinates.
|
||||
* This transformation is done in layer coordinates
|
||||
*/
|
||||
bool canBeTransformed( const QList<int> &transformedFaces, const std::function<const QgsMeshVertex( int )> &transformFunction ) const; SIP_SKIP
|
||||
|
||||
/**
|
||||
* Changes the (X,Y) coordinates values of the vertices with indexes in \a vertices indexes with the values in \a newValues.
|
||||
* The caller has the responsibility to check if changing the vertices coordinates does not lead to topological errors
|
||||
@ -175,7 +184,7 @@ class CORE_EXPORT QgsMeshEditor : public QObject
|
||||
//----------- access element methods
|
||||
|
||||
//! Returns all the free vertices indexes
|
||||
QList<int> freeVerticesIndexes();
|
||||
QList<int> freeVerticesIndexes() const;
|
||||
|
||||
//! Returns whether the vertex with index \a vertexIndex is on a boundary
|
||||
bool isVertexOnBoundary( int vertexIndex ) const;
|
||||
|
@ -585,7 +585,7 @@ QList<int> QgsTopologicalMesh::freeVerticesIndexes() const
|
||||
#endif
|
||||
}
|
||||
|
||||
QgsMeshEditingError QgsTopologicalMesh::counterClockWiseFaces( QgsMeshFace &face, QgsMesh *mesh )
|
||||
QgsMeshEditingError QgsTopologicalMesh::counterClockwiseFaces( QgsMeshFace &face, QgsMesh *mesh )
|
||||
{
|
||||
// First check if the face is convex and put it counter clockwise
|
||||
// If the index are not well ordered (edges intersect), invalid face --> return false
|
||||
@ -1227,7 +1227,7 @@ QgsTopologicalMesh QgsTopologicalMesh::createTopologicalMesh( QgsMesh *mesh, int
|
||||
break;
|
||||
}
|
||||
|
||||
error = counterClockWiseFaces( mesh->faces[i], mesh );
|
||||
error = counterClockwiseFaces( mesh->faces[i], mesh );
|
||||
if ( error.errorType != Qgis::MeshEditingErrorType::NoError )
|
||||
{
|
||||
if ( error.errorType == Qgis::MeshEditingErrorType::InvalidFace || error.errorType == Qgis::MeshEditingErrorType::FlatFace )
|
||||
|
@ -285,8 +285,8 @@ class CORE_EXPORT QgsTopologicalMesh
|
||||
//! Reverses the changes
|
||||
void reverseChanges( const Changes &changes );
|
||||
|
||||
//! Checks the topology of the face and sets it counter clock wise if necessary
|
||||
static QgsMeshEditingError counterClockWiseFaces( QgsMeshFace &face, QgsMesh *mesh );
|
||||
//! Checks the topology of the face and sets it counter clockwise if necessary
|
||||
static QgsMeshEditingError counterClockwiseFaces( QgsMeshFace &face, QgsMesh *mesh );
|
||||
|
||||
/**
|
||||
* Reindexes faces and vertices, after this operation, the topological
|
||||
|
@ -142,7 +142,8 @@ QString QgsExpressionLineEdit::expression() const
|
||||
bool QgsExpressionLineEdit::isValidExpression( QString *expressionError ) const
|
||||
{
|
||||
QString temp;
|
||||
return QgsExpression::checkExpression( expression(), &mExpressionContext, expressionError ? *expressionError : temp );
|
||||
const QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : mExpressionContext;
|
||||
return QgsExpression::checkExpression( expression(), &context, expressionError ? *expressionError : temp );
|
||||
}
|
||||
|
||||
void QgsExpressionLineEdit::registerExpressionContextGenerator( const QgsExpressionContextGenerator *generator )
|
||||
@ -230,6 +231,8 @@ void QgsExpressionLineEdit::updateLineEditStyle( const QString &expression )
|
||||
bool QgsExpressionLineEdit::isExpressionValid( const QString &expressionStr )
|
||||
{
|
||||
QgsExpression expression( expressionStr );
|
||||
|
||||
const QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : mExpressionContext;
|
||||
expression.prepare( &mExpressionContext );
|
||||
return !expression.hasParserError();
|
||||
}
|
||||
|
110
src/ui/mesh/qgsmeshtransformcoordinatesdockwidgetbase.ui
Normal file
110
src/ui/mesh/qgsmeshtransformcoordinatesdockwidgetbase.ui
Normal file
@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QgsMeshTransformCoordinatesDockWidgetBase</class>
|
||||
<widget class="QgsDockWidget" name="QgsMeshTransformCoordinatesDockWidgetBase">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>256</width>
|
||||
<height>174</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="floating">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">DockWidget</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="dockWidgetContents">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="1">
|
||||
<widget class="QgsExpressionLineEdit" name="mExpressionEditZ" native="true"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QgsExpressionLineEdit" name="mExpressionEditY" native="true"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="mCheckBoxY">
|
||||
<property name="text">
|
||||
<string>Y coordinate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="mCheckBoxX">
|
||||
<property name="text">
|
||||
<string>X coordinate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QgsExpressionLineEdit" name="mExpressionEditX" native="true"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="mCheckBoxZ">
|
||||
<property name="text">
|
||||
<string>Z value</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QToolButton" name="mButtonPreview">
|
||||
<property name="text">
|
||||
<string>Preview transform</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="mButtonApply">
|
||||
<property name="text">
|
||||
<string>Apply transform</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="mLabelInformation">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QgsDockWidget</class>
|
||||
<extends>QDockWidget</extends>
|
||||
<header>qgsdockwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QgsExpressionLineEdit</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>qgsexpressionlineedit.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -57,6 +57,8 @@ class TestQgsMeshEditor : public QObject
|
||||
|
||||
void refineMesh();
|
||||
|
||||
void transformByExpression();
|
||||
|
||||
void particularCases();
|
||||
};
|
||||
|
||||
@ -1679,5 +1681,132 @@ void TestQgsMeshEditor::refineMesh()
|
||||
}
|
||||
}
|
||||
|
||||
void TestQgsMeshEditor::transformByExpression()
|
||||
{
|
||||
std::unique_ptr<QgsMeshLayer> layer = std::make_unique<QgsMeshLayer>( mDataDir + "/quad_flower_to_edit.2dm", "mesh", "mdal" );
|
||||
|
||||
const QgsCoordinateTransform transform;
|
||||
layer->startFrameEditing( transform );
|
||||
|
||||
QgsMeshTransformVerticesByExpression transformVertex;
|
||||
|
||||
transformVertex.setExpressions( QStringLiteral( "$vertex_x + 50" ), QStringLiteral( "$vertex_y - 50" ), QStringLiteral( "$vertex_z + 100" ) );
|
||||
|
||||
// no input set
|
||||
QVERIFY( !transformVertex.calculate( layer.get() ) );
|
||||
|
||||
transformVertex.setInputVertices( {0, 1, 3, 4} );
|
||||
|
||||
QVERIFY( transformVertex.calculate( layer.get() ) );
|
||||
|
||||
QCOMPARE( transformVertex.mChangeCoordinateVerticesIndexes, QList<int>( {0, 1, 3, 4} ) );
|
||||
QVERIFY( transformVertex.mOldXYValues.at( 0 ).compare( QgsPointXY( 1000, 2000 ), 0.1 ) );
|
||||
QVERIFY( transformVertex.mOldXYValues.at( 1 ).compare( QgsPointXY( 2000, 2000 ), 0.1 ) );
|
||||
QVERIFY( transformVertex.mOldXYValues.at( 2 ).compare( QgsPointXY( 2000, 3000 ), 0.1 ) );
|
||||
QVERIFY( transformVertex.mOldXYValues.at( 3 ).compare( QgsPointXY( 1000, 3000 ), 0.1 ) );
|
||||
QVERIFY( transformVertex.mNewXYValues.at( 0 ).compare( QgsPointXY( 1050, 1950 ), 0.1 ) );
|
||||
QVERIFY( transformVertex.mNewXYValues.at( 1 ).compare( QgsPointXY( 2050, 1950 ), 0.1 ) );
|
||||
QVERIFY( transformVertex.mNewXYValues.at( 2 ).compare( QgsPointXY( 2050, 2950 ), 0.1 ) );
|
||||
QVERIFY( transformVertex.mNewXYValues.at( 3 ).compare( QgsPointXY( 1050, 2950 ), 0.1 ) );
|
||||
QCOMPARE( transformVertex.mNewZValues.at( 0 ), 300 );
|
||||
QCOMPARE( transformVertex.mNewZValues.at( 1 ), 300 );
|
||||
QCOMPARE( transformVertex.mNewZValues.at( 2 ), 300 );
|
||||
QCOMPARE( transformVertex.mNewZValues.at( 3 ), 300 );
|
||||
|
||||
layer->meshEditor()->advancedEdit( &transformVertex );
|
||||
|
||||
QgsMesh &mesh = *layer->nativeMesh();
|
||||
|
||||
QVERIFY( QgsPoint( 1050, 1950, 300 ).compareTo( &mesh.vertices.at( 0 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 2050, 1950, 300 ).compareTo( &mesh.vertices.at( 1 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 2500, 2500, 800 ).compareTo( &mesh.vertices.at( 2 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 2050, 2950, 300 ).compareTo( &mesh.vertices.at( 3 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 1050, 2950, 300 ).compareTo( &mesh.vertices.at( 4 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 500, 2500, 800 ).compareTo( &mesh.vertices.at( 5 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 1500, 1500, 800 ).compareTo( &mesh.vertices.at( 6 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 1500, 3500, 800 ).compareTo( &mesh.vertices.at( 7 ) ) == 0 );
|
||||
|
||||
layer->undoStack()->undo();
|
||||
mesh = *layer->nativeMesh();
|
||||
|
||||
QVERIFY( QgsPoint( 1000, 2000, 200 ).compareTo( &mesh.vertices.at( 0 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 2000, 2000, 200 ).compareTo( &mesh.vertices.at( 1 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 2500, 2500, 800 ).compareTo( &mesh.vertices.at( 2 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 2000, 3000, 200 ).compareTo( &mesh.vertices.at( 3 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 1000, 3000, 200 ).compareTo( &mesh.vertices.at( 4 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 500, 2500, 800 ).compareTo( &mesh.vertices.at( 5 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 1500, 1500, 800 ).compareTo( &mesh.vertices.at( 6 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 1500, 3500, 800 ).compareTo( &mesh.vertices.at( 7 ) ) == 0 );
|
||||
|
||||
layer->undoStack()->redo();
|
||||
mesh = *layer->nativeMesh();
|
||||
|
||||
QVERIFY( QgsPoint( 1050, 1950, 300 ).compareTo( &mesh.vertices.at( 0 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 2050, 1950, 300 ).compareTo( &mesh.vertices.at( 1 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 2500, 2500, 800 ).compareTo( &mesh.vertices.at( 2 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 2050, 2950, 300 ).compareTo( &mesh.vertices.at( 3 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 1050, 2950, 300 ).compareTo( &mesh.vertices.at( 4 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 500, 2500, 800 ).compareTo( &mesh.vertices.at( 5 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 1500, 1500, 800 ).compareTo( &mesh.vertices.at( 6 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 1500, 3500, 800 ).compareTo( &mesh.vertices.at( 7 ) ) == 0 );
|
||||
|
||||
layer->undoStack()->undo();
|
||||
|
||||
// leads to an invalid mesh
|
||||
transformVertex.clear();
|
||||
transformVertex.setInputVertices( {1, 3} );
|
||||
transformVertex.setExpressions( QStringLiteral( "$vertex_x -1500" ), QStringLiteral( "$vertex_y - 1500" ), QString() );
|
||||
|
||||
QVERIFY( !transformVertex.calculate( layer.get() ) );
|
||||
|
||||
// transforme with intersecting existing faces
|
||||
transformVertex.clear();
|
||||
transformVertex.setInputVertices( {2, 3, 7} );
|
||||
transformVertex.setExpressions( QStringLiteral( "$vertex_x+700" ), QStringLiteral( "$vertex_y + 700" ), QString() );
|
||||
|
||||
QVERIFY( transformVertex.calculate( layer.get() ) );
|
||||
|
||||
//add a other face that will intersects transformed ones
|
||||
layer->meshEditor()->addVertices(
|
||||
{
|
||||
{2000, 3500, 0}, // 8
|
||||
{2500, 3500, 10}, // 9
|
||||
{2500, 4000, 20}} // 10
|
||||
, 1 );
|
||||
|
||||
QVERIFY( !transformVertex.calculate( layer.get() ) );
|
||||
|
||||
layer->meshEditor()->addFace( {8, 9, 10} );
|
||||
|
||||
QVERIFY( !transformVertex.calculate( layer.get() ) );
|
||||
|
||||
// undo adding vertices and face
|
||||
layer->undoStack()->undo();
|
||||
layer->undoStack()->undo();
|
||||
|
||||
QVERIFY( transformVertex.calculate( layer.get() ) );
|
||||
|
||||
// composed expression
|
||||
transformVertex.clear();
|
||||
transformVertex.setInputVertices( {0, 1, 2, 3, 4, 5, 6, 7} );
|
||||
transformVertex.setExpressions( QStringLiteral( "$vertex_y + 50" ),
|
||||
QStringLiteral( "-$vertex_x" ),
|
||||
QStringLiteral( "if( $vertex_x <= 1500 , $vertex_z + 80 , $vertex_z - 150)" ) );
|
||||
|
||||
QVERIFY( transformVertex.calculate( layer.get() ) );
|
||||
layer->meshEditor()->advancedEdit( &transformVertex );
|
||||
|
||||
mesh = *layer->nativeMesh();
|
||||
|
||||
QVERIFY( QgsPoint( 2050, -1000, 280 ).compareTo( &mesh.vertices.at( 0 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 2050, -2000, 50 ).compareTo( &mesh.vertices.at( 1 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 2550, -2500, 650 ).compareTo( &mesh.vertices.at( 2 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 3050, -2000, 50 ).compareTo( &mesh.vertices.at( 3 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 3050, -1000, 280 ).compareTo( &mesh.vertices.at( 4 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 2550, -500, 880 ).compareTo( &mesh.vertices.at( 5 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 1550, -1500, 880 ).compareTo( &mesh.vertices.at( 6 ) ) == 0 );
|
||||
QVERIFY( QgsPoint( 3550, -1500, 880 ).compareTo( &mesh.vertices.at( 7 ) ) == 0 );
|
||||
}
|
||||
|
||||
QGSTEST_MAIN( TestQgsMeshEditor )
|
||||
#include "testqgsmesheditor.moc"
|
||||
|
Loading…
x
Reference in New Issue
Block a user