Skip to content
Permalink
Browse files

Merge pull request #36200 from olivierdalang/self_snap

[feature] Snapping also snaps to the currently digitised feature
  • Loading branch information
m-kuhn committed May 26, 2020
2 parents 2a2fe1e + 47fdd4b commit 38673f5a7ff5a05a082d61e4b2f7fb2249444c78
@@ -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>
@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="mIconSnappingSelf.svg"
id="svg68"
version="1.1"
width="24"
viewBox="0 0 24 24"
height="24">
<metadata
id="metadata74">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs72" />
<sodipodi:namedview
inkscape:current-layer="svg68"
inkscape:window-maximized="0"
inkscape:window-y="120"
inkscape:window-x="351"
inkscape:cy="15.032678"
inkscape:cx="21.715662"
inkscape:zoom="14.849242"
showgrid="false"
id="namedview70"
inkscape:window-height="1163"
inkscape:window-width="1445"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<g
id="g66"
transform="translate(0.04085775,-7.9316355)"
stroke-linecap="round">
<path
sodipodi:nodetypes="cccc"
id="path60"
stroke-width="2"
stroke-linejoin="round"
stroke="#8cbe8c"
fill="none"
d="M 20.566062,23.218131 4.4877133,14.977485 9.0817013,27.060272 2,30" />
<ellipse
id="ellipse62"
stroke-width="2"
stroke="#ffffff"
ry="2.5"
rx="0.75"
fill="#322825"
cy="14"
cx="-14.75" />
<circle
id="circle64"
stroke-width="1.031"
stroke="#8c8c8c"
r="2.034729"
fill="#bebebe"
cy="26.858242"
cx="8.6102972" />
<circle
cx="4.4877133"
cy="15.179515"
fill="#bebebe"
r="2.034729"
stroke="#8c8c8c"
stroke-width="1.031"
id="circle64-5" />
</g>
<g
transform="matrix(1.2651778,0,0,1.2651778,-4.6265242,-0.70591315)"
id="g1921">
<path
sodipodi:nodetypes="cccc"
id="path1853"
stroke-width="0.35534"
stroke-linecap="round"
stroke="#505050"
overflow="visible"
fill="#505050"
d="m 19.461512,9.2175166 -2.259422,1.3635194 2.663548,1.925751 z" />
<path
id="path1855"
stroke-width="0.35534"
stroke-linecap="round"
stroke="#8b7617"
overflow="visible"
fill="#fce94f"
d="M 15.362026,1.9819405 12.900159,3.4032995 16.927342,10.37859 19.38921,8.9572295 15.362026,1.9819405" />
<path
id="path1861"
style="overflow:visible;opacity:0.5;fill:#fce94f;stroke:#8b7617;stroke-width:0.35534;stroke-linecap:round"
d="m 13.957675,3.3398245 3.79029,6.564978" />
<path
id="path1863"
stroke-width="0.236893"
stroke-linecap="square"
stroke="#969696"
overflow="visible"
fill="#969696"
d="M 19.369733,12.033342 17.379211,10.47792 18.198585,10.004853 Z" />
<path
id="path1865"
style="overflow:visible;opacity:0.5;fill:#fce94f;stroke:#8b7617;stroke-width:0.35534;stroke-linecap:round"
d="m 14.983453,2.7475915 3.790289,6.564978" />
<path
id="path1867"
stroke-width="0.236893"
stroke-linecap="round"
stroke="#969696"
overflow="visible"
fill="#e6e6e6"
d="m 15.192412,1.4694895 -2.651241,1.530695 0.516222,0.894122 2.65124,-1.530695 z" />
<path
id="path1869"
stroke-width="0.236893"
stroke-linecap="square"
stroke="#e6e6e6"
overflow="visible"
fill="#e6e6e6"
d="M 19.70856,11.92433 19.319283,9.3578195 18.499909,9.8308845 Z" />
</g>
</svg>
@@ -53,6 +53,8 @@ The QgsCadUtils class provides routines for CAD editing.

QgsPointXY finalMapPoint;

QgsPointLocator::Match snapMatch;

QgsPointLocator::Match edgeMatch;

double softLockCommonAngle;
@@ -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;
@@ -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 );
@@ -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 )
{
@@ -332,6 +340,7 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
mAvoidIntersectionsModeAction = tb->addWidget( mAvoidIntersectionsModeButton );
tb->addAction( mIntersectionSnappingAction );
tb->addAction( mEnableTracingAction );
tb->addAction( mSelfSnappingAction );
}
else
{
@@ -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 );
@@ -504,6 +519,11 @@ void QgsSnappingWidget::projectSnapSettingsChanged()
mIntersectionSnappingAction->setChecked( config.intersectionSnapping() );
}

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

toggleSnappingWidgets( config.enabled() );

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

@@ -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();
@@ -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 );
@@ -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;
@@ -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 )
@@ -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();
@@ -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();
@@ -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

@@ -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 );
@@ -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)

0 comments on commit 38673f5

Please sign in to comment.
You can’t perform that action at this time.