1 /***************************************************************************
2 qgsmaptooleditmeshframe.cpp - QgsMapToolEditMeshFrame
3
4 ---------------------
5 begin : 24.6.2021
6 copyright : (C) 2021 by Vincent Cloarec
7 email : vcloarec at gmail dot com
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16 #include "qgsmaptooleditmeshframe.h"
17
18 #include <QMessageBox>
19
20 #include "qgis.h"
21 #include "qgisapp.h"
22 #include "qgsapplication.h"
23
24 #include "qgsadvanceddigitizingdockwidget.h"
25 #include "qgsdoublespinbox.h"
26 #include "qgsmeshdataprovider.h"
27 #include "qgsmapcanvas.h"
28 #include "qgsmapmouseevent.h"
29 #include "qgsmessagebar.h"
30 #include "qgsmeshlayer.h"
31 #include "qgsmeshlayerutils.h"
32 #include "qgsmesheditor.h"
33 #include "qgspolygon.h"
34 #include "qgstriangularmesh.h"
35 #include "qgsrubberband.h"
36 #include "qgssnapindicator.h"
37 #include "qgsvertexmarker.h"
38 #include "qgsguiutils.h"
39 #include "qgsmeshtriangulation.h"
40 #include "qgsmeshtransformcoordinatesdockwidget.h"
41 #include "qgsmeshforcebypolylines.h"
42 #include "qgsmaptoolselectionhandler.h"
43 #include "qgsvectorlayer.h"
44 #include "qgsunitselectionwidget.h"
45 #include "qgssettingsregistrycore.h"
46
47 #include "qgsexpressionbuilderwidget.h"
48 #include "qgsmeshselectbyexpressiondialog.h"
49 #include "qgsexpressioncontext.h"
50 #include "qgsexpressioncontextutils.h"
51 #include "qgsexpressionutils.h"
52 #include "qgssettingsregistrycore.h"
53 #include "qgsmaptoolidentify.h"
54 #include "qgsidentifymenu.h"
55
56
57 //
58 // QgsZValueWidget
59 //
60
61
QgsZValueWidget(const QString & label,QWidget * parent)62 QgsZValueWidget::QgsZValueWidget( const QString &label, QWidget *parent ): QWidget( parent )
63 {
64 QHBoxLayout *layout = new QHBoxLayout( this );
65 layout->setContentsMargins( 0, 0, 0, 0 );
66 setLayout( layout );
67
68 if ( !label.isEmpty() )
69 {
70 QLabel *lbl = new QLabel( label, this );
71 lbl->setAlignment( Qt::AlignRight | Qt::AlignCenter );
72 layout->addWidget( lbl );
73 }
74
75 mZValueSpinBox = new QgsDoubleSpinBox( this );
76 mZValueSpinBox->setSingleStep( 1 );
77
78 mZValueSpinBox->setMinimum( -std::numeric_limits<double>::max() );
79 mZValueSpinBox->setMaximum( std::numeric_limits<double>::max() );
80 mZValueSpinBox->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
81 layout->addWidget( mZValueSpinBox );
82 mZValueSpinBox->setClearValue( 0.0 );
83 mZValueSpinBox->clear();
84
85 mZValueSpinBox->setFocusPolicy( Qt::NoFocus );
86 }
87
zValue() const88 double QgsZValueWidget::zValue() const
89 {
90 mZValueSpinBox->interpretText();
91 return mZValueSpinBox->value();
92 }
93
setZValue(double z)94 void QgsZValueWidget::setZValue( double z )
95 {
96 mZValueSpinBox->setValue( z );
97 mZValueSpinBox->selectAll();
98 }
99
setDefaultValue(double z)100 void QgsZValueWidget::setDefaultValue( double z )
101 {
102 mZValueSpinBox->setClearValue( z );
103 mZValueSpinBox->clear();
104 mZValueSpinBox->selectAll();
105 }
106
keyboardEntryWidget() const107 QWidget *QgsZValueWidget::keyboardEntryWidget() const
108 {
109 return mZValueSpinBox;
110 }
111
QgsMeshEditForceByLineAction(QObject * parent)112 QgsMeshEditForceByLineAction::QgsMeshEditForceByLineAction( QObject *parent )
113 : QWidgetAction( parent )
114 {
115 QGridLayout *gLayout = new QGridLayout();
116 gLayout->setContentsMargins( 3, 2, 3, 2 );
117
118 QgsSettings settings;
119
120 mCheckBoxNewVertex = new QCheckBox( tr( "Add new vertex on intersecting edges" ) );
121
122 bool newVertex = settings.value( QStringLiteral( "UI/Mesh/ForceByLineNewVertex" ) ).toBool();
123 mCheckBoxNewVertex->setChecked( newVertex );
124
125 QLabel *labelInterpolation = new QLabel( tr( "Interpolate Z value from" ) );
126 mComboInterpolateFrom = new QComboBox();
127 mComboInterpolateFrom->addItem( tr( "Mesh" ), Mesh );
128 mComboInterpolateFrom->addItem( tr( "Forcing line" ), Lines );
129
130 int interpolateFromValue = settings.enumValue( QStringLiteral( "UI/Mesh/ForceByLineInterpolateFrom" ), Mesh );
131 mComboInterpolateFrom->setCurrentIndex( interpolateFromValue );
132
133 QLabel *labelTolerance = new QLabel( tr( "Tolerance" ) );
134 mToleranceSpinBox = new QgsDoubleSpinBox();
135
136 bool ok;
137 double toleranceValue = settings.value( QStringLiteral( "UI/Mesh/ForceByLineToleranceValue" ), QgsUnitTypes::RenderMapUnits ).toDouble( &ok );
138 if ( !ok )
139 toleranceValue = 1.0;
140 mToleranceSpinBox->setValue( toleranceValue );
141 mToleranceSpinBox->setKeyboardTracking( false );
142 mToleranceSpinBox->setWrapping( false );
143 mToleranceSpinBox->setSingleStep( 0.1 );
144 mToleranceSpinBox->setClearValue( 1.0 );
145
146 mUnitSelecionWidget = new QgsUnitSelectionWidget();
147 mUnitSelecionWidget->setUnits( QgsUnitTypes::RenderUnitList() <<
148 QgsUnitTypes::RenderMetersInMapUnits <<
149 QgsUnitTypes::RenderMapUnits );
150
151 QgsUnitTypes::RenderUnit toleranceUnit = settings.enumValue( QStringLiteral( "UI/Mesh/ForceByLineToleranceUnit" ), QgsUnitTypes::RenderMapUnits );
152 mUnitSelecionWidget->setUnit( toleranceUnit );
153
154 gLayout->addWidget( mCheckBoxNewVertex, 1, 0, 1, 4 );
155 gLayout->addWidget( labelInterpolation, 2, 0, 1, 3 );
156 gLayout->addWidget( mComboInterpolateFrom, 2, 3, 1, 1 );
157 gLayout->addWidget( labelTolerance, 3, 0, 1, 2 );
158 gLayout->addWidget( mToleranceSpinBox, 3, 2, 1, 1 );
159 gLayout->addWidget( mUnitSelecionWidget, 3, 3, 1, 1 );
160
161 QWidget *w = new QWidget();
162 w->setLayout( gLayout );
163 setDefaultWidget( w );
164
165 connect( mCheckBoxNewVertex, &QCheckBox::toggled, this, &QgsMeshEditForceByLineAction::updateSettings );
166 connect( mComboInterpolateFrom, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsMeshEditForceByLineAction::updateSettings );
167 connect( mToleranceSpinBox, qOverload<double>( &QgsDoubleSpinBox::valueChanged ), this, &QgsMeshEditForceByLineAction::updateSettings );
168 connect( mUnitSelecionWidget, &QgsUnitSelectionWidget::changed, this, &QgsMeshEditForceByLineAction::updateSettings );
169 }
170
setMapCanvas(QgsMapCanvas * canvas)171 void QgsMeshEditForceByLineAction::setMapCanvas( QgsMapCanvas *canvas )
172 {
173 mUnitSelecionWidget->setMapCanvas( canvas );
174 }
175
interpolationMode() const176 QgsMeshEditForceByLineAction::IntepolationMode QgsMeshEditForceByLineAction::interpolationMode() const
177 {
178 return static_cast<IntepolationMode>( mComboInterpolateFrom->currentData().toInt() );
179 }
180
newVertexOnIntersectingEdge() const181 bool QgsMeshEditForceByLineAction::newVertexOnIntersectingEdge() const
182 {
183 return mCheckBoxNewVertex->isChecked();
184 }
185
toleranceValue() const186 double QgsMeshEditForceByLineAction::toleranceValue() const
187 {
188 return mToleranceSpinBox->value();
189 }
190
toleranceUnit() const191 QgsUnitTypes::RenderUnit QgsMeshEditForceByLineAction::toleranceUnit() const
192 {
193 return mUnitSelecionWidget->unit();
194 }
195
updateSettings()196 void QgsMeshEditForceByLineAction::updateSettings()
197 {
198 QgsSettings settings;
199
200 settings.setValue( QStringLiteral( "UI/Mesh/ForceByLineNewVertex" ), mCheckBoxNewVertex->isChecked() );
201 settings.setEnumValue( QStringLiteral( "UI/Mesh/ForceByLineInterpolateFrom" ),
202 static_cast<IntepolationMode>( mComboInterpolateFrom->currentData().toInt() ) );
203 settings.setValue( QStringLiteral( "UI/Mesh/ForceByLineToleranceValue" ), mToleranceSpinBox->value() );
204 settings.setEnumValue( QStringLiteral( "UI/Mesh/ForceByLineToleranceUnit" ), mUnitSelecionWidget->unit() );
205 }
206
207 //
208 // QgsMapToolEditMeshFrame
209 //
210
QgsMapToolEditMeshFrame(QgsMapCanvas * canvas)211 QgsMapToolEditMeshFrame::QgsMapToolEditMeshFrame( QgsMapCanvas *canvas )
212 : QgsMapToolAdvancedDigitizing( canvas, QgisApp::instance()->cadDockWidget() )
213 , mSnapIndicator( new QgsSnapIndicator( canvas ) )
214 {
215 mActionDigitizing = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshDigitizing.svg" ) ), tr( "Digitize Mesh elements" ), this );
216 mActionDigitizing->setCheckable( true );
217
218 mActionSelectByPolygon = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshSelectPolygon.svg" ) ), tr( "Select Mesh Elements by Polygon" ), this );
219 mActionSelectByPolygon->setCheckable( true );
220 mActionSelectByPolygon->setObjectName( QStringLiteral( "ActionMeshSelectByPolygon" ) );
221 mActionSelectByExpression = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshSelectExpression.svg" ) ), tr( "Select Mesh Elements by Expression" ), this );
222 mActionSelectByExpression->setObjectName( QStringLiteral( "ActionMeshSelectByExpression" ) );
223
224 mSelectionHandler = std::make_unique<QgsMapToolSelectionHandler>( canvas, QgsMapToolSelectionHandler::SelectPolygon );
225
226 mSelectActions << mActionSelectByPolygon
227 << mActionSelectByExpression;
228
229 mActionTransformCoordinates = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshTransformByExpression.svg" ) ), tr( "Transform Vertices Coordinates" ), this );
230 mActionTransformCoordinates->setCheckable( true );
231
232 mActionForceByLines = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshEditForceByVectorLines.svg" ) ), tr( "Force by Selected Geometries" ), this );
233 mActionForceByLines->setCheckable( true );
234
235 mWidgetActionForceByLine = new QgsMeshEditForceByLineAction( this );
236 mWidgetActionForceByLine->setMapCanvas( canvas );
237
238 mActionReindexMesh = new QAction( QgsApplication::getThemePixmap( QStringLiteral( "/mActionMeshReindex.svg" ) ), tr( "Reindex Faces and Vertices" ), this );
239
240 mActionRemoveVerticesFillingHole = new QAction( this );
241 mActionDelaunayTriangulation = new QAction( tr( "Delaunay Triangulation with Selected Vertices" ), this );
242 mActionFacesRefinement = new QAction( tr( "Refine Current Face" ), this );
243 mActionRemoveVerticesWithoutFillingHole = new QAction( this );
244 mActionRemoveFaces = new QAction( tr( "Remove Current Face" ), this );
245 mActionSplitFaces = new QAction( tr( "Split Current Face" ), this );
246
247 connect( mActionRemoveVerticesFillingHole, &QAction::triggered, this, [this] {removeSelectedVerticesFromMesh( true );} );
248 connect( mActionRemoveVerticesWithoutFillingHole, &QAction::triggered, this, [this] {removeSelectedVerticesFromMesh( false );} );
249 connect( mActionRemoveFaces, &QAction::triggered, this, &QgsMapToolEditMeshFrame::removeFacesFromMesh );
250 connect( mActionSplitFaces, &QAction::triggered, this, &QgsMapToolEditMeshFrame::splitSelectedFaces );
251
252 connect( mActionDigitizing, &QAction::toggled, this, [this]( bool checked )
253 {
254 if ( checked )
255 activateWithState( Digitizing );
256 } );
257
258 for ( int i = 0; i < mSelectActions.count(); ++i )
259 {
260 connect( mSelectActions.at( i ), &QAction::triggered, this, [i]
261 {
262 QgsSettings settings;
263 settings.setValue( QStringLiteral( "UI/Mesh/defaultSelection" ), i );
264 } );
265 }
266
267 connect( mActionSelectByPolygon, &QAction::triggered, this, [this]
268 {
269 if ( mActionSelectByPolygon->isChecked() )
270 {
271 activateWithState( SelectingByPolygon );
272 }
273 else
274 mSelectionBand->reset( QgsWkbTypes::PolygonGeometry );
275 } );
276
277 connect( mActionSelectByExpression, &QAction::triggered, this, &QgsMapToolEditMeshFrame::showSelectByExpressionDialog );
278 connect( mActionTransformCoordinates, &QAction::triggered, this, &QgsMapToolEditMeshFrame::triggerTransformCoordinatesDockWidget );
279 connect( mActionReindexMesh, &QAction::triggered, this, &QgsMapToolEditMeshFrame::reindexMesh );
280 connect( mActionDelaunayTriangulation, &QAction::triggered, this, [this]
281 {
282 if ( mCurrentEditor && mSelectedVertices.count() >= 3 )
283 {
284 QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
285 QgsMeshEditingDelaunayTriangulation triangulation;
286 triangulation.setInputVertices( mSelectedVertices.keys() );
287 mCurrentEditor->advancedEdit( &triangulation );
288
289 if ( !triangulation.message().isEmpty() )
290 QgisApp::instance()->messageBar()->pushInfo( tr( "Delaunay triangulation" ), triangulation.message() );
291 }
292 } );
293 connect( mActionFacesRefinement, &QAction::triggered, this, [this]
294 {
295 QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
296 QgsMeshEditRefineFaces refinement;
297 if ( mCurrentEditor && mSelectedFaces.count() > 0 )
298 {
299 refinement.setInputFaces( mSelectedFaces.values() );
300 mCurrentEditor->advancedEdit( &refinement );
301 }
302 else if ( mCurrentFaceIndex != -1 )
303 {
304 refinement.setInputFaces( {mCurrentFaceIndex} );
305 mCurrentEditor->advancedEdit( &refinement );
306 }
307 } );
308
309 connect( mSelectionHandler.get(), &QgsMapToolSelectionHandler::geometryChanged, this, [this]( Qt::KeyboardModifiers modifiers )
310 {
311 mIsSelectingPolygonInProgress = false;
312 selectByGeometry( mSelectionHandler->selectedGeometry(), modifiers );
313 } );
314
315 connect( mActionForceByLines, &QAction::toggled, this, [this]( bool checked )
316 {
317 if ( mIsInitialized )
318 mForceByLineRubberBand->reset( QgsWkbTypes::LineGeometry );
319 mForcingLineZValue.clear();
320 if ( checked )
321 {
322 onEditingStarted();
323 clearCanvasHelpers();
324 activateWithState( ForceByLines );
325 }
326 } );
327
328 connect( cadDockWidget(), &QgsAdvancedDigitizingDockWidget::cadEnabledChanged, this, [this]( bool enable )
329 {
330 if ( !isActive() || !mCurrentEditor )
331 return;
332
333 if ( enable && mSelectedVertices.isEmpty() )
334 deleteZValueWidget();
335 else if ( !mZValueWidget )
336 createZValueWidget();
337 } );
338
339 setAutoSnapEnabled( true );
340 }
341
activateWithState(State state)342 void QgsMapToolEditMeshFrame::activateWithState( State state )
343 {
344 if ( mCanvas->mapTool() != this )
345 {
346 mCanvas->setMapTool( this );
347 mCanvas->setFocus();
348 onEditingStarted();
349 }
350 mCurrentState = state;
351 }
352
backToDigitizing()353 void QgsMapToolEditMeshFrame::backToDigitizing()
354 {
355 activateWithState( Digitizing );
356 mActionDigitizing->setChecked( true );
357 }
358
~QgsMapToolEditMeshFrame()359 QgsMapToolEditMeshFrame::~QgsMapToolEditMeshFrame()
360 {
361 deleteZValueWidget();
362 }
363
setActionsEnable(bool enable)364 void QgsMapToolEditMeshFrame::setActionsEnable( bool enable )
365 {
366 QList<QAction *> actions;
367 actions
368 << mActionDigitizing
369 << mActionSelectByPolygon
370 << mActionSelectByExpression
371 << mActionTransformCoordinates
372 << mActionForceByLines
373 << mActionReindexMesh;
374
375 for ( QAction *action : std::as_const( actions ) )
376 action->setEnabled( enable );
377 }
378
379
mapToolActions()380 QList<QAction *> QgsMapToolEditMeshFrame::mapToolActions()
381 {
382 return QList<QAction *>()
383 << mActionDigitizing
384 << mActionSelectByPolygon
385 << mActionForceByLines;
386 }
387
digitizeAction() const388 QAction *QgsMapToolEditMeshFrame::digitizeAction() const
389 {
390 return mActionDigitizing;
391 }
392
selectActions() const393 QList<QAction *> QgsMapToolEditMeshFrame::selectActions() const
394 {
395 return mSelectActions;
396 }
397
defaultSelectActions() const398 QAction *QgsMapToolEditMeshFrame::defaultSelectActions() const
399 {
400 QgsSettings settings;
401 bool ok = false;
402 int defaultIndex = settings.value( QStringLiteral( "UI/Mesh/defaultSelection" ) ).toInt( &ok );
403
404 if ( ok )
405 return mSelectActions.at( defaultIndex );
406
407 return mActionSelectByPolygon;
408 }
409
transformAction() const410 QAction *QgsMapToolEditMeshFrame::transformAction() const
411 {
412 return mActionTransformCoordinates;
413 }
414
forceByLinesActions() const415 QList<QAction *> QgsMapToolEditMeshFrame::forceByLinesActions() const
416 {
417 return QList<QAction *>()
418 << mActionForceByLines;
419 }
420
defaultForceAction() const421 QAction *QgsMapToolEditMeshFrame::defaultForceAction() const
422 {
423 return mActionForceByLines;
424 }
425
forceByLineWidgetActionSettings() const426 QWidgetAction *QgsMapToolEditMeshFrame::forceByLineWidgetActionSettings() const
427 {
428 return mWidgetActionForceByLine;
429 }
430
reindexAction() const431 QAction *QgsMapToolEditMeshFrame::reindexAction() const
432 {
433 return mActionReindexMesh;
434 }
435
initialize()436 void QgsMapToolEditMeshFrame::initialize()
437 {
438 if ( !mFaceRubberBand )
439 mFaceRubberBand = createRubberBand( QgsWkbTypes::PolygonGeometry );
440 mFaceRubberBand->setVisible( false );
441 mFaceRubberBand->setZValue( 5 );
442
443 QColor color = digitizingStrokeColor();
444 if ( !mFaceVerticesBand )
445 mFaceVerticesBand = new QgsRubberBand( mCanvas );
446 mFaceVerticesBand->setIcon( QgsRubberBand::ICON_CIRCLE );
447 mFaceVerticesBand->setColor( color );
448 mFaceVerticesBand->setWidth( QgsGuiUtils::scaleIconSize( 2 ) );
449 mFaceVerticesBand->setBrushStyle( Qt::NoBrush );
450 mFaceVerticesBand->setIconSize( QgsGuiUtils::scaleIconSize( 6 ) );
451 mFaceVerticesBand->setVisible( false );
452 mFaceVerticesBand->setZValue( 5 );
453
454 if ( !mVertexBand )
455 mVertexBand = new QgsRubberBand( mCanvas );
456 mVertexBand->setIcon( QgsRubberBand::ICON_CIRCLE );
457 mVertexBand->setColor( color );
458 mVertexBand->setWidth( QgsGuiUtils::scaleIconSize( 2 ) );
459 mVertexBand->setBrushStyle( Qt::NoBrush );
460 mVertexBand->setIconSize( QgsGuiUtils::scaleIconSize( 15 ) );
461 mVertexBand->setVisible( false );
462 mVertexBand->setZValue( 5 );
463
464 if ( !mEdgeBand )
465 mEdgeBand = new QgsRubberBand( mCanvas );
466 QColor color2( color );
467 color2.setAlpha( color2.alpha() / 3 );
468 mEdgeBand->setColor( color2 );
469 mEdgeBand->setWidth( QgsGuiUtils::scaleIconSize( 10 ) );
470 mEdgeBand->setVisible( false );
471
472 if ( !mNewFaceBand )
473 mNewFaceBand = createRubberBand( QgsWkbTypes::PolygonGeometry );
474 mInvalidFaceColor = QColor( 255, 0, 0, mNewFaceBand->fillColor().alpha() ); //override color and keep only the transparency
475 mValidFaceColor = QColor( 0, 255, 0, mNewFaceBand->fillColor().alpha() ); //override color and keep only the transparency
476 mNewFaceBand->setFillColor( mInvalidFaceColor );
477 mNewFaceBand->setVisible( false );
478 mNewFaceBand->setZValue( 10 );
479
480 if ( !mSelectionBand )
481 mSelectionBand = new QgsRubberBand( mCanvas, QgsWkbTypes::PolygonGeometry );
482 mSelectionBand->setFillColor( QColor( 254, 178, 76, 63 ) );
483 mSelectionBand->setStrokeColor( QColor( 254, 58, 29, 100 ) );
484 mSelectionBand->setZValue( 10 );
485
486 if ( !mSelectedFacesRubberband )
487 mSelectedFacesRubberband = new QgsRubberBand( mCanvas, QgsWkbTypes::PolygonGeometry );
488 mSelectedFacesRubberband->setZValue( 1 );
489
490 if ( !mNewFaceMarker )
491 mNewFaceMarker = new QgsVertexMarker( canvas() );
492 mNewFaceMarker->setIconType( QgsVertexMarker::ICON_TRIANGLE );
493 mNewFaceMarker->setIconSize( QgsGuiUtils::scaleIconSize( 12 ) );
494 mNewFaceMarker->setColor( Qt::gray );
495 mNewFaceMarker->setVisible( false );
496 mNewFaceMarker->setPenWidth( 3 );
497
498 if ( !mSelectFaceMarker )
499 mSelectFaceMarker = new QgsVertexMarker( canvas() );
500 mSelectFaceMarker->setIconType( QgsVertexMarker::ICON_BOX );
501 mSelectFaceMarker->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
502 mSelectFaceMarker->setColor( Qt::gray );
503 mSelectFaceMarker->setFillColor( Qt::gray );
504 mSelectFaceMarker->setVisible( false );
505 mSelectFaceMarker->setPenWidth( 3 );
506 mSelectFaceMarker->setZValue( 10 );
507
508 if ( !mSelectEdgeMarker )
509 mSelectEdgeMarker = new QgsVertexMarker( canvas() );
510 mSelectEdgeMarker->setIconType( QgsVertexMarker::ICON_BOX );
511 mSelectEdgeMarker->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
512 mSelectEdgeMarker->setColor( Qt::gray );
513 mSelectEdgeMarker->setFillColor( Qt::gray );
514 mSelectEdgeMarker->setVisible( false );
515 mSelectEdgeMarker->setPenWidth( 3 );
516 mSelectEdgeMarker->setZValue( 10 );
517
518 if ( !mMovingEdgesRubberband )
519 mMovingEdgesRubberband = createRubberBand( QgsWkbTypes::LineGeometry );
520
521 if ( !mMovingFacesRubberband )
522 mMovingFacesRubberband = createRubberBand( QgsWkbTypes::PolygonGeometry );
523
524 if ( !mMovingFreeVertexRubberband )
525 {
526 mMovingFreeVertexRubberband = createRubberBand( QgsWkbTypes::PointGeometry );
527 mMovingFreeVertexRubberband->setIcon( QgsRubberBand::ICON_X );
528 mMovingFreeVertexRubberband->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
529 mMovingFreeVertexRubberband->setWidth( QgsGuiUtils::scaleIconSize( 3 ) );
530 mMovingFreeVertexRubberband->setVisible( true );
531 }
532
533 if ( !mFlipEdgeMarker )
534 mFlipEdgeMarker = new QgsVertexMarker( canvas() );
535 mFlipEdgeMarker->setIconType( QgsVertexMarker::ICON_CIRCLE );
536 mFlipEdgeMarker->setIconSize( QgsGuiUtils::scaleIconSize( 12 ) );
537 mFlipEdgeMarker->setColor( Qt::gray );
538 mFlipEdgeMarker->setVisible( false );
539 mFlipEdgeMarker->setPenWidth( 3 );
540 mFlipEdgeMarker->setZValue( 10 );
541
542 if ( !mMergeFaceMarker )
543 mMergeFaceMarker = new QgsVertexMarker( canvas() );
544 mMergeFaceMarker->setIconType( QgsVertexMarker::ICON_X );
545 mMergeFaceMarker->setIconSize( QgsGuiUtils::scaleIconSize( 12 ) );
546 mMergeFaceMarker->setColor( Qt::gray );
547 mMergeFaceMarker->setVisible( false );
548 mMergeFaceMarker->setPenWidth( 3 );
549 mMergeFaceMarker->setZValue( 10 );
550
551 if ( !mForceByLineRubberBand )
552 mForceByLineRubberBand = createRubberBand( QgsWkbTypes::LineGeometry );
553
554 connect( mCanvas, &QgsMapCanvas::currentLayerChanged, this, &QgsMapToolEditMeshFrame::setCurrentLayer );
555
556 mUserZValue = defaultZValue();
557 createZValueWidget();
558 updateFreeVertices();
559
560 mIsInitialized = true;
561 }
562
deactivate()563 void QgsMapToolEditMeshFrame::deactivate()
564 {
565 QgsMapToolAdvancedDigitizing::deactivate();
566 clearSelection();
567 clearCanvasHelpers();
568 deleteZValueWidget();
569 qDeleteAll( mFreeVertexMarker );
570 mFreeVertexMarker.clear();
571
572 }
573
clearAll()574 void QgsMapToolEditMeshFrame::clearAll()
575 {
576 delete mNewFaceMarker;
577 mNewFaceMarker = nullptr;
578
579 delete mSelectFaceMarker;
580 mSelectFaceMarker = nullptr;
581
582 delete mSelectEdgeMarker;
583 mSelectEdgeMarker = nullptr;
584
585 delete mFlipEdgeMarker;
586 mFlipEdgeMarker = nullptr;
587
588 delete mMergeFaceMarker;
589 mMergeFaceMarker = nullptr;
590
591 mFaceRubberBand->deleteLater();
592 mFaceRubberBand = nullptr;
593
594 mFaceVerticesBand ->deleteLater();
595 mFaceVerticesBand = nullptr;
596
597 mVertexBand->deleteLater();
598 mVertexBand = nullptr;
599
600 mNewFaceBand->deleteLater();
601 mNewFaceBand = nullptr;
602
603 mSelectionBand->deleteLater();
604 mSelectionBand = nullptr;
605
606 mSelectedFacesRubberband->deleteLater();
607 mSelectedFacesRubberband = nullptr;
608
609 deleteZValueWidget();
610 }
611
activate()612 void QgsMapToolEditMeshFrame::activate()
613 {
614 QgsMapToolAdvancedDigitizing::activate();
615 if ( !cadDockWidget()->cadEnabled() )
616 createZValueWidget();
617 }
618
populateContextMenuWithEvent(QMenu * menu,QgsMapMouseEvent * event)619 bool QgsMapToolEditMeshFrame::populateContextMenuWithEvent( QMenu *menu, QgsMapMouseEvent *event )
620 {
621 Q_UNUSED( event );
622
623 switch ( mCurrentState )
624 {
625 case Digitizing:
626 case SelectingByPolygon:
627 {
628 QList<QAction * > newActions;
629 QList<QAction * > lastActions;
630
631 if ( !mSelectedVertices.isEmpty() )
632 {
633 if ( mSelectedVertices.count() >= 3 )
634 lastActions << mActionDelaunayTriangulation;
635
636 newActions << mActionRemoveVerticesFillingHole << mActionRemoveVerticesWithoutFillingHole;
637 }
638
639 if ( !mSelectedFaces.isEmpty() ||
640 ( mCurrentFaceIndex != -1 && mCurrentState == Digitizing ) )
641 {
642 newActions << mActionRemoveFaces;
643 lastActions << mActionFacesRefinement;
644 }
645
646 if ( mSplittableFaceCount > 0 ||
647 ( mCurrentFaceIndex != -1 && mCurrentEditor->faceCanBeSplit( mCurrentFaceIndex ) ) )
648 newActions << mActionSplitFaces;
649
650 QList<QAction * > existingActions = menu->actions();
651 if ( !newActions.isEmpty() )
652 {
653 if ( existingActions.isEmpty() )
654 {
655 menu->addActions( newActions );
656 if ( !lastActions.empty() )
657 {
658 menu->addSeparator();
659 menu->addActions( lastActions );
660 }
661 }
662 else
663 {
664 menu->insertActions( existingActions.first(), newActions );
665 menu->insertSeparator( existingActions.first() );
666 menu->insertActions( existingActions.first(), lastActions );
667 menu->insertSeparator( existingActions.first() );
668 }
669 return true;
670 }
671 return false;
672 }
673 case AddingNewFace:
674 case Selecting:
675 case MovingSelection:
676 case ForceByLines:
677 return false;
678 }
679
680 return false;
681 }
682
flags() const683 QgsMapTool::Flags QgsMapToolEditMeshFrame::flags() const
684 {
685 switch ( mCurrentState )
686 {
687 case Digitizing:
688 if ( !mCadDockWidget->cadEnabled() || !mSelectedVertices.isEmpty() || mCurrentFaceIndex != -1 )
689 return QgsMapTool::Flags() | QgsMapTool::ShowContextMenu;
690 FALLTHROUGH
691 case AddingNewFace:
692 case Selecting:
693 case MovingSelection:
694 case SelectingByPolygon:
695 case ForceByLines:
696 return QgsMapTool::Flags();
697 break;
698 }
699
700 return QgsMapTool::Flags();
701 }
702
searchFeatureOnMap(QgsMapMouseEvent * e,QgsMapCanvas * canvas,const QList<QgsWkbTypes::GeometryType> & geomType)703 static QList<QgsMapToolIdentify::IdentifyResult> searchFeatureOnMap( QgsMapMouseEvent *e, QgsMapCanvas *canvas, const QList<QgsWkbTypes::GeometryType> &geomType )
704 {
705 QList<QgsMapToolIdentify::IdentifyResult> results;
706 const QMap< QString, QString > derivedAttributes;
707
708 QgsPointXY mapPoint = e->mapPoint();
709 double x = mapPoint.x(), y = mapPoint.y();
710 const double sr = QgsMapTool::searchRadiusMU( canvas );
711
712 const QList<QgsMapLayer *> layers = canvas->layers();
713 for ( QgsMapLayer *layer : layers )
714 {
715 if ( layer->type() == QgsMapLayerType::VectorLayer )
716 {
717 QgsVectorLayer *vectorLayer = static_cast<QgsVectorLayer *>( layer );
718
719 bool typeIsSelectable = false;
720 for ( const QgsWkbTypes::GeometryType &type : geomType )
721 if ( vectorLayer->geometryType() == type )
722 {
723 typeIsSelectable = true;
724 break;
725 }
726 if ( typeIsSelectable )
727 {
728 QgsRectangle rect( x - sr, y - sr, x + sr, y + sr );
729 QgsCoordinateTransform transform = canvas->mapSettings().layerTransform( vectorLayer );
730
731 try
732 {
733 rect = transform.transformBoundingBox( rect, Qgis::TransformDirection::Reverse );
734 }
735 catch ( QgsCsException & )
736 {
737 QgsDebugMsg( QStringLiteral( "Could not transform geometry to layer CRS" ) );
738 }
739
740 QgsFeatureIterator fit = vectorLayer->getFeatures( QgsFeatureRequest()
741 .setFilterRect( rect )
742 .setFlags( QgsFeatureRequest::ExactIntersect ) );
743 QgsFeature f;
744 while ( fit.nextFeature( f ) )
745 {
746 results << QgsMapToolIdentify::IdentifyResult( vectorLayer, f, derivedAttributes );
747 }
748 }
749 }
750 }
751
752 return results;
753 }
754
forceByLineBySelectedFeature(QgsMapMouseEvent * e)755 void QgsMapToolEditMeshFrame::forceByLineBySelectedFeature( QgsMapMouseEvent *e )
756 {
757 const QList<QgsMapToolIdentify::IdentifyResult> &results =
758 searchFeatureOnMap( e, mCanvas, QList<QgsWkbTypes::GeometryType>() << QgsWkbTypes::PolygonGeometry << QgsWkbTypes::LineGeometry );
759
760 QgsIdentifyMenu *menu = new QgsIdentifyMenu( mCanvas );
761 menu->setExecWithSingleResult( true );
762 menu->setAllowMultipleReturn( false );
763 const QPoint globalPos = mCanvas->mapToGlobal( QPoint( e->pos().x() + 5, e->pos().y() + 5 ) );
764 const QList<QgsMapToolIdentify::IdentifyResult> selectedFeatures = menu->exec( results, globalPos );
765 menu->deleteLater();
766
767 if ( !selectedFeatures.empty() && selectedFeatures[0].mFeature.hasGeometry() )
768 {
769 QgsCoordinateTransform transform = mCanvas->mapSettings().layerTransform( selectedFeatures.at( 0 ).mLayer );
770 QgsGeometry geom = selectedFeatures[0].mFeature.geometry();
771 try
772 {
773 geom.transform( transform );
774 }
775 catch ( QgsCsException & )
776 {
777 QgsDebugMsg( QStringLiteral( "Could not transform geometry to layer CRS" ) );
778 }
779 forceByLine( geom );
780 }
781
782 return;
783 }
784
cadCanvasPressEvent(QgsMapMouseEvent * e)785 void QgsMapToolEditMeshFrame::cadCanvasPressEvent( QgsMapMouseEvent *e )
786 {
787 if ( !mCurrentEditor )
788 return;
789
790 if ( e->button() == Qt::LeftButton &&
791 ( !mCadDockWidget->cadEnabled() ||
792 mCadDockWidget->additionalConstraint() == QgsAdvancedDigitizingDockWidget::AdditionalConstraint::NoConstraint ) )
793 mLeftButtonPressed = true;
794
795 switch ( mCurrentState )
796 {
797 case Digitizing:
798 if ( e->button() == Qt::LeftButton )
799 mStartSelectionPos = e->pos();
800 break;
801 case AddingNewFace:
802 case Selecting:
803 case MovingSelection:
804 case ForceByLines:
805 if ( e->button() == Qt::LeftButton )
806 mSelectionBand->reset( QgsWkbTypes::PolygonGeometry );
807 break;
808 case SelectingByPolygon:
809 if ( mSelectionHandler )
810 {
811 if ( e->button() == Qt::RightButton )
812 {
813 // here, quite tricky because 3 possibilities:
814 // - a polygon has started to be digitized for selection -> right click validate the selection
815 // - right click on an existing vector layer feature -> a menu is executed to choose a feature
816 // - other case -> context menu of mesh editing to apply an edit on selected element
817 // The last case is launched only if the other cases do not appears
818 // With the selection handler, if we can know if the selecting polygon change, that means the first case appears,
819 // we can't know if a feature is found or not (if the user do not choose a feature, nothing happen like if no feature was found).
820 // The workaround is to check if a feature exist under the mouse before sending the event to the selection handler.
821 // This is not ideal because that leads to a double search but no better idea for now to allow the editing context menu with selecting by polygon
822
823 bool hasSelectableFeature = !searchFeatureOnMap( e, mCanvas, QList<QgsWkbTypes::GeometryType>() << QgsWkbTypes::PolygonGeometry ).isEmpty();
824
825 if ( hasSelectableFeature || mIsSelectingPolygonInProgress )
826 mSelectionHandler->canvasPressEvent( e );
827 else
828 {
829 QMenu menu;
830 populateContextMenuWithEvent( &menu, e );
831 menu.exec( e->globalPos() );
832 }
833 }
834 else
835 {
836 mIsSelectingPolygonInProgress = true;
837 mSelectionHandler->canvasPressEvent( e );
838 }
839 }
840
841 break;
842 }
843
844 QgsMapToolAdvancedDigitizing::cadCanvasPressEvent( e );
845 }
846
cadCanvasMoveEvent(QgsMapMouseEvent * e)847 void QgsMapToolEditMeshFrame::cadCanvasMoveEvent( QgsMapMouseEvent *e )
848 {
849 if ( !mCurrentEditor )
850 return;
851
852 const QgsPointXY &mapPoint = e->mapPoint();
853
854 mSnapIndicator->setMatch( e->mapPointMatch() );
855
856 if ( mLeftButtonPressed && mCurrentState == Digitizing )
857 {
858 mCurrentState = Selecting;
859 }
860
861 switch ( mCurrentState )
862 {
863 case Digitizing:
864 highLight( mapPoint );
865 break;
866 case AddingNewFace:
867 mNewFaceBand->movePoint( mapPoint );
868 highLight( mapPoint );
869 if ( testNewVertexInFaceCanditate( mCurrentVertexIndex ) )
870 mNewFaceBand->setColor( mValidFaceColor );
871 else
872 mNewFaceBand->setColor( mInvalidFaceColor );
873 break;
874 case Selecting:
875 {
876 const QRect &rect = QRect( e->pos(), mStartSelectionPos );
877 mSelectionBand->setToCanvasRectangle( rect );
878 }
879 break;
880 case MovingSelection:
881 {
882 moveSelection( mapPoint );
883 }
884 break;
885 case SelectingByPolygon:
886 if ( mSelectionHandler )
887 mSelectionHandler->canvasMoveEvent( e );
888 break;
889 case ForceByLines:
890 searchFace( mapPoint );
891 searchEdge( mapPoint );
892 highlightCloseVertex( mapPoint );
893
894 const QgsPointLocator::Match &matchPoint = e->mapPointMatch();
895
896 if ( mCurrentVertexIndex != -1 )
897 {
898 mForceByLineRubberBand->movePoint( mapVertexXY( mCurrentVertexIndex ) );
899 if ( mZValueWidget )
900 mZValueWidget->setZValue( mapVertex( mCurrentVertexIndex ).z() );
901 }
902 else if ( matchPoint.isValid() && matchPoint.layer() && QgsWkbTypes::hasZ( matchPoint.layer()->wkbType() ) )
903 {
904 mForceByLineRubberBand->movePoint( mapPoint );
905 if ( mZValueWidget )
906 mZValueWidget->setZValue( e->mapPointMatch().interpolatedPoint( mCanvas->mapSettings().destinationCrs() ).z() );
907 }
908 else
909 {
910 mForceByLineRubberBand->movePoint( mapPoint );
911 if ( mZValueWidget )
912 mZValueWidget->setZValue( mUserZValue );
913 }
914 break;
915 }
916
917 QgsMapToolAdvancedDigitizing::cadCanvasMoveEvent( e );
918 }
919
cadCanvasReleaseEvent(QgsMapMouseEvent * e)920 void QgsMapToolEditMeshFrame::cadCanvasReleaseEvent( QgsMapMouseEvent *e )
921 {
922 if ( !mCurrentEditor )
923 return;
924 double tolerance = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
925
926 QgsPointXY mapPoint = e->mapPoint();
927
928 // advanced digitizing constraint only the first release of double clicks
929 // so we need to store the click point for the next one that could be a double clicks
930 if ( !mDoubleClicks )
931 {
932 mFirstClickPoint = mapPoint;
933 mFirstClickZValue = currentZValue();
934 }
935
936 if ( e->button() == Qt::LeftButton )
937 mLeftButtonPressed = false;
938
939 switch ( mCurrentState )
940 {
941 case Digitizing:
942 if ( e->button() == Qt::LeftButton )
943 {
944 if ( mDoubleClicks ) //double clicks --> add a vertex
945 {
946 addVertex( mFirstClickPoint, e->mapPointMatch() );
947 mCadDockWidget->setPoints( QList<QgsPointXY>() << mFirstClickPoint << mFirstClickPoint );
948 }
949 else if ( mNewFaceMarker->isVisible() &&
950 mapPoint.distance( mNewFaceMarker->center() ) < tolerance
951 && mCurrentVertexIndex >= 0 ) //new face marker clicked --> start adding a new face
952 {
953 clearSelection();
954 mCurrentState = AddingNewFace;
955 mNewFaceMarker->setVisible( false );
956 mNewFaceBand->setVisible( true );
957 mNewFaceBand->reset( QgsWkbTypes::PolygonGeometry );
958 addVertexToFaceCanditate( mCurrentVertexIndex );
959 const QgsPointXY ¤tPoint = mapVertexXY( mCurrentVertexIndex );
960 cadDockWidget()->setPoints( QList<QgsPointXY>() << currentPoint << currentPoint );
961 }
962 else if ( isSelectionGrapped( mapPoint ) && //click on a selected vertex, an edge or face box
963 !( e->modifiers() &Qt::ControlModifier ) ) // without control modifier that is used to remove from the selection
964 {
965 mCurrentState = MovingSelection;
966 mCadDockWidget->setEnabledZ( false );
967 mStartMovingPoint = mapPoint;
968 cadDockWidget()->setPoints( QList<QgsPointXY>() << mapPoint << mapPoint );
969 }
970 else if ( mFlipEdgeMarker->isVisible() &&
971 e->mapPoint().distance( mFlipEdgeMarker->center() ) < tolerance &&
972 mCurrentEdge.first != -1 && mCurrentEdge.second != -1 ) // flip edge
973 {
974 clearSelection();
975 QVector<int> edgeVert = edgeVertices( mCurrentEdge );
976 mCurrentEditor->flipEdge( edgeVert.at( 0 ), edgeVert.at( 1 ) );
977 mCurrentEdge = {-1, -1};
978 highLight( mapPoint );
979 }
980 else if ( mMergeFaceMarker->isVisible() &&
981 e->mapPoint().distance( mMergeFaceMarker->center() ) < tolerance &&
982 mCurrentEdge.first != -1 && mCurrentEdge.second != -1 ) // merge two faces
983 {
984 clearSelection();
985 QVector<int> edgeVert = edgeVertices( mCurrentEdge );
986 mCurrentEditor->merge( edgeVert.at( 0 ), edgeVert.at( 1 ) );
987 mCurrentEdge = {-1, -1};
988 highLight( mapPoint );
989 }
990 else
991 select( mapPoint, e->modifiers(), tolerance );
992 }
993 break;
994 case AddingNewFace:
995 if ( e->button() == Qt::LeftButton ) //eventually add a vertex to the face
996 {
997 if ( mDoubleClicks )
998 {
999 addVertex( mFirstClickPoint, e->mapPointMatch() );
1000 highlightCloseVertex( mFirstClickPoint );
1001 }
1002
1003 if ( mCurrentVertexIndex != -1 )
1004 {
1005 addVertexToFaceCanditate( mCurrentVertexIndex );
1006 QgsPointXY currentPoint = mapVertexXY( mCurrentVertexIndex );
1007 cadDockWidget()->setPoints( QList<QgsPointXY>() << currentPoint << currentPoint );
1008 }
1009 }
1010 else if ( e->button() == Qt::RightButton ) //if possible validate and add the face to the mesh
1011 {
1012 if ( testNewVertexInFaceCanditate( -1 ) )
1013 {
1014 mCurrentEditor->addFace( mNewFaceCandidate.toVector() );
1015 mNewFaceBand->reset( QgsWkbTypes::PolygonGeometry );
1016 mNewFaceCandidate.clear();
1017 mCurrentState = Digitizing;
1018 }
1019 }
1020 break;
1021 case Selecting:
1022 {
1023 QgsGeometry selectionGeom = mSelectionBand->asGeometry();
1024 selectByGeometry( selectionGeom, e->modifiers() );
1025 mSelectionBand->reset( QgsWkbTypes::PolygonGeometry );
1026 mCurrentState = Digitizing;
1027 }
1028 break;
1029 case MovingSelection:
1030 if ( mIsMovingAllowed )
1031 {
1032 const QList<int> verticesIndexes = mSelectedVertices.keys();
1033 QList<QgsPointXY> newPosition;
1034 newPosition.reserve( verticesIndexes.count() );
1035
1036 const QgsMeshVertex &mapPointInNativeCoordinate =
1037 mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( QgsMeshVertex( mapPoint.x(), mapPoint.y() ) );
1038 const QgsMeshVertex &startingPointInNativeCoordinate =
1039 mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( QgsMeshVertex( mStartMovingPoint.x(), mStartMovingPoint.y() ) );
1040 const QgsVector &translationInLayerCoordinate = mapPointInNativeCoordinate - startingPointInNativeCoordinate;
1041
1042 const QgsMesh &mesh = *mCurrentLayer->nativeMesh();
1043 mKeepSelectionOnEdit = true;
1044 if ( verticesIndexes.count() != 1 )
1045 {
1046 for ( int i = 0; i < verticesIndexes.count(); ++i )
1047 newPosition.append( QgsPointXY( mesh.vertex( verticesIndexes.at( i ) ) ) + translationInLayerCoordinate );
1048 mCurrentEditor->changeXYValues( verticesIndexes, newPosition );
1049 }
1050 else
1051 {
1052 //only one vertex, change also the Z value if snap on a 3D vector layer
1053 if ( e->mapPointMatch().isValid() &&
1054 QgsWkbTypes::hasZ( e->mapPointMatch().layer()->wkbType() ) )
1055 {
1056 const QgsMeshVertex mapPointInMapCoordinate =
1057 QgsMeshVertex( mapPoint.x(), mapPoint.y(), e->mapPointMatch().interpolatedPoint( mCanvas->mapSettings().destinationCrs() ).z() );
1058
1059 const QgsMeshVertex &mapPointInNativeCoordinate =
1060 mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( mapPointInMapCoordinate ) ;
1061 mCurrentEditor->changeCoordinates( verticesIndexes,
1062 QList<QgsPoint>()
1063 << mapPointInNativeCoordinate ) ;
1064 }
1065 else
1066 mCurrentEditor->changeXYValues( verticesIndexes, QList<QgsPointXY>()
1067 << QgsPointXY( mesh.vertex( verticesIndexes.at( 0 ) ) ) + translationInLayerCoordinate );
1068 }
1069 }
1070 updateSelectecVerticesMarker();
1071 prepareSelection();
1072 clearCanvasHelpers();
1073 mMovingEdgesRubberband->reset();
1074 mMovingFacesRubberband->reset();
1075 mMovingFreeVertexRubberband->reset();
1076 mCadDockWidget->setEnabledZ( mCadDockWidget->cadEnabled() );
1077 mCurrentState = Digitizing;
1078 break;
1079 case SelectingByPolygon:
1080 if ( mSelectionHandler )
1081 mSelectionHandler->canvasReleaseEvent( e );
1082 break;
1083 case ForceByLines:
1084 forceByLineReleaseEvent( e );
1085 break;
1086 }
1087 mDoubleClicks = false;
1088
1089 QgsMapToolAdvancedDigitizing::cadCanvasReleaseEvent( e );
1090 }
1091
moveSelection(const QgsPointXY & destinationPoint)1092 void QgsMapToolEditMeshFrame::moveSelection( const QgsPointXY &destinationPoint )
1093 {
1094 const QgsVector &translation = destinationPoint - mStartMovingPoint;
1095 mMovingEdgesRubberband->reset( QgsWkbTypes::LineGeometry );
1096 mMovingFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
1097 mMovingFreeVertexRubberband->reset( QgsWkbTypes::PointGeometry );
1098 QgsGeometry movingFacesGeometry = mSelectedFacesRubberband->asGeometry();
1099 movingFacesGeometry.translate( translation.x(), translation.y() );
1100 mMovingFacesRubberband->setToGeometry( movingFacesGeometry );
1101
1102 QSet<int> borderMovingFace;
1103
1104 for ( QMap<int, SelectedVertexData>::const_iterator it = mSelectedVertices.constBegin(); it != mSelectedVertices.constEnd(); ++it )
1105 {
1106 const QgsPointXY &point1 = mapVertexXY( it.key() ) + translation;
1107 const SelectedVertexData &vertexData = it.value();
1108 for ( int i = 0; i < vertexData.meshFixedEdges.count(); ++i )
1109 {
1110 const QgsPointXY point2 = mapVertexXY( vertexData.meshFixedEdges.at( i ).second );
1111 QgsGeometry edge( new QgsLineString( {point1, point2} ) );
1112 mMovingEdgesRubberband->addGeometry( edge );
1113 int associateFace = vertexData.meshFixedEdges.at( i ).first;
1114 if ( associateFace != -1 )
1115 borderMovingFace.insert( associateFace );
1116 }
1117
1118 for ( int i = 0; i < vertexData.borderEdges.count(); ++i )
1119 {
1120 const QgsPointXY point2 = mapVertexXY( vertexData.borderEdges.at( i ).second ) + translation;
1121 const QgsGeometry edge( new QgsLineString( {point1, point2} ) );
1122 mMovingEdgesRubberband->addGeometry( edge );
1123 }
1124
1125 if ( mCurrentEditor->isVertexFree( it.key() ) )
1126 mMovingFreeVertexRubberband->addPoint( mapVertexXY( it.key() ) + translation, false );
1127 }
1128
1129 mMovingFreeVertexRubberband->setVisible( true );
1130 mMovingFreeVertexRubberband->updatePosition();
1131 mMovingFreeVertexRubberband->update();
1132
1133 const QgsMeshVertex &mapPointInNativeCoordinate =
1134 mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( QgsMeshVertex( destinationPoint.x(), destinationPoint.y() ) );
1135 const QgsMeshVertex &startingPointInNativeCoordinate =
1136 mCurrentLayer->triangularMesh()->triangularToNativeCoordinates( QgsMeshVertex( mStartMovingPoint.x(), mStartMovingPoint.y() ) );
1137 const QgsVector &translationInLayerCoordinate = mapPointInNativeCoordinate - startingPointInNativeCoordinate;
1138
1139 auto transformFunction = [translationInLayerCoordinate, this ]( int vi )-> const QgsMeshVertex
1140 {
1141 if ( mSelectedVertices.contains( vi ) )
1142 return mCurrentLayer->nativeMesh()->vertex( vi ) + translationInLayerCoordinate;
1143 else
1144 return mCurrentLayer->nativeMesh()->vertex( vi );
1145 };
1146
1147 // we test only the faces that are deformed on the border, moving and not deformed faces are tested later
1148 mIsMovingAllowed = mCurrentEditor->canBeTransformed( qgis::setToList( borderMovingFace ), transformFunction );
1149
1150 if ( mIsMovingAllowed )
1151 {
1152 //to finish test if the polygons formed by the moving faces contains something else
1153 const QList<int> &faceIndexesIntersect = mCurrentLayer->triangularMesh()->nativeFaceIndexForRectangle( movingFacesGeometry.boundingBox() );
1154 for ( const int faceIndex : faceIndexesIntersect )
1155 {
1156 if ( mConcernedFaceBySelection.contains( faceIndex ) )
1157 continue;
1158 const QgsGeometry otherFaceGeom( new QgsPolygon( new QgsLineString( nativeFaceGeometry( faceIndex ) ) ) );
1159 mIsMovingAllowed &= !movingFacesGeometry.intersects( otherFaceGeom );
1160 if ( !mIsMovingAllowed )
1161 break;
1162 }
1163
1164 if ( mIsMovingAllowed ) //last check, the free vertices...
1165 {
1166 const QList<int> &freeVerticesIndexes = mCurrentEditor->freeVerticesIndexes();
1167 for ( const int vertexIndex : freeVerticesIndexes )
1168 {
1169 const QgsMeshVertex transformedVertex = transformFunction( vertexIndex );
1170 const QgsMeshVertex &mapTransformedVertex = mCurrentLayer->triangularMesh()->nativeToTriangularCoordinates( transformedVertex );
1171 const QgsPointXY pointInMap( mapTransformedVertex );
1172 mIsMovingAllowed &= !movingFacesGeometry.contains( &pointInMap );
1173 if ( !mIsMovingAllowed )
1174 break;
1175 }
1176 }
1177 }
1178
1179 setMovingRubberBandValidity( mIsMovingAllowed );
1180 }
1181
select(const QgsPointXY & mapPoint,Qt::KeyboardModifiers modifiers,double tolerance)1182 void QgsMapToolEditMeshFrame::select( const QgsPointXY &mapPoint, Qt::KeyboardModifiers modifiers, double tolerance )
1183 {
1184 Qgis::SelectBehavior behavior;
1185 if ( modifiers & Qt::ShiftModifier )
1186 behavior = Qgis::SelectBehavior::AddToSelection;
1187 else if ( modifiers & Qt::ControlModifier )
1188 behavior = Qgis::SelectBehavior::RemoveFromSelection;
1189 else
1190 behavior = Qgis::SelectBehavior::SetSelection;
1191
1192 QgsPointXY currentPoint = mapPoint;
1193
1194 if ( mSelectFaceMarker->isVisible() &&
1195 mapPoint.distance( mSelectFaceMarker->center() ) < tolerance
1196 && mCurrentFaceIndex >= 0 )
1197 {
1198 setSelectedVertices( nativeFace( mCurrentFaceIndex ).toList(), behavior );
1199 currentPoint = mCurrentLayer->triangularMesh()->faceCentroids().at( mCurrentFaceIndex );
1200 }
1201 else if ( mCurrentVertexIndex != -1 )
1202 {
1203 setSelectedVertices( QList<int>() << mCurrentVertexIndex, behavior );
1204 currentPoint = mCurrentLayer->triangularMesh()->vertices().at( mCurrentVertexIndex );
1205 }
1206 else if ( mSelectEdgeMarker->isVisible() &&
1207 mapPoint.distance( mSelectEdgeMarker->center() ) < tolerance &&
1208 mCurrentEdge.first != -1 && mCurrentEdge.second != -1 )
1209 {
1210 QVector<int> edgeVert = edgeVertices( mCurrentEdge );
1211 setSelectedVertices( edgeVert.toList(), behavior );
1212 const QgsMeshVertex v1 = mCurrentLayer->triangularMesh()->vertices().at( edgeVert.at( 0 ) );
1213 const QgsMeshVertex v2 = mCurrentLayer->triangularMesh()->vertices().at( edgeVert.at( 1 ) );
1214 currentPoint = QgsPointXY( ( v1.x() + v2.x() ) / 2, ( v1.y() + v2.y() ) / 2 );
1215 }
1216 else
1217 setSelectedVertices( QList<int>(), behavior );
1218 mCadDockWidget->setPoints( QList < QgsPointXY>() << currentPoint << currentPoint );
1219 }
1220
keyPressEvent(QKeyEvent * e)1221 void QgsMapToolEditMeshFrame::keyPressEvent( QKeyEvent *e )
1222 {
1223 bool consumned = false;
1224 switch ( mCurrentState )
1225 {
1226 case Digitizing:
1227 {
1228 if ( e->key() == Qt::Key_Delete && ( e->modifiers() & Qt::ControlModifier ) )
1229 {
1230 removeSelectedVerticesFromMesh( !( e->modifiers() & Qt::ShiftModifier ) );
1231 consumned = true;
1232 }
1233 else if ( e->key() == Qt::Key_Delete && ( e->modifiers() & Qt::ShiftModifier ) )
1234 {
1235 removeFacesFromMesh();
1236 consumned = true;
1237 }
1238
1239 if ( e->key() == Qt::Key_Escape )
1240 {
1241 clearSelection();
1242 consumned = true;
1243 }
1244
1245 if ( e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter )
1246 {
1247 applyZValueOnSelectedVertices();
1248 consumned = true;
1249 }
1250 }
1251 break;
1252 case AddingNewFace:
1253 {
1254 if ( e->key() == Qt::Key_Backspace )
1255 {
1256 mNewFaceBand->removePoint( -2, true );
1257 if ( !mNewFaceCandidate.isEmpty() )
1258 mNewFaceCandidate.removeLast();
1259 if ( mNewFaceCandidate.isEmpty() )
1260 mCurrentState = Digitizing;
1261 consumned = true;
1262 }
1263
1264 if ( e->key() == Qt::Key_Escape )
1265 {
1266 mNewFaceBand->reset( QgsWkbTypes::PolygonGeometry );
1267 mNewFaceCandidate.clear();
1268 mCurrentState = Digitizing;
1269 consumned = true;
1270 }
1271 }
1272 break;
1273 case MovingSelection:
1274 if ( e->key() == Qt::Key_Escape )
1275 {
1276 mCurrentState = Digitizing;
1277 mMovingEdgesRubberband->reset( QgsWkbTypes::LineGeometry );
1278 mMovingFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
1279 mMovingFreeVertexRubberband->reset( QgsWkbTypes::PointGeometry );
1280 mCadDockWidget->setEnabledZ( mCadDockWidget->cadEnabled() );
1281 }
1282 break;
1283 case ForceByLines:
1284 if ( e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter )
1285 {
1286 if ( !mCadDockWidget->cadEnabled() )
1287 mUserZValue = currentZValue();
1288 }
1289
1290 if ( e->key() == Qt::Key_Escape )
1291 {
1292 mForceByLineRubberBand->reset( QgsWkbTypes::LineGeometry );
1293 mForcingLineZValue.clear();
1294 }
1295 break;
1296 case Selecting:
1297 case SelectingByPolygon:
1298 if ( e->key() == Qt::Key_Escape )
1299 {
1300 clearSelection();
1301 consumned = true;
1302 }
1303 break;
1304 }
1305
1306 if ( !consumned && mZValueWidget )
1307 QgsApplication::sendEvent( mZValueWidget->keyboardEntryWidget(), e );
1308 else
1309 e->ignore();
1310
1311 if ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete )
1312 e->ignore();
1313
1314 QgsMapToolAdvancedDigitizing::keyPressEvent( e );
1315 }
1316
keyReleaseEvent(QKeyEvent * e)1317 void QgsMapToolEditMeshFrame::keyReleaseEvent( QKeyEvent *e )
1318 {
1319 bool consumned = false;
1320 switch ( mCurrentState )
1321 {
1322 case Digitizing:
1323 break;
1324 case AddingNewFace:
1325 if ( e->key() == Qt::Key_Backspace )
1326 consumned = true; //to avoid removing the value of the ZvalueWidget
1327 break;
1328 case SelectingByPolygon:
1329 if ( mSelectionHandler )
1330 mSelectionHandler->keyReleaseEvent( e );
1331 break;
1332 case Selecting:
1333 case MovingSelection:
1334 case ForceByLines:
1335 break;
1336 }
1337
1338 if ( !consumned && mZValueWidget )
1339 QgsApplication::sendEvent( mZValueWidget->keyboardEntryWidget(), e );
1340
1341 QgsMapToolAdvancedDigitizing::keyReleaseEvent( e );
1342 }
1343
canvasDoubleClickEvent(QgsMapMouseEvent * e)1344 void QgsMapToolEditMeshFrame::canvasDoubleClickEvent( QgsMapMouseEvent *e )
1345 {
1346 Q_UNUSED( e )
1347 //canvasReleseaseEvent() will be called just after the last click, so just flag the double clicks
1348 mDoubleClicks = true;
1349
1350 QgsMapToolAdvancedDigitizing::canvasDoubleClickEvent( e );
1351 }
1352
onEditingStopped()1353 void QgsMapToolEditMeshFrame::onEditingStopped()
1354 {
1355 mCurrentEditor = nullptr;
1356 deactivate();
1357 }
1358
mapVertex(int index) const1359 const QgsMeshVertex QgsMapToolEditMeshFrame::mapVertex( int index ) const
1360 {
1361 if ( mCurrentLayer.isNull() || ! mCurrentLayer->triangularMesh() )
1362 return QgsMeshVertex();
1363
1364 return mCurrentLayer->triangularMesh()->vertices().at( index );
1365 }
1366
mapVertexXY(int index) const1367 const QgsPointXY QgsMapToolEditMeshFrame::mapVertexXY( int index ) const
1368 {
1369 const QgsMeshVertex &v = mapVertex( index );
1370 return QgsPointXY( v.x(), v.y() );
1371 }
1372
nativeFace(int index) const1373 const QgsMeshFace QgsMapToolEditMeshFrame::nativeFace( int index ) const
1374 {
1375 if ( mCurrentLayer.isNull() || ! mCurrentLayer->nativeMesh() )
1376 return QgsMeshFace();
1377
1378 return mCurrentLayer->nativeMesh()->face( index );
1379 }
1380
currentZValue()1381 double QgsMapToolEditMeshFrame::currentZValue()
1382 {
1383 if ( mDoubleClicks )
1384 return mFirstClickZValue;
1385 else if ( mZValueWidget )
1386 return mZValueWidget->zValue();
1387 else if ( mCadDockWidget->cadEnabled() )
1388 return mCadDockWidget->currentPointV2().z();
1389
1390 return defaultZValue();
1391 }
1392
searchFace(const QgsPointXY & mapPoint)1393 void QgsMapToolEditMeshFrame::searchFace( const QgsPointXY &mapPoint )
1394 {
1395 if ( !mCurrentLayer.isNull() && mCurrentLayer->triangularMesh() )
1396 mCurrentFaceIndex = mCurrentLayer->triangularMesh()->nativeFaceIndexForPoint( mapPoint );
1397 }
1398
searchEdge(const QgsPointXY & mapPoint)1399 void QgsMapToolEditMeshFrame::searchEdge( const QgsPointXY &mapPoint )
1400 {
1401 mCurrentEdge = {-1, -1};
1402 double tolerance = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
1403
1404 QList<int> candidateFaceIndexes;
1405
1406 if ( mCurrentFaceIndex != -1 )
1407 {
1408 candidateFaceIndexes.append( mCurrentFaceIndex );
1409 }
1410 else
1411 {
1412 const QgsRectangle searchRect( mapPoint.x() - tolerance, mapPoint.y() - tolerance, mapPoint.x() + tolerance, mapPoint.y() + tolerance );
1413 candidateFaceIndexes = mCurrentLayer->triangularMesh()->nativeFaceIndexForRectangle( searchRect );
1414 }
1415
1416 double minimumDistance = std::numeric_limits<double>::max();
1417 for ( const int faceIndex : std::as_const( candidateFaceIndexes ) )
1418 {
1419 const QgsMeshFace &face = nativeFace( faceIndex );
1420 int faceSize = face.count();
1421 for ( int i = 0; i < faceSize; ++i )
1422 {
1423 int iv1 = face.at( i );
1424 int iv2 = face.at( ( i + 1 ) % faceSize );
1425
1426 QgsPointXY pt1 = mapVertexXY( iv1 );
1427 QgsPointXY pt2 = mapVertexXY( iv2 );
1428
1429 QgsPointXY pointOneEdge;
1430 double distance = sqrt( mapPoint.sqrDistToSegment( pt1.x(), pt1.y(), pt2.x(), pt2.y(), pointOneEdge ) );
1431 if ( distance < tolerance && distance < minimumDistance && edgeCanBeInteractive( iv1, iv2 ) )
1432 {
1433 mCurrentEdge = {faceIndex, iv2};
1434 minimumDistance = distance;
1435 }
1436 }
1437 }
1438 }
1439
highLight(const QgsPointXY & mapPoint)1440 void QgsMapToolEditMeshFrame::highLight( const QgsPointXY &mapPoint )
1441 {
1442 searchFace( mapPoint );
1443 highlightCurrentHoveredFace( mapPoint );
1444 searchEdge( mapPoint );
1445 highlightCloseVertex( mapPoint );
1446 highlightCloseEdge( mapPoint );
1447 }
1448
onEditingStarted()1449 void QgsMapToolEditMeshFrame::onEditingStarted()
1450 {
1451 setCurrentLayer( canvas()->currentLayer() );
1452 }
1453
setCurrentLayer(QgsMapLayer * layer)1454 void QgsMapToolEditMeshFrame::setCurrentLayer( QgsMapLayer *layer )
1455 {
1456 QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer *>( layer );
1457
1458 if ( mCurrentLayer == meshLayer && mCurrentEditor != nullptr )
1459 return;
1460
1461 if ( mCurrentEditor )
1462 deactivate();
1463
1464 if ( mIsInitialized )
1465 clearSelection(); //TODO later: implement a mechanism to retrieve selection if the layer is again selected
1466
1467 if ( mCurrentLayer )
1468 {
1469 disconnect( mCurrentLayer, &QgsMeshLayer::editingStarted, this, &QgsMapToolEditMeshFrame::onEditingStarted );
1470 disconnect( mCurrentLayer, &QgsMeshLayer::editingStopped, this, &QgsMapToolEditMeshFrame::onEditingStopped );
1471 }
1472
1473 mCurrentLayer = meshLayer;
1474 mCurrentFaceIndex = -1;
1475
1476 if ( mCurrentEditor )
1477 {
1478 disconnect( mCurrentEditor, &QgsMeshEditor::meshEdited, this, &QgsMapToolEditMeshFrame::onEdit );
1479 }
1480
1481 mCurrentEditor = nullptr;
1482
1483 if ( mCurrentLayer )
1484 {
1485 connect( mCurrentLayer, &QgsMeshLayer::editingStarted, this, &QgsMapToolEditMeshFrame::onEditingStarted );
1486 connect( mCurrentLayer, &QgsMeshLayer::editingStopped, this, &QgsMapToolEditMeshFrame::onEditingStopped );
1487
1488 if ( mCurrentLayer->isEditable() )
1489 {
1490 mCurrentEditor = mCurrentLayer->meshEditor();
1491 if ( !mIsInitialized )
1492 initialize();
1493 connect( mCurrentEditor, &QgsMeshEditor::meshEdited, this, &QgsMapToolEditMeshFrame::onEdit );
1494 }
1495 }
1496
1497 if ( mCurrentEditor )
1498 {
1499 activate();
1500 updateFreeVertices();
1501 }
1502
1503 emit selectionChange( mCurrentLayer, mSelectedVertices.keys() );
1504 }
1505
onEdit()1506 void QgsMapToolEditMeshFrame::onEdit()
1507 {
1508 if ( !mKeepSelectionOnEdit )
1509 clearSelection();
1510 mKeepSelectionOnEdit = false;
1511 clearCanvasHelpers();
1512 updateFreeVertices();
1513 }
1514
nativeFaceGeometry(int faceIndex) const1515 QgsPointSequence QgsMapToolEditMeshFrame::nativeFaceGeometry( int faceIndex ) const
1516 {
1517 QgsPointSequence faceGeometry;
1518 const QgsMeshFace &face = mCurrentLayer->nativeMesh()->face( faceIndex );
1519
1520 for ( const int index : face )
1521 faceGeometry.append( mapVertex( index ) );
1522
1523 return faceGeometry;
1524 }
1525
edgeGeometry(const QgsMapToolEditMeshFrame::Edge & edge) const1526 QVector<QgsPointXY> QgsMapToolEditMeshFrame::edgeGeometry( const QgsMapToolEditMeshFrame::Edge &edge ) const
1527 {
1528 const QVector<int> &vertexIndexes = edgeVertices( edge );
1529
1530 return {mapVertexXY( vertexIndexes.at( 0 ) ), mapVertexXY( vertexIndexes.at( 1 ) )};
1531 }
1532
edgeVertices(const QgsMapToolEditMeshFrame::Edge & edge) const1533 QVector<int> QgsMapToolEditMeshFrame::edgeVertices( const QgsMapToolEditMeshFrame::Edge &edge ) const
1534 {
1535 const QgsMeshFace &face = nativeFace( edge.first );
1536 int faceSize = face.count();
1537 int posInface = ( face.indexOf( edge.second ) + faceSize - 1 ) % faceSize;
1538
1539 return {face.at( posInface ), edge.second};
1540 }
1541
newFaceMarkerPosition(int vertexIndex)1542 QgsPointXY QgsMapToolEditMeshFrame::newFaceMarkerPosition( int vertexIndex )
1543 {
1544 QgsVector directionVector;
1545
1546 const QgsMeshVertex &v = mapVertex( vertexIndex );
1547
1548 if ( mCurrentEditor->isVertexFree( vertexIndex ) )
1549 {
1550 directionVector = QgsVector( 1, 0 );
1551 }
1552 else
1553 {
1554 QgsMeshVertexCirculator circulator = mCurrentEditor->vertexCirculator( vertexIndex );
1555 circulator.goBoundaryClockwise();
1556 int indexPt1 = circulator.oppositeVertexClockwise();
1557 circulator.goBoundaryCounterClockwise();
1558 int indexPt2 = circulator.oppositeVertexCounterClockwise();
1559
1560 const QgsMeshVertex &v1 = mapVertex( indexPt1 );
1561 const QgsMeshVertex &v2 = mapVertex( indexPt2 );
1562
1563 QgsVector vector1 = v - v2;
1564 QgsVector vector2 = v - v1;
1565
1566 vector1 = vector1.normalized();
1567 vector2 = vector2.normalized();
1568
1569 double crossProduct = vector1.crossProduct( vector2 );
1570
1571 if ( crossProduct < - 1e-8 )
1572 directionVector = ( vector1 + vector2 ).normalized();
1573 else if ( crossProduct > 1e-8 )
1574 directionVector = -( vector1 + vector2 ).normalized();
1575 else
1576 directionVector = vector2.perpVector();
1577 }
1578
1579 double dist = 15 * canvas()->mapSettings().mapUnitsPerPixel();
1580 return v + directionVector * dist;
1581 }
1582
addVertexToFaceCanditate(int vertexIndex)1583 void QgsMapToolEditMeshFrame::addVertexToFaceCanditate( int vertexIndex )
1584 {
1585 if ( vertexIndex == -1 || ( !mNewFaceCandidate.isEmpty() && vertexIndex == mNewFaceCandidate.last() ) )
1586 return;
1587
1588 mNewFaceBand->movePoint( mapVertexXY( vertexIndex ) );
1589 mNewFaceBand->addPoint( mapVertexXY( vertexIndex ) );
1590 mNewFaceCandidate.append( vertexIndex );
1591 }
1592
testNewVertexInFaceCanditate(int vertexIndex)1593 bool QgsMapToolEditMeshFrame::testNewVertexInFaceCanditate( int vertexIndex )
1594 {
1595 QgsMeshFace face = mNewFaceCandidate.toVector();
1596 if ( vertexIndex != -1 && !face.empty() && vertexIndex != mNewFaceCandidate.last() )
1597 face.append( vertexIndex );
1598 return mCurrentEditor->faceCanBeAdded( face );
1599 }
1600
1601
addNewSelectedVertex(int vertexIndex)1602 void QgsMapToolEditMeshFrame::addNewSelectedVertex( int vertexIndex )
1603 {
1604 mSelectedVertices.insert( vertexIndex, SelectedVertexData() );
1605 QgsVertexMarker *marker = new QgsVertexMarker( canvas() );
1606 marker->setIconType( QgsVertexMarker::ICON_CIRCLE );
1607 marker->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
1608 marker->setPenWidth( QgsGuiUtils::scaleIconSize( 2 ) );
1609 marker->setColor( Qt::blue );
1610 marker->setCenter( mapVertexXY( vertexIndex ) );
1611 marker->setZValue( 2 );
1612 mSelectedVerticesMarker[vertexIndex] = marker;
1613 }
1614
removeFromSelection(int vertexIndex)1615 void QgsMapToolEditMeshFrame::removeFromSelection( int vertexIndex )
1616 {
1617 mSelectedVertices.remove( vertexIndex );
1618 delete mSelectedVerticesMarker.value( vertexIndex );
1619 mSelectedVerticesMarker.remove( vertexIndex );
1620 }
1621
isFaceSelected(int faceIndex)1622 bool QgsMapToolEditMeshFrame::isFaceSelected( int faceIndex )
1623 {
1624 const QgsMeshFace &face = nativeFace( faceIndex );
1625
1626 for ( int i = 0; i < face.size(); ++i )
1627 {
1628 if ( !mSelectedVertices.contains( face.at( i ) ) )
1629 return false;
1630 }
1631 return true;
1632 }
1633
setSelectedVertices(const QList<int> newSelectedVertices,Qgis::SelectBehavior behavior)1634 void QgsMapToolEditMeshFrame::setSelectedVertices( const QList<int> newSelectedVertices, Qgis::SelectBehavior behavior )
1635 {
1636 if ( mSelectedVertices.isEmpty() )
1637 {
1638 mUserZValue = currentZValue();
1639 }
1640
1641 bool removeVertices = false;
1642
1643 switch ( behavior )
1644 {
1645 case Qgis::SelectBehavior::SetSelection:
1646 clearSelection();
1647 break;
1648 case Qgis::SelectBehavior::AddToSelection:
1649 break;
1650 case Qgis::SelectBehavior::RemoveFromSelection:
1651 removeVertices = true;
1652 break;
1653 case Qgis::SelectBehavior::IntersectSelection:
1654 return;
1655 break;
1656 }
1657
1658 for ( const int vertexIndex : newSelectedVertices )
1659 {
1660 bool contained = mSelectedVertices.contains( vertexIndex );
1661 if ( contained && removeVertices )
1662 removeFromSelection( vertexIndex );
1663 else if ( ! removeVertices && !contained )
1664 addNewSelectedVertex( vertexIndex );
1665 }
1666
1667 prepareSelection();
1668 }
1669
setSelectedFaces(const QList<int> newSelectedFaces,Qgis::SelectBehavior behavior)1670 void QgsMapToolEditMeshFrame::setSelectedFaces( const QList<int> newSelectedFaces, Qgis::SelectBehavior behavior )
1671 {
1672 bool removeFaces = false;
1673
1674 switch ( behavior )
1675 {
1676 case Qgis::SelectBehavior::SetSelection:
1677 clearSelection();
1678 break;
1679 case Qgis::SelectBehavior::AddToSelection:
1680 break;
1681 case Qgis::SelectBehavior::RemoveFromSelection:
1682 removeFaces = true;
1683 break;
1684 case Qgis::SelectBehavior::IntersectSelection:
1685 return;
1686 break;
1687 }
1688
1689 const QSet<int> facesToTreat = qgis::listToSet( newSelectedFaces );
1690
1691 for ( const int faceIndex : newSelectedFaces )
1692 {
1693 const QgsMeshFace &face = nativeFace( faceIndex );
1694 for ( int i = 0; i < face.size(); ++i )
1695 {
1696 int vertexIndex = face.at( i );
1697 bool vertexContained = mSelectedVertices.contains( vertexIndex );
1698 if ( vertexContained && removeFaces )
1699 {
1700 const QList<int> facesAround = mCurrentEditor->topologicalMesh().facesAroundVertex( vertexIndex );
1701 bool keepVertex = false;
1702 for ( const int faceAroundIndex : facesAround )
1703 keepVertex |= !facesToTreat.contains( faceAroundIndex ) && isFaceSelected( faceAroundIndex );
1704 if ( !keepVertex )
1705 removeFromSelection( vertexIndex );
1706 }
1707 else if ( !removeFaces && !vertexContained )
1708 addNewSelectedVertex( vertexIndex );
1709 }
1710 }
1711
1712 prepareSelection();
1713 }
1714
removeSelectedVerticesFromMesh(bool fillHole)1715 void QgsMapToolEditMeshFrame::removeSelectedVerticesFromMesh( bool fillHole )
1716 {
1717 if ( fillHole )
1718 {
1719
1720 QList<int> remainingVertex = mCurrentEditor->removeVerticesFillHoles( mSelectedVertices.keys() );
1721
1722 if ( !remainingVertex.isEmpty() )
1723 {
1724 QgisApp::instance()->messageBar()->pushWarning(
1725 tr( "Mesh editing" ),
1726 tr( "%n vertices were not removed", nullptr, remainingVertex.count() ) );
1727 }
1728 }
1729 else
1730 {
1731 QgsMeshEditingError error = mCurrentEditor->removeVerticesWithoutFillHoles( mSelectedVertices.keys() );
1732 if ( error != QgsMeshEditingError() )
1733 {
1734 QgisApp::instance()->messageBar()->pushWarning(
1735 tr( "Mesh editing" ),
1736 tr( "removing the vertex %1 leads to a topological error, operation canceled." ).arg( error.elementIndex ) );
1737 }
1738 }
1739 }
1740
removeFacesFromMesh()1741 void QgsMapToolEditMeshFrame::removeFacesFromMesh()
1742 {
1743 QgsMeshEditingError error;
1744 if ( ! mSelectedFaces.isEmpty() )
1745 error = mCurrentEditor->removeFaces( mSelectedFaces.values() );
1746 else if ( mCurrentFaceIndex != -1 )
1747 error = mCurrentEditor->removeFaces( {mCurrentFaceIndex} );
1748 else
1749 return;
1750
1751 if ( error != QgsMeshEditingError() )
1752 {
1753 QgisApp::instance()->messageBar()->pushWarning(
1754 tr( "Mesh editing" ),
1755 tr( "removing the faces %1 leads to a topological error, operation canceled." ).arg( error.elementIndex ) );
1756 }
1757 else
1758 {
1759 clearSelection();
1760 }
1761 }
1762
splitSelectedFaces()1763 void QgsMapToolEditMeshFrame::splitSelectedFaces()
1764 {
1765 QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
1766 if ( mSplittableFaceCount > 0 )
1767 mCurrentEditor->splitFaces( mSelectedFaces.values() );
1768 else if ( mCurrentFaceIndex != -1 && mCurrentEditor->faceCanBeSplit( mCurrentFaceIndex ) )
1769 mCurrentEditor->splitFaces( {mCurrentFaceIndex} );
1770 }
1771
triggerTransformCoordinatesDockWidget(bool checked)1772 void QgsMapToolEditMeshFrame::triggerTransformCoordinatesDockWidget( bool checked )
1773 {
1774 if ( mTransformDockWidget )
1775 {
1776 mTransformDockWidget->setUserVisible( checked );
1777 return;
1778 }
1779
1780 onEditingStarted();
1781 mTransformDockWidget = new QgsMeshTransformCoordinatesDockWidget( QgisApp::instance() );
1782 mTransformDockWidget->setToggleVisibilityAction( mActionTransformCoordinates );
1783 mTransformDockWidget->setWindowTitle( tr( "Transform Mesh Vertices" ) );
1784 mTransformDockWidget->setObjectName( QStringLiteral( "TransformMeshVerticesDockWidget" ) );
1785 const QList<int> &inputVertices = mSelectedVertices.keys();
1786 mTransformDockWidget->setInput( mCurrentLayer, inputVertices );
1787
1788 if ( !QgisApp::instance()->restoreDockWidget( mTransformDockWidget ) )
1789 QgisApp::instance()->addDockWidget( Qt::LeftDockWidgetArea, mTransformDockWidget );
1790 else
1791 QgisApp::instance()->panelMenu()->addAction( mTransformDockWidget->toggleViewAction() );
1792
1793 mTransformDockWidget->show();
1794
1795 connect( this, &QgsMapToolEditMeshFrame::selectionChange, mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::setInput );
1796
1797 connect( mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::calculationUpdated, this, [this]
1798 {
1799 mMovingFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
1800 mMovingEdgesRubberband->reset( QgsWkbTypes::LineGeometry );
1801 mMovingFreeVertexRubberband->reset( QgsWkbTypes::PointGeometry );
1802 setMovingRubberBandValidity( mTransformDockWidget->isResultValid() );
1803
1804 if ( !mCurrentLayer || !mCurrentEditor )
1805 return;
1806
1807 QList<int> faceList = qgis::setToList( mSelectedFaces );
1808 QgsGeometry faceGeometrie;
1809 if ( faceList.count() == 1 )
1810 {
1811 const QgsMeshFace &face = mCurrentLayer->nativeMesh()->face( faceList.at( 0 ) );
1812 const int faceSize = face.size();
1813 QVector<QgsPointXY> faceVertices( faceSize );
1814 for ( int j = 0; j < faceSize; ++j )
1815 faceVertices[j] = mTransformDockWidget->transformedVertex( face.at( j ) );
1816
1817 faceGeometrie = QgsGeometry::fromPolygonXY( {faceVertices} );
1818 }
1819 else
1820 {
1821 std::unique_ptr<QgsGeometryEngine> geomEngine( QgsGeometry::createGeometryEngine( faceGeometrie.constGet() ) );
1822 geomEngine->prepareGeometry();
1823 QVector<QgsGeometry> faces( mSelectedFaces.count() );
1824 for ( int i = 0; i < faceList.count(); ++i )
1825 {
1826 const QgsMeshFace &face = mCurrentLayer->nativeMesh()->face( faceList.at( i ) );
1827 const int faceSize = face.size();
1828 QVector<QgsPointXY> faceVertices( faceSize );
1829 for ( int j = 0; j < faceSize; ++j )
1830 faceVertices[j] = mTransformDockWidget->transformedVertex( face.at( j ) );
1831
1832 faces[i] = QgsGeometry::fromPolygonXY( {faceVertices} );
1833 }
1834 QString error;
1835 faceGeometrie = QgsGeometry( geomEngine->combine( faces, &error ) );
1836 }
1837
1838 QgsGeometry edgesGeom = QgsGeometry::fromMultiPolylineXY( QgsMultiPolylineXY() );
1839 for ( QMap<int, SelectedVertexData>::const_iterator it = mSelectedVertices.constBegin(); it != mSelectedVertices.constEnd(); ++it )
1840 {
1841 const QgsPointXY &point1 = mTransformDockWidget->transformedVertex( it.key() ) ;
1842 const SelectedVertexData &vertexData = it.value();
1843 for ( int i = 0; i < vertexData.meshFixedEdges.count(); ++i )
1844 {
1845 const QgsPointXY point2 = mTransformDockWidget->transformedVertex( vertexData.meshFixedEdges.at( i ).second );
1846 QgsGeometry edge( new QgsLineString( {point1, point2} ) );
1847 edgesGeom.addPart( edge );
1848 }
1849
1850 for ( int i = 0; i < vertexData.borderEdges.count(); ++i )
1851 {
1852 const QgsPointXY point2 = mTransformDockWidget->transformedVertex( vertexData.borderEdges.at( i ).second );
1853 const QgsGeometry edge( new QgsLineString( {point1, point2} ) );
1854 edgesGeom.addPart( edge );
1855 }
1856 }
1857
1858 QgsCoordinateTransform coordinateTransform( mCurrentLayer->crs(), canvas()->mapSettings().destinationCrs(), QgsProject::instance() );
1859
1860 try
1861 {
1862 faceGeometrie.transform( coordinateTransform );
1863 }
1864 catch ( QgsCsException & )
1865 {}
1866
1867 try
1868 {
1869 edgesGeom.transform( coordinateTransform );
1870 }
1871 catch ( QgsCsException & )
1872 {}
1873
1874 mMovingFacesRubberband->setToGeometry( faceGeometrie );
1875 mMovingEdgesRubberband->setToGeometry( edgesGeom );
1876 for ( const int vertexIndex : mSelectedVertices.keys() )
1877 if ( mCurrentEditor->isVertexFree( vertexIndex ) )
1878 {
1879 QgsMeshVertex transformedVertex = mTransformDockWidget->transformedVertex( vertexIndex );
1880 mMovingFreeVertexRubberband->addPoint( mCurrentLayer->triangularMesh()->nativeToTriangularCoordinates( transformedVertex ), false );
1881 }
1882
1883 mMovingFreeVertexRubberband->setVisible( true );
1884 mMovingFreeVertexRubberband->updatePosition();
1885 mMovingFreeVertexRubberband->update();
1886
1887 setMovingRubberBandValidity( mTransformDockWidget->isResultValid() );
1888 } );
1889
1890 connect( mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::aboutToBeApplied, this, [this]
1891 {
1892 mKeepSelectionOnEdit = true;
1893 } );
1894
1895 connect( mTransformDockWidget, &QgsMeshTransformCoordinatesDockWidget::applied, this, [this]
1896 {
1897 mTransformDockWidget->setInput( mCurrentLayer, mSelectedVertices.keys() );
1898 updateSelectecVerticesMarker();
1899 prepareSelection();
1900 } );
1901
1902 connect( mTransformDockWidget, &QgsDockWidget::closed, this, [this]
1903 {
1904 mActionTransformCoordinates->setChecked( false );
1905 if ( !mIsInitialized )
1906 return;
1907 mMovingFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
1908 mMovingEdgesRubberband->reset( QgsWkbTypes::LineGeometry );
1909 mMovingFreeVertexRubberband->reset( QgsWkbTypes::PointGeometry );
1910 setMovingRubberBandValidity( false );
1911 } );
1912
1913 }
1914
reindexMesh()1915 void QgsMapToolEditMeshFrame::reindexMesh()
1916 {
1917 onEditingStarted();
1918
1919 if ( !mCurrentLayer || !mCurrentLayer->isEditable() )
1920 return;
1921
1922 if ( QMessageBox::question( canvas(), tr( "Reindex the Mesh" ),
1923 tr( "Do you want to reindex the faces and vertices of the mesh layer %1?" ).arg( mCurrentLayer->name() ),
1924 QMessageBox::Yes | QMessageBox::No, QMessageBox::No )
1925 == QMessageBox::No )
1926 return;
1927
1928
1929 QgsCoordinateTransform transform( mCurrentLayer->crs(), canvas()->mapSettings().destinationCrs(), QgsProject::instance() );
1930
1931 QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
1932 mCurrentLayer->reindex( transform, true );
1933 }
1934
selectByGeometry(const QgsGeometry & geometry,Qt::KeyboardModifiers modifiers)1935 void QgsMapToolEditMeshFrame::selectByGeometry( const QgsGeometry &geometry, Qt::KeyboardModifiers modifiers )
1936 {
1937 if ( mCurrentLayer.isNull() || !mCurrentLayer->triangularMesh() || mCurrentEditor.isNull() )
1938 return;
1939
1940 Qgis::SelectBehavior behavior;
1941 if ( modifiers & Qt::ShiftModifier )
1942 behavior = Qgis::SelectBehavior::AddToSelection;
1943 else if ( modifiers & Qt::ControlModifier )
1944 behavior = Qgis::SelectBehavior::RemoveFromSelection;
1945 else
1946 behavior = Qgis::SelectBehavior::SetSelection;
1947
1948 if ( modifiers & Qt::AltModifier )
1949 selectContainedByGeometry( geometry, behavior );
1950 else
1951 selectTouchedByGeometry( geometry, behavior );
1952 }
1953
selectTouchedByGeometry(const QgsGeometry & geometry,Qgis::SelectBehavior behavior)1954 void QgsMapToolEditMeshFrame::selectTouchedByGeometry( const QgsGeometry &geometry, Qgis::SelectBehavior behavior )
1955 {
1956 QSet<int> selectedVertices;
1957 const QList<int> nativeFaceIndexes = mCurrentLayer->triangularMesh()->nativeFaceIndexForRectangle( geometry.boundingBox() );
1958
1959 std::unique_ptr<QgsGeometryEngine> engine( QgsGeometry::createGeometryEngine( geometry.constGet() ) );
1960 engine->prepareGeometry();
1961
1962 for ( const int faceIndex : nativeFaceIndexes )
1963 {
1964 const QgsMeshFace &face = nativeFace( faceIndex );
1965 std::unique_ptr<QgsPolygon> faceGeom( new QgsPolygon( new QgsLineString( nativeFaceGeometry( faceIndex ) ) ) );
1966 if ( engine->intersects( faceGeom.get() ) )
1967 {
1968 QSet<int> faceToAdd = qgis::listToSet( face.toList() );
1969 selectedVertices.unite( faceToAdd );
1970 }
1971 }
1972
1973 const QList<int> &freeVerticesIndexes = mCurrentEditor->freeVerticesIndexes();
1974 for ( const int freeVertexIndex : freeVerticesIndexes )
1975 {
1976 const QgsMeshVertex &vertex = mapVertex( freeVertexIndex );
1977 if ( engine->contains( &vertex ) )
1978 selectedVertices.insert( freeVertexIndex );
1979 }
1980
1981 setSelectedVertices( selectedVertices.values(), behavior );
1982 }
1983
selectContainedByGeometry(const QgsGeometry & geometry,Qgis::SelectBehavior behavior)1984 void QgsMapToolEditMeshFrame::selectContainedByGeometry( const QgsGeometry &geometry, Qgis::SelectBehavior behavior )
1985 {
1986 QSet<int> selectedVertices;
1987 const QList<int> nativeFaceIndexes = mCurrentLayer->triangularMesh()->nativeFaceIndexForRectangle( geometry.boundingBox() );
1988
1989 std::unique_ptr<QgsGeometryEngine> engine( QgsGeometry::createGeometryEngine( geometry.constGet() ) );
1990 engine->prepareGeometry();
1991 for ( const int faceIndex : nativeFaceIndexes )
1992 {
1993 const QgsMeshFace &face = nativeFace( faceIndex );
1994 for ( const int vertexIndex : face )
1995 {
1996 const QgsMeshVertex &vertex = mapVertex( vertexIndex );
1997 if ( engine->contains( &vertex ) )
1998 selectedVertices.insert( vertexIndex );
1999 }
2000 }
2001
2002 const QList<int> &freeVerticesIndexes = mCurrentEditor->freeVerticesIndexes();
2003 for ( const int freeVertexIndex : freeVerticesIndexes )
2004 {
2005 const QgsMeshVertex &vertex = mapVertex( freeVertexIndex );
2006 if ( engine->contains( &vertex ) )
2007 selectedVertices.insert( freeVertexIndex );
2008 }
2009
2010 setSelectedVertices( selectedVertices.values(), behavior );
2011 }
2012
applyZValueOnSelectedVertices()2013 void QgsMapToolEditMeshFrame::applyZValueOnSelectedVertices()
2014 {
2015 if ( !mZValueWidget )
2016 return;
2017
2018 if ( mSelectedVertices.isEmpty() )
2019 return;
2020
2021 QList<double> zValues;
2022 zValues.reserve( mSelectedVertices.count() );
2023 mUserZValue = currentZValue();
2024 for ( int i = 0; i < mSelectedVertices.count(); ++i )
2025 zValues.append( mUserZValue );
2026
2027 mCurrentEditor->changeZValues( mSelectedVertices.keys(), zValues );
2028 }
2029
prepareSelection()2030 void QgsMapToolEditMeshFrame::prepareSelection()
2031 {
2032 if ( !mSelectedVertices.isEmpty() )
2033 {
2034 double vertexZValue = 0;
2035 for ( int i : mSelectedVertices.keys() )
2036 vertexZValue += mapVertex( i ).z();
2037 vertexZValue /= mSelectedVertices.count();
2038
2039 if ( !mZValueWidget )
2040 createZValueWidget();
2041
2042 mZValueWidget->setZValue( vertexZValue );
2043 }
2044 else
2045 {
2046 if ( cadDockWidget()->cadEnabled() && mZValueWidget )
2047 deleteZValueWidget();
2048 else if ( mZValueWidget )
2049 mZValueWidget->setZValue( mUserZValue );
2050 }
2051
2052 mConcernedFaceBySelection.clear();
2053 QMap<int, SelectedVertexData> movingVertices;
2054
2055
2056 double xMin = std::numeric_limits<double>::max();
2057 double xMax = -std::numeric_limits<double>::max();
2058 double yMin = std::numeric_limits<double>::max();
2059 double yMax = -std::numeric_limits<double>::max();
2060
2061 // search for moving edges and mesh fixed edges
2062 for ( QMap<int, SelectedVertexData>::iterator it = mSelectedVertices.begin(); it != mSelectedVertices.end(); ++it )
2063 {
2064 SelectedVertexData &vertexData = it.value();
2065 int vertexIndex = it.key();
2066
2067 QgsPointXY vert = mapVertex( vertexIndex );
2068 if ( vert.x() < xMin )
2069 xMin = vert.x();
2070 if ( vert.x() > xMax )
2071 xMax = vert.x();
2072 if ( vert.y() < yMin )
2073 yMin = vert.y();
2074 if ( vert.y() > yMax )
2075 yMax = vert.y();
2076
2077 vertexData.borderEdges.clear();
2078 vertexData.meshFixedEdges.clear();
2079 QgsMeshVertexCirculator circulator = mCurrentEditor->vertexCirculator( vertexIndex );
2080
2081 if ( !circulator.isValid() )
2082 continue;
2083
2084 circulator.goBoundaryClockwise();
2085 int firstface = circulator.currentFaceIndex();
2086 do
2087 {
2088 int oppositeVertex = circulator.oppositeVertexClockwise();
2089 if ( mSelectedVertices.contains( oppositeVertex ) )
2090 vertexData.borderEdges.append( {circulator.currentFaceIndex(), oppositeVertex} );
2091 else
2092 vertexData.meshFixedEdges.append( {circulator.currentFaceIndex(), oppositeVertex} );
2093
2094 mConcernedFaceBySelection.insert( circulator.currentFaceIndex() );
2095 }
2096 while ( circulator.turnCounterClockwise() != firstface && circulator.currentFaceIndex() != -1 );
2097
2098 if ( circulator.currentFaceIndex() == -1 )
2099 {
2100 circulator.turnClockwise();
2101 int oppositeVertex = circulator.oppositeVertexCounterClockwise();
2102 if ( mSelectedVertices.contains( oppositeVertex ) )
2103 vertexData.borderEdges.append( {-1, oppositeVertex} );
2104 else
2105 vertexData.meshFixedEdges.append( {-1, oppositeVertex} );
2106 }
2107 }
2108
2109 mSelectedMapExtent = QgsRectangle( xMin, yMin, xMax, yMax );
2110
2111 // remove faces that have at least one vertex not selected
2112 mSelectedFaces = mConcernedFaceBySelection;
2113 for ( const int faceIndex : std::as_const( mConcernedFaceBySelection ) )
2114 {
2115 const QgsMeshFace &face = nativeFace( faceIndex );
2116 for ( const int vi : std::as_const( face ) )
2117 if ( !mSelectedVertices.contains( vi ) )
2118 {
2119 mSelectedFaces.remove( faceIndex );
2120 continue;
2121 }
2122 }
2123
2124 // here, we search for border edges that have associate face in the selection and remove it
2125 for ( QMap<int, SelectedVertexData>::iterator it = mSelectedVertices.begin(); it != mSelectedVertices.end(); ++it )
2126 {
2127 SelectedVertexData &vertexData = it.value();
2128 int i = 0;
2129 while ( i < vertexData.borderEdges.count() )
2130 {
2131 int associateFace = vertexData.borderEdges.at( i ).first;
2132 if ( associateFace == -1 || mSelectedFaces.contains( associateFace ) )
2133 vertexData.borderEdges.removeAt( i );
2134 else
2135 i++;
2136 }
2137 }
2138
2139 if ( !mSelectedFaces.isEmpty() )
2140 {
2141 const QList<int> faceList = qgis::setToList( mSelectedFaces );
2142 QgsGeometry faceGeometrie( new QgsPolygon( new QgsLineString( nativeFaceGeometry( faceList.at( 0 ) ) ) ) );
2143 if ( mSelectedFaces.count() == 1 )
2144 {
2145 mSelectedFacesRubberband->setToGeometry( faceGeometrie );
2146 }
2147 else
2148 {
2149 std::unique_ptr<QgsGeometryEngine> geomEngine( QgsGeometry::createGeometryEngine( faceGeometrie.constGet() ) );
2150 geomEngine->prepareGeometry();
2151
2152 QVector<QgsGeometry> otherFaces( mSelectedFaces.count() );
2153 for ( int i = 0; i < faceList.count(); ++i )
2154 otherFaces[i] = QgsGeometry( new QgsPolygon( new QgsLineString( nativeFaceGeometry( faceList.at( i ) ) ) ) );
2155 QString error;
2156 const QgsGeometry allFaces( geomEngine->combine( otherFaces, &error ) );
2157 mSelectedFacesRubberband->setToGeometry( allFaces );
2158 }
2159
2160 QColor fillColor = canvas()->mapSettings().selectionColor();
2161
2162 if ( fillColor.alpha() > 100 ) //set alpha to 150 if the transparency is not enough to see the mesh
2163 fillColor.setAlpha( 100 );
2164
2165 mSelectedFacesRubberband->setFillColor( fillColor );
2166 }
2167 else
2168 mSelectedFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
2169
2170 if ( mSelectedVertices.count() == 1 )
2171 {
2172 mActionRemoveVerticesFillingHole->setText( tr( "Remove selected vertex and fill hole" ) );
2173 mActionRemoveVerticesWithoutFillingHole->setText( tr( "Remove selected vertex without filling hole" ) );
2174 }
2175 else if ( mSelectedVertices.count() > 1 )
2176 {
2177 mActionRemoveVerticesFillingHole->setText( tr( "Remove selected vertices and fill hole(s)" ) );
2178 mActionRemoveVerticesWithoutFillingHole->setText( tr( "Remove selected vertices without filling hole(s)" ) );
2179 }
2180
2181 if ( mSelectedFaces.count() == 1 )
2182 {
2183 mActionRemoveFaces->setText( tr( "Remove selected face" ) );
2184 mActionFacesRefinement->setText( tr( "Refine selected face" ) );
2185 }
2186 else if ( mSelectedFaces.count() > 1 )
2187 {
2188 mActionRemoveFaces->setText( tr( "Remove %1 selected faces" ).arg( mSelectedFaces.count() ) );
2189 mActionFacesRefinement->setText( tr( "Refine %1 selected faces" ).arg( mSelectedFaces.count() ) );
2190 }
2191 else
2192 {
2193 mActionRemoveFaces->setText( tr( "Remove current face" ) );
2194 mActionFacesRefinement->setText( tr( "Refine current face" ) );
2195 }
2196
2197 mSplittableFaceCount = 0;
2198 for ( const int faceIndex : std::as_const( mSelectedFaces ) )
2199 {
2200 if ( mCurrentEditor->faceCanBeSplit( faceIndex ) )
2201 mSplittableFaceCount++;
2202 }
2203
2204 if ( mSplittableFaceCount == 1 )
2205 mActionSplitFaces->setText( tr( "Split selected face" ) );
2206 else if ( mSplittableFaceCount > 1 )
2207 mActionSplitFaces->setText( tr( "Split %1 selected faces" ).arg( mSplittableFaceCount ) );
2208 else
2209 mActionSplitFaces->setText( tr( "Split current face" ) );
2210
2211 emit selectionChange( mCurrentLayer, mSelectedVertices.keys() );
2212 }
2213
updateSelectecVerticesMarker()2214 void QgsMapToolEditMeshFrame::updateSelectecVerticesMarker()
2215 {
2216 qDeleteAll( mSelectedVerticesMarker );
2217 mSelectedVerticesMarker.clear();
2218 for ( const int vertexIndex : mSelectedVertices.keys() )
2219 {
2220 QgsVertexMarker *marker = new QgsVertexMarker( canvas() );
2221 marker->setIconType( QgsVertexMarker::ICON_CIRCLE );
2222 marker->setIconSize( QgsGuiUtils::scaleIconSize( 10 ) );
2223 marker->setPenWidth( QgsGuiUtils::scaleIconSize( 2 ) );
2224 marker->setColor( Qt::blue );
2225 marker->setCenter( mapVertexXY( vertexIndex ) );
2226 marker->setZValue( 2 );
2227 mSelectedVerticesMarker[vertexIndex] = marker;
2228 }
2229 }
2230
setMovingRubberBandValidity(bool valid)2231 void QgsMapToolEditMeshFrame::setMovingRubberBandValidity( bool valid )
2232 {
2233 if ( valid )
2234 {
2235 mMovingFacesRubberband->setFillColor( QColor( 0, 200, 0, 100 ) );
2236 mMovingFacesRubberband->setStrokeColor( QColor( 0, 200, 0 ) );
2237 mMovingEdgesRubberband->setColor( QColor( 0, 200, 0 ) );
2238 mMovingFreeVertexRubberband->setColor( QColor( 0, 200, 0 ) );
2239 }
2240 else
2241 {
2242 mMovingFacesRubberband->setFillColor( QColor( 200, 0, 0, 100 ) );
2243 mMovingFacesRubberband->setStrokeColor( QColor( 200, 0, 0 ) );
2244 mMovingEdgesRubberband->setColor( QColor( 200, 0, 0 ) );
2245 mMovingFreeVertexRubberband->setColor( QColor( 200, 0, 0 ) );
2246 }
2247 }
2248
isSelectionGrapped(QgsPointXY & grappedPoint)2249 bool QgsMapToolEditMeshFrame::isSelectionGrapped( QgsPointXY &grappedPoint )
2250 {
2251 if ( mCurrentVertexIndex != -1 && mSelectedVertices.contains( mCurrentVertexIndex ) )
2252 {
2253 grappedPoint = mapVertexXY( mCurrentVertexIndex );
2254 return true;
2255 }
2256
2257 double tolerance = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
2258
2259 if ( mCurrentEdge.first != -1 && mCurrentEdge.second != -1 &&
2260 mSelectEdgeMarker->isVisible() &&
2261 grappedPoint.distance( mSelectEdgeMarker->center() ) < tolerance )
2262 {
2263 const QVector<int> vertices = edgeVertices( mCurrentEdge );
2264 if ( mSelectedVertices.contains( vertices.at( 0 ) ) && mSelectedVertices.contains( vertices.at( 1 ) ) )
2265 {
2266 const QgsPointXY &point1 = mapVertexXY( vertices.at( 0 ) );
2267 const QgsPointXY &point2 = mapVertexXY( vertices.at( 1 ) );
2268 grappedPoint = QgsPointXY( point1.x() + point2.x(), point1.y() + point2.y() ) / 2;
2269 return true;
2270 }
2271 }
2272
2273
2274 if ( ( mSelectFaceMarker->isVisible() &&
2275 grappedPoint.distance( mSelectFaceMarker->center() ) < tolerance
2276 && mCurrentFaceIndex >= 0
2277 && mSelectedFaces.contains( mCurrentFaceIndex ) ) )
2278 {
2279 grappedPoint = mCurrentLayer->triangularMesh()->faceCentroids().at( mCurrentFaceIndex );
2280 return true;
2281 }
2282
2283 return false;
2284 }
2285
forceByLineReleaseEvent(QgsMapMouseEvent * e)2286 void QgsMapToolEditMeshFrame::forceByLineReleaseEvent( QgsMapMouseEvent *e )
2287 {
2288 QgsPointXY mapPoint = e->mapPoint();
2289
2290 if ( e->button() == Qt::LeftButton )
2291 {
2292 double zValue = currentZValue();
2293
2294 if ( mCurrentVertexIndex != -1 )
2295 {
2296 const QgsPointXY currentPoint = mapVertexXY( mCurrentVertexIndex );
2297 mForceByLineRubberBand->addPoint( currentPoint );
2298 mCadDockWidget->setZ( QString::number( mapVertex( mCurrentVertexIndex ).z(), 'f' ), QgsAdvancedDigitizingDockWidget::WidgetSetMode::TextEdited );
2299 mCadDockWidget->setPoints( QList < QgsPointXY>() << currentPoint << currentPoint );
2300 }
2301 else
2302 {
2303 if ( e->mapPointMatch().isValid() )
2304 {
2305 QgsPoint layerPoint = e->mapPointMatch().interpolatedPoint( mCanvas->mapSettings().destinationCrs() );
2306 zValue = layerPoint.z();
2307 }
2308
2309 mForceByLineRubberBand->addPoint( mapPoint );
2310 }
2311
2312 mForcingLineZValue.append( zValue );
2313 }
2314 else if ( e->button() == Qt::RightButton )
2315 {
2316 if ( mForceByLineRubberBand->numberOfVertices() == 0 )
2317 {
2318 forceByLineBySelectedFeature( e );
2319 return;
2320 }
2321
2322 QVector<QgsPoint> points;
2323 QgsPolylineXY rubbergandLines = mForceByLineRubberBand->asGeometry().asPolyline();
2324 double defaultValue = currentZValue();
2325
2326 if ( std::isnan( defaultValue ) )
2327 defaultValue = defaultZValue();
2328
2329 for ( int i = 0; i < rubbergandLines.count() - 1; ++i )
2330 {
2331 points.append( QgsPoint( rubbergandLines.at( i ).x(),
2332 rubbergandLines.at( i ).y(),
2333 mForcingLineZValue.isEmpty() ? defaultValue : mForcingLineZValue.at( i ) ) );
2334 }
2335 std::unique_ptr<QgsLineString> forcingLine = std::make_unique<QgsLineString>( points );
2336 forceByLine( QgsGeometry( forcingLine.release() ) );
2337 mForceByLineRubberBand->reset( QgsWkbTypes::LineGeometry );
2338 mForcingLineZValue.clear();
2339 }
2340 }
2341
forceByLine(const QgsGeometry & lineGeometry)2342 void QgsMapToolEditMeshFrame::forceByLine( const QgsGeometry &lineGeometry )
2343 {
2344 QgsMeshEditForceByPolylines forceByPolyline;
2345
2346 double defaultValue = currentZValue();
2347 if ( std::isnan( defaultValue ) )
2348 defaultValue = defaultZValue();
2349
2350 forceByPolyline.setDefaultZValue( defaultValue );
2351 forceByPolyline.addLineFromGeometry( lineGeometry );
2352 forceByPolyline.setAddVertexOnIntersection( mWidgetActionForceByLine->newVertexOnIntersectingEdge() );
2353 forceByPolyline.setInterpolateZValueOnMesh( mWidgetActionForceByLine->interpolationMode() == QgsMeshEditForceByLineAction::Mesh );
2354 forceByPolyline.setTolerance( mWidgetActionForceByLine->toleranceValue() );
2355
2356 mCurrentEditor->advancedEdit( &forceByPolyline );
2357 }
2358
2359
highlightCurrentHoveredFace(const QgsPointXY & mapPoint)2360 void QgsMapToolEditMeshFrame::highlightCurrentHoveredFace( const QgsPointXY &mapPoint )
2361 {
2362 searchFace( mapPoint );
2363 if ( mSelectFaceMarker->isVisible() )
2364 {
2365 double tol = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
2366 if ( mapPoint.distance( mSelectFaceMarker->center() ) < tol )
2367 {
2368 mSelectFaceMarker->setColor( Qt::red );
2369 mSelectFaceMarker->setFillColor( Qt::red );
2370 }
2371 else
2372 {
2373 mSelectFaceMarker->setColor( Qt::gray );
2374 mSelectFaceMarker->setFillColor( Qt::gray );
2375 }
2376 }
2377
2378 QgsPointSequence faceGeometry = nativeFaceGeometry( mCurrentFaceIndex );
2379 mFaceRubberBand->reset( QgsWkbTypes::PolygonGeometry );
2380 mFaceVerticesBand->reset( QgsWkbTypes::PointGeometry );
2381 for ( const QgsPoint &pt : faceGeometry )
2382 {
2383 mFaceRubberBand->addPoint( pt );
2384 mFaceVerticesBand->addPoint( pt );
2385 }
2386
2387 if ( mCurrentFaceIndex != -1 && faceCanBeInteractive( mCurrentFaceIndex ) )
2388 {
2389 mSelectFaceMarker->setCenter( mCurrentLayer->triangularMesh()->faceCentroids().at( mCurrentFaceIndex ) );
2390 mSelectFaceMarker->setVisible( true );
2391 }
2392 else
2393 mSelectFaceMarker->setVisible( false );
2394
2395 return;
2396 }
2397
highlightCloseVertex(const QgsPointXY & mapPoint)2398 void QgsMapToolEditMeshFrame::highlightCloseVertex( const QgsPointXY &mapPoint )
2399 {
2400 if ( !mCurrentEditor )
2401 return;
2402
2403
2404 if ( mCurrentState == Digitizing && mNewFaceMarker->isVisible() )
2405 {
2406 double tol = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
2407 if ( mapPoint.distance( mNewFaceMarker->center() ) < tol )
2408 {
2409 mNewFaceMarker->setColor( Qt::red );
2410 mVertexBand->setVisible( false );
2411 return;
2412 }
2413 else if ( !mVertexBand->isVisible() )
2414 {
2415 mNewFaceMarker->setVisible( false );
2416 mCurrentVertexIndex = -1;
2417 }
2418 }
2419
2420 if ( mVertexBand->isVisible() && mVertexBand->numberOfVertices() > 0 )
2421 {
2422 double tol = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
2423 if ( mVertexBand->getPoint( 0 )->distance( mapPoint ) > tol )
2424 {
2425 mVertexBand->reset( QgsWkbTypes::PointGeometry );
2426 mVertexBand->setVisible( false );
2427 mNewFaceMarker->setVisible( false );
2428 mCurrentVertexIndex = -1;
2429 }
2430 }
2431 else
2432 {
2433 int closeVert = closeVertex( mapPoint );
2434 mCurrentVertexIndex = -1;
2435 bool isBoundary = mCurrentEditor->isVertexOnBoundary( closeVert );
2436 bool isFree = mCurrentEditor->isVertexFree( closeVert );
2437 mVertexBand->reset( QgsWkbTypes::PointGeometry );
2438
2439 if ( closeVert >= 0 && ( mCurrentState != AddingNewFace || isBoundary || isFree ) )
2440 {
2441 mCurrentVertexIndex = closeVert;
2442 mVertexBand->addPoint( mapVertexXY( closeVert ) );
2443 if ( mCurrentState == Digitizing )
2444 {
2445 if ( isBoundary || isFree )
2446 {
2447 mNewFaceMarker->setCenter( newFaceMarkerPosition( closeVert ) );
2448 mNewFaceMarker->setVisible( true );
2449 mNewFaceMarker->setColor( Qt::gray );
2450 }
2451 }
2452 }
2453 }
2454
2455 if ( mCadDockWidget->cadEnabled() && !mCadDockWidget->constraintZ()->isLocked() && mCurrentVertexIndex != -1 )
2456 mCadDockWidget->setZ( QString::number( mapVertex( mCurrentVertexIndex ).z(), 'f' ), QgsAdvancedDigitizingDockWidget::WidgetSetMode::TextEdited );
2457 }
2458
highlightCloseEdge(const QgsPointXY & mapPoint)2459 void QgsMapToolEditMeshFrame::highlightCloseEdge( const QgsPointXY &mapPoint )
2460 {
2461 if ( mCurrentLayer.isNull() )
2462 return;
2463
2464 double tolerance = QgsTolerance::vertexSearchRadius( mCanvas->mapSettings() );
2465
2466 if ( mFlipEdgeMarker->isVisible() )
2467 {
2468 if ( mapPoint.distance( mFlipEdgeMarker->center() ) < tolerance )
2469 mFlipEdgeMarker->setColor( Qt::red );
2470 else
2471 mFlipEdgeMarker->setColor( Qt::gray );
2472 }
2473
2474 if ( mSelectEdgeMarker->isVisible() )
2475 {
2476 if ( mapPoint.distance( mSelectEdgeMarker->center() ) < tolerance )
2477 {
2478 mSelectEdgeMarker->setColor( Qt::red );
2479 mSelectEdgeMarker->setFillColor( Qt::red );
2480 }
2481 else
2482 {
2483 mSelectEdgeMarker->setColor( Qt::gray );
2484 mSelectEdgeMarker->setFillColor( Qt::gray );
2485 }
2486 }
2487
2488 if ( mMergeFaceMarker->isVisible() )
2489 {
2490 if ( mapPoint.distance( mMergeFaceMarker->center() ) < tolerance )
2491 mMergeFaceMarker->setColor( Qt::red );
2492 else
2493 mMergeFaceMarker->setColor( Qt::gray );
2494 }
2495
2496 searchEdge( mapPoint );
2497
2498 mEdgeBand->reset();
2499 mFlipEdgeMarker->setVisible( false );
2500 mMergeFaceMarker->setVisible( false );
2501 mSelectEdgeMarker->setVisible( false );
2502 if ( mCurrentEdge.first != -1 && mCurrentEdge.second != -1 && mCurrentState == Digitizing )
2503 {
2504 const QVector<QgsPointXY> &edgeGeom = edgeGeometry( mCurrentEdge );
2505 mEdgeBand->addPoint( edgeGeom.at( 0 ) );
2506 mEdgeBand->addPoint( edgeGeom.at( 1 ) );
2507
2508 if ( mCurrentFaceIndex == -1 )
2509 {
2510 mFaceVerticesBand->reset( QgsWkbTypes::PointGeometry );
2511 mFaceVerticesBand->addPoint( edgeGeom.at( 0 ) );
2512 mFaceVerticesBand->addPoint( edgeGeom.at( 1 ) );
2513 }
2514
2515 const QVector<int> edgeVert = edgeVertices( mCurrentEdge );
2516 QgsPointXY basePoint;
2517 QgsVector intervalOfMarkers;
2518 if ( std::fabs( edgeGeom.at( 0 ).x() - edgeGeom.at( 1 ).x() ) > std::fabs( edgeGeom.at( 0 ).y() - edgeGeom.at( 1 ).y() ) )
2519 {
2520 // edge are more horizontal than vertical, take the vertex on the left side
2521 if ( edgeGeom.at( 0 ).x() < edgeGeom.at( 1 ).x() )
2522 {
2523 basePoint = edgeGeom.at( 0 );
2524 intervalOfMarkers = ( edgeGeom.at( 1 ) - edgeGeom.at( 0 ) ) / 4;
2525 }
2526 else
2527 {
2528 basePoint = edgeGeom.at( 1 );
2529 intervalOfMarkers = ( edgeGeom.at( 0 ) - edgeGeom.at( 1 ) ) / 4;
2530 }
2531 }
2532 else
2533 {
2534 // edge are more vertical than horizontal, take the vertex on the bottom
2535 if ( edgeGeom.at( 0 ).y() < edgeGeom.at( 1 ).y() )
2536 {
2537 basePoint = edgeGeom.at( 0 );
2538 intervalOfMarkers = ( edgeGeom.at( 1 ) - edgeGeom.at( 0 ) ) / 4;
2539 }
2540 else
2541 {
2542 basePoint = edgeGeom.at( 1 );
2543 intervalOfMarkers = ( edgeGeom.at( 0 ) - edgeGeom.at( 1 ) ) / 4;
2544 }
2545 }
2546
2547 if ( mCurrentEditor->edgeCanBeFlipped( edgeVert.at( 0 ), edgeVert.at( 1 ) ) )
2548 {
2549 mFlipEdgeMarker->setVisible( true );
2550 mFlipEdgeMarker->setCenter( basePoint + intervalOfMarkers );
2551 }
2552 else
2553 mFlipEdgeMarker->setVisible( false );
2554
2555 mSelectEdgeMarker->setVisible( true );
2556 mSelectEdgeMarker->setCenter( basePoint + intervalOfMarkers * 2 );
2557
2558 if ( mCurrentEditor->canBeMerged( edgeVert.at( 0 ), edgeVert.at( 1 ) ) )
2559 {
2560 mMergeFaceMarker->setVisible( true );
2561 mMergeFaceMarker->setCenter( basePoint + intervalOfMarkers * 3 );
2562 }
2563 else
2564 mMergeFaceMarker->setVisible( false );
2565 }
2566 }
2567
edgeCanBeInteractive(int vertexIndex1,int vertexIndex2) const2568 bool QgsMapToolEditMeshFrame::edgeCanBeInteractive( int vertexIndex1, int vertexIndex2 ) const
2569 {
2570 // If the edge is less than 90px width, the interactive marker will not be displayed to avoid too close marker and
2571 // avoit the user to click on a marker if he doesn't want
2572 double mapUnitPerPixel = mCanvas->mapSettings().mapUnitsPerPixel();
2573 return mapVertexXY( vertexIndex1 ).distance( mapVertexXY( vertexIndex2 ) ) / mapUnitPerPixel > 90;
2574 }
2575
faceCanBeInteractive(int faceIndex) const2576 bool QgsMapToolEditMeshFrame::faceCanBeInteractive( int faceIndex ) const
2577 {
2578 // If both side of the face boundng box is less than 60px width, the interactive marker will not be displayed to avoid too close marker and
2579 // avoit the user to click on a marker if he doesn't want
2580
2581 double mapUnitPerPixel = mCanvas->mapSettings().mapUnitsPerPixel();
2582 QgsGeometry faceGeom( new QgsLineString( nativeFaceGeometry( faceIndex ) ) );
2583 QgsRectangle bbox = faceGeom.boundingBox();
2584
2585 return bbox.width() / mapUnitPerPixel > 60 || bbox.height() / mapUnitPerPixel > 60;
2586 }
2587
createZValueWidget()2588 void QgsMapToolEditMeshFrame::createZValueWidget()
2589 {
2590 if ( !mCanvas )
2591 return;
2592
2593 deleteZValueWidget();
2594
2595 mZValueWidget = new QgsZValueWidget( tr( "Vertex Z value:" ) );
2596 mZValueWidget->setDefaultValue( defaultZValue() );
2597 mZValueWidget->setZValue( mUserZValue );
2598 QgisApp::instance()->addUserInputWidget( mZValueWidget );
2599 }
2600
deleteZValueWidget()2601 void QgsMapToolEditMeshFrame::deleteZValueWidget()
2602 {
2603 if ( mZValueWidget )
2604 {
2605 mZValueWidget->releaseKeyboard();
2606 mZValueWidget->deleteLater();
2607 }
2608
2609 mZValueWidget = nullptr;
2610 }
2611
clearSelection()2612 void QgsMapToolEditMeshFrame::clearSelection()
2613 {
2614 mSelectedVertices.clear();
2615 mSelectedFaces.clear();
2616 mSelectionBand->reset( QgsWkbTypes::PolygonGeometry );
2617 mSelectedFacesRubberband->reset( QgsWkbTypes::PolygonGeometry );
2618 qDeleteAll( mSelectedVerticesMarker );
2619 mSelectedVerticesMarker.clear();
2620 prepareSelection();
2621 }
2622
clearCanvasHelpers()2623 void QgsMapToolEditMeshFrame::clearCanvasHelpers()
2624 {
2625 mCurrentFaceIndex = -1;
2626 mCurrentVertexIndex = -1;
2627 mFaceRubberBand->reset();
2628 mFaceVerticesBand->reset();
2629 mVertexBand->reset();
2630
2631 mSnapIndicator->setMatch( QgsPointLocator::Match() );
2632 mSelectFaceMarker->setVisible( false );
2633 clearEdgeHelpers();
2634 }
2635
clearEdgeHelpers()2636 void QgsMapToolEditMeshFrame::clearEdgeHelpers()
2637 {
2638 mCurrentEdge = {-1, -1};
2639 mEdgeBand->reset();
2640 mSelectEdgeMarker->setVisible( false );
2641 mFlipEdgeMarker->setVisible( false );
2642 mMergeFaceMarker->setVisible( false );
2643 }
2644
addVertex(const QgsPointXY & mapPoint,const QgsPointLocator::Match & mapPointMatch)2645 void QgsMapToolEditMeshFrame::addVertex(
2646 const QgsPointXY &mapPoint,
2647 const QgsPointLocator::Match &mapPointMatch )
2648 {
2649 QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
2650
2651 double zValue;
2652
2653 if ( mCadDockWidget->cadEnabled() && mCurrentFaceIndex == -1 )
2654 zValue = currentZValue();
2655 else if ( mapPointMatch.isValid() )
2656 {
2657 QgsPoint layerPoint = mapPointMatch.interpolatedPoint( mCanvas->mapSettings().destinationCrs() );
2658 zValue = layerPoint.z();
2659 }
2660 else if ( mCurrentFaceIndex != -1 ) //we are on a face -->interpolate the z value
2661 {
2662 const QgsTriangularMesh &triangularMesh = *mCurrentLayer->triangularMesh();
2663 int triangleFaceIndex = triangularMesh.faceIndexForPoint_v2( mapPoint );
2664 const QgsMeshFace &triangleFace = triangularMesh.triangles().at( triangleFaceIndex );
2665 const QgsMeshVertex &v1 = triangularMesh.vertices().at( triangleFace.at( 0 ) );
2666 const QgsMeshVertex &v2 = triangularMesh.vertices().at( triangleFace.at( 1 ) );
2667 const QgsMeshVertex &v3 = triangularMesh.vertices().at( triangleFace.at( 2 ) );
2668 zValue = QgsMeshLayerUtils::interpolateFromVerticesData( v1, v2, v3, v1.z(), v2.z(), v3.z(), mapPoint );
2669 }
2670 else
2671 zValue = currentZValue();
2672
2673 QVector<QgsMeshVertex> points( 1, QgsMeshVertex( mapPoint.x(), mapPoint.y(), zValue ) );
2674 if ( mCurrentEditor )
2675 {
2676 double tolerance = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
2677 mCurrentEditor->addVertices( points, tolerance );
2678 }
2679 }
2680
updateFreeVertices()2681 void QgsMapToolEditMeshFrame::updateFreeVertices()
2682 {
2683 qDeleteAll( mFreeVertexMarker );
2684 mFreeVertexMarker.clear();
2685
2686 if ( mCurrentLayer.isNull() || mCurrentEditor.isNull() )
2687 return;
2688
2689 const QList<int> &freeVertexIndexes = mCurrentEditor->freeVerticesIndexes();
2690 int freeVertexCount = freeVertexIndexes.count();
2691 mFreeVertexMarker.reserve( freeVertexCount );
2692
2693 QColor freeVertexColor = digitizingStrokeColor();
2694 QColor fillFreeVertexColor = freeVertexColor.lighter();
2695
2696 for ( const int freeVertexIndex : freeVertexIndexes )
2697 {
2698 mFreeVertexMarker.append( new QgsVertexMarker( canvas() ) );
2699 QgsVertexMarker *marker = mFreeVertexMarker.last();
2700 marker->setCenter( mapVertexXY( freeVertexIndex ) );
2701 marker->setIconType( QgsVertexMarker::ICON_CIRCLE );
2702 marker->setIconSize( QgsGuiUtils::scaleIconSize( 7 ) );
2703 marker->setColor( freeVertexColor );
2704 marker->setFillColor( fillFreeVertexColor );
2705 }
2706 }
2707
closeVertex(const QgsPointXY & mapPoint) const2708 int QgsMapToolEditMeshFrame::closeVertex( const QgsPointXY &mapPoint ) const
2709 {
2710 if ( !mCurrentEditor )
2711 return -1;
2712
2713 double tolerance = QgsTolerance::vertexSearchRadius( canvas()->mapSettings() );
2714
2715 if ( mCurrentEdge.first != -1 && mCurrentEdge.second != -1 )
2716 {
2717 const QVector<int> &edge = edgeVertices( mCurrentEdge );
2718
2719 for ( const int vertexIndex : edge )
2720 {
2721 const QgsPointXY &meshVertex = mapVertexXY( vertexIndex );
2722 if ( meshVertex.distance( mapPoint ) < tolerance )
2723 return vertexIndex;
2724 }
2725 }
2726
2727 if ( mCurrentFaceIndex >= 0 )
2728 {
2729 const QgsMeshFace &face = mCurrentLayer->nativeMesh()->face( mCurrentFaceIndex );
2730 for ( const int vertexIndex : face )
2731 {
2732 const QgsPointXY &meshVertex = mapVertexXY( vertexIndex );
2733 if ( meshVertex.distance( mapPoint ) < tolerance )
2734 return vertexIndex;
2735 }
2736
2737 //nothing found int the face --> return -1;
2738 return -1;
2739 }
2740
2741 //if we are here, we are outside faces, need to search for free vertices;
2742 const QList<int> &freeVertexIndexes = mCurrentEditor->freeVerticesIndexes();
2743 for ( const int vertexIndex : freeVertexIndexes )
2744 {
2745 const QgsPointXY &meshVertex = mapVertexXY( vertexIndex );
2746 if ( meshVertex.distance( mapPoint ) < tolerance )
2747 return vertexIndex;
2748 }
2749
2750 return -1;
2751 }
2752
selectByExpression(const QString & textExpression,Qgis::SelectBehavior behavior,QgsMesh::ElementType elementType)2753 void QgsMapToolEditMeshFrame::selectByExpression( const QString &textExpression, Qgis::SelectBehavior behavior, QgsMesh::ElementType elementType )
2754 {
2755 if ( !mCurrentEditor || !mCurrentLayer )
2756 return;
2757 QgsExpression expression( textExpression );
2758
2759 std::unique_ptr<QgsDistanceArea> distArea = std::make_unique<QgsDistanceArea>();
2760 distArea->setSourceCrs( mCurrentLayer->crs(), QgsProject::instance()->transformContext() );
2761 distArea->setEllipsoid( QgsProject::instance()->ellipsoid() );
2762 expression.setAreaUnits( QgsProject::instance()->areaUnits() );
2763 expression.setDistanceUnits( QgsProject::instance()->distanceUnits() );
2764 expression.setGeomCalculator( distArea.release() );
2765
2766 switch ( elementType )
2767 {
2768 case QgsMesh::Vertex:
2769 setSelectedVertices( mCurrentLayer->selectVerticesByExpression( expression ), behavior );
2770 break;
2771 case QgsMesh::Face:
2772 setSelectedFaces( mCurrentLayer->selectFacesByExpression( expression ), behavior );
2773 break;
2774 case QgsMesh::Edge:
2775 //not supported
2776 break;
2777 }
2778 }
2779
2780
onZoomToSelected()2781 void QgsMapToolEditMeshFrame::onZoomToSelected()
2782 {
2783 canvas()->zoomToFeatureExtent( mSelectedMapExtent );
2784 }
2785
showSelectByExpressionDialog()2786 void QgsMapToolEditMeshFrame::showSelectByExpressionDialog()
2787 {
2788 onEditingStarted();
2789 QgsMeshSelectByExpressionDialog *dialog = new QgsMeshSelectByExpressionDialog( canvas() );
2790 dialog->setAttribute( Qt::WA_DeleteOnClose );
2791 dialog->show();
2792 connect( dialog, &QgsMeshSelectByExpressionDialog::select, this, &QgsMapToolEditMeshFrame::selectByExpression );
2793 connect( dialog, &QgsMeshSelectByExpressionDialog::zoomToSelected, this, &QgsMapToolEditMeshFrame::onZoomToSelected );
2794 }
2795