Add configurable hysteresis to PCB snapping

Makes it harder to enter and harder to exit a snap.  ideally this should
help make snapping more intuitive and easier to use.  Once you have a
snap, it is harder to lose it but it avoids being overly snappy in the
begining
This commit is contained in:
Seth Hillbrand 2025-08-29 16:21:35 -07:00
parent a811f61c39
commit e282dca102
3 changed files with 48 additions and 2 deletions

View File

@ -119,6 +119,7 @@ static const wxChar EnableExtensionSnaps[] = wxT( "EnableExtensionSnaps" );
static const wxChar ExtensionSnapTimeoutMs[] = wxT( "ExtensionSnapTimeoutMs" );
static const wxChar ExtensionSnapActivateOnHover[] = wxT( "ExtensionSnapActivateOnHover" );
static const wxChar EnableSnapAnchorsDebug[] = wxT( "EnableSnapAnchorsDebug" );
static const wxChar SnapHysteresis[] = wxT( "SnapHysteresis" );
static const wxChar MinParallelAngle[] = wxT( "MinParallelAngle" );
static const wxChar HoleWallPaintingMultiplier[] = wxT( "HoleWallPaintingMultiplier" );
static const wxChar MsgPanelShowUuids[] = wxT( "MsgPanelShowUuids" );
@ -299,6 +300,7 @@ ADVANCED_CFG::ADVANCED_CFG()
m_ExtensionSnapTimeoutMs = 500;
m_ExtensionSnapActivateOnHover = true;
m_EnableSnapAnchorsDebug = false;
m_SnapHysteresis = 5;
m_MinParallelAngle = 0.001;
m_HoleWallPaintingMultiplier = 1.5;
@ -592,6 +594,10 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
&m_EnableSnapAnchorsDebug,
m_EnableSnapAnchorsDebug ) );
m_entries.push_back( std::make_unique<PARAM_CFG_INT>( true, AC_KEYS::SnapHysteresis,
&m_SnapHysteresis, m_SnapHysteresis,
0, 100 ) );
m_entries.push_back( std::make_unique<PARAM_CFG_DOUBLE>( true, AC_KEYS::MinParallelAngle,
&m_MinParallelAngle, m_MinParallelAngle,
0.0, 45.0 ) );

View File

@ -701,6 +701,15 @@ public:
*/
bool m_EnableSnapAnchorsDebug;
/**
* Hysteresis in pixels used for snap activation and deactivation.
*
* Setting name: "SnapHysteresis"
* Default value: 5
* Valid values: 0 to 100
*/
int m_SnapHysteresis;
/**
* Minimum overlapping angle for which an arc is considered to be parallel
* to its paired arc.

View File

@ -26,6 +26,7 @@
#include "pcb_grid_helper.h"
#include <functional>
#include <algorithm>
#include <advanced_config.h>
#include <pcb_dimension.h>
@ -539,7 +540,6 @@ VECTOR2I PCB_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, const LSET& a
BOX2ISafe( VECTOR2D( aOrigin ) - snapRange / 2.0, VECTOR2D( snapRange, snapRange ) );
clearAnchors();
m_snapItem = std::nullopt;
const std::vector<BOARD_ITEM*> visibleItems = queryVisible( visibilityHorizon, aSkip );
computeAnchors( visibleItems, aOrigin, false, nullptr, &aLayers, false );
@ -547,6 +547,11 @@ VECTOR2I PCB_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, const LSET& a
ANCHOR* nearest = nearestAnchor( aOrigin, SNAPPABLE );
VECTOR2I nearestGrid = Align( aOrigin, aGrid );
const int hysteresisWorld =
KiROUND( m_toolMgr->GetView()->ToWorld( ADVANCED_CFG::GetCfg().m_SnapHysteresis ) );
const int snapIn = std::max( 0, snapRange - hysteresisWorld );
const int snapOut = snapRange + hysteresisWorld;
if( KIGFX::ANCHOR_DEBUG* ad = enableAndGetAnchorDebug(); ad )
{
ad->ClearAnchors();
@ -564,6 +569,13 @@ VECTOR2I PCB_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, const LSET& a
if( nearest )
snapDist = nearest->Distance( aOrigin );
if( m_snapItem )
{
int existingDist = m_snapItem->Distance( aOrigin );
if( !snapDist || existingDist < *snapDist )
snapDist = existingDist;
}
showConstructionGeometry( m_enableSnap );
SNAP_MANAGER& snapManager = getSnapManager();
@ -635,8 +647,27 @@ VECTOR2I PCB_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, const LSET& a
}
}
if( m_snapItem )
{
int dist = m_snapItem->Distance( aOrigin );
if( dist <= snapOut )
{
if( nearest && ptIsReferenceOnly( nearest->pos ) &&
nearest->Distance( aOrigin ) <= snapRange )
snapLineManager.SetSnapLineOrigin( nearest->pos );
snapLineManager.SetSnappedAnchor( m_snapItem->pos );
updateSnapPoint( { m_snapItem->pos, m_snapItem->pointTypes } );
return m_snapItem->pos;
}
m_snapItem = std::nullopt;
}
// If there's a snap anchor within range, use it if we can
if( nearest && nearest->Distance( aOrigin ) <= snapRange )
if( nearest && nearest->Distance( aOrigin ) <= snapIn )
{
const bool anchorIsConstructed = nearest->flags & ANCHOR_FLAGS::CONSTRUCTED;