Skip to content

Commit

Permalink
Merge pull request #36200 from olivierdalang/self_snap
Browse files Browse the repository at this point in the history
[feature] Snapping also snaps to the currently digitised feature
  • Loading branch information
m-kuhn committed May 26, 2020
2 parents 2a2fe1e + 47fdd4b commit 38673f5
Show file tree
Hide file tree
Showing 18 changed files with 532 additions and 27 deletions.
1 change: 1 addition & 0 deletions images/images.qrc
Expand Up @@ -700,6 +700,7 @@
<file>themes/default/mIconSnappingMiddle.svg</file>
<file>themes/default/mIconSnappingOnScale.svg</file>
<file>themes/default/mIconSnappingVertex.svg</file>
<file>themes/default/mIconSnappingSelf.svg</file>
<file>themes/default/mIconSnappingSegment.svg</file>
<file>themes/default/mIconTopologicalEditing.svg</file>
<file>themes/default/mIconSnappingIntersection.svg</file>
Expand Down
141 changes: 141 additions & 0 deletions images/themes/default/mIconSnappingSelf.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions python/core/auto_generated/qgscadutils.sip.in
Expand Up @@ -53,6 +53,8 @@ The QgsCadUtils class provides routines for CAD editing.

QgsPointXY finalMapPoint;

QgsPointLocator::Match snapMatch;

QgsPointLocator::Match edgeMatch;

double softLockCommonAngle;
Expand Down
14 changes: 14 additions & 0 deletions python/core/auto_generated/qgssnappingconfig.sip.in
Expand Up @@ -343,6 +343,20 @@ Returns if the snapping on intersection is enabled
void setIntersectionSnapping( bool enabled );
%Docstring
Sets if the snapping on intersection is enabled
%End

bool selfSnapping() const;
%Docstring
Returns if self snapping (snapping to the currently digitised feature) is enabled

.. versionadded:: 3.14
%End

void setSelfSnapping( bool enabled );
%Docstring
Sets if self snapping (snapping to the currently digitised feature) is enabled

.. versionadded:: 3.14
%End

SIP_PYDICT individualLayerSettings() const;
Expand Down
36 changes: 36 additions & 0 deletions python/core/auto_generated/qgssnappingutils.sip.in
Expand Up @@ -166,6 +166,42 @@ Set if invisible features must be snapped or not.
.. versionadded:: 3.2
%End

void addExtraSnapLayer( QgsVectorLayer *vl );
%Docstring
Supply an extra snapping layer (typically a memory layer).
This can be used by map tools to provide additionnal
snappings points.

.. seealso:: :py:func:`removeExtraSnapLayer`

.. seealso:: :py:func:`getExtraSnapLayers`

.. versionadded:: 3.14
%End

void removeExtraSnapLayer( QgsVectorLayer *vl );
%Docstring
Removes an extra snapping layer

.. seealso:: :py:func:`addExtraSnapLayer`

.. seealso:: :py:func:`getExtraSnapLayers`

.. versionadded:: 3.14
%End

QSet<QgsVectorLayer *> getExtraSnapLayers();
%Docstring
Returns the list of extra snapping layers

.. seealso:: :py:func:`addExtraSnapLayer`

.. seealso:: :py:func:`removeExtraSnapLayer`

.. versionadded:: 3.14
%End


public slots:

void setConfig( const QgsSnappingConfig &snappingConfig );
Expand Down
27 changes: 27 additions & 0 deletions src/app/qgssnappingwidget.cpp
Expand Up @@ -303,6 +303,14 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
tracingMenu->addAction( widgetAction );
mEnableTracingAction->setMenu( tracingMenu );

// self-snapping button
mSelfSnappingAction = new QAction( tr( "Self-snapping" ), this );
mSelfSnappingAction->setCheckable( true );
mSelfSnappingAction->setIcon( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingSelf.svg" ) ) );
mSelfSnappingAction->setToolTip( tr( "If self snapping is enabled, snapping will also take the current state of the digitized feature into consideration." ) );
mSelfSnappingAction->setObjectName( QStringLiteral( "SelfSnappingAction" ) );
connect( mSelfSnappingAction, &QAction::toggled, this, &QgsSnappingWidget::enableSelfSnapping );

// layout
if ( mDisplayMode == ToolBar )
{
Expand Down Expand Up @@ -332,6 +340,7 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
mAvoidIntersectionsModeAction = tb->addWidget( mAvoidIntersectionsModeButton );
tb->addAction( mIntersectionSnappingAction );
tb->addAction( mEnableTracingAction );
tb->addAction( mSelfSnappingAction );
}
else
{
Expand Down Expand Up @@ -366,6 +375,12 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
interButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
layout->addWidget( interButton );

QToolButton *selfsnapButton = new QToolButton();
selfsnapButton->addAction( mSelfSnappingAction );
selfsnapButton->setDefaultAction( mSelfSnappingAction );
selfsnapButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
layout->addWidget( selfsnapButton );

layout->setContentsMargins( 0, 0, 0, 0 );
layout->setAlignment( Qt::AlignRight );
layout->setSpacing( mDisplayMode == Widget ? 3 : 0 );
Expand Down Expand Up @@ -504,6 +519,11 @@ void QgsSnappingWidget::projectSnapSettingsChanged()
mIntersectionSnappingAction->setChecked( config.intersectionSnapping() );
}

if ( config.selfSnapping() != mSelfSnappingAction->isChecked() )
{
mSelfSnappingAction->setChecked( config.selfSnapping() );
}

toggleSnappingWidgets( config.enabled() );

}
Expand Down Expand Up @@ -568,6 +588,7 @@ void QgsSnappingWidget::toggleSnappingWidgets( bool enabled )
mAdvancedConfigWidget->setEnabled( enabled );
}
mIntersectionSnappingAction->setEnabled( enabled );
mSelfSnappingAction->setEnabled( enabled );
mEnableTracingAction->setEnabled( enabled );
}

Expand Down Expand Up @@ -609,6 +630,12 @@ void QgsSnappingWidget::enableIntersectionSnapping( bool enabled )
mProject->setSnappingConfig( mConfig );
}

void QgsSnappingWidget::enableSelfSnapping( bool enabled )
{
mConfig.setSelfSnapping( enabled );
mProject->setSnappingConfig( mConfig );
}

void QgsSnappingWidget::onSnappingTreeLayersChanged()
{
mLayerTreeView->expandAll();
Expand Down
3 changes: 3 additions & 0 deletions src/app/qgssnappingwidget.h
Expand Up @@ -114,6 +114,8 @@ class APP_EXPORT QgsSnappingWidget : public QWidget

void enableIntersectionSnapping( bool enabled );

void enableSelfSnapping( bool enabled );

void modeButtonTriggered( QAction *action );
void avoidIntersectionsModeButtonTriggered( QAction *action );
void typeButtonTriggered( QAction *action );
Expand Down Expand Up @@ -172,6 +174,7 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
QAction *mIntersectionSnappingAction = nullptr;
QAction *mEnableTracingAction = nullptr;
QgsDoubleSpinBox *mTracingOffsetSpinBox = nullptr;
QAction *mSelfSnappingAction = nullptr;
QTreeView *mLayerTreeView = nullptr;
QWidget *mAdvancedConfigWidget = nullptr;
QgsFloatingWidget *mAdvancedConfigContainer = nullptr;
Expand Down
27 changes: 15 additions & 12 deletions src/core/qgscadutils.cpp
Expand Up @@ -42,16 +42,19 @@ QgsCadUtils::AlignMapPointOutput QgsCadUtils::alignMapPoint( const QgsPointXY &o

// try to snap to anything
QgsPointLocator::Match snapMatch = ctx.snappingUtils->snapToMap( originalMapPoint, nullptr, true );
res.snapMatch = snapMatch;
QgsPointXY point = snapMatch.isValid() ? snapMatch.point() : originalMapPoint;

// try to snap explicitly to a segment - useful for some constraints
QgsPointXY edgePt0, edgePt1;
EdgesOnlyFilter edgesOnlyFilter;
QgsPointLocator::Match edgeMatch = ctx.snappingUtils->snapToMap( originalMapPoint, &edgesOnlyFilter, true );
if ( edgeMatch.hasEdge() )
edgeMatch.edgePoints( edgePt0, edgePt1 );

res.edgeMatch = edgeMatch;
if ( snapMatch.hasEdge() )
{
snapMatch.edgePoints( edgePt0, edgePt1 );
// note : res.edgeMatch should be removed, as we can just check snapMatch.hasEdge()
res.edgeMatch = snapMatch;
}
else
{
res.edgeMatch = QgsPointLocator::Match();
}

QgsPointXY previousPt, penultimatePt;
if ( ctx.cadPointList.count() >= 2 )
Expand All @@ -71,7 +74,7 @@ QgsCadUtils::AlignMapPointOutput QgsCadUtils::alignMapPoint( const QgsPointXY &o
{
point.setX( previousPt.x() + ctx.xConstraint.value );
}
if ( edgeMatch.hasEdge() && !ctx.yConstraint.locked )
if ( snapMatch.hasEdge() && !ctx.yConstraint.locked )
{
// intersect with snapped segment line at X coordinate
const double dx = edgePt1.x() - edgePt0.x();
Expand Down Expand Up @@ -99,7 +102,7 @@ QgsCadUtils::AlignMapPointOutput QgsCadUtils::alignMapPoint( const QgsPointXY &o
{
point.setY( previousPt.y() + ctx.yConstraint.value );
}
if ( edgeMatch.hasEdge() && !ctx.xConstraint.locked )
if ( snapMatch.hasEdge() && !ctx.xConstraint.locked )
{
// intersect with snapped segment line at Y coordinate
const double dy = edgePt1.y() - edgePt0.y();
Expand Down Expand Up @@ -224,7 +227,7 @@ QgsCadUtils::AlignMapPointOutput QgsCadUtils::alignMapPoint( const QgsPointXY &o
point.setY( previousPt.y() + sina * v );
}

if ( edgeMatch.hasEdge() && !ctx.distanceConstraint.locked )
if ( snapMatch.hasEdge() && !ctx.distanceConstraint.locked )
{
// magnetize to the intersection of the snapped segment and the lockedAngle

Expand Down Expand Up @@ -287,7 +290,7 @@ QgsCadUtils::AlignMapPointOutput QgsCadUtils::alignMapPoint( const QgsPointXY &o
previousPt.y() + ( point.y() - previousPt.y() ) * vP );
}

if ( edgeMatch.hasEdge() && !ctx.angleConstraint.locked )
if ( snapMatch.hasEdge() && !ctx.angleConstraint.locked )
{
// we will magnietize to the intersection of that segment and the lockedDistance !
res.valid &= QgsGeometryUtils::lineCircleIntersection( previousPt, ctx.distanceConstraint.value, edgePt0, edgePt1, point );
Expand Down
11 changes: 10 additions & 1 deletion src/core/qgscadutils.h
Expand Up @@ -92,7 +92,16 @@ class CORE_EXPORT QgsCadUtils
//! map point aligned according to the constraints
QgsPointXY finalMapPoint;

//! Snapped segment - only valid if actually used for something
/**
* Snapped point - only valid if actually used for something
* \since QGIS 3.14
*/
QgsPointLocator::Match snapMatch;

/**
* Snapped segment - only valid if actually used for something
* \deprecated will be removed in QGIS 4.0 - use snapMatch instead
*/
QgsPointLocator::Match edgeMatch;

//! Angle (in degrees) to which we have soft-locked ourselves (if not set it is -1)
Expand Down

0 comments on commit 38673f5

Please sign in to comment.