You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1714 lines
58 KiB

11 years ago
9 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
11 years ago
1 year ago
1 year ago
11 years ago
11 years ago
4 years ago
4 years ago
4 years ago
11 years ago
11 years ago
11 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2014 CERN
  5. * Copyright The 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. #include "pcb_grid_helper.h"
  26. #include <functional>
  27. #include <advanced_config.h>
  28. #include <pcb_dimension.h>
  29. #include <pcb_shape.h>
  30. #include <footprint.h>
  31. #include <pad.h>
  32. #include <pcb_group.h>
  33. #include <pcb_reference_image.h>
  34. #include <pcb_track.h>
  35. #include <zone.h>
  36. #include <gal/graphics_abstraction_layer.h>
  37. #include <geometry/intersection.h>
  38. #include <geometry/nearest.h>
  39. #include <geometry/oval.h>
  40. #include <geometry/shape_circle.h>
  41. #include <geometry/shape_line_chain.h>
  42. #include <geometry/shape_rect.h>
  43. #include <geometry/shape_segment.h>
  44. #include <geometry/shape_simple.h>
  45. #include <geometry/shape_utils.h>
  46. #include <macros.h>
  47. #include <math/util.h> // for KiROUND
  48. #include <gal/painter.h>
  49. #include <pcbnew_settings.h>
  50. #include <tool/tool_manager.h>
  51. #include <tools/pcb_tool_base.h>
  52. #include <view/view.h>
  53. namespace
  54. {
  55. /**
  56. * Get the INTERSECTABLE_GEOM for a BOARD_ITEM if it's supported.
  57. *
  58. * This is the idealised geometry, e.g. a zero-width line or circle.
  59. */
  60. std::optional<INTERSECTABLE_GEOM> GetBoardIntersectable( const BOARD_ITEM& aItem )
  61. {
  62. switch( aItem.Type() )
  63. {
  64. case PCB_SHAPE_T:
  65. {
  66. const PCB_SHAPE& shape = static_cast<const PCB_SHAPE&>( aItem );
  67. switch( shape.GetShape() )
  68. {
  69. case SHAPE_T::SEGMENT: return SEG{ shape.GetStart(), shape.GetEnd() };
  70. case SHAPE_T::CIRCLE: return CIRCLE{ shape.GetCenter(), shape.GetRadius() };
  71. case SHAPE_T::ARC:
  72. return SHAPE_ARC{ shape.GetStart(), shape.GetArcMid(), shape.GetEnd(), 0 };
  73. case SHAPE_T::RECTANGLE: return BOX2I::ByCorners( shape.GetStart(), shape.GetEnd() );
  74. default: break;
  75. }
  76. break;
  77. }
  78. case PCB_TRACE_T:
  79. {
  80. const PCB_TRACK& track = static_cast<const PCB_TRACK&>( aItem );
  81. return SEG{ track.GetStart(), track.GetEnd() };
  82. }
  83. case PCB_ARC_T:
  84. {
  85. const PCB_ARC& arc = static_cast<const PCB_ARC&>( aItem );
  86. return SHAPE_ARC{ arc.GetStart(), arc.GetMid(), arc.GetEnd(), 0 };
  87. }
  88. case PCB_REFERENCE_IMAGE_T:
  89. {
  90. const PCB_REFERENCE_IMAGE& refImage = static_cast<const PCB_REFERENCE_IMAGE&>( aItem );
  91. return refImage.GetBoundingBox();
  92. }
  93. default: break;
  94. }
  95. return std::nullopt;
  96. }
  97. /**
  98. * Find the closest point on a BOARD_ITEM to a given point.
  99. *
  100. * Only works for items that have a NEARABLE_GEOM defined, it's
  101. * not a general purpose function.
  102. *
  103. * @return The closest point on the item to aPos, or std::nullopt if the item
  104. * doesn't have a NEARABLE_GEOM defined.
  105. */
  106. std::optional<int64_t> FindSquareDistanceToItem( const BOARD_ITEM& item, const VECTOR2I& aPos )
  107. {
  108. std::optional<INTERSECTABLE_GEOM> intersectable = GetBoardIntersectable( item );
  109. std::optional<NEARABLE_GEOM> nearable;
  110. if( intersectable )
  111. {
  112. // Exploit the intersectable as a nearable
  113. std::visit(
  114. [&]( auto& geom )
  115. {
  116. nearable = NEARABLE_GEOM( std::move( geom ) );
  117. },
  118. *intersectable );
  119. }
  120. // Whatever the item is, we don't have a nearable for it
  121. if( !nearable )
  122. return std::nullopt;
  123. const VECTOR2I nearestPt = GetNearestPoint( *nearable, aPos );
  124. return nearestPt.SquaredDistance( aPos );
  125. }
  126. } // namespace
  127. PCB_GRID_HELPER::PCB_GRID_HELPER( TOOL_MANAGER* aToolMgr, MAGNETIC_SETTINGS* aMagneticSettings ) :
  128. GRID_HELPER( aToolMgr, LAYER_ANCHOR ), m_magneticSettings( aMagneticSettings )
  129. {
  130. KIGFX::VIEW* view = m_toolMgr->GetView();
  131. KIGFX::RENDER_SETTINGS* settings = view->GetPainter()->GetSettings();
  132. KIGFX::COLOR4D auxItemsColor = settings->GetLayerColor( LAYER_AUX_ITEMS );
  133. m_viewAxis.SetSize( 20000 );
  134. m_viewAxis.SetStyle( KIGFX::ORIGIN_VIEWITEM::CROSS );
  135. m_viewAxis.SetColor( auxItemsColor.WithAlpha( 0.4 ) );
  136. m_viewAxis.SetDrawAtZero( true );
  137. view->Add( &m_viewAxis );
  138. view->SetVisible( &m_viewAxis, false );
  139. m_viewSnapPoint.SetSize( 10 );
  140. m_viewSnapPoint.SetStyle( KIGFX::ORIGIN_VIEWITEM::CIRCLE_CROSS );
  141. m_viewSnapPoint.SetColor( auxItemsColor );
  142. m_viewSnapPoint.SetDrawAtZero( true );
  143. view->Add( &m_viewSnapPoint );
  144. view->SetVisible( &m_viewSnapPoint, false );
  145. }
  146. PCB_GRID_HELPER::~PCB_GRID_HELPER()
  147. {
  148. KIGFX::VIEW* view = m_toolMgr->GetView();
  149. view->Remove( &m_viewAxis );
  150. view->Remove( &m_viewSnapPoint );
  151. }
  152. void PCB_GRID_HELPER::AddConstructionItems( std::vector<BOARD_ITEM*> aItems, bool aExtensionOnly,
  153. bool aIsPersistent )
  154. {
  155. if( !ADVANCED_CFG::GetCfg().m_EnableExtensionSnaps )
  156. {
  157. return;
  158. }
  159. // For all the elements that get drawn construction geometry,
  160. // add something suitable to the construction helper.
  161. // This can be nothing.
  162. auto constructionItemsBatch = std::make_unique<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH>();
  163. std::vector<VECTOR2I> referenceOnlyPoints;
  164. for( BOARD_ITEM* item : aItems )
  165. {
  166. std::vector<KIGFX::CONSTRUCTION_GEOM::DRAWABLE> constructionDrawables;
  167. switch( item->Type() )
  168. {
  169. case PCB_SHAPE_T:
  170. {
  171. PCB_SHAPE& shape = static_cast<PCB_SHAPE&>( *item );
  172. switch( shape.GetShape() )
  173. {
  174. case SHAPE_T::SEGMENT:
  175. {
  176. if( !aExtensionOnly )
  177. {
  178. constructionDrawables.emplace_back( LINE{ shape.GetStart(), shape.GetEnd() } );
  179. }
  180. else
  181. {
  182. // Two rays, extending from the segment ends
  183. const VECTOR2I segVec = shape.GetEnd() - shape.GetStart();
  184. constructionDrawables.emplace_back(
  185. HALF_LINE{ shape.GetStart(), shape.GetStart() - segVec } );
  186. constructionDrawables.emplace_back(
  187. HALF_LINE{ shape.GetEnd(), shape.GetEnd() + segVec } );
  188. }
  189. if( aIsPersistent )
  190. {
  191. // include the original endpoints as construction items
  192. // (this allows H/V snapping)
  193. constructionDrawables.emplace_back( shape.GetStart() );
  194. constructionDrawables.emplace_back( shape.GetEnd() );
  195. // But mark them as references, so they don't get snapped to themsevles
  196. referenceOnlyPoints.emplace_back( shape.GetStart() );
  197. referenceOnlyPoints.emplace_back( shape.GetEnd() );
  198. }
  199. break;
  200. }
  201. case SHAPE_T::ARC:
  202. {
  203. if( !aExtensionOnly )
  204. {
  205. constructionDrawables.push_back(
  206. CIRCLE{ shape.GetCenter(), shape.GetRadius() } );
  207. }
  208. else
  209. {
  210. // The rest of the circle is the arc through the opposite point to the midpoint
  211. const VECTOR2I oppositeMid =
  212. shape.GetCenter() + ( shape.GetCenter() - shape.GetArcMid() );
  213. constructionDrawables.push_back(
  214. SHAPE_ARC{ shape.GetStart(), oppositeMid, shape.GetEnd(), 0 } );
  215. }
  216. constructionDrawables.push_back( shape.GetCenter() );
  217. if( aIsPersistent )
  218. {
  219. // include the original endpoints as construction items
  220. // (this allows H/V snapping)
  221. constructionDrawables.emplace_back( shape.GetStart() );
  222. constructionDrawables.emplace_back( shape.GetEnd() );
  223. // But mark them as references, so they don't get snapped to themselves
  224. referenceOnlyPoints.emplace_back( shape.GetStart() );
  225. referenceOnlyPoints.emplace_back( shape.GetEnd() );
  226. }
  227. break;
  228. }
  229. case SHAPE_T::CIRCLE:
  230. case SHAPE_T::RECTANGLE:
  231. {
  232. constructionDrawables.push_back( shape.GetCenter() );
  233. break;
  234. }
  235. default:
  236. // This shape doesn't have any construction geometry to draw
  237. break;
  238. }
  239. break;
  240. }
  241. case PCB_REFERENCE_IMAGE_T:
  242. {
  243. const PCB_REFERENCE_IMAGE& pcbRefImg = static_cast<PCB_REFERENCE_IMAGE&>( *item );
  244. const REFERENCE_IMAGE& refImg = pcbRefImg.GetReferenceImage();
  245. constructionDrawables.push_back( refImg.GetPosition() );
  246. if( refImg.GetTransformOriginOffset() != VECTOR2I( 0, 0 ) )
  247. {
  248. constructionDrawables.push_back( refImg.GetPosition()
  249. + refImg.GetTransformOriginOffset() );
  250. }
  251. for( const SEG& seg : KIGEOM::BoxToSegs( refImg.GetBoundingBox() ) )
  252. {
  253. constructionDrawables.push_back( seg );
  254. }
  255. break;
  256. }
  257. default:
  258. // This item doesn't have any construction geometry to draw
  259. break;
  260. }
  261. // At this point, constructionDrawables can be empty, which is fine
  262. // (it means there's no additional construction geometry to draw, but
  263. // the item is still going to be proposed for activation)
  264. constructionItemsBatch->emplace_back( CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM{
  265. CONSTRUCTION_MANAGER::SOURCE::FROM_ITEMS,
  266. item,
  267. std::move( constructionDrawables ),
  268. } );
  269. }
  270. if( referenceOnlyPoints.size() )
  271. {
  272. getSnapManager().SetReferenceOnlyPoints( std::move( referenceOnlyPoints ) );
  273. }
  274. // Let the manager handle it
  275. getSnapManager().GetConstructionManager().ProposeConstructionItems(
  276. std::move( constructionItemsBatch ), aIsPersistent );
  277. }
  278. VECTOR2I PCB_GRID_HELPER::AlignToSegment( const VECTOR2I& aPoint, const SEG& aSeg )
  279. {
  280. const int c_gridSnapEpsilon_sq = 4;
  281. VECTOR2I aligned = Align( aPoint );
  282. if( !m_enableSnap )
  283. return aligned;
  284. std::vector<VECTOR2I> points;
  285. const SEG testSegments[] = { SEG( aligned, aligned + VECTOR2( 1, 0 ) ),
  286. SEG( aligned, aligned + VECTOR2( 0, 1 ) ),
  287. SEG( aligned, aligned + VECTOR2( 1, 1 ) ),
  288. SEG( aligned, aligned + VECTOR2( 1, -1 ) ) };
  289. for( const SEG& seg : testSegments )
  290. {
  291. OPT_VECTOR2I vec = aSeg.IntersectLines( seg );
  292. if( vec && aSeg.SquaredDistance( *vec ) <= c_gridSnapEpsilon_sq )
  293. points.push_back( *vec );
  294. }
  295. VECTOR2I nearest = aligned;
  296. SEG::ecoord min_d_sq = VECTOR2I::ECOORD_MAX;
  297. // Snap by distance between pointer and endpoints
  298. for( const VECTOR2I& pt : { aSeg.A, aSeg.B } )
  299. {
  300. SEG::ecoord d_sq = ( pt - aPoint ).SquaredEuclideanNorm();
  301. if( d_sq < min_d_sq )
  302. {
  303. min_d_sq = d_sq;
  304. nearest = pt;
  305. }
  306. }
  307. // Snap by distance between aligned cursor and intersections
  308. for( const VECTOR2I& pt : points )
  309. {
  310. SEG::ecoord d_sq = ( pt - aligned ).SquaredEuclideanNorm();
  311. if( d_sq < min_d_sq )
  312. {
  313. min_d_sq = d_sq;
  314. nearest = pt;
  315. }
  316. }
  317. return nearest;
  318. }
  319. VECTOR2I PCB_GRID_HELPER::AlignToArc( const VECTOR2I& aPoint, const SHAPE_ARC& aArc )
  320. {
  321. VECTOR2I aligned = Align( aPoint );
  322. if( !m_enableSnap )
  323. return aligned;
  324. std::vector<VECTOR2I> points;
  325. aArc.IntersectLine( SEG( aligned, aligned + VECTOR2( 1, 0 ) ), &points );
  326. aArc.IntersectLine( SEG( aligned, aligned + VECTOR2( 0, 1 ) ), &points );
  327. aArc.IntersectLine( SEG( aligned, aligned + VECTOR2( 1, 1 ) ), &points );
  328. aArc.IntersectLine( SEG( aligned, aligned + VECTOR2( 1, -1 ) ), &points );
  329. VECTOR2I nearest = aligned;
  330. SEG::ecoord min_d_sq = VECTOR2I::ECOORD_MAX;
  331. // Snap by distance between pointer and endpoints
  332. for( const VECTOR2I& pt : { aArc.GetP0(), aArc.GetP1() } )
  333. {
  334. SEG::ecoord d_sq = ( pt - aPoint ).SquaredEuclideanNorm();
  335. if( d_sq < min_d_sq )
  336. {
  337. min_d_sq = d_sq;
  338. nearest = pt;
  339. }
  340. }
  341. // Snap by distance between aligned cursor and intersections
  342. for( const VECTOR2I& pt : points )
  343. {
  344. SEG::ecoord d_sq = ( pt - aligned ).SquaredEuclideanNorm();
  345. if( d_sq < min_d_sq )
  346. {
  347. min_d_sq = d_sq;
  348. nearest = pt;
  349. }
  350. }
  351. return nearest;
  352. }
  353. VECTOR2I PCB_GRID_HELPER::SnapToPad( const VECTOR2I& aMousePos, std::deque<PAD*>& aPads )
  354. {
  355. clearAnchors();
  356. for( BOARD_ITEM* item : aPads )
  357. {
  358. if( item->HitTest( aMousePos ) )
  359. computeAnchors( item, aMousePos, true, nullptr );
  360. }
  361. double minDist = std::numeric_limits<double>::max();
  362. ANCHOR* nearestOrigin = nullptr;
  363. for( ANCHOR& a : m_anchors )
  364. {
  365. if( ( ORIGIN & a.flags ) != ORIGIN )
  366. continue;
  367. double dist = a.Distance( aMousePos );
  368. if( dist < minDist )
  369. {
  370. minDist = dist;
  371. nearestOrigin = &a;
  372. }
  373. }
  374. return nearestOrigin ? nearestOrigin->pos : aMousePos;
  375. }
  376. VECTOR2I PCB_GRID_HELPER::BestDragOrigin( const VECTOR2I &aMousePos,
  377. std::vector<BOARD_ITEM*>& aItems,
  378. GRID_HELPER_GRIDS aGrid,
  379. const PCB_SELECTION_FILTER_OPTIONS* aSelectionFilter )
  380. {
  381. clearAnchors();
  382. computeAnchors( aItems, aMousePos, true, aSelectionFilter, nullptr, true );
  383. double lineSnapMinCornerDistance = m_toolMgr->GetView()->ToWorld( 50 );
  384. ANCHOR* nearestOutline = nearestAnchor( aMousePos, OUTLINE );
  385. ANCHOR* nearestCorner = nearestAnchor( aMousePos, CORNER );
  386. ANCHOR* nearestOrigin = nearestAnchor( aMousePos, ORIGIN );
  387. ANCHOR* best = nullptr;
  388. double minDist = std::numeric_limits<double>::max();
  389. if( nearestOrigin )
  390. {
  391. minDist = nearestOrigin->Distance( aMousePos );
  392. best = nearestOrigin;
  393. }
  394. if( nearestCorner )
  395. {
  396. double dist = nearestCorner->Distance( aMousePos );
  397. if( dist < minDist )
  398. {
  399. minDist = dist;
  400. best = nearestCorner;
  401. }
  402. }
  403. if( nearestOutline )
  404. {
  405. double dist = nearestOutline->Distance( aMousePos );
  406. if( minDist > lineSnapMinCornerDistance && dist < minDist )
  407. best = nearestOutline;
  408. }
  409. return best ? best->pos : aMousePos;
  410. }
  411. VECTOR2I PCB_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, BOARD_ITEM* aReferenceItem,
  412. GRID_HELPER_GRIDS aGrid )
  413. {
  414. LSET layers;
  415. std::vector<BOARD_ITEM*> item;
  416. if( aReferenceItem )
  417. {
  418. layers = aReferenceItem->GetLayerSet();
  419. item.push_back( aReferenceItem );
  420. }
  421. else
  422. {
  423. layers = LSET::AllLayersMask();
  424. }
  425. return BestSnapAnchor( aOrigin, layers, aGrid, item );
  426. }
  427. VECTOR2I PCB_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, const LSET& aLayers,
  428. GRID_HELPER_GRIDS aGrid,
  429. const std::vector<BOARD_ITEM*>& aSkip )
  430. {
  431. // Tuning constant: snap radius in screen space
  432. const int snapSize = 25;
  433. // Snapping distance is in screen space, clamped to the current grid to ensure that the grid
  434. // points that are visible can always be snapped to.
  435. // see https://gitlab.com/kicad/code/kicad/-/issues/5638
  436. // see https://gitlab.com/kicad/code/kicad/-/issues/7125
  437. // see https://gitlab.com/kicad/code/kicad/-/issues/12303
  438. double snapScale = m_toolMgr->GetView()->ToWorld( snapSize );
  439. // warning: GetVisibleGrid().x sometimes returns a value > INT_MAX. Intermediate calculation
  440. // needs double.
  441. int snapRange = KiROUND( m_enableGrid ? std::min( snapScale, GetVisibleGrid().x ) : snapScale );
  442. //Respect limits of coordinates representation
  443. const BOX2I visibilityHorizon =
  444. BOX2ISafe( VECTOR2D( aOrigin ) - snapRange / 2.0, VECTOR2D( snapRange, snapRange ) );
  445. clearAnchors();
  446. const std::vector<BOARD_ITEM*> visibleItems = queryVisible( visibilityHorizon, aSkip );
  447. computeAnchors( visibleItems, aOrigin, false, nullptr, &aLayers, false );
  448. ANCHOR* nearest = nearestAnchor( aOrigin, SNAPPABLE );
  449. VECTOR2I nearestGrid = Align( aOrigin, aGrid );
  450. if( KIGFX::ANCHOR_DEBUG* ad = enableAndGetAnchorDebug(); ad )
  451. {
  452. ad->ClearAnchors();
  453. for( const ANCHOR& anchor : m_anchors )
  454. ad->AddAnchor( anchor.pos );
  455. ad->SetNearest( nearest ? OPT_VECTOR2I{ nearest->pos } : std::nullopt );
  456. m_toolMgr->GetView()->Update( ad, KIGFX::GEOMETRY );
  457. }
  458. // The distance to the nearest snap point, if any
  459. std::optional<int> snapDist;
  460. if( nearest )
  461. snapDist = nearest->Distance( aOrigin );
  462. showConstructionGeometry( m_enableSnap );
  463. SNAP_MANAGER& snapManager = getSnapManager();
  464. SNAP_LINE_MANAGER& snapLineManager = snapManager.GetSnapLineManager();
  465. const auto ptIsReferenceOnly = [&]( const VECTOR2I& aPt )
  466. {
  467. const std::vector<VECTOR2I>& referenceOnlyPoints = snapManager.GetReferenceOnlyPoints();
  468. return std::find( referenceOnlyPoints.begin(), referenceOnlyPoints.end(), aPt )
  469. != referenceOnlyPoints.end();
  470. };
  471. const auto proposeConstructionForItems = [&]( const std::vector<EDA_ITEM*>& aItems )
  472. {
  473. // Add any involved item as a temporary construction item
  474. // (de-duplication with existing construction items is handled later)
  475. std::vector<BOARD_ITEM*> items;
  476. for( EDA_ITEM* item : aItems )
  477. {
  478. BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
  479. // Null items are allowed to arrive here as they represent geometry that isn't
  480. // specifically tied to a board item. For example snap lines from some
  481. // other anchor.
  482. // But they don't produce new construction items.
  483. if( boardItem )
  484. {
  485. if( m_magneticSettings->allLayers
  486. || ( ( aLayers & boardItem->GetLayerSet() ).any() ) )
  487. {
  488. items.push_back( boardItem );
  489. }
  490. }
  491. }
  492. // Temporary construction items are not persistent and don't
  493. // overlay the items themselves (as the items will not be moved)
  494. AddConstructionItems( items, true, false );
  495. };
  496. bool snapValid = false;
  497. if( m_enableSnap )
  498. {
  499. // Existing snap lines need priority over new snaps
  500. if( m_enableSnapLine )
  501. {
  502. OPT_VECTOR2I snapLineSnap = snapLineManager.GetNearestSnapLinePoint(
  503. aOrigin, nearestGrid, snapDist, snapRange );
  504. // We found a better snap point that the nearest one
  505. if( snapLineSnap && m_skipPoint != *snapLineSnap )
  506. {
  507. snapLineManager.SetSnapLineEnd( *snapLineSnap );
  508. snapValid = true;
  509. // Don't show a snap point if we're snapping to a grid rather than an anchor
  510. m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, false );
  511. m_viewSnapPoint.SetSnapTypes( POINT_TYPE::PT_NONE );
  512. // Only return the snap line end as a snap if it's not a reference point
  513. // (we don't snap to reference points, but we can use them to update the snap line,
  514. // without actually snapping)
  515. if( !ptIsReferenceOnly( *snapLineSnap ) )
  516. {
  517. return *snapLineSnap;
  518. }
  519. }
  520. }
  521. // If there's a snap anchor within range, use it if we can
  522. if( nearest && nearest->Distance( aOrigin ) <= snapRange )
  523. {
  524. const bool anchorIsConstructed = nearest->flags & ANCHOR_FLAGS::CONSTRUCTED;
  525. // If the nearest anchor is a reference point, we don't snap to it,
  526. // but we can update the snap line origin
  527. if( ptIsReferenceOnly( nearest->pos ) )
  528. {
  529. // We can set the snap line origin, but don't mess with the
  530. // accepted snap point
  531. snapLineManager.SetSnapLineOrigin( nearest->pos );
  532. }
  533. else
  534. {
  535. // 'Intrinsic' points of items can trigger adding construction geometry
  536. // for _that_ item by proximity. E.g. just mousing over the intersection
  537. // of an item doesn't add a construction item for the second item).
  538. // This is to make construction items less intrusive and more
  539. // a result of user intent.
  540. if( !anchorIsConstructed )
  541. {
  542. proposeConstructionForItems( nearest->items );
  543. }
  544. const auto shouldAcceptAnchor = [&]( const ANCHOR& aAnchor )
  545. {
  546. // If no extension snaps are enabled, don't inhibit
  547. static const bool haveExtensions =
  548. ADVANCED_CFG::GetCfg().m_EnableExtensionSnaps;
  549. if( !haveExtensions )
  550. return true;
  551. // Check that any involved real items are 'active'
  552. // (i.e. the user has moused over a key point previously)
  553. // If any are not real (e.g. snap lines), they are allowed to be involved
  554. //
  555. // This is an area most likely to be controversial/need tuning,
  556. // as some users will think it's fiddly; without 'activation', others will
  557. // think the snaps are intrusive.
  558. bool allRealAreInvolved =
  559. snapManager.GetConstructionManager().InvolvesAllGivenRealItems(
  560. aAnchor.items );
  561. return allRealAreInvolved;
  562. };
  563. if( shouldAcceptAnchor( *nearest ) )
  564. {
  565. m_snapItem = *nearest;
  566. // Set the snap line origin or end as needed
  567. snapLineManager.SetSnappedAnchor( m_snapItem->pos );
  568. // Show the correct snap point marker
  569. updateSnapPoint( { m_snapItem->pos, m_snapItem->pointTypes } );
  570. return m_snapItem->pos;
  571. }
  572. }
  573. snapValid = true;
  574. }
  575. else
  576. {
  577. static const bool canActivateByHitTest =
  578. ADVANCED_CFG::GetCfg().m_ExtensionSnapActivateOnHover;
  579. if( canActivateByHitTest )
  580. {
  581. // An exact hit on an item, even if not near a snap point
  582. // If it's tool hard to hit by hover, this can be increased
  583. // to make it non-exact.
  584. const int hoverAccuracy = 0;
  585. for( BOARD_ITEM* item : visibleItems )
  586. {
  587. if( item->HitTest( aOrigin, hoverAccuracy ) )
  588. {
  589. proposeConstructionForItems( { item } );
  590. snapValid = true;
  591. break;
  592. }
  593. }
  594. }
  595. }
  596. // If we got here, we didn't snap to an anchor or snap line
  597. // If we're snapping to a grid, on-element snaps would be too intrusive
  598. // but they're useful when there isn't a grid to snap to
  599. if( !m_enableGrid )
  600. {
  601. OPT_VECTOR2I nearestPointOnAnElement =
  602. GetNearestPoint( m_pointOnLineCandidates, aOrigin );
  603. // Got any nearest point - snap if in range
  604. if( nearestPointOnAnElement
  605. && nearestPointOnAnElement->Distance( aOrigin ) <= snapRange )
  606. {
  607. updateSnapPoint( { *nearestPointOnAnElement, POINT_TYPE::PT_ON_ELEMENT } );
  608. // Clear the snap end, but keep the origin so touching another line
  609. // doesn't kill a snap line
  610. snapLineManager.SetSnapLineEnd( std::nullopt );
  611. return *nearestPointOnAnElement;
  612. }
  613. }
  614. }
  615. // Completely failed to find any snap point, so snap to the grid
  616. m_snapItem = std::nullopt;
  617. if( !snapValid )
  618. {
  619. snapLineManager.ClearSnapLine();
  620. snapManager.GetConstructionManager().CancelProposal();
  621. }
  622. else
  623. {
  624. snapLineManager.SetSnapLineEnd( std::nullopt );
  625. }
  626. m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, false );
  627. return nearestGrid;
  628. }
  629. BOARD_ITEM* PCB_GRID_HELPER::GetSnapped() const
  630. {
  631. if( !m_snapItem )
  632. return nullptr;
  633. // The snap anchor doesn't have an item associated with it
  634. // (odd, could it be entirely made of construction geometry?)
  635. if( m_snapItem->items.empty() )
  636. return nullptr;
  637. return static_cast<BOARD_ITEM*>( m_snapItem->items[0] );
  638. }
  639. GRID_HELPER_GRIDS PCB_GRID_HELPER::GetItemGrid( const EDA_ITEM* aItem ) const
  640. {
  641. if( !aItem )
  642. return GRID_CURRENT;
  643. switch( aItem->Type() )
  644. {
  645. case PCB_FOOTPRINT_T:
  646. case PCB_PAD_T:
  647. return GRID_CONNECTABLE;
  648. case PCB_TEXT_T:
  649. case PCB_FIELD_T:
  650. return GRID_TEXT;
  651. case PCB_SHAPE_T:
  652. case PCB_DIMENSION_T:
  653. case PCB_REFERENCE_IMAGE_T:
  654. case PCB_TEXTBOX_T:
  655. return GRID_GRAPHICS;
  656. case PCB_TRACE_T:
  657. case PCB_ARC_T:
  658. return GRID_WIRES;
  659. case PCB_VIA_T:
  660. return GRID_VIAS;
  661. default:
  662. return GRID_CURRENT;
  663. }
  664. }
  665. VECTOR2D PCB_GRID_HELPER::GetGridSize( GRID_HELPER_GRIDS aGrid ) const
  666. {
  667. const GRID_SETTINGS& grid = m_toolMgr->GetSettings()->m_Window.grid;
  668. int idx = -1;
  669. VECTOR2D g = m_toolMgr->GetView()->GetGAL()->GetGridSize();
  670. if( !grid.overrides_enabled )
  671. return g;
  672. switch( aGrid )
  673. {
  674. case GRID_CONNECTABLE:
  675. if( grid.override_connected )
  676. idx = grid.override_connected_idx;
  677. break;
  678. case GRID_WIRES:
  679. if( grid.override_wires )
  680. idx = grid.override_wires_idx;
  681. break;
  682. case GRID_VIAS:
  683. if( grid.override_vias )
  684. idx = grid.override_vias_idx;
  685. break;
  686. case GRID_TEXT:
  687. if( grid.override_text )
  688. idx = grid.override_text_idx;
  689. break;
  690. case GRID_GRAPHICS:
  691. if( grid.override_graphics )
  692. idx = grid.override_graphics_idx;
  693. break;
  694. default:
  695. break;
  696. }
  697. if( idx >= 0 && idx < (int) grid.grids.size() )
  698. g = grid.grids[idx].ToDouble( pcbIUScale );
  699. return g;
  700. }
  701. std::vector<BOARD_ITEM*>
  702. PCB_GRID_HELPER::queryVisible( const BOX2I& aArea, const std::vector<BOARD_ITEM*>& aSkip ) const
  703. {
  704. std::set<BOARD_ITEM*> items;
  705. std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> selectedItems;
  706. PCB_TOOL_BASE* currentTool = static_cast<PCB_TOOL_BASE*>( m_toolMgr->GetCurrentTool() );
  707. KIGFX::VIEW* view = m_toolMgr->GetView();
  708. RENDER_SETTINGS* settings = view->GetPainter()->GetSettings();
  709. const std::set<int>& activeLayers = settings->GetHighContrastLayers();
  710. bool isHighContrast = settings->GetHighContrast();
  711. view->Query( aArea, selectedItems );
  712. for( const auto& [ viewItem, layer ] : selectedItems )
  713. {
  714. BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( viewItem );
  715. if( currentTool->IsFootprintEditor() )
  716. {
  717. // If we are in the footprint editor, don't use the footprint itself
  718. if( boardItem->Type() == PCB_FOOTPRINT_T )
  719. continue;
  720. }
  721. else
  722. {
  723. // If we are not in the footprint editor, don't use footprint-editor-private items
  724. if( FOOTPRINT* parentFP = boardItem->GetParentFootprint() )
  725. {
  726. if( IsPcbLayer( layer ) && parentFP->GetPrivateLayers().test( layer ) )
  727. continue;
  728. }
  729. }
  730. // The boardItem must be visible and on an active layer
  731. if( view->IsVisible( boardItem )
  732. && ( !isHighContrast || activeLayers.count( layer ) )
  733. && boardItem->ViewGetLOD( layer, view ) < view->GetScale() )
  734. {
  735. items.insert ( boardItem );
  736. }
  737. }
  738. std::function<void( BOARD_ITEM* )> skipItem =
  739. [&]( BOARD_ITEM* aItem )
  740. {
  741. items.erase( aItem );
  742. aItem->RunOnChildren(
  743. [&]( BOARD_ITEM* aChild )
  744. {
  745. skipItem( aChild );
  746. },
  747. RECURSE_MODE::RECURSE );
  748. };
  749. for( BOARD_ITEM* item : aSkip )
  750. skipItem( item );
  751. return {items.begin(), items.end()};
  752. }
  753. struct PCB_INTERSECTABLE
  754. {
  755. BOARD_ITEM* Item;
  756. INTERSECTABLE_GEOM Geometry;
  757. // Clang wants this constructor
  758. PCB_INTERSECTABLE( BOARD_ITEM* aItem, INTERSECTABLE_GEOM aSeg ) :
  759. Item( aItem ), Geometry( std::move( aSeg ) )
  760. {
  761. }
  762. };
  763. void PCB_GRID_HELPER::computeAnchors( const std::vector<BOARD_ITEM*>& aItems,
  764. const VECTOR2I& aRefPos, bool aFrom,
  765. const PCB_SELECTION_FILTER_OPTIONS* aSelectionFilter,
  766. const LSET* aMatchLayers, bool aForDrag )
  767. {
  768. std::vector<PCB_INTERSECTABLE> intersectables;
  769. // These could come from a more granular snap mode filter
  770. // But when looking for drag points, we don't want construction geometry
  771. const bool computeIntersections = !aForDrag;
  772. const bool computePointsOnElements = !aForDrag;
  773. const bool excludeGraphics = aSelectionFilter && !aSelectionFilter->graphics;
  774. const bool excludeTracks = aSelectionFilter && !aSelectionFilter->tracks;
  775. const auto itemIsSnappable = [&]( const BOARD_ITEM& aItem )
  776. {
  777. // If we are filtering by layers, check if the item matches
  778. if( aMatchLayers )
  779. {
  780. return m_magneticSettings->allLayers
  781. || ( ( *aMatchLayers & aItem.GetLayerSet() ).any() );
  782. }
  783. return true;
  784. };
  785. const auto processItem = [&]( BOARD_ITEM& item )
  786. {
  787. // Don't even process the item if it doesn't match the layers
  788. if( !itemIsSnappable( item ) )
  789. {
  790. return;
  791. }
  792. // First, add all the key points of the item itself
  793. computeAnchors( &item, aRefPos, aFrom, aSelectionFilter );
  794. // If we are computing intersections, construct the relevant intersectables
  795. // Points on elements also use the intersectables.
  796. if( computeIntersections || computePointsOnElements )
  797. {
  798. std::optional<INTERSECTABLE_GEOM> intersectableGeom;
  799. if( !excludeGraphics
  800. && ( item.Type() == PCB_SHAPE_T || item.Type() == PCB_REFERENCE_IMAGE_T ) )
  801. {
  802. intersectableGeom = GetBoardIntersectable( item );
  803. }
  804. else if( !excludeTracks && ( item.Type() == PCB_TRACE_T || item.Type() == PCB_ARC_T ) )
  805. {
  806. intersectableGeom = GetBoardIntersectable( item );
  807. }
  808. if( intersectableGeom )
  809. {
  810. intersectables.emplace_back( &item, *intersectableGeom );
  811. }
  812. }
  813. };
  814. for( BOARD_ITEM* item : aItems )
  815. {
  816. processItem( *item );
  817. }
  818. for( const CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH& batch :
  819. getSnapManager().GetConstructionItems() )
  820. {
  821. for( const CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM& constructionItem : batch )
  822. {
  823. BOARD_ITEM* involvedItem = static_cast<BOARD_ITEM*>( constructionItem.Item );
  824. for( const KIGFX::CONSTRUCTION_GEOM::DRAWABLE& drawable :
  825. constructionItem.Constructions )
  826. {
  827. std::visit(
  828. [&]( const auto& visited )
  829. {
  830. using ItemType = std::decay_t<decltype( visited )>;
  831. if constexpr( std::is_same_v<ItemType, LINE>
  832. || std::is_same_v<ItemType, CIRCLE>
  833. || std::is_same_v<ItemType, HALF_LINE>
  834. || std::is_same_v<ItemType, SHAPE_ARC> )
  835. {
  836. intersectables.emplace_back( involvedItem, visited );
  837. }
  838. else if constexpr( std::is_same_v<ItemType, VECTOR2I> )
  839. {
  840. // Add any free-floating points as snap points.
  841. addAnchor( visited, SNAPPABLE | CONSTRUCTED, involvedItem,
  842. POINT_TYPE::PT_NONE );
  843. }
  844. },
  845. drawable );
  846. }
  847. }
  848. }
  849. // Now, add all the intersections between the items
  850. // This is obviously quadratic, so performance may be a concern for large selections
  851. // But, so far up to ~20k comparisons seems not to be an issue with run times in the ms range
  852. // and it's usually only a handful of items.
  853. if( computeIntersections )
  854. {
  855. for( std::size_t ii = 0; ii < intersectables.size(); ++ii )
  856. {
  857. const PCB_INTERSECTABLE& intersectableA = intersectables[ii];
  858. for( std::size_t jj = ii + 1; jj < intersectables.size(); ++jj )
  859. {
  860. const PCB_INTERSECTABLE& intersectableB = intersectables[jj];
  861. // An item and its own extension will often have intersections (as they are on top of each other),
  862. // but they not useful points to snap to
  863. if( intersectableA.Item == intersectableB.Item )
  864. continue;
  865. std::vector<VECTOR2I> intersections;
  866. const INTERSECTION_VISITOR visitor{ intersectableA.Geometry, intersections };
  867. std::visit( visitor, intersectableB.Geometry );
  868. // For each intersection, add an intersection snap anchor
  869. for( const VECTOR2I& intersection : intersections )
  870. {
  871. std::vector<EDA_ITEM*> items = {
  872. intersectableA.Item,
  873. intersectableB.Item,
  874. };
  875. addAnchor( intersection, SNAPPABLE | CONSTRUCTED, std::move( items ),
  876. POINT_TYPE::PT_INTERSECTION );
  877. }
  878. }
  879. }
  880. }
  881. // The intersectables can also be used for fall-back snapping to "point on line"
  882. // snaps if no other snap is found
  883. m_pointOnLineCandidates.clear();
  884. if( computePointsOnElements )
  885. {
  886. // For the moment, it's trivial to make a NEARABLE from an INTERSECTABLE,
  887. // because all INTERSECTABLEs are also NEARABLEs.
  888. for( const PCB_INTERSECTABLE& intersectable : intersectables )
  889. {
  890. std::visit(
  891. [&]( const auto& geom )
  892. {
  893. NEARABLE_GEOM nearable( geom );
  894. m_pointOnLineCandidates.emplace_back( nearable );
  895. },
  896. intersectable.Geometry );
  897. }
  898. }
  899. }
  900. void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos, bool aFrom,
  901. const PCB_SELECTION_FILTER_OPTIONS* aSelectionFilter )
  902. {
  903. KIGFX::VIEW* view = m_toolMgr->GetView();
  904. RENDER_SETTINGS* settings = view->GetPainter()->GetSettings();
  905. const std::set<int>& activeLayers = settings->GetHighContrastLayers();
  906. bool isHighContrast = settings->GetHighContrast();
  907. auto checkVisibility =
  908. [&]( BOARD_ITEM* item )
  909. {
  910. // New moved items don't yet have view flags so VIEW will call them invisible
  911. if( !view->IsVisible( item ) && !item->IsMoving() )
  912. return false;
  913. bool onActiveLayer = !isHighContrast;
  914. bool isLODVisible = false;
  915. for( PCB_LAYER_ID layer : item->GetLayerSet().Seq() )
  916. {
  917. if( !onActiveLayer && activeLayers.count( layer ) )
  918. onActiveLayer = true;
  919. if( !isLODVisible && item->ViewGetLOD( layer, view ) < view->GetScale() )
  920. isLODVisible = true;
  921. if( onActiveLayer && isLODVisible )
  922. return true;
  923. }
  924. return false;
  925. };
  926. // As defaults, these are probably reasonable to avoid spamming key points
  927. const KIGEOM::OVAL_KEY_POINT_FLAGS ovalKeyPointFlags =
  928. KIGEOM::OVAL_CENTER | KIGEOM::OVAL_CAP_TIPS | KIGEOM::OVAL_SIDE_MIDPOINTS
  929. | KIGEOM::OVAL_CARDINAL_EXTREMES;
  930. auto handlePadShape = [&]( PAD* aPad, PCB_LAYER_ID aLayer )
  931. {
  932. addAnchor( aPad->GetPosition(), ORIGIN | SNAPPABLE, aPad, POINT_TYPE::PT_CENTER );
  933. /// If we are getting a drag point, we don't want to center the edge of pads
  934. if( aFrom )
  935. return;
  936. switch( aPad->GetShape( aLayer ) )
  937. {
  938. case PAD_SHAPE::CIRCLE:
  939. {
  940. const CIRCLE circle( aPad->ShapePos( aLayer ), aPad->GetSizeX() / 2 );
  941. for( const TYPED_POINT2I& pt : KIGEOM::GetCircleKeyPoints( circle, false ) )
  942. {
  943. addAnchor( pt.m_point, OUTLINE | SNAPPABLE, aPad, pt.m_types );
  944. }
  945. break;
  946. }
  947. case PAD_SHAPE::OVAL:
  948. {
  949. const OVAL oval( aPad->GetSize( aLayer ), aPad->GetPosition(), aPad->GetOrientation() );
  950. for( const TYPED_POINT2I& pt : KIGEOM::GetOvalKeyPoints( oval, ovalKeyPointFlags ) )
  951. {
  952. addAnchor( pt.m_point, OUTLINE | SNAPPABLE, aPad, pt.m_types );
  953. }
  954. break;
  955. }
  956. case PAD_SHAPE::RECTANGLE:
  957. case PAD_SHAPE::TRAPEZOID:
  958. case PAD_SHAPE::ROUNDRECT:
  959. case PAD_SHAPE::CHAMFERED_RECT:
  960. {
  961. VECTOR2I half_size( aPad->GetSize( aLayer ) / 2 );
  962. VECTOR2I trap_delta( 0, 0 );
  963. if( aPad->GetShape( aLayer ) == PAD_SHAPE::TRAPEZOID )
  964. trap_delta = aPad->GetDelta( aLayer ) / 2;
  965. SHAPE_LINE_CHAIN corners;
  966. corners.Append( -half_size.x - trap_delta.y, half_size.y + trap_delta.x );
  967. corners.Append( half_size.x + trap_delta.y, half_size.y - trap_delta.x );
  968. corners.Append( half_size.x - trap_delta.y, -half_size.y + trap_delta.x );
  969. corners.Append( -half_size.x + trap_delta.y, -half_size.y - trap_delta.x );
  970. corners.SetClosed( true );
  971. corners.Rotate( aPad->GetOrientation() );
  972. corners.Move( aPad->ShapePos( aLayer ) );
  973. for( std::size_t ii = 0; ii < corners.GetSegmentCount(); ++ii )
  974. {
  975. const SEG& seg = corners.GetSegment( ii );
  976. addAnchor( seg.A, OUTLINE | SNAPPABLE, aPad, POINT_TYPE::PT_CORNER );
  977. addAnchor( seg.Center(), OUTLINE | SNAPPABLE, aPad, POINT_TYPE::PT_MID );
  978. if( ii == corners.GetSegmentCount() - 1 )
  979. addAnchor( seg.B, OUTLINE | SNAPPABLE, aPad, POINT_TYPE::PT_CORNER );
  980. }
  981. break;
  982. }
  983. default:
  984. {
  985. const auto& outline = aPad->GetEffectivePolygon( aLayer, ERROR_INSIDE );
  986. if( !outline->IsEmpty() )
  987. {
  988. for( const VECTOR2I& pt : outline->Outline( 0 ).CPoints() )
  989. addAnchor( pt, OUTLINE | SNAPPABLE, aPad );
  990. }
  991. break;
  992. }
  993. }
  994. if( aPad->HasHole() )
  995. {
  996. // Holes are at the pad centre (it's the shape that may be offset)
  997. const VECTOR2I hole_pos = aPad->GetPosition();
  998. const VECTOR2I hole_size = aPad->GetDrillSize();
  999. std::vector<TYPED_POINT2I> snap_pts;
  1000. if( hole_size.x == hole_size.y )
  1001. {
  1002. // Circle
  1003. const CIRCLE circle( hole_pos, hole_size.x / 2 );
  1004. snap_pts = KIGEOM::GetCircleKeyPoints( circle, true );
  1005. }
  1006. else
  1007. {
  1008. // Oval
  1009. // For now there's no way to have an off-angle hole, so this is the
  1010. // same as the pad. In future, this may not be true:
  1011. // https://gitlab.com/kicad/code/kicad/-/issues/4124
  1012. const OVAL oval( hole_size, hole_pos, aPad->GetOrientation() );
  1013. snap_pts = KIGEOM::GetOvalKeyPoints( oval, ovalKeyPointFlags );
  1014. }
  1015. for( const TYPED_POINT2I& snap_pt : snap_pts )
  1016. addAnchor( snap_pt.m_point, OUTLINE | SNAPPABLE, aPad, snap_pt.m_types );
  1017. }
  1018. };
  1019. const auto addRectPoints = [&]( const BOX2I& aBox, EDA_ITEM& aRelatedItem )
  1020. {
  1021. const VECTOR2I topRight( aBox.GetRight(), aBox.GetTop() );
  1022. const VECTOR2I bottomLeft( aBox.GetLeft(), aBox.GetBottom() );
  1023. const SEG first( aBox.GetOrigin(), topRight );
  1024. const SEG second( topRight, aBox.GetEnd() );
  1025. const SEG third( aBox.GetEnd(), bottomLeft );
  1026. const SEG fourth( bottomLeft, aBox.GetOrigin() );
  1027. const int snapFlags = CORNER | SNAPPABLE;
  1028. addAnchor( aBox.GetCenter(), snapFlags, &aRelatedItem, POINT_TYPE::PT_CENTER );
  1029. addAnchor( first.A, snapFlags, &aRelatedItem, POINT_TYPE::PT_CORNER );
  1030. addAnchor( first.Center(), snapFlags, &aRelatedItem, POINT_TYPE::PT_MID );
  1031. addAnchor( second.A, snapFlags, &aRelatedItem, POINT_TYPE::PT_CORNER );
  1032. addAnchor( second.Center(), snapFlags, &aRelatedItem, POINT_TYPE::PT_MID );
  1033. addAnchor( third.A, snapFlags, &aRelatedItem, POINT_TYPE::PT_CORNER );
  1034. addAnchor( third.Center(), snapFlags, &aRelatedItem, POINT_TYPE::PT_MID );
  1035. addAnchor( fourth.A, snapFlags, &aRelatedItem, POINT_TYPE::PT_CORNER );
  1036. addAnchor( fourth.Center(), snapFlags, &aRelatedItem, POINT_TYPE::PT_MID );
  1037. };
  1038. const auto handleShape =
  1039. [&]( PCB_SHAPE* shape )
  1040. {
  1041. VECTOR2I start = shape->GetStart();
  1042. VECTOR2I end = shape->GetEnd();
  1043. switch( shape->GetShape() )
  1044. {
  1045. case SHAPE_T::CIRCLE:
  1046. {
  1047. const int r = ( start - end ).EuclideanNorm();
  1048. addAnchor( start, ORIGIN | SNAPPABLE, shape, POINT_TYPE::PT_CENTER );
  1049. addAnchor( start + VECTOR2I( -r, 0 ), OUTLINE | SNAPPABLE, shape,
  1050. POINT_TYPE::PT_QUADRANT );
  1051. addAnchor( start + VECTOR2I( r, 0 ), OUTLINE | SNAPPABLE, shape,
  1052. POINT_TYPE::PT_QUADRANT );
  1053. addAnchor( start + VECTOR2I( 0, -r ), OUTLINE | SNAPPABLE, shape,
  1054. POINT_TYPE::PT_QUADRANT );
  1055. addAnchor( start + VECTOR2I( 0, r ), OUTLINE | SNAPPABLE, shape,
  1056. POINT_TYPE::PT_QUADRANT );
  1057. break;
  1058. }
  1059. case SHAPE_T::ARC:
  1060. addAnchor( shape->GetStart(), CORNER | SNAPPABLE, shape,
  1061. POINT_TYPE::PT_END );
  1062. addAnchor( shape->GetEnd(), CORNER | SNAPPABLE, shape,
  1063. POINT_TYPE::PT_END );
  1064. addAnchor( shape->GetArcMid(), CORNER | SNAPPABLE, shape,
  1065. POINT_TYPE::PT_MID );
  1066. addAnchor( shape->GetCenter(), ORIGIN | SNAPPABLE, shape,
  1067. POINT_TYPE::PT_CENTER );
  1068. break;
  1069. case SHAPE_T::RECTANGLE:
  1070. {
  1071. addRectPoints( BOX2I::ByCorners( start, end ), *shape );
  1072. break;
  1073. }
  1074. case SHAPE_T::SEGMENT:
  1075. addAnchor( start, CORNER | SNAPPABLE, shape, POINT_TYPE::PT_END );
  1076. addAnchor( end, CORNER | SNAPPABLE, shape, POINT_TYPE::PT_END );
  1077. addAnchor( shape->GetCenter(), CORNER | SNAPPABLE, shape,
  1078. POINT_TYPE::PT_MID );
  1079. break;
  1080. case SHAPE_T::POLY:
  1081. {
  1082. SHAPE_LINE_CHAIN lc;
  1083. lc.SetClosed( true );
  1084. std::vector<VECTOR2I> poly;
  1085. shape->DupPolyPointsList( poly );
  1086. for( const VECTOR2I& p : poly )
  1087. {
  1088. addAnchor( p, CORNER | SNAPPABLE, shape, POINT_TYPE::PT_CORNER );
  1089. lc.Append( p );
  1090. }
  1091. addAnchor( lc.NearestPoint( aRefPos ), OUTLINE, aItem );
  1092. break;
  1093. }
  1094. case SHAPE_T::BEZIER:
  1095. addAnchor( start, CORNER | SNAPPABLE, shape, POINT_TYPE::PT_END );
  1096. addAnchor( end, CORNER | SNAPPABLE, shape, POINT_TYPE::PT_END );
  1097. KI_FALLTHROUGH;
  1098. default:
  1099. addAnchor( shape->GetPosition(), ORIGIN | SNAPPABLE, shape );
  1100. break;
  1101. }
  1102. };
  1103. switch( aItem->Type() )
  1104. {
  1105. case PCB_FOOTPRINT_T:
  1106. {
  1107. FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem );
  1108. for( PAD* pad : footprint->Pads() )
  1109. {
  1110. if( aFrom )
  1111. {
  1112. if( aSelectionFilter && !aSelectionFilter->pads )
  1113. continue;
  1114. }
  1115. else
  1116. {
  1117. if( m_magneticSettings->pads != MAGNETIC_OPTIONS::CAPTURE_ALWAYS )
  1118. continue;
  1119. }
  1120. if( !checkVisibility( pad ) )
  1121. continue;
  1122. if( !pad->GetBoundingBox().Contains( aRefPos ) )
  1123. continue;
  1124. pad->Padstack().ForEachUniqueLayer(
  1125. [&]( PCB_LAYER_ID aLayer )
  1126. {
  1127. if( !isHighContrast || activeLayers.count( aLayer ) )
  1128. handlePadShape( pad, aLayer );
  1129. } );
  1130. }
  1131. if( aFrom && aSelectionFilter && !aSelectionFilter->footprints )
  1132. break;
  1133. // If the cursor is not over a pad, snap to the anchor (if visible) or the center
  1134. // (if markedly different from the anchor).
  1135. VECTOR2I position = footprint->GetPosition();
  1136. VECTOR2I center = footprint->GetBoundingBox( false ).Centre();
  1137. VECTOR2I grid( GetGrid() );
  1138. if( view->IsLayerVisible( LAYER_ANCHOR ) )
  1139. addAnchor( position, ORIGIN | SNAPPABLE, footprint, POINT_TYPE::PT_CENTER );
  1140. if( ( center - position ).SquaredEuclideanNorm() > grid.SquaredEuclideanNorm() )
  1141. addAnchor( center, ORIGIN | SNAPPABLE, footprint, POINT_TYPE::PT_CENTER );
  1142. break;
  1143. }
  1144. case PCB_PAD_T:
  1145. if( aFrom )
  1146. {
  1147. if( aSelectionFilter && !aSelectionFilter->pads )
  1148. break;
  1149. }
  1150. else
  1151. {
  1152. if( m_magneticSettings->pads != MAGNETIC_OPTIONS::CAPTURE_ALWAYS )
  1153. break;
  1154. }
  1155. if( checkVisibility( aItem ) )
  1156. {
  1157. PAD* pad = static_cast<PAD*>( aItem );
  1158. pad->Padstack().ForEachUniqueLayer(
  1159. [&]( PCB_LAYER_ID aLayer )
  1160. {
  1161. if( !isHighContrast || activeLayers.count( aLayer ) )
  1162. handlePadShape( pad, aLayer );
  1163. } );
  1164. }
  1165. break;
  1166. case PCB_TEXTBOX_T:
  1167. if( aFrom )
  1168. {
  1169. if( aSelectionFilter && !aSelectionFilter->text )
  1170. break;
  1171. }
  1172. else
  1173. {
  1174. if( !m_magneticSettings->graphics )
  1175. break;
  1176. }
  1177. if( checkVisibility( aItem ) )
  1178. handleShape( static_cast<PCB_SHAPE*>( aItem ) );
  1179. break;
  1180. case PCB_SHAPE_T:
  1181. if( aFrom )
  1182. {
  1183. if( aSelectionFilter && !aSelectionFilter->graphics )
  1184. break;
  1185. }
  1186. else
  1187. {
  1188. if( !m_magneticSettings->graphics )
  1189. break;
  1190. }
  1191. if( checkVisibility( aItem ) )
  1192. handleShape( static_cast<PCB_SHAPE*>( aItem ) );
  1193. break;
  1194. case PCB_TRACE_T:
  1195. case PCB_ARC_T:
  1196. if( aFrom )
  1197. {
  1198. if( aSelectionFilter && !aSelectionFilter->tracks )
  1199. break;
  1200. }
  1201. else
  1202. {
  1203. if( m_magneticSettings->tracks != MAGNETIC_OPTIONS::CAPTURE_ALWAYS )
  1204. break;
  1205. }
  1206. if( checkVisibility( aItem ) )
  1207. {
  1208. PCB_TRACK* track = static_cast<PCB_TRACK*>( aItem );
  1209. addAnchor( track->GetStart(), CORNER | SNAPPABLE, track, POINT_TYPE::PT_END );
  1210. addAnchor( track->GetEnd(), CORNER | SNAPPABLE, track, POINT_TYPE::PT_END );
  1211. addAnchor( track->GetCenter(), ORIGIN, track, POINT_TYPE::PT_MID );
  1212. }
  1213. break;
  1214. case PCB_MARKER_T:
  1215. case PCB_TARGET_T:
  1216. addAnchor( aItem->GetPosition(), ORIGIN | CORNER | SNAPPABLE, aItem,
  1217. POINT_TYPE::PT_CENTER );
  1218. break;
  1219. case PCB_VIA_T:
  1220. if( aFrom )
  1221. {
  1222. if( aSelectionFilter && !aSelectionFilter->vias )
  1223. break;
  1224. }
  1225. else
  1226. {
  1227. if( m_magneticSettings->tracks != MAGNETIC_OPTIONS::CAPTURE_ALWAYS )
  1228. break;
  1229. }
  1230. if( checkVisibility( aItem ) )
  1231. addAnchor( aItem->GetPosition(), ORIGIN | CORNER | SNAPPABLE, aItem,
  1232. POINT_TYPE::PT_CENTER );
  1233. break;
  1234. case PCB_ZONE_T:
  1235. if( aFrom && aSelectionFilter && !aSelectionFilter->zones )
  1236. break;
  1237. if( checkVisibility( aItem ) )
  1238. {
  1239. const SHAPE_POLY_SET* outline = static_cast<const ZONE*>( aItem )->Outline();
  1240. SHAPE_LINE_CHAIN lc;
  1241. lc.SetClosed( true );
  1242. for( auto iter = outline->CIterateWithHoles(); iter; iter++ )
  1243. {
  1244. addAnchor( *iter, CORNER | SNAPPABLE, aItem, POINT_TYPE::PT_CORNER );
  1245. lc.Append( *iter );
  1246. }
  1247. addAnchor( lc.NearestPoint( aRefPos ), OUTLINE, aItem );
  1248. }
  1249. break;
  1250. case PCB_DIM_ALIGNED_T:
  1251. case PCB_DIM_ORTHOGONAL_T:
  1252. if( aFrom && aSelectionFilter && !aSelectionFilter->dimensions )
  1253. break;
  1254. if( checkVisibility( aItem ) )
  1255. {
  1256. const PCB_DIM_ALIGNED* dim = static_cast<const PCB_DIM_ALIGNED*>( aItem );
  1257. addAnchor( dim->GetCrossbarStart(), CORNER | SNAPPABLE, aItem );
  1258. addAnchor( dim->GetCrossbarEnd(), CORNER | SNAPPABLE, aItem );
  1259. addAnchor( dim->GetStart(), CORNER | SNAPPABLE, aItem );
  1260. addAnchor( dim->GetEnd(), CORNER | SNAPPABLE, aItem );
  1261. }
  1262. break;
  1263. case PCB_DIM_CENTER_T:
  1264. if( aFrom && aSelectionFilter && !aSelectionFilter->dimensions )
  1265. break;
  1266. if( checkVisibility( aItem ) )
  1267. {
  1268. const PCB_DIM_CENTER* dim = static_cast<const PCB_DIM_CENTER*>( aItem );
  1269. addAnchor( dim->GetStart(), CORNER | SNAPPABLE, aItem );
  1270. addAnchor( dim->GetEnd(), CORNER | SNAPPABLE, aItem );
  1271. VECTOR2I start( dim->GetStart() );
  1272. VECTOR2I radial( dim->GetEnd() - dim->GetStart() );
  1273. for( int i = 0; i < 2; i++ )
  1274. {
  1275. RotatePoint( radial, -ANGLE_90 );
  1276. addAnchor( start + radial, CORNER | SNAPPABLE, aItem );
  1277. }
  1278. }
  1279. break;
  1280. case PCB_DIM_RADIAL_T:
  1281. if( aFrom && aSelectionFilter && !aSelectionFilter->dimensions )
  1282. break;
  1283. if( checkVisibility( aItem ) )
  1284. {
  1285. const PCB_DIM_RADIAL* radialDim = static_cast<const PCB_DIM_RADIAL*>( aItem );
  1286. addAnchor( radialDim->GetStart(), CORNER | SNAPPABLE, aItem );
  1287. addAnchor( radialDim->GetEnd(), CORNER | SNAPPABLE, aItem );
  1288. addAnchor( radialDim->GetKnee(), CORNER | SNAPPABLE, aItem );
  1289. addAnchor( radialDim->GetTextPos(), CORNER | SNAPPABLE, aItem );
  1290. }
  1291. break;
  1292. case PCB_DIM_LEADER_T:
  1293. if( aFrom && aSelectionFilter && !aSelectionFilter->dimensions )
  1294. break;
  1295. if( checkVisibility( aItem ) )
  1296. {
  1297. const PCB_DIM_LEADER* leader = static_cast<const PCB_DIM_LEADER*>( aItem );
  1298. addAnchor( leader->GetStart(), CORNER | SNAPPABLE, aItem );
  1299. addAnchor( leader->GetEnd(), CORNER | SNAPPABLE, aItem );
  1300. addAnchor( leader->GetTextPos(), CORNER | SNAPPABLE, aItem );
  1301. }
  1302. break;
  1303. case PCB_FIELD_T:
  1304. case PCB_TEXT_T:
  1305. if( aFrom && aSelectionFilter && !aSelectionFilter->text )
  1306. break;
  1307. if( checkVisibility( aItem ) )
  1308. addAnchor( aItem->GetPosition(), ORIGIN, aItem );
  1309. break;
  1310. case PCB_GROUP_T:
  1311. for( BOARD_ITEM* item : static_cast<const PCB_GROUP*>( aItem )->GetItems() )
  1312. {
  1313. if( checkVisibility( item ) )
  1314. computeAnchors( item, aRefPos, aFrom, nullptr );
  1315. }
  1316. break;
  1317. case PCB_REFERENCE_IMAGE_T:
  1318. if( aFrom && aSelectionFilter && !aSelectionFilter->graphics )
  1319. break;
  1320. if( checkVisibility( aItem ) )
  1321. {
  1322. const PCB_REFERENCE_IMAGE& image =
  1323. static_cast<const PCB_REFERENCE_IMAGE&>( *aItem );
  1324. const REFERENCE_IMAGE& refImg = image.GetReferenceImage();
  1325. const BOX2I bbox = refImg.GetBoundingBox();
  1326. addRectPoints( bbox, *aItem );
  1327. if( refImg.GetTransformOriginOffset() != VECTOR2I( 0, 0 ) )
  1328. {
  1329. addAnchor( aItem->GetPosition() + refImg.GetTransformOriginOffset(), ORIGIN,
  1330. aItem, POINT_TYPE::PT_CENTER );
  1331. }
  1332. }
  1333. break;
  1334. default:
  1335. break;
  1336. }
  1337. }
  1338. PCB_GRID_HELPER::ANCHOR* PCB_GRID_HELPER::nearestAnchor( const VECTOR2I& aPos, int aFlags )
  1339. {
  1340. // Do this all in squared distances as we only care about relative distances
  1341. using ecoord = VECTOR2I::extended_type;
  1342. ecoord minDist = std::numeric_limits<ecoord>::max();
  1343. std::vector<ANCHOR*> anchorsAtMinDistance;
  1344. for( ANCHOR& anchor : m_anchors )
  1345. {
  1346. // There is no need to filter by layers here, as the items are already filtered
  1347. // by layer (if needed) when the anchors are computed.
  1348. if( ( aFlags & anchor.flags ) != aFlags )
  1349. continue;
  1350. if( !anchorsAtMinDistance.empty() && anchor.pos == anchorsAtMinDistance.front()->pos )
  1351. {
  1352. // Same distance as the previous best anchor
  1353. anchorsAtMinDistance.push_back( &anchor );
  1354. }
  1355. else
  1356. {
  1357. const double dist = anchor.pos.SquaredDistance( aPos );
  1358. if( dist < minDist )
  1359. {
  1360. // New minimum distance
  1361. minDist = dist;
  1362. anchorsAtMinDistance.clear();
  1363. anchorsAtMinDistance.push_back( &anchor );
  1364. }
  1365. }
  1366. }
  1367. // More than one anchor can be at the same distance, for example
  1368. // two lines end-to-end each have the same endpoint anchor.
  1369. // So, check which one has an involved item that's closest to the origin,
  1370. // and use that one (which allows the user to choose which items
  1371. // gets extended - it's the one nearest the cursor)
  1372. ecoord minDistToItem = std::numeric_limits<ecoord>::max();
  1373. ANCHOR* best = nullptr;
  1374. // One of the anchors at the minimum distance
  1375. for( ANCHOR* const anchor : anchorsAtMinDistance )
  1376. {
  1377. ecoord distToNearestItem = std::numeric_limits<ecoord>::max();
  1378. for( EDA_ITEM* const item : anchor->items )
  1379. {
  1380. if( !item )
  1381. continue;
  1382. std::optional<ecoord> distToThisItem =
  1383. FindSquareDistanceToItem( static_cast<const BOARD_ITEM&>( *item ), aPos );
  1384. if( distToThisItem )
  1385. distToNearestItem = std::min( distToNearestItem, *distToThisItem );
  1386. }
  1387. // If the item doesn't have any special min-dist handler,
  1388. // just use the distance to the anchor
  1389. distToNearestItem = std::min( distToNearestItem, minDist );
  1390. if( distToNearestItem < minDistToItem )
  1391. {
  1392. minDistToItem = distToNearestItem;
  1393. best = anchor;
  1394. }
  1395. }
  1396. return best;
  1397. }