pcbnew: Implement consistent graphical snapping

This creates a standard snapping framework in the GRID_HELPER class that
allows snapping to items on the same layer as the object being
created/moved as well as consistent toggling of this using the Shift key
modifier.

Fixes: lp:806260
* https://bugs.launchpad.net/kicad/+bug/806260

Fixes: lp:1604616
* https://bugs.launchpad.net/kicad/+bug/1604616
This commit is contained in:
Seth Hillbrand 2018-10-03 17:15:54 -07:00
parent f714d2fa64
commit 03e642a8db
6 changed files with 78 additions and 51 deletions

View File

@ -975,6 +975,7 @@ bool DRAWING_TOOL::drawSegment( int aShape, DRAWSEGMENT*& aGraphic,
assert( aShape == S_SEGMENT || aShape == S_CIRCLE );
DRAWSEGMENT line45;
GRID_HELPER grid( m_frame );
m_frame->SetActiveLayer( getDrawingLayer() );
@ -998,9 +999,11 @@ bool DRAWING_TOOL::drawSegment( int aShape, DRAWSEGMENT*& aGraphic,
// Init the new item attributes
aGraphic->SetShape( (STROKE_T) aShape );
aGraphic->SetWidth( m_lineWidth );
aGraphic->SetStart( wxPoint( aStartingPoint->x, aStartingPoint->y ) );
aGraphic->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
aGraphic->SetLayer( getDrawingLayer() );
aGraphic->SetStart( wxPoint( aStartingPoint->x, aStartingPoint->y ) );
cursorPos = grid.BestSnapAnchor( cursorPos, aGraphic );
aGraphic->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
if( aShape == S_SEGMENT )
line45 = *aGraphic; // used only for direction 45 mode with lines
@ -1018,7 +1021,9 @@ bool DRAWING_TOOL::drawSegment( int aShape, DRAWSEGMENT*& aGraphic,
// Main loop: keep receiving events
while( OPT_TOOL_EVENT evt = Wait() )
{
cursorPos = m_controls->GetCursorPosition();
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
cursorPos = grid.BestSnapAnchor( evt->Position(), aGraphic );
// 45 degree angle constraint enabled with an option and toggled with Ctrl
const bool limit45 = ( frame()->Settings().m_use45DegreeGraphicSegments != !!( evt->Modifier( MD_CTRL ) ) );
@ -1030,7 +1035,7 @@ bool DRAWING_TOOL::drawSegment( int aShape, DRAWSEGMENT*& aGraphic,
if( direction45 )
{
preview.Add( &line45 );
make45DegLine( aGraphic, &line45 );
make45DegLine( aGraphic, &line45, cursorPos );
}
else
{
@ -1121,7 +1126,7 @@ bool DRAWING_TOOL::drawSegment( int aShape, DRAWSEGMENT*& aGraphic,
{
// 45 degree lines
if( direction45 && aShape == S_SEGMENT )
make45DegLine( aGraphic, &line45 );
make45DegLine( aGraphic, &line45, cursorPos );
else
aGraphic->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
@ -1198,6 +1203,7 @@ bool DRAWING_TOOL::drawArc( DRAWSEGMENT*& aGraphic )
SELECTION preview;
m_view->Add( &preview );
m_view->Add( &arcAsst );
GRID_HELPER grid( m_frame );
m_controls->ShowCursor( true );
m_controls->SetSnapping( true );
@ -1209,7 +1215,11 @@ bool DRAWING_TOOL::drawArc( DRAWSEGMENT*& aGraphic )
// Main loop: keep receiving events
while( OPT_TOOL_EVENT evt = Wait() )
{
const VECTOR2I cursorPos = m_controls->GetCursorPosition();
PCB_LAYER_ID layer = getDrawingLayer();
aGraphic->SetLayer( layer );
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
const VECTOR2I cursorPos = grid.BestSnapAnchor( evt->Position(), aGraphic );
if( evt->IsClick( BUT_LEFT ) )
{
@ -1218,13 +1228,10 @@ bool DRAWING_TOOL::drawArc( DRAWSEGMENT*& aGraphic )
m_controls->SetAutoPan( true );
m_controls->CaptureCursor( true );
PCB_LAYER_ID layer = getDrawingLayer();
// Init the new item attributes
// (non-geometric, those are handled by the manager)
aGraphic->SetShape( S_ARC );
aGraphic->SetWidth( m_lineWidth );
aGraphic->SetLayer( layer );
preview.Add( aGraphic );
firstPoint = true;
@ -1342,13 +1349,16 @@ void DRAWING_TOOL::runPolygonEventLoop( POLYGON_GEOM_MANAGER& polyGeomMgr )
auto& controls = *getViewControls();
bool started = false;
GRID_HELPER grid( m_frame );
STATUS_TEXT_POPUP status( m_frame );
status.SetTextColor( wxColour( 255, 0, 0 ) );
status.SetText( _( "Self-intersecting polygons are not allowed" ) );
while( OPT_TOOL_EVENT evt = Wait() )
{
VECTOR2I cursorPos = controls.GetCursorPosition();
LSET layers( m_frame->GetActiveLayer() );
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
VECTOR2I cursorPos = grid.BestSnapAnchor( evt->Position(), layers );
if( TOOL_EVT_UTILS::IsCancelInteractive( *evt ) )
{
@ -1489,12 +1499,12 @@ int DRAWING_TOOL::drawZone( bool aKeepout, ZONE_MODE aMode )
}
void DRAWING_TOOL::make45DegLine( DRAWSEGMENT* aSegment, DRAWSEGMENT* aHelper ) const
void DRAWING_TOOL::make45DegLine( DRAWSEGMENT* aSegment, DRAWSEGMENT* aHelper,
VECTOR2I& aPos ) const
{
VECTOR2I cursorPos = m_controls->GetCursorPosition();
VECTOR2I origin( aSegment->GetStart() );
DIRECTION_45 direction( origin - cursorPos );
SHAPE_LINE_CHAIN newChain = direction.BuildInitialTrace( origin, cursorPos );
DIRECTION_45 direction( origin - aPos );
SHAPE_LINE_CHAIN newChain = direction.BuildInitialTrace( origin, aPos );
if( newChain.PointCount() > 2 )
{
@ -1504,9 +1514,9 @@ void DRAWING_TOOL::make45DegLine( DRAWSEGMENT* aSegment, DRAWSEGMENT* aHelper )
}
else
{
aSegment->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
aHelper->SetStart( wxPoint( cursorPos.x, cursorPos.y ) );
aHelper->SetEnd( wxPoint( cursorPos.x, cursorPos.y ) );
aSegment->SetEnd( wxPoint( aPos.x, aPos.y ) );
aHelper->SetStart( wxPoint( aPos.x, aPos.y ) );
aHelper->SetEnd( wxPoint( aPos.x, aPos.y ) );
}
}
@ -1593,13 +1603,7 @@ int DRAWING_TOOL::DrawVia( const TOOL_EVENT& aEvent )
// bool do_snap = ( m_frame->Settings().m_magneticTracks == CAPTURE_CURSOR_IN_TRACK_TOOL
// || m_frame->Settings().m_magneticTracks == CAPTURE_ALWAYS );
bool do_snap = true;
if( m_modifiers & MD_SHIFT )
do_snap = !do_snap;
if( do_snap )
{
m_gridHelper.SetSnap( !( m_modifiers & MD_SHIFT ) );
auto via = static_cast<VIA*>( aItem );
wxPoint pos = via->GetPosition();
TRACK* track = findTrack( via );
@ -1612,7 +1616,6 @@ int DRAWING_TOOL::DrawVia( const TOOL_EVENT& aEvent )
aItem->SetPosition( wxPoint( snap.x, snap.y ) );
}
}
}
void PlaceItem( BOARD_ITEM* aItem, BOARD_COMMIT& aCommit ) override
{

View File

@ -251,8 +251,9 @@ private:
* the end of the aSegment is modified according to the current cursor position.
* @param aSegment is the segment that is currently drawn.
* @param aHelper is a helper line that shows the next possible segment.
* @param aPos is the position of the cursor for this event
*/
void make45DegLine( DRAWSEGMENT* aSegment, DRAWSEGMENT* aHelper ) const;
void make45DegLine( DRAWSEGMENT* aSegment, DRAWSEGMENT* aHelper, VECTOR2I& aPos ) const;
/**
* Function constrainDimension()

View File

@ -372,6 +372,8 @@ int EDIT_TOOL::Main( const TOOL_EVENT& aEvent )
// Main loop: keep receiving events
do
{
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
if( evt->IsAction( &PCB_ACTIONS::editActivate ) ||
evt->IsAction( &PCB_ACTIONS::move ) ||
evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )

View File

@ -46,6 +46,8 @@ GRID_HELPER::GRID_HELPER( PCB_BASE_FRAME* aFrame ) :
m_frame( aFrame )
{
m_diagonalAuxAxesEnable = true;
m_enableSnap = true;
m_snapSize = 100;
KIGFX::VIEW* view = m_frame->GetGalCanvas()->GetView();
m_viewAxis.SetSize( 20000 );
@ -141,6 +143,9 @@ VECTOR2I GRID_HELPER::AlignToSegment( const VECTOR2I& aPoint, const SEG& aSeg )
{
OPT_VECTOR2I pts[6];
if( !m_enableSnap )
return aPoint;
const VECTOR2D gridOffset( GetOrigin() );
const VECTOR2D gridSize( GetGrid() );
@ -238,9 +243,22 @@ std::set<BOARD_ITEM*> GRID_HELPER::queryVisible( const BOX2I& aArea ) const
VECTOR2I GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, BOARD_ITEM* aDraggedItem )
{
LSET layers;
if( aDraggedItem )
layers = aDraggedItem->GetLayer();
else
layers = LSET::AllLayersMask();
return BestSnapAnchor( aOrigin, layers );
}
VECTOR2I GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, LSET& aLayers )
{
double worldScale = m_frame->GetGalCanvas()->GetGAL()->GetWorldScale();
int snapRange = (int) ( 100.0 / worldScale );
int snapRange = (int) ( m_snapSize / worldScale );
BOX2I bb( VECTOR2I( aOrigin.x - snapRange / 2, aOrigin.y - snapRange / 2 ), VECTOR2I( snapRange, snapRange ) );
@ -251,19 +269,12 @@ VECTOR2I GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, BOARD_ITEM* aDrag
computeAnchors( item, aOrigin );
}
LSET layers;
if( aDraggedItem )
layers = aDraggedItem->GetLayer();
else
layers = LSET::AllLayersMask();
ANCHOR* nearest = nearestAnchor( aOrigin, CORNER | SNAPPABLE, layers );
ANCHOR* nearest = nearestAnchor( aOrigin, CORNER | SNAPPABLE, aLayers );
VECTOR2I nearestGrid = Align( aOrigin );
double gridDist = ( nearestGrid - aOrigin ).EuclideanNorm();
if( nearest )
if( nearest && m_enableSnap )
{
double snapDist = nearest->Distance( aOrigin );

View File

@ -57,6 +57,12 @@ public:
VECTOR2I BestDragOrigin( const VECTOR2I& aMousePos, BOARD_ITEM* aItem );
VECTOR2I BestSnapAnchor( const VECTOR2I& aOrigin, BOARD_ITEM* aDraggedItem );
VECTOR2I BestSnapAnchor( const VECTOR2I& aOrigin, LSET& aLayers );
void SetSnap( bool aSnap )
{
m_enableSnap = aSnap;
}
private:
enum ANCHOR_FLAGS {
@ -79,8 +85,6 @@ private:
{
return ( aP - pos ).EuclideanNorm();
}
//bool CanSnapItem( const BOARD_ITEM* aItem ) const;
};
std::vector<ANCHOR> m_anchors;
@ -103,8 +107,13 @@ private:
PCB_BASE_FRAME* m_frame;
OPT<VECTOR2I> m_auxAxis;
bool m_diagonalAuxAxesEnable;
KIGFX::ORIGIN_VIEWITEM m_viewSnapPoint, m_viewAxis;
bool m_diagonalAuxAxesEnable; ///< If true, use the aux axis for snapping as well
bool m_enableSnap; ///< If true, allow snapping to other items on the layers
int m_snapSize; ///< Sets the radius in screen units for snapping to items
KIGFX::ORIGIN_VIEWITEM m_viewSnapPoint;
KIGFX::ORIGIN_VIEWITEM m_viewAxis;
};
#endif

View File

@ -329,6 +329,8 @@ int POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent )
if( revert )
break;
grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
if( !m_editPoints ||
evt->Matches( m_selectionTool->ClearedEvent ) ||
evt->Matches( m_selectionTool->UnselectedEvent ) ||
@ -346,7 +348,6 @@ int POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent )
{
commit.StageItems( selection, CHT_MODIFY );
controls->ForceCursorPosition( false );
m_original = *m_editedPoint; // Save the original position
controls->SetAutoPan( true );
modified = true;
@ -358,14 +359,14 @@ int POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent )
if( enableAltConstraint != (bool) m_altConstraint ) // alternative constraint
setAltConstraint( enableAltConstraint );
m_editedPoint->SetPosition( controls->GetCursorPosition() );
if( m_altConstraint )
m_altConstraint->Apply();
else
m_editedPoint->ApplyConstraint();
m_editedPoint->SetPosition( grid.Align( m_editedPoint->GetPosition() ) );
m_editedPoint->SetPosition( grid.BestSnapAnchor( evt->Position(),
static_cast<BOARD_ITEM*>( item ) ) );
updateItem();
updatePoints();
}