1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2014 CERN
5  * Copyright (C) 2018-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, you may find one here:
20  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21  * or you may search the http://www.gnu.org website for the version 2 license,
22  * or you may write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
24  */
25 
26 #include <functional>
27 #include <board.h>
28 #include <pcb_dimension.h>
29 #include <fp_shape.h>
30 #include <footprint.h>
31 #include <pad.h>
32 #include <pcb_group.h>
33 #include <pcb_track.h>
34 #include <zone.h>
35 #include <geometry/shape_circle.h>
36 #include <geometry/shape_line_chain.h>
37 #include <geometry/shape_rect.h>
38 #include <geometry/shape_segment.h>
39 #include <geometry/shape_simple.h>
40 #include <macros.h>
41 #include <math/util.h> // for KiROUND
42 #include <painter.h>
43 #include <pcbnew_settings.h>
44 #include <tool/tool_manager.h>
45 #include <tools/pcb_tool_base.h>
46 #include <view/view.h>
47 #include "pcb_grid_helper.h"
48 
49 
PCB_GRID_HELPER(TOOL_MANAGER * aToolMgr,MAGNETIC_SETTINGS * aMagneticSettings)50 PCB_GRID_HELPER::PCB_GRID_HELPER( TOOL_MANAGER* aToolMgr, MAGNETIC_SETTINGS* aMagneticSettings ) :
51     GRID_HELPER( aToolMgr ),
52     m_magneticSettings( aMagneticSettings )
53 {
54     KIGFX::VIEW*            view = m_toolMgr->GetView();
55     KIGFX::RENDER_SETTINGS* settings = view->GetPainter()->GetSettings();
56     KIGFX::COLOR4D          auxItemsColor = settings->GetLayerColor( LAYER_AUX_ITEMS );
57     KIGFX::COLOR4D          umbilicalColor = settings->GetLayerColor( LAYER_ANCHOR );
58 
59     m_viewAxis.SetSize( 20000 );
60     m_viewAxis.SetStyle( KIGFX::ORIGIN_VIEWITEM::CROSS );
61     m_viewAxis.SetColor( auxItemsColor.WithAlpha( 0.4 ) );
62     m_viewAxis.SetDrawAtZero( true );
63     view->Add( &m_viewAxis );
64     view->SetVisible( &m_viewAxis, false );
65 
66     m_viewSnapPoint.SetStyle( KIGFX::ORIGIN_VIEWITEM::CIRCLE_CROSS );
67     m_viewSnapPoint.SetColor( auxItemsColor );
68     m_viewSnapPoint.SetDrawAtZero( true );
69     view->Add( &m_viewSnapPoint );
70     view->SetVisible( &m_viewSnapPoint, false );
71 
72     m_viewSnapLine.SetStyle( KIGFX::ORIGIN_VIEWITEM::DASH_LINE );
73     m_viewSnapLine.SetColor( umbilicalColor );
74     m_viewSnapLine.SetDrawAtZero( true );
75     view->Add( &m_viewSnapLine );
76     view->SetVisible( &m_viewSnapLine, false );
77 }
78 
79 
AlignToSegment(const VECTOR2I & aPoint,const SEG & aSeg)80 VECTOR2I PCB_GRID_HELPER::AlignToSegment( const VECTOR2I& aPoint, const SEG& aSeg )
81 {
82     OPT_VECTOR2I pts[6];
83 
84     const int c_gridSnapEpsilon = 2;
85 
86     if( !m_enableSnap )
87         return aPoint;
88 
89     VECTOR2I nearest = Align( aPoint );
90 
91     SEG pos_slope( nearest + VECTOR2I( -1, 1 ), nearest + VECTOR2I( 1, -1 ) );
92     SEG neg_slope( nearest + VECTOR2I( -1, -1 ), nearest + VECTOR2I( 1, 1 ) );
93     int max_i = 2;
94 
95     pts[0] = aSeg.A;
96     pts[1] = aSeg.B;
97 
98     if( !aSeg.ApproxParallel( pos_slope ) )
99         pts[max_i++] = aSeg.IntersectLines( pos_slope );
100 
101     if( !aSeg.ApproxParallel( neg_slope ) )
102         pts[max_i++] = aSeg.IntersectLines( neg_slope );
103 
104     int min_d = std::numeric_limits<int>::max();
105 
106     for( int i = 0; i < max_i; i++ )
107     {
108         if( pts[i] && aSeg.Distance( *pts[i] ) <= c_gridSnapEpsilon )
109         {
110             int d = (*pts[i] - aPoint).EuclideanNorm();
111 
112             if( d < min_d )
113             {
114                 min_d = d;
115                 nearest = *pts[i];
116             }
117         }
118     }
119 
120     return nearest;
121 }
122 
123 
AlignToArc(const VECTOR2I & aPoint,const SHAPE_ARC & aArc)124 VECTOR2I PCB_GRID_HELPER::AlignToArc( const VECTOR2I& aPoint, const SHAPE_ARC& aArc )
125 {
126     if( !m_enableSnap )
127         return aPoint;
128 
129     const VECTOR2D gridOffset( GetOrigin() );
130     const VECTOR2D gridSize( GetGrid() );
131 
132     VECTOR2I nearest( KiROUND( ( aPoint.x - gridOffset.x ) / gridSize.x ) * gridSize.x + gridOffset.x,
133                       KiROUND( ( aPoint.y - gridOffset.y ) / gridSize.y ) * gridSize.y + gridOffset.y );
134 
135     int min_d = std::numeric_limits<int>::max();
136 
137     for( auto pt : { aArc.GetP0(), aArc.GetP1() } )
138     {
139         int d = ( pt - aPoint ).EuclideanNorm();
140 
141         if( d < min_d )
142         {
143             min_d = d;
144             nearest = pt;
145         }
146         else
147             break;
148     }
149 
150     return nearest;
151 }
152 
153 
BestDragOrigin(const VECTOR2I & aMousePos,std::vector<BOARD_ITEM * > & aItems)154 VECTOR2I PCB_GRID_HELPER::BestDragOrigin( const VECTOR2I &aMousePos,
155                                           std::vector<BOARD_ITEM*>& aItems )
156 {
157     clearAnchors();
158 
159     for( BOARD_ITEM* item : aItems )
160         computeAnchors( item, aMousePos, true );
161 
162     double worldScale = m_toolMgr->GetView()->GetGAL()->GetWorldScale();
163     double lineSnapMinCornerDistance = 50.0 / worldScale;
164 
165     ANCHOR* nearestOutline = nearestAnchor( aMousePos, OUTLINE, LSET::AllLayersMask() );
166     ANCHOR* nearestCorner = nearestAnchor( aMousePos, CORNER, LSET::AllLayersMask() );
167     ANCHOR* nearestOrigin = nearestAnchor( aMousePos, ORIGIN, LSET::AllLayersMask() );
168     ANCHOR* best = nullptr;
169     double minDist = std::numeric_limits<double>::max();
170 
171     if( nearestOrigin )
172     {
173         minDist = nearestOrigin->Distance( aMousePos );
174         best = nearestOrigin;
175     }
176 
177     if( nearestCorner )
178     {
179         double dist = nearestCorner->Distance( aMousePos );
180 
181         if( dist < minDist )
182         {
183             minDist = dist;
184             best = nearestCorner;
185         }
186     }
187 
188     if( nearestOutline )
189     {
190         double dist = nearestOutline->Distance( aMousePos );
191 
192         if( minDist > lineSnapMinCornerDistance && dist < minDist )
193             best = nearestOutline;
194     }
195 
196     return best ? best->pos : aMousePos;
197 }
198 
199 
BestSnapAnchor(const VECTOR2I & aOrigin,BOARD_ITEM * aReferenceItem)200 VECTOR2I PCB_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, BOARD_ITEM* aReferenceItem )
201 {
202     LSET layers;
203     std::vector<BOARD_ITEM*> item;
204 
205     if( aReferenceItem )
206     {
207         layers = aReferenceItem->GetLayerSet();
208         item.push_back( aReferenceItem );
209     }
210     else
211     {
212         layers = LSET::AllLayersMask();
213     }
214 
215     return BestSnapAnchor( aOrigin, layers, item );
216 }
217 
218 
BestSnapAnchor(const VECTOR2I & aOrigin,const LSET & aLayers,const std::vector<BOARD_ITEM * > & aSkip)219 VECTOR2I PCB_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, const LSET& aLayers,
220                                           const std::vector<BOARD_ITEM*>& aSkip )
221 {
222     // Tuning constant: snap radius in screen space
223     const int snapSize = 25;
224 
225     // Snapping distance is in screen space, clamped to the current grid to ensure that the grid
226     // points that are visible can always be snapped to.
227     // see https://gitlab.com/kicad/code/kicad/-/issues/5638
228     // see https://gitlab.com/kicad/code/kicad/-/issues/7125
229     double snapScale = snapSize / m_toolMgr->GetView()->GetGAL()->GetWorldScale();
230     int    snapRange = std::min( KiROUND( snapScale ), GetGrid().x );
231     int    snapDist  = snapRange;
232 
233     BOX2I bb( VECTOR2I( aOrigin.x - snapRange / 2, aOrigin.y - snapRange / 2 ),
234               VECTOR2I( snapRange, snapRange ) );
235 
236     clearAnchors();
237 
238     for( BOARD_ITEM* item : queryVisible( bb, aSkip ) )
239         computeAnchors( item, aOrigin );
240 
241     ANCHOR*  nearest = nearestAnchor( aOrigin, SNAPPABLE, aLayers );
242     VECTOR2I nearestGrid = Align( aOrigin );
243 
244     if( nearest )
245         snapDist = nearest->Distance( aOrigin );
246 
247     // Existing snap lines need priority over new snaps
248     if( m_snapItem && m_enableSnapLine && m_enableSnap )
249     {
250         bool snapLine = false;
251         int x_dist = std::abs( m_viewSnapLine.GetPosition().x - aOrigin.x );
252         int y_dist = std::abs( m_viewSnapLine.GetPosition().y - aOrigin.y );
253 
254         /// Allows de-snapping from the line if you are closer to another snap point
255         if( x_dist < snapRange && ( !nearest || snapDist > snapRange ) )
256         {
257             nearestGrid.x = m_viewSnapLine.GetPosition().x;
258             snapLine      = true;
259         }
260 
261         if( y_dist < snapRange && ( !nearest || snapDist > snapRange ) )
262         {
263             nearestGrid.y = m_viewSnapLine.GetPosition().y;
264             snapLine      = true;
265         }
266 
267         if( snapLine && m_skipPoint != VECTOR2I( m_viewSnapLine.GetPosition() ) )
268         {
269             m_viewSnapLine.SetEndPosition( nearestGrid );
270 
271             if( m_toolMgr->GetView()->IsVisible( &m_viewSnapLine ) )
272                 m_toolMgr->GetView()->Update( &m_viewSnapLine, KIGFX::GEOMETRY );
273             else
274                 m_toolMgr->GetView()->SetVisible( &m_viewSnapLine, true );
275 
276             return nearestGrid;
277         }
278     }
279 
280     if( nearest && m_enableSnap )
281     {
282         if( nearest->Distance( aOrigin ) <= snapRange )
283         {
284             m_viewSnapPoint.SetPosition( wxPoint( nearest->pos ) );
285             m_viewSnapLine.SetPosition( wxPoint( nearest->pos ) );
286             m_toolMgr->GetView()->SetVisible( &m_viewSnapLine, false );
287 
288             if( m_toolMgr->GetView()->IsVisible( &m_viewSnapPoint ) )
289                 m_toolMgr->GetView()->Update( &m_viewSnapPoint, KIGFX::GEOMETRY);
290             else
291                 m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, true );
292 
293             m_snapItem = nearest;
294             return nearest->pos;
295         }
296     }
297 
298     m_snapItem = nullptr;
299     m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, false );
300     m_toolMgr->GetView()->SetVisible( &m_viewSnapLine, false );
301     return nearestGrid;
302 }
303 
304 
GetSnapped() const305 BOARD_ITEM* PCB_GRID_HELPER::GetSnapped() const
306 {
307     if( !m_snapItem )
308         return nullptr;
309 
310     return static_cast<BOARD_ITEM*>( m_snapItem->item );
311 }
312 
313 
queryVisible(const BOX2I & aArea,const std::vector<BOARD_ITEM * > & aSkip) const314 std::set<BOARD_ITEM*> PCB_GRID_HELPER::queryVisible( const BOX2I& aArea,
315                                                      const std::vector<BOARD_ITEM*>& aSkip ) const
316 {
317     std::set<BOARD_ITEM*> items;
318     std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> selectedItems;
319 
320     KIGFX::VIEW*                  view = m_toolMgr->GetView();
321     RENDER_SETTINGS*              settings = view->GetPainter()->GetSettings();
322     const std::set<unsigned int>& activeLayers = settings->GetHighContrastLayers();
323     bool                          isHighContrast = settings->GetHighContrast();
324 
325     view->Query( aArea, selectedItems );
326 
327     for( const KIGFX::VIEW::LAYER_ITEM_PAIR& it : selectedItems )
328     {
329         BOARD_ITEM* item = static_cast<BOARD_ITEM*>( it.first );
330 
331         // If we are in the footprint editor, don't use the footprint itself
332         if( static_cast<PCB_TOOL_BASE*>( m_toolMgr->GetCurrentTool() )->IsFootprintEditor()
333                 && item->Type() == PCB_FOOTPRINT_T )
334         {
335             continue;
336         }
337 
338         // The item must be visible and on an active layer
339         if( view->IsVisible( item )
340                 && ( !isHighContrast || activeLayers.count( it.second ) )
341                 && item->ViewGetLOD( it.second, view ) < view->GetScale() )
342         {
343             items.insert ( item );
344         }
345     }
346 
347     for( BOARD_ITEM* skipItem : aSkip )
348         items.erase( skipItem );
349 
350     return items;
351 }
352 
353 
computeAnchors(BOARD_ITEM * aItem,const VECTOR2I & aRefPos,bool aFrom)354 void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos, bool aFrom )
355 {
356     KIGFX::VIEW*                  view = m_toolMgr->GetView();
357     RENDER_SETTINGS*              settings = view->GetPainter()->GetSettings();
358     const std::set<unsigned int>& activeLayers = settings->GetHighContrastLayers();
359     bool                          isHighContrast = settings->GetHighContrast();
360 
361     auto handlePadShape =
362             [&]( PAD* aPad )
363             {
364                 addAnchor( aPad->GetPosition(), ORIGIN | SNAPPABLE, aPad );
365 
366                 /// If we are getting a drag point, we don't want to center the edge of pads
367                 if( aFrom )
368                     return;
369 
370                 const std::shared_ptr<SHAPE> eshape = aPad->GetEffectiveShape( aPad->GetLayer() );
371 
372                 wxASSERT( eshape->Type() == SH_COMPOUND );
373                 const std::vector<SHAPE*> shapes =
374                         static_cast<const SHAPE_COMPOUND*>( eshape.get() )->Shapes();
375 
376                 for( const SHAPE* shape : shapes )
377                 {
378                     switch( shape->Type() )
379                     {
380                     case SH_RECT:
381                     {
382                         const SHAPE_RECT* rect    = static_cast<const SHAPE_RECT*>( shape );
383                         SHAPE_LINE_CHAIN  outline = rect->Outline();
384 
385                         for( int i = 0; i < outline.SegmentCount(); i++ )
386                         {
387                             const SEG& seg = outline.CSegment( i );
388                             addAnchor( seg.A,         OUTLINE | SNAPPABLE, aPad );
389                             addAnchor( seg.Center(),  OUTLINE | SNAPPABLE, aPad );
390                         }
391 
392                         break;
393                     }
394 
395                     case SH_SEGMENT:
396                     {
397                         const SHAPE_SEGMENT* segment = static_cast<const SHAPE_SEGMENT*>( shape );
398 
399                         int offset = segment->GetWidth() / 2;
400                         SEG seg    = segment->GetSeg();
401                         VECTOR2I normal = ( seg.B - seg.A ).Resize( offset ).Rotate( -M_PI_2 );
402 
403                         /*
404                          * TODO: This creates more snap points than necessary for rounded rect pads
405                          * because they are built up of overlapping segments.  We could fix this if
406                          * desired by testing these to see if they are "inside" the pad.
407                          */
408 
409                         addAnchor( seg.A + normal, OUTLINE | SNAPPABLE, aPad );
410                         addAnchor( seg.A - normal, OUTLINE | SNAPPABLE, aPad );
411                         addAnchor( seg.B + normal, OUTLINE | SNAPPABLE, aPad );
412                         addAnchor( seg.B - normal, OUTLINE | SNAPPABLE, aPad );
413                         addAnchor( seg.Center() + normal, OUTLINE | SNAPPABLE, aPad );
414                         addAnchor( seg.Center() - normal, OUTLINE | SNAPPABLE, aPad );
415 
416                         normal = normal.Rotate( M_PI_2 );
417 
418                         addAnchor( seg.A - normal, OUTLINE | SNAPPABLE, aPad );
419                         addAnchor( seg.B + normal, OUTLINE | SNAPPABLE, aPad );
420                         break;
421                     }
422 
423                     case SH_CIRCLE:
424                     {
425                         const SHAPE_CIRCLE* circle = static_cast<const SHAPE_CIRCLE*>( shape );
426 
427                         int      r     = circle->GetRadius();
428                         VECTOR2I start = circle->GetCenter();
429 
430                         addAnchor( start + VECTOR2I( -r, 0 ), OUTLINE | SNAPPABLE, aPad );
431                         addAnchor( start + VECTOR2I( r, 0 ), OUTLINE | SNAPPABLE, aPad );
432                         addAnchor( start + VECTOR2I( 0, -r ), OUTLINE | SNAPPABLE, aPad );
433                         addAnchor( start + VECTOR2I( 0, r ), OUTLINE | SNAPPABLE, aPad );
434                         break;
435                     }
436 
437                     case SH_ARC:
438                     {
439                         const SHAPE_ARC* arc = static_cast<const SHAPE_ARC*>( shape );
440 
441                         addAnchor( arc->GetP0(), OUTLINE | SNAPPABLE, aPad );
442                         addAnchor( arc->GetP1(), OUTLINE | SNAPPABLE, aPad );
443                         addAnchor( arc->GetArcMid(), OUTLINE | SNAPPABLE, aPad );
444                         break;
445                     }
446 
447                     case SH_SIMPLE:
448                     {
449                         const SHAPE_SIMPLE* poly = static_cast<const SHAPE_SIMPLE*>( shape );
450 
451                         for( size_t i = 0; i < poly->GetSegmentCount(); i++ )
452                         {
453                             const SEG& seg = poly->GetSegment( i );
454 
455                             addAnchor( seg.A, OUTLINE | SNAPPABLE, aPad );
456                             addAnchor( seg.Center(), OUTLINE | SNAPPABLE, aPad );
457 
458                             if( i == poly->GetSegmentCount() - 1 )
459                                 addAnchor( seg.B, OUTLINE | SNAPPABLE, aPad );
460                         }
461 
462                         break;
463                     }
464 
465                     case SH_POLY_SET:
466                     case SH_LINE_CHAIN:
467                     case SH_COMPOUND:
468                     case SH_POLY_SET_TRIANGLE:
469                     case SH_NULL:
470                     default:
471                         break;
472                     }
473                 }
474             };
475 
476     switch( aItem->Type() )
477     {
478         case PCB_FOOTPRINT_T:
479         {
480             FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem );
481 
482             for( PAD* pad : footprint->Pads() )
483             {
484                 // Getting pads from the footprint requires re-checking that the pad is shown
485                 if( ( aFrom || m_magneticSettings->pads == MAGNETIC_OPTIONS::CAPTURE_ALWAYS )
486                     && pad->GetBoundingBox().Contains( wxPoint( aRefPos.x, aRefPos.y ) )
487                     && view->IsVisible( pad )
488                     && ( !isHighContrast || activeLayers.count( pad->GetLayer() ) )
489                     && pad->ViewGetLOD( pad->GetLayer(), view ) < view->GetScale() )
490                 {
491                     handlePadShape( pad );
492                     break;
493                 }
494             }
495 
496             // if the cursor is not over a pad, then drag the footprint by its origin
497             VECTOR2I position = footprint->GetPosition();
498             addAnchor( position, ORIGIN | SNAPPABLE, footprint );
499 
500             // Add the footprint center point if it is markedly different from the origin
501             VECTOR2I center = footprint->GetBoundingBox( false, false ).Centre();
502             VECTOR2I grid( GetGrid() );
503 
504             if( ( center - position ).SquaredEuclideanNorm() > grid.SquaredEuclideanNorm() )
505                 addAnchor( center, ORIGIN | SNAPPABLE, footprint );
506 
507             break;
508         }
509 
510         case PCB_PAD_T:
511         {
512             if( aFrom || m_magneticSettings->pads == MAGNETIC_OPTIONS::CAPTURE_ALWAYS )
513             {
514                 PAD* pad = static_cast<PAD*>( aItem );
515                 handlePadShape( pad );
516             }
517 
518             break;
519         }
520 
521         case PCB_FP_SHAPE_T:
522         case PCB_SHAPE_T:
523         {
524             if( !m_magneticSettings->graphics )
525                 break;
526 
527             PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
528             VECTOR2I   start = shape->GetStart();
529             VECTOR2I   end = shape->GetEnd();
530 
531             switch( shape->GetShape() )
532             {
533                 case SHAPE_T::CIRCLE:
534                 {
535                     int r = ( start - end ).EuclideanNorm();
536 
537                     addAnchor( start, ORIGIN | SNAPPABLE, shape );
538                     addAnchor( start + VECTOR2I( -r, 0 ), OUTLINE | SNAPPABLE, shape );
539                     addAnchor( start + VECTOR2I( r, 0 ), OUTLINE | SNAPPABLE, shape );
540                     addAnchor( start + VECTOR2I( 0, -r ), OUTLINE | SNAPPABLE, shape );
541                     addAnchor( start + VECTOR2I( 0, r ), OUTLINE | SNAPPABLE, shape );
542                     break;
543                 }
544 
545                 case SHAPE_T::ARC:
546                     addAnchor( shape->GetStart(), CORNER | SNAPPABLE, shape );
547                     addAnchor( shape->GetEnd(), CORNER | SNAPPABLE, shape );
548                     addAnchor( shape->GetArcMid(), CORNER | SNAPPABLE, shape );
549                     addAnchor( shape->GetCenter(), ORIGIN | SNAPPABLE, shape );
550                     break;
551 
552                 case SHAPE_T::RECT:
553                 {
554                     VECTOR2I point2( end.x, start.y );
555                     VECTOR2I point3( start.x, end.y );
556                     SEG first( start, point2 );
557                     SEG second( point2, end );
558                     SEG third( end, point3 );
559                     SEG fourth( point3, start );
560 
561                     addAnchor( first.A,         CORNER | SNAPPABLE, shape );
562                     addAnchor( first.Center(),  CORNER | SNAPPABLE, shape );
563                     addAnchor( second.A,        CORNER | SNAPPABLE, shape );
564                     addAnchor( second.Center(), CORNER | SNAPPABLE, shape );
565                     addAnchor( third.A,         CORNER | SNAPPABLE, shape );
566                     addAnchor( third.Center(),  CORNER | SNAPPABLE, shape );
567                     addAnchor( fourth.A,        CORNER | SNAPPABLE, shape );
568                     addAnchor( fourth.Center(), CORNER | SNAPPABLE, shape );
569                     break;
570                 }
571 
572                 case SHAPE_T::SEGMENT:
573                     addAnchor( start, CORNER | SNAPPABLE, shape );
574                     addAnchor( end, CORNER | SNAPPABLE, shape );
575                     addAnchor( shape->GetCenter(), CORNER | SNAPPABLE, shape );
576                     break;
577 
578                 case SHAPE_T::POLY:
579                 {
580                     SHAPE_LINE_CHAIN lc;
581                     lc.SetClosed( true );
582                     std::vector<wxPoint> poly;
583                     shape->DupPolyPointsList( poly );
584 
585                     for( const wxPoint& p : poly )
586                     {
587                         addAnchor( p, CORNER | SNAPPABLE, shape );
588                         lc.Append( p );
589                     }
590 
591                     addAnchor( lc.NearestPoint( aRefPos ), OUTLINE, aItem );
592                     break;
593                 }
594 
595                 case SHAPE_T::BEZIER:
596                     addAnchor( start, CORNER | SNAPPABLE, shape );
597                     addAnchor( end, CORNER | SNAPPABLE, shape );
598                     KI_FALLTHROUGH;
599 
600                 default:
601                     addAnchor( shape->GetPosition(), ORIGIN | SNAPPABLE, shape );
602                     break;
603             }
604             break;
605         }
606 
607         case PCB_TRACE_T:
608         case PCB_ARC_T:
609         {
610             if( aFrom || m_magneticSettings->tracks == MAGNETIC_OPTIONS::CAPTURE_ALWAYS )
611             {
612                 PCB_TRACK* track = static_cast<PCB_TRACK*>( aItem );
613 
614                 addAnchor( track->GetStart(), CORNER | SNAPPABLE, track );
615                 addAnchor( track->GetEnd(), CORNER | SNAPPABLE, track );
616                 addAnchor( track->GetCenter(), ORIGIN, track);
617             }
618 
619             break;
620         }
621 
622         case PCB_MARKER_T:
623         case PCB_TARGET_T:
624             addAnchor( aItem->GetPosition(), ORIGIN | CORNER | SNAPPABLE, aItem );
625             break;
626 
627         case PCB_VIA_T:
628         {
629             if( aFrom || m_magneticSettings->tracks == MAGNETIC_OPTIONS::CAPTURE_ALWAYS )
630                 addAnchor( aItem->GetPosition(), ORIGIN | CORNER | SNAPPABLE, aItem );
631 
632             break;
633         }
634 
635         case PCB_ZONE_T:
636         {
637             const SHAPE_POLY_SET* outline = static_cast<const ZONE*>( aItem )->Outline();
638 
639             SHAPE_LINE_CHAIN lc;
640             lc.SetClosed( true );
641 
642             for( auto iter = outline->CIterateWithHoles(); iter; iter++ )
643             {
644                 addAnchor( *iter, CORNER, aItem );
645                 lc.Append( *iter );
646             }
647 
648             addAnchor( lc.NearestPoint( aRefPos ), OUTLINE, aItem );
649 
650             break;
651         }
652 
653         case PCB_DIM_ALIGNED_T:
654         case PCB_DIM_ORTHOGONAL_T:
655         {
656             const PCB_DIM_ALIGNED* dim = static_cast<const PCB_DIM_ALIGNED*>( aItem );
657             addAnchor( dim->GetCrossbarStart(), CORNER | SNAPPABLE, aItem );
658             addAnchor( dim->GetCrossbarEnd(), CORNER | SNAPPABLE, aItem );
659             addAnchor( dim->GetStart(), CORNER | SNAPPABLE, aItem );
660             addAnchor( dim->GetEnd(), CORNER | SNAPPABLE, aItem );
661             break;
662         }
663 
664         case PCB_DIM_CENTER_T:
665         {
666             const PCB_DIM_CENTER* dim = static_cast<const PCB_DIM_CENTER*>( aItem );
667             addAnchor( dim->GetStart(), CORNER | SNAPPABLE, aItem );
668             addAnchor( dim->GetEnd(), CORNER | SNAPPABLE, aItem );
669 
670             VECTOR2I start( dim->GetStart() );
671             VECTOR2I radial( dim->GetEnd() - dim->GetStart() );
672 
673             for( int i = 0; i < 2; i++ )
674             {
675                 radial = radial.Rotate( DEG2RAD( 90 ) );
676                 addAnchor( start + radial, CORNER | SNAPPABLE, aItem );
677             }
678 
679             break;
680         }
681 
682         case PCB_DIM_LEADER_T:
683         {
684             const PCB_DIM_LEADER* leader = static_cast<const PCB_DIM_LEADER*>( aItem );
685             addAnchor( leader->GetStart(), CORNER | SNAPPABLE, aItem );
686             addAnchor( leader->GetEnd(), CORNER | SNAPPABLE, aItem );
687             addAnchor( leader->Text().GetPosition(), CORNER | SNAPPABLE, aItem );
688             break;
689         }
690 
691         case PCB_FP_TEXT_T:
692         case PCB_TEXT_T:
693             addAnchor( aItem->GetPosition(), ORIGIN, aItem );
694             break;
695 
696         case PCB_GROUP_T:
697         {
698             const PCB_GROUP* group = static_cast<const PCB_GROUP*>( aItem );
699 
700             for( BOARD_ITEM* item : group->GetItems() )
701                 computeAnchors( item, aRefPos, aFrom );
702 
703             break;
704         }
705 
706         default:
707             break;
708    }
709 }
710 
711 
nearestAnchor(const VECTOR2I & aPos,int aFlags,LSET aMatchLayers)712 PCB_GRID_HELPER::ANCHOR* PCB_GRID_HELPER::nearestAnchor( const VECTOR2I& aPos, int aFlags,
713                                                          LSET aMatchLayers )
714 {
715     double  minDist = std::numeric_limits<double>::max();
716     ANCHOR* best = nullptr;
717 
718     for( ANCHOR& a : m_anchors )
719     {
720         BOARD_ITEM* item = static_cast<BOARD_ITEM*>( a.item );
721 
722         if( ( aMatchLayers & item->GetLayerSet() ) == 0 )
723             continue;
724 
725         if( ( aFlags & a.flags ) != aFlags )
726             continue;
727 
728         double dist = a.Distance( aPos );
729 
730         if( dist < minDist )
731         {
732             minDist = dist;
733             best = &a;
734         }
735     }
736 
737     return best;
738 }
739