KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_grid_helper.cpp
Go to the documentation of this file.
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 <[email protected]>
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, see <https://www.gnu.org/licenses/>.
20 */
21
22#include "pcb_grid_helper.h"
23
24#include <functional>
25#include <algorithm>
26#include <cmath>
27#include <unordered_set>
28
29#include <advanced_config.h>
30#include <board_item.h>
31#include <pcb_dimension.h>
32#include <pcb_shape.h>
33#include <footprint.h>
34#include <pcb_table.h>
35#include <pad.h>
36#include <pcb_group.h>
37#include <pcb_point.h>
38#include <pcb_barcode.h>
39#include <pcb_reference_image.h>
40#include <pcb_track.h>
41#include <zone.h>
44#include <geometry/nearest.h>
45#include <geometry/oval.h>
48#include <geometry/shape_rect.h>
52#include <macros.h>
53#include <math/util.h> // for KiROUND
54#include <gal/painter.h>
56#include <pcb_base_frame.h>
57#include <pcbnew_settings.h>
59#include <snap/snap_inference.h>
60#include <tool/snap_frame.h>
61#include <tool/tool_manager.h>
62#include <view/view.h>
63#include <trace_helpers.h>
64
65namespace
66{
72std::optional<INTERSECTABLE_GEOM> GetBoardIntersectable( const BOARD_ITEM& aItem )
73{
74 switch( aItem.Type() )
75 {
76 case PCB_SHAPE_T:
77 {
78 const PCB_SHAPE& shape = static_cast<const PCB_SHAPE&>( aItem );
79
80 switch( shape.GetShape() )
81 {
82 case SHAPE_T::SEGMENT: return SEG{ shape.GetStart(), shape.GetEnd() };
83 case SHAPE_T::CIRCLE: return CIRCLE{ shape.GetCenter(), shape.GetRadius() };
84 case SHAPE_T::ARC: return SHAPE_ARC{ shape.GetStart(), shape.GetArcMid(), shape.GetEnd(), 0 };
85 case SHAPE_T::RECTANGLE: return BOX2I::ByCorners( shape.GetStart(), shape.GetEnd() );
86 default: break;
87 }
88
89 break;
90 }
91
92 case PCB_TRACE_T:
93 {
94 const PCB_TRACK& track = static_cast<const PCB_TRACK&>( aItem );
95 return SEG{ track.GetStart(), track.GetEnd() };
96 }
97
98 case PCB_ARC_T:
99 {
100 const PCB_ARC& arc = static_cast<const PCB_ARC&>( aItem );
101 return SHAPE_ARC{ arc.GetStart(), arc.GetMid(), arc.GetEnd(), 0 };
102 }
103
105 {
106 const PCB_REFERENCE_IMAGE& refImage = static_cast<const PCB_REFERENCE_IMAGE&>( aItem );
107 return refImage.GetBoundingBox();
108 }
109
110 default:
111 break;
112 }
113
114 return std::nullopt;
115}
116
126std::optional<int64_t> FindSquareDistanceToItem( const BOARD_ITEM& item, const VECTOR2I& aPos )
127{
128 std::optional<INTERSECTABLE_GEOM> intersectable = GetBoardIntersectable( item );
129 std::optional<NEARABLE_GEOM> nearable;
130
131 if( intersectable )
132 {
133 // Exploit the intersectable as a nearable
134 std::visit(
135 [&]( const auto& geom )
136 {
137 nearable = NEARABLE_GEOM( geom );
138 },
139 *intersectable );
140 }
141
142 // Whatever the item is, we don't have a nearable for it
143 if( !nearable )
144 return std::nullopt;
145
146 const VECTOR2I nearestPt = GetNearestPoint( *nearable, aPos );
147 return nearestPt.SquaredDistance( aPos );
148}
149
150} // namespace
151
157
158
160 GRID_HELPER( aToolMgr, LAYER_ANCHOR ),
161 m_magneticSettings( aMagneticSettings )
162{
163 if( !m_toolMgr )
164 return;
165
166 KIGFX::VIEW* view = m_toolMgr->GetView();
167 KIGFX::RENDER_SETTINGS* settings = view->GetPainter()->GetSettings();
168 KIGFX::COLOR4D auxItemsColor = settings->GetLayerColor( LAYER_AUX_ITEMS );
169 KIGFX::COLOR4D anchorColor = settings->GetLayerColor( LAYER_ANCHOR );
170
171 m_viewAxis.SetSize( 20000 );
173 m_viewAxis.SetColor( auxItemsColor.WithAlpha( 0.4 ) );
174 m_viewAxis.SetDrawAtZero( true );
175 view->Add( &m_viewAxis );
176 view->SetVisible( &m_viewAxis, false );
177
178 m_viewSnapPoint.SetSize( 10 );
180 m_viewSnapPoint.SetColor( auxItemsColor );
181 m_viewSnapPoint.SetDrawAtZero( true );
182 view->Add( &m_viewSnapPoint );
183 getSnapManager().SetSnapGuideColors( anchorColor, anchorColor.Brightened( 0.2 ) );
184 view->SetVisible( &m_viewSnapPoint, false );
185
186 if( m_toolMgr->GetModel() )
187 static_cast<BOARD*>( aToolMgr->GetModel() )->AddListener( this );
188}
189
190
192{
193 if( !m_toolMgr )
194 return;
195
196 KIGFX::VIEW* view = m_toolMgr->GetView();
197
198 view->Remove( &m_viewAxis );
199 view->Remove( &m_viewSnapPoint );
200
201 if( m_toolMgr->GetModel() )
202 static_cast<BOARD*>( m_toolMgr->GetModel() )->RemoveListener( this );
203}
204
205
206void PCB_GRID_HELPER::AddConstructionItems( std::vector<BOARD_ITEM*> aItems, bool aExtensionOnly, bool aIsPersistent )
207{
208 if( !ADVANCED_CFG::GetCfg().m_EnableExtensionSnaps )
209 return;
210
211 if( !snapInferenceSettings().constructionExtensions )
212 return;
213
214 // For all the elements that get drawn construction geometry,
215 // add something suitable to the construction helper.
216 // This can be nothing.
217 auto constructionItemsBatch = std::make_unique<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH>();
218
219 std::vector<VECTOR2I> referenceOnlyPoints;
220
221 for( BOARD_ITEM* item : aItems )
222 {
223 std::vector<KIGFX::CONSTRUCTION_GEOM::DRAWABLE> constructionDrawables;
224
225 switch( item->Type() )
226 {
227 case PCB_SHAPE_T:
228 {
229 PCB_SHAPE& shape = static_cast<PCB_SHAPE&>( *item );
230
231 switch( shape.GetShape() )
232 {
233 case SHAPE_T::SEGMENT:
234 {
235 if( !aExtensionOnly )
236 {
237 constructionDrawables.emplace_back( LINE{ shape.GetStart(), shape.GetEnd() } );
238 }
239 else
240 {
241 // Two rays, extending from the segment ends
242 const VECTOR2I segVec = shape.GetEnd() - shape.GetStart();
243 constructionDrawables.emplace_back( HALF_LINE{ shape.GetStart(), shape.GetStart() - segVec } );
244 constructionDrawables.emplace_back( HALF_LINE{ shape.GetEnd(), shape.GetEnd() + segVec } );
245 }
246
247 if( aIsPersistent )
248 {
249 // include the original endpoints as construction items
250 // (this allows H/V snapping)
251 constructionDrawables.emplace_back( shape.GetStart() );
252 constructionDrawables.emplace_back( shape.GetEnd() );
253
254 // But mark them as references, so they don't get snapped to themsevles
255 referenceOnlyPoints.emplace_back( shape.GetStart() );
256 referenceOnlyPoints.emplace_back( shape.GetEnd() );
257 }
258 break;
259 }
260 case SHAPE_T::ARC:
261 {
262 if( !aExtensionOnly )
263 {
264 constructionDrawables.push_back( CIRCLE{ shape.GetCenter(), shape.GetRadius() } );
265 }
266 else
267 {
268 // The rest of the circle is the arc through the opposite point to the midpoint
269 const VECTOR2I oppositeMid = shape.GetCenter() + ( shape.GetCenter() - shape.GetArcMid() );
270 constructionDrawables.push_back( SHAPE_ARC{ shape.GetStart(), oppositeMid, shape.GetEnd(), 0 } );
271 }
272
273 constructionDrawables.push_back( shape.GetCenter() );
274
275 if( aIsPersistent )
276 {
277 // include the original endpoints as construction items
278 // (this allows H/V snapping)
279 constructionDrawables.emplace_back( shape.GetStart() );
280 constructionDrawables.emplace_back( shape.GetEnd() );
281
282 // But mark them as references, so they don't get snapped to themselves
283 referenceOnlyPoints.emplace_back( shape.GetStart() );
284 referenceOnlyPoints.emplace_back( shape.GetEnd() );
285 }
286
287 break;
288 }
289 case SHAPE_T::CIRCLE:
291 {
292 constructionDrawables.push_back( shape.GetCenter() );
293 break;
294 }
295 case SHAPE_T::ELLIPSE:
297 {
298 constructionDrawables.push_back( shape.GetEllipseCenter() );
299 break;
300 }
301 default:
302 // This shape doesn't have any construction geometry to draw
303 break;
304 }
305 break;
306 }
308 {
309 const PCB_REFERENCE_IMAGE& pcbRefImg = static_cast<PCB_REFERENCE_IMAGE&>( *item );
310 const REFERENCE_IMAGE& refImg = pcbRefImg.GetReferenceImage();
311
312 constructionDrawables.push_back( refImg.GetPosition() );
313
314 if( refImg.GetTransformOriginOffset() != VECTOR2I( 0, 0 ) )
315 constructionDrawables.push_back( refImg.GetPosition() + refImg.GetTransformOriginOffset() );
316
317 for( const SEG& seg : KIGEOM::BoxToSegs( refImg.GetBoundingBox() ) )
318 constructionDrawables.push_back( seg );
319
320 break;
321 }
322 default:
323 // This item doesn't have any construction geometry to draw
324 break;
325 }
326
327 // At this point, constructionDrawables can be empty, which is fine
328 // (it means there's no additional construction geometry to draw, but
329 // the item is still going to be proposed for activation)
330
331 // Convert the drawables to DRAWABLE_ENTRY format
332 std::vector<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM::DRAWABLE_ENTRY> drawableEntries;
333 drawableEntries.reserve( constructionDrawables.size() );
334 for( auto& drawable : constructionDrawables )
335 {
336 drawableEntries.emplace_back(
338 }
339
340 constructionItemsBatch->emplace_back( CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM{
342 item,
343 std::move( drawableEntries ),
344 } );
345 }
346
347 if( referenceOnlyPoints.size() )
348 getSnapManager().SetReferenceOnlyPoints( std::move( referenceOnlyPoints ) );
349
350 // Let the manager handle it
351 getSnapManager().GetConstructionManager().ProposeConstructionItems( std::move( constructionItemsBatch ),
352 aIsPersistent );
353}
354
355
357{
358 const int c_gridSnapEpsilon_sq = 4;
359
360 VECTOR2I aligned = Align( aPoint );
361
362 if( !m_enableSnap )
363 return aligned;
364
365 std::vector<VECTOR2I> points;
366
367 const SEG testSegments[] = { SEG( aligned, aligned + VECTOR2( 1, 0 ) ),
368 SEG( aligned, aligned + VECTOR2( 0, 1 ) ),
369 SEG( aligned, aligned + VECTOR2( 1, 1 ) ),
370 SEG( aligned, aligned + VECTOR2( 1, -1 ) ) };
371
372 for( const SEG& seg : testSegments )
373 {
374 OPT_VECTOR2I vec = aSeg.IntersectLines( seg );
375
376 if( vec && aSeg.SquaredDistance( *vec ) <= c_gridSnapEpsilon_sq )
377 points.push_back( *vec );
378 }
379
380 VECTOR2I nearest = aligned;
382
383 // Snap by distance between pointer and endpoints
384 for( const VECTOR2I& pt : { aSeg.A, aSeg.B } )
385 {
386 SEG::ecoord d_sq = ( pt - aPoint ).SquaredEuclideanNorm();
387
388 if( d_sq < min_d_sq )
389 {
390 min_d_sq = d_sq;
391 nearest = pt;
392 }
393 }
394
395 // Snap by distance between aligned cursor and intersections
396 for( const VECTOR2I& pt : points )
397 {
398 SEG::ecoord d_sq = ( pt - aligned ).SquaredEuclideanNorm();
399
400 if( d_sq < min_d_sq )
401 {
402 min_d_sq = d_sq;
403 nearest = pt;
404 }
405 }
406
407 return nearest;
408}
409
410
412{
413 VECTOR2I aligned = Align( aPoint );
414
415 if( !m_enableSnap )
416 return aligned;
417
418 std::vector<VECTOR2I> points;
419
420 aArc.IntersectLine( SEG( aligned, aligned + VECTOR2( 1, 0 ) ), &points );
421 aArc.IntersectLine( SEG( aligned, aligned + VECTOR2( 0, 1 ) ), &points );
422 aArc.IntersectLine( SEG( aligned, aligned + VECTOR2( 1, 1 ) ), &points );
423 aArc.IntersectLine( SEG( aligned, aligned + VECTOR2( 1, -1 ) ), &points );
424
425 VECTOR2I nearest = aligned;
427
428 // Snap by distance between pointer and endpoints
429 for( const VECTOR2I& pt : { aArc.GetP0(), aArc.GetP1() } )
430 {
431 SEG::ecoord d_sq = ( pt - aPoint ).SquaredEuclideanNorm();
432
433 if( d_sq < min_d_sq )
434 {
435 min_d_sq = d_sq;
436 nearest = pt;
437 }
438 }
439
440 // Snap by distance between aligned cursor and intersections
441 for( const VECTOR2I& pt : points )
442 {
443 SEG::ecoord d_sq = ( pt - aligned ).SquaredEuclideanNorm();
444
445 if( d_sq < min_d_sq )
446 {
447 min_d_sq = d_sq;
448 nearest = pt;
449 }
450 }
451
452 return nearest;
453}
454
455
456VECTOR2I PCB_GRID_HELPER::SnapToPad( const VECTOR2I& aMousePos, std::deque<PAD*>& aPads )
457{
458 wxLogTrace( traceSnap, "SnapToPad: mouse pos (%d, %d), pads count: %zu", aMousePos.x, aMousePos.y, aPads.size() );
459 clearAnchors();
460
461 for( BOARD_ITEM* item : aPads )
462 {
463 if( item->HitTest( aMousePos ) )
464 computeAnchors( item, aMousePos, true, nullptr );
465 }
466
467 double minDist = std::numeric_limits<double>::max();
468 ANCHOR* nearestOrigin = nullptr;
469
470 for( ANCHOR& a : m_anchors )
471 {
472 if( ( ORIGIN & a.flags ) != ORIGIN )
473 continue;
474
475 double dist = a.Distance( aMousePos );
476
477 if( dist < minDist )
478 {
479 minDist = dist;
480 nearestOrigin = &a;
481 }
482 }
483
484 return nearestOrigin ? nearestOrigin->pos : aMousePos;
485}
486
487
489{
490 // If the item being removed is involved in the snap, clear the snap item
491 if( m_snapItem )
492 {
493 for( EDA_ITEM* eda_item : m_snapItem->items )
494 {
495 if( eda_item->IsBOARD_ITEM() )
496 {
497 BOARD_ITEM* item = static_cast<BOARD_ITEM*>( eda_item );
498
499 if( item == aRemovedItem || item->GetParentFootprint() == aRemovedItem )
500 {
501 m_snapItem = std::nullopt;
502 break;
503 }
504 }
505 }
506 }
507}
508
509
510void PCB_GRID_HELPER::OnBoardItemsRemoved( BOARD& aBoard, std::vector<BOARD_ITEM*>& aBoardItems )
511{
512 // This is a bulk-remove. Simply clearing the snap item will be the most performant.
513 m_snapItem = std::nullopt;
514}
515
516
518{
519 if( aItem.Type() == PCB_FOOTPRINT_T )
520 return static_cast<const FOOTPRINT&>( aItem ).GetBoundingBox( false );
521
522 return aItem.GetBoundingBox();
523}
524
525
527{
528 if( !m_toolMgr )
529 return false;
530
531 // Keyed off the board rather than the current tool: PCB_TOOL_BASE lives in the pcbnew
532 // kiface, so casting to it from pcbcommon leaves cvpcb with an undefined typeinfo
533 const BOARD* board = static_cast<const BOARD*>( m_toolMgr->GetModel() );
534
535 return board && board->IsFootprintHolder();
536}
537
538
540{
542
543 if( !m_toolMgr )
544 return settings;
545
546 if( PCB_BASE_FRAME* frame = dynamic_cast<PCB_BASE_FRAME*>( m_toolMgr->GetToolHolder() ) )
547 {
549 {
550 if( FOOTPRINT_EDITOR_SETTINGS* cfg = frame->GetFootprintEditorSettings() )
551 settings = cfg->m_SnapInference;
552 }
553 else if( PCBNEW_SETTINGS* cfg = frame->GetPcbNewSettings() )
554 {
555 settings = cfg->m_SnapInference;
556 }
557 }
558 else if( PCBNEW_SETTINGS* cfg = dynamic_cast<PCBNEW_SETTINGS*>( m_toolMgr->GetSettings() ) )
559 {
560 // Headless callers have settings but no PCB frame.
561 settings = cfg->m_SnapInference;
562 }
563
564 return settings;
565}
566
567
568VECTOR2I PCB_GRID_HELPER::BestDragOrigin( const VECTOR2I& aMousePos, std::vector<BOARD_ITEM*>& aItems,
569 GRID_HELPER_GRIDS aGrid,
570 const PCB_SELECTION_FILTER_OPTIONS* aSelectionFilter )
571{
572 wxLogTrace( traceSnap, "BestDragOrigin: mouse pos (%d, %d), items count: %zu", aMousePos.x, aMousePos.y,
573 aItems.size() );
574 clearAnchors();
575
576 computeAnchors( aItems, aMousePos, true, aSelectionFilter, nullptr, true );
577
578 double lineSnapMinCornerDistance = m_toolMgr->GetView()->ToWorld( 50 );
579
580 ANCHOR* nearestOutline = nearestAnchor( aMousePos, OUTLINE );
581 ANCHOR* nearestCorner = nearestAnchor( aMousePos, CORNER );
582 ANCHOR* nearestOrigin = nearestAnchor( aMousePos, ORIGIN );
583 ANCHOR* best = nullptr;
584 double minDist = std::numeric_limits<double>::max();
585
586 if( nearestOrigin )
587 {
588 minDist = nearestOrigin->Distance( aMousePos );
589 best = nearestOrigin;
590
591 wxLogTrace( traceSnap, " nearest origin winning at (%d, %d), distance=%f", nearestOrigin->pos.x,
592 nearestOrigin->pos.y, minDist );
593 }
594
595 if( nearestCorner )
596 {
597 double dist = nearestCorner->Distance( aMousePos );
598
599 if( dist < minDist )
600 {
601 minDist = dist;
602 best = nearestCorner;
603
604 wxLogTrace( traceSnap, " nearest corner winning at (%d, %d), distance=%f", nearestCorner->pos.x,
605 nearestCorner->pos.y, dist );
606 }
607 }
608
609 if( nearestOutline )
610 {
611 double dist = nearestOutline->Distance( aMousePos );
612
613 if( minDist > lineSnapMinCornerDistance && dist < minDist )
614 {
615 best = nearestOutline;
616
617 wxLogTrace( traceSnap, " nearest outline winning at (%d, %d), distance=%f", nearestOutline->pos.x,
618 nearestOutline->pos.y, dist );
619 }
620 }
621
622 VECTOR2I ret = best ? best->pos : aMousePos;
623
624 if( best )
625 {
626 std::optional<BOX2I> movingBounds;
627
628 for( BOARD_ITEM* item : aItems )
629 {
630 if( !item )
631 continue;
632
633 if( movingBounds )
634 movingBounds->Merge( layoutBounds( *item ) );
635 else
636 movingBounds = layoutBounds( *item );
637 }
638
639 bool padCenter = ( best->pointTypes & POINT_TYPE::PT_CENTER )
640 && std::any_of( best->items.begin(), best->items.end(),
641 []( const EDA_ITEM* aItem )
642 {
643 return aItem && aItem->Type() == PCB_PAD_T;
644 } );
645
646 setLayoutReference( ret, movingBounds, padCenter );
647 }
648 else
649 {
650 setLayoutReference( ret, std::nullopt, false );
651 }
652
653 wxLogTrace( traceSnap, " have best: %s, returning (%d, %d)", best ? "yes" : "no", ret.x, ret.y );
654 return ret;
655}
656
657
659{
660 LSET layers;
661 std::vector<BOARD_ITEM*> item;
662
663 if( aReferenceItem )
664 {
665 layers = aReferenceItem->GetLayerSet();
666 item.push_back( aReferenceItem );
667 }
668 else if( PCB_BASE_FRAME* frame = dynamic_cast<PCB_BASE_FRAME*>( m_toolMgr->GetToolHolder() );
669 frame && frame->GetScreen() )
670 {
671 layers = LSET( { frame->GetActiveLayer() } );
672 }
673 else
674 {
675 layers = LSET::AllLayersMask();
676 }
677
678 return ResolveSnap( aOrigin, layers, aGrid, item );
679}
680
681
683 const std::vector<BOARD_ITEM*>& aSkip,
684 std::optional<VECTOR2I> aMovingReferencePoint )
685{
686 wxLogTrace( traceSnap, "ResolveSnap: origin (%d, %d), enableSnap=%d, enableGrid=%d, enableSnapLine=%d", aOrigin.x,
688
690 const double snapScale = ranges.scale;
691 const int snapRange = ranges.range;
692
693 const SNAP_INFERENCE_SETTINGS inferenceSettings = snapInferenceSettings();
694
695 const bool constructionEnabled =
697
698 if( !constructionEnabled )
700
701 //Respect limits of coordinates representation
702 const BOX2I visibilityHorizon =
703 BOX2ISafe( VECTOR2D( aOrigin ) - snapRange / 2.0, VECTOR2D( snapRange, snapRange ) );
704
705 clearAnchors();
706
707 const std::vector<BOARD_ITEM*> visibleItems = queryVisible( { visibilityHorizon }, aSkip );
708 computeAnchors( visibleItems, aOrigin, false, nullptr, &aLayers, false );
709
710 ANCHOR* nearest = nearestAnchor( aOrigin, SNAPPABLE );
711 VECTOR2I nearestGrid = Align( aOrigin, aGrid );
712 const VECTOR2D gridSize = GetGridSize( aGrid );
713
714 SNAP_SOURCE_CONTEXT context;
716 context.sourcePoint = aOrigin;
717 context.movingReferencePoint = aMovingReferencePoint;
719
720 for( BOARD_ITEM* item : aSkip )
721 {
722 if( !item )
723 continue;
724
725 if( context.movingBounds )
726 context.movingBounds->Merge( layoutBounds( *item ) );
727 else
728 context.movingBounds = layoutBounds( *item );
729 }
730
731 if( aSkip.size() == 1 && aSkip.front() )
732 {
733 BOARD_ITEM* sourceItem = aSkip.front();
735 std::optional<std::pair<VECTOR2I, VECTOR2I>> endpoints;
736
739
740 if( sourceItem->Type() == PCB_TRACE_T )
741 {
742 PCB_TRACK* track = static_cast<PCB_TRACK*>( sourceItem );
743 endpoints = std::pair( track->GetStart(), track->GetEnd() );
744 }
745 else if( sourceItem->Type() == PCB_SHAPE_T )
746 {
747 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( sourceItem );
748
749 if( shape->GetShape() == SHAPE_T::SEGMENT )
750 endpoints = std::pair( shape->GetStart(), shape->GetEnd() );
751 }
752
753 if( endpoints && m_pointEditProfile )
754 {
755 context.stationarySourceLeg =
756 endpoints->first.SquaredDistance( aOrigin ) > endpoints->second.SquaredDistance( aOrigin )
757 ? endpoints->first
758 : endpoints->second;
759 }
760 }
761
762 SNAP_INFERENCE_PROVIDER inferenceProvider;
763 const bool inferenceEnabled = m_enableSnap
764 && ( inferenceSettings.objectGeometry || inferenceSettings.tangentNormal
765 || inferenceSettings.alignmentDistribution );
766 const bool geometryEnabled =
767 m_enableSnap && ( inferenceSettings.objectGeometry || inferenceSettings.tangentNormal );
768
769 if( geometryEnabled && context.movingItem )
770 {
771 for( size_t i = 0; i < m_stationarySelfSegments.size(); ++i )
772 {
773 SNAP_STABLE_ID id =
774 MakeDerivedSnapId( SNAP_ID_KIND::SELF_SEGMENT, *context.movingItem, static_cast<int>( i ) );
775 context.stationarySelfFeatures.push_back( id );
776 inferenceProvider.AddPath( { id, m_stationarySelfSegments[i], false } );
777 }
778 }
779
780 if( inferenceEnabled )
781 {
782 const auto eligibleInferenceTarget = [&]( BOARD_ITEM* aItem )
783 {
784 if( !m_magneticSettings->allLayers && !( aLayers & aItem->GetLayerSet() ).any() )
785 {
786 return false;
787 }
788
789 switch( aItem->Type() )
790 {
791 case PCB_TRACE_T:
793
795
796 default: return m_magneticSettings->graphics;
797 }
798 };
799
800 for( BOARD_ITEM* item : visibleItems )
801 {
802 if( !eligibleInferenceTarget( item ) )
803 continue;
804
805 if( !geometryEnabled )
806 continue;
807
808 std::optional<INTERSECTABLE_GEOM> geometry = GetBoardIntersectable( *item );
809
810 if( !geometry )
811 continue;
812
813 inferenceProvider.AddPath( { { SNAP_ID_KIND::ITEM_GEOMETRY, SnapTargetId( item->m_Uuid ),
814 static_cast<int>( item->Type() ), 0 },
815 std::move( *geometry ),
816 false } );
817 }
818
819 if( inferenceSettings.alignmentDistribution && context.movingBounds )
820 {
821 std::vector<BOARD_ITEM*> layoutItems;
822 const BOX2I viewport = BOX2ISafe( m_toolMgr->GetView()->GetViewport() );
823 BOX2I movingBounds = *context.movingBounds;
824
825 if( context.movingReferencePoint )
826 movingBounds.Offset( context.sourcePoint - *context.movingReferencePoint );
827
828 // Alignment ignores separation along its guide; equal spacing only requires overlap
829 // perpendicular to its axis. Their exact query closure is therefore a cross.
830 BOX2I verticalStrip =
831 BOX2ISafe( VECTOR2D( static_cast<double>( movingBounds.GetLeft() ) - snapRange, viewport.GetTop() ),
832 VECTOR2D( movingBounds.GetWidth() + 2.0 * snapRange, viewport.GetHeight() ) );
833 BOX2I horizontalStrip =
834 BOX2ISafe( VECTOR2D( viewport.GetLeft(), static_cast<double>( movingBounds.GetTop() ) - snapRange ),
835 VECTOR2D( viewport.GetWidth(), movingBounds.GetHeight() + 2.0 * snapRange ) );
836 verticalStrip = verticalStrip.Intersect( viewport );
837 horizontalStrip = horizontalStrip.Intersect( viewport );
838 std::vector<BOARD_ITEM*> visibleLayoutItems = queryVisible( { verticalStrip, horizontalStrip }, aSkip );
839
840 const bool insideFootprint = editingInsideFootprint();
841
842 for( BOARD_ITEM* item : visibleLayoutItems )
843 {
844 FOOTPRINT* footprint = item->GetParentFootprint();
845
846 if( footprint && !insideFootprint )
847 layoutItems.push_back( footprint );
848 else
849 layoutItems.push_back( item );
850 }
851
852 std::sort( layoutItems.begin(), layoutItems.end(), std::less<>() );
853 layoutItems.erase( std::unique( layoutItems.begin(), layoutItems.end() ), layoutItems.end() );
854
855 // Moving items and their containers. A container's bounds enclose what is being
856 // moved, and pads reached through their footprint bypass the queryVisible skip list.
857 std::unordered_set<BOARD_ITEM*> moving( aSkip.begin(), aSkip.end() );
858
859 for( BOARD_ITEM* item : aSkip )
860 {
861 for( FOOTPRINT* parent = item ? item->GetParentFootprint() : nullptr; parent;
862 parent = parent->GetParentFootprint() )
863 {
864 moving.insert( parent );
865 }
866 }
867
868 for( BOARD_ITEM* item : layoutItems )
869 {
870 // Aligning to a container of the move would align the move to itself. The
871 // container's other children remain valid targets.
872 if( !eligibleInferenceTarget( item ) || moving.count( item ) )
873 continue;
874
875 std::optional<SNAP_TARGET_ID> parent;
876
877 if( item->GetParent() )
878 parent = SnapTargetId( item->GetParent()->m_Uuid );
879
880 inferenceProvider.AddBounds( { { SNAP_ID_KIND::ITEM_GEOMETRY, SnapTargetId( item->m_Uuid ),
881 static_cast<int>( item->Type() ), 0 },
882 layoutBounds( *item ),
883 std::move( parent ) } );
884 }
885
886 size_t padCenters = 0;
887
888 const auto addPadCenter = [&]( PAD* aPad )
889 {
890 if( !eligibleInferenceTarget( aPad ) || moving.count( aPad ) )
891 return;
892
893 std::optional<SNAP_TARGET_ID> parent;
894
895 if( FOOTPRINT* footprint = aPad->GetParentFootprint() )
896 parent = SnapTargetId( footprint->m_Uuid );
897
898 inferenceProvider.AddAlignmentPoint( { { SNAP_ID_KIND::INTRINSIC_ANCHOR, SnapTargetId( aPad->m_Uuid ) },
899 aPad->GetPosition(),
900 std::move( parent ) } );
901 ++padCenters;
902 };
903
904 for( BOARD_ITEM* item : layoutItems )
905 {
906 if( item->Type() == PCB_PAD_T )
907 {
908 addPadCenter( static_cast<PAD*>( item ) );
909 }
910 else if( item->Type() == PCB_FOOTPRINT_T )
911 {
912 for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
913 addPadCenter( pad );
914 }
915 }
916
917 wxLogTrace( wxT( "KICAD_SNAP_RESOLVER" ), "layout targets=%zu pad-centers=%zu", layoutItems.size(),
918 padCenters );
919 }
920 }
921
922 enum class PRESENTATION_KIND
923 {
924 ANCHOR_MARKER,
925 GUIDE,
926 POINT_ON_ELEMENT
927 };
928
929 struct PRESENTATION
930 {
931 PRESENTATION_KIND kind;
932 std::optional<ANCHOR> anchor;
933 bool proposeConstruction = false;
934 };
935
937 frame.context = context;
941 frame.trace = snapTraceCallback( context );
942 emitAngleBranchCandidates( frame.candidates, aOrigin, snapScale );
943
944 if( m_enableSnap && inferenceSettings.objectGeometry )
945 {
946 for( SNAP_CANDIDATE& candidate : inferenceProvider.CollectObjectGeometry( context, snapRange ) )
947 frame.candidates.push_back( std::move( candidate ) );
948 }
949
950 if( m_enableSnap && inferenceSettings.tangentNormal && context.stationarySourceLeg )
951 {
952 for( SNAP_CANDIDATE& candidate : inferenceProvider.CollectTangentNormal( context, snapRange, true, true ) )
953 frame.candidates.push_back( std::move( candidate ) );
954 }
955
956 if( m_enableSnap && inferenceSettings.alignmentDistribution && context.movingBounds )
957 {
958 std::vector<SNAP_CANDIDATE> alignment = inferenceProvider.CollectAlignment( context, snapRange );
959 std::vector<SNAP_CANDIDATE> spacing = inferenceProvider.CollectEqualSpacing( context, snapRange );
960
961 wxLogTrace( wxT( "KICAD_SNAP_RESOLVER" ), "layout candidates alignment=%zu spacing=%zu", alignment.size(),
962 spacing.size() );
963
964 for( SNAP_CANDIDATE& candidate : alignment )
965 frame.candidates.push_back( std::move( candidate ) );
966
967 for( SNAP_CANDIDATE& candidate : spacing )
968 frame.candidates.push_back( std::move( candidate ) );
969 }
970
971 emitSelfAndGridCandidates( frame.candidates, context, aOrigin, nearestGrid, snapScale, snapRange, m_enableGrid );
972
973 const int snapIn = ranges.in;
974 const int snapOut = ranges.out;
975
976 wxLogTrace( traceSnap, " snapRange=%d, snapIn=%d, snapOut=%d", snapRange, snapIn, snapOut );
977 wxLogTrace( traceSnap, " visibleItems count=%zu, anchors count=%zu", visibleItems.size(), m_anchors.size() );
978 wxLogTrace( traceSnap, " nearest anchor: %s at (%d, %d), distance=%f", nearest ? "found" : "none",
979 nearest ? nearest->pos.x : 0, nearest ? nearest->pos.y : 0,
980 nearest ? nearest->Distance( aOrigin ) : -1.0 );
981 wxLogTrace( traceSnap, " nearestGrid: (%d, %d)", nearestGrid.x, nearestGrid.y );
982
984 {
985 ad->ClearAnchors();
986
987 for( const ANCHOR& anchor : m_anchors )
988 ad->AddAnchor( anchor.pos );
989
990 ad->SetNearest( nearest ? OPT_VECTOR2I{ nearest->pos } : std::nullopt );
991 m_toolMgr->GetView()->Update( ad, KIGFX::GEOMETRY );
992 }
993
994 // The distance to the nearest snap point, if any
995 std::optional<int> snapDist;
996
997 if( nearest )
998 snapDist = nearest->Distance( aOrigin );
999
1000 if( m_snapItem )
1001 {
1002 int existingDist = m_snapItem->Distance( aOrigin );
1003 if( !snapDist || existingDist < *snapDist )
1004 snapDist = existingDist;
1005 }
1006
1007 wxLogTrace( traceSnap, " snapDist: %s (value=%d)", snapDist ? "set" : "none", snapDist ? *snapDist : -1 );
1008 wxLogTrace( traceSnap, " m_snapItem: %s", m_snapItem ? "exists" : "none" );
1009
1010 showConstructionGeometry( constructionEnabled );
1011
1012 SNAP_MANAGER& snapManager = getSnapManager();
1013 SNAP_LINE_MANAGER& snapLineManager = snapManager.GetSnapLineManager();
1014
1015 const auto ptIsReferenceOnly = [&]( const VECTOR2I& aPt )
1016 {
1017 const std::vector<VECTOR2I>& referenceOnlyPoints = snapManager.GetReferenceOnlyPoints();
1018 return std::find( referenceOnlyPoints.begin(), referenceOnlyPoints.end(), aPt ) != referenceOnlyPoints.end();
1019 };
1020
1021 const auto proposeConstructionForItems = [&]( const std::vector<EDA_ITEM*>& aItems )
1022 {
1023 // Add any involved item as a temporary construction item
1024 // (de-duplication with existing construction items is handled later)
1025 std::vector<BOARD_ITEM*> items;
1026
1027 for( EDA_ITEM* item : aItems )
1028 {
1029 if( !item->IsBOARD_ITEM() )
1030 continue;
1031
1032 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
1033
1034 // Null items are allowed to arrive here as they represent geometry that isn't
1035 // specifically tied to a board item. For example snap lines from some
1036 // other anchor.
1037 // But they don't produce new construction items.
1038 if( boardItem )
1039 {
1040 if( m_magneticSettings->allLayers || ( ( aLayers & boardItem->GetLayerSet() ).any() ) )
1041 items.push_back( boardItem );
1042 }
1043 }
1044
1045 // Temporary construction items are not persistent and don't
1046 // overlay the items themselves (as the items will not be moved)
1047 if( constructionEnabled )
1048 AddConstructionItems( items, true, false );
1049 };
1050
1051 const auto anchorId = [&]( const ANCHOR& aAnchor )
1052 {
1053 std::vector<SNAP_TARGET_ID> targets;
1054
1055 for( const EDA_ITEM* item : aAnchor.items )
1056 {
1057 if( item )
1058 targets.push_back( SnapTargetId( item->m_Uuid ) );
1059 }
1060
1061 SNAP_ID_KIND kind =
1063 SNAP_STABLE_ID pointId = MakePointSnapId( kind, aAnchor.pos, aAnchor.pointTypes );
1064
1065 if( targets.empty() )
1066 return pointId;
1067
1068 targets.push_back( pointId.target );
1069 return MakeCompositeSnapId( kind, targets, aAnchor.pointTypes );
1070 };
1071
1072 const auto addAnchorCandidate = [&]( const ANCHOR& aAnchor, bool aRetained )
1073 {
1074 SNAP_STABLE_ID id = anchorId( aAnchor );
1075
1076 if( frame.presentation.contains( id ) )
1077 {
1078 if( aRetained )
1079 frame.retainedId = id;
1080
1081 return;
1082 }
1083
1084 const bool constructed = aAnchor.flags & CONSTRUCTED;
1088 aAnchor.pos, aAnchor.Distance( aOrigin ) / snapScale ) );
1089 frame.presentation.emplace(
1090 id, PRESENTATION{ PRESENTATION_KIND::ANCHOR_MARKER, aAnchor, !aRetained && !constructed } );
1091
1092 if( aRetained )
1093 frame.retainedId = id;
1094 };
1095
1096 // Hover activation, snap-line suppression and anchor acceptance are all the same question.
1097 const bool nearestCaptured = nearest && nearest->Distance( aOrigin ) <= snapIn;
1098 bool keepConstructionProposal = false;
1099 bool allowHoverActivation = false;
1100
1101 if( m_enableSnap )
1102 {
1103 wxLogTrace( traceSnap, " Snap enabled, checking snap options..." );
1104 allowHoverActivation = !nearestCaptured;
1105
1106 if( m_enableSnapLine )
1107 {
1108 wxLogTrace( traceSnap, " Checking snap lines..." );
1109
1110 OPT_VECTOR2I snapLineSnap = snapLineManager.GetNearestSnapLinePoint( aOrigin, nearestGrid, snapDist,
1111 snapRange, gridSize, GetOrigin() );
1112
1113 if( !snapLineSnap && constructionEnabled )
1114 {
1115 std::optional<VECTOR2I> constructionSnap =
1116 SnapToConstructionLines( aOrigin, nearestGrid, gridSize, snapRange );
1117
1118 if( constructionSnap )
1119 snapLineSnap = *constructionSnap;
1120 }
1121
1122 if( snapLineSnap && m_skipPoint != *snapLineSnap )
1123 {
1124 wxLogTrace( traceSnap, " Snap line found at (%d, %d)", snapLineSnap->x, snapLineSnap->y );
1125
1126 if( !nearestCaptured )
1127 {
1128 if( !ptIsReferenceOnly( *snapLineSnap ) )
1129 {
1131 frame.candidates.push_back( SNAP_CANDIDATE::Point(
1133 *snapLineSnap, snapLineSnap->Distance( aOrigin ) / snapScale ) );
1134 frame.presentation.emplace( id, PRESENTATION{ PRESENTATION_KIND::GUIDE, std::nullopt, false } );
1135 }
1136 else
1137 {
1138 wxLogTrace( traceSnap, " Snap line point is reference-only, continuing..." );
1139 keepConstructionProposal = true;
1140 }
1141 }
1142 }
1143 }
1144
1145 if( m_snapItem )
1146 {
1147 int dist = m_snapItem->Distance( aOrigin );
1148
1149 wxLogTrace( traceSnap, " Checking existing m_snapItem, dist=%d (snapOut=%d)", dist, snapOut );
1150
1151 if( dist <= snapOut && !ptIsReferenceOnly( m_snapItem->pos ) )
1152 {
1153 if( nearest && ptIsReferenceOnly( nearest->pos ) && nearest->Distance( aOrigin ) <= snapRange )
1154 snapLineManager.SetSnapLineOrigin( nearest->pos );
1155
1156 addAnchorCandidate( *m_snapItem, true );
1157 }
1158 }
1159
1160 if( nearestCaptured )
1161 {
1162 wxLogTrace( traceSnap, " Nearest anchor within snapIn range" );
1163
1164 if( ptIsReferenceOnly( nearest->pos ) )
1165 {
1166 wxLogTrace( traceSnap, " Nearest anchor is reference-only, setting snap line origin" );
1167 snapLineManager.SetSnapLineOrigin( nearest->pos );
1168 keepConstructionProposal = true;
1169 }
1170 else
1171 {
1172 addAnchorCandidate( *nearest, false );
1173 }
1174 }
1175
1176 if( !m_enableGrid )
1177 {
1178 wxLogTrace( traceSnap, " Grid disabled, checking point-on-element snap..." );
1179
1180 OPT_VECTOR2I nearestPointOnAnElement = GetNearestPoint( m_pointOnLineCandidates, aOrigin );
1181
1182 if( nearestPointOnAnElement && nearestPointOnAnElement->Distance( aOrigin ) <= snapRange )
1183 {
1184 SNAP_STABLE_ID id = MakePointSnapId( SNAP_ID_KIND::ITEM_GEOMETRY, *nearestPointOnAnElement );
1185 frame.candidates.push_back( SNAP_CANDIDATE::Point(
1187 *nearestPointOnAnElement, nearestPointOnAnElement->Distance( aOrigin ) / snapScale ) );
1188 frame.presentation.emplace( id,
1189 PRESENTATION{ PRESENTATION_KIND::POINT_ON_ELEMENT, std::nullopt, false } );
1190 }
1191 }
1192 }
1193
1194 // Object retention wins because its tier already outranks angle restriction.
1195 if( !frame.retainedId && m_retainedAngleBranch )
1197
1198 SNAP_FRAME_OUTPUT<PRESENTATION> output = ResolveSnapFrame( std::move( frame ) );
1199 SNAP_RESULT& result = output.result;
1201
1202 m_snapItem = std::nullopt;
1203 snapLineManager.SetSnapLineEnd( std::nullopt );
1204 bool suppressHoverActivation = false;
1205
1206 if( output.presentation )
1207 {
1208 const PRESENTATION& presentation = output.presentation->payload;
1209 keepConstructionProposal = true;
1210
1211 if( presentation.kind == PRESENTATION_KIND::ANCHOR_MARKER && presentation.anchor )
1212 {
1213 suppressHoverActivation = true;
1214 m_snapItem = *presentation.anchor;
1215 snapLineManager.SetSnappedAnchor( m_snapItem->pos );
1216 updateSnapPoint( { m_snapItem->pos, m_snapItem->pointTypes } );
1217
1218 if( presentation.proposeConstruction )
1219 proposeConstructionForItems( m_snapItem->items );
1220 }
1221 else if( presentation.kind == PRESENTATION_KIND::GUIDE )
1222 {
1223 suppressHoverActivation = true;
1224 snapLineManager.SetSnapLineEnd( result.position );
1225 m_viewSnapPoint.SetSnapTypes( POINT_TYPE::PT_NONE );
1226 m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, false );
1227 }
1228 else if( presentation.kind == PRESENTATION_KIND::POINT_ON_ELEMENT )
1229 {
1231 }
1232 }
1233 else
1234 {
1235 m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, false );
1236 }
1237
1239
1240 static const bool canActivateByHitTest = ADVANCED_CFG::GetCfg().m_ExtensionSnapActivateOnHover;
1241
1242 if( constructionEnabled && canActivateByHitTest && allowHoverActivation && !suppressHoverActivation )
1243 {
1244 for( BOARD_ITEM* item : visibleItems )
1245 {
1246 if( item->HitTest( aOrigin, 0 ) )
1247 {
1248 proposeConstructionForItems( { item } );
1249 keepConstructionProposal = true;
1250 break;
1251 }
1252 }
1253 }
1254
1255 if( !keepConstructionProposal )
1256 snapManager.GetConstructionManager().CancelProposal();
1257
1258 return result;
1259}
1260
1261
1263{
1264 if( !m_snapItem )
1265 return nullptr;
1266
1267 // The snap anchor doesn't have an item associated with it
1268 // (odd, could it be entirely made of construction geometry?)
1269 if( m_snapItem->items.empty() )
1270 return nullptr;
1271
1272 return static_cast<BOARD_ITEM*>( m_snapItem->items[0] );
1273}
1274
1275
1277{
1278 m_snapItem = std::nullopt;
1281 manager.ClearSnapLine();
1282 m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, false );
1283}
1284
1285
1287{
1288 if( !aItem )
1289 return GRID_CURRENT;
1290
1291 switch( aItem->Type() )
1292 {
1293 case PCB_FOOTPRINT_T:
1294 case PCB_PAD_T:
1295 return GRID_CONNECTABLE;
1296
1297 case PCB_TEXT_T:
1298 case PCB_FIELD_T:
1299 return GRID_TEXT;
1300
1301 case PCB_SHAPE_T:
1302 case PCB_DIMENSION_T:
1304 case PCB_TEXTBOX_T:
1305 case PCB_BARCODE_T:
1306 return GRID_GRAPHICS;
1307
1308 case PCB_TRACE_T:
1309 case PCB_ARC_T:
1310 return GRID_WIRES;
1311
1312 case PCB_VIA_T:
1313 return GRID_VIAS;
1314
1315 default:
1316 return GRID_CURRENT;
1317 }
1318}
1319
1320
1322{
1323 const GRID_SETTINGS& grid = m_toolMgr->GetSettings()->m_Window.grid;
1324 int idx = -1;
1325
1326 VECTOR2D g = m_toolMgr->GetView()->GetGAL()->GetGridSize();
1327
1328 if( !grid.overrides_enabled )
1329 return g;
1330
1331 switch( aGrid )
1332 {
1333 case GRID_CONNECTABLE:
1334 if( grid.override_connected )
1335 idx = grid.override_connected_idx;
1336
1337 break;
1338
1339 case GRID_WIRES:
1340 if( grid.override_wires )
1341 idx = grid.override_wires_idx;
1342
1343 break;
1344
1345 case GRID_VIAS:
1346 if( grid.override_vias )
1347 idx = grid.override_vias_idx;
1348
1349 break;
1350
1351 case GRID_TEXT:
1352 if( grid.override_text )
1353 idx = grid.override_text_idx;
1354
1355 break;
1356
1357 case GRID_GRAPHICS:
1358 if( grid.override_graphics )
1359 idx = grid.override_graphics_idx;
1360
1361 break;
1362
1363 default:
1364 break;
1365 }
1366
1367 if( idx >= 0 && idx < (int) grid.grids.size() )
1368 g = grid.grids[idx].ToDouble( pcbIUScale );
1369
1370 return g;
1371}
1372
1373
1374std::vector<BOARD_ITEM*> PCB_GRID_HELPER::queryVisible( std::initializer_list<BOX2I> aAreas,
1375 const std::vector<BOARD_ITEM*>& aSkip ) const
1376{
1377 std::vector<BOARD_ITEM*> items;
1378 std::vector<KIGFX::VIEW::LAYER_ITEM_PAIR> visibleItems;
1379
1380 const bool inFootprintEditor = editingInsideFootprint();
1381 KIGFX::VIEW* view = m_toolMgr->GetView();
1382 RENDER_SETTINGS* settings = view->GetPainter()->GetSettings();
1383 const std::set<int>& activeLayers = settings->GetHighContrastLayers();
1384 bool isHighContrast = settings->GetHighContrast();
1385
1386 for( const BOX2I& area : aAreas )
1387 {
1388 if( area.GetWidth() > 0 && area.GetHeight() > 0 )
1389 view->Query( area, visibleItems );
1390 }
1391
1392 for( const auto& [viewItem, layer] : visibleItems )
1393 {
1394 if( !viewItem->IsBOARD_ITEM() )
1395 continue;
1396
1397 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( viewItem );
1398
1399 if( inFootprintEditor )
1400 {
1401 // If we are in the footprint editor, don't use the footprint itself
1402 if( boardItem->Type() == PCB_FOOTPRINT_T )
1403 continue;
1404 }
1405 else
1406 {
1407 // If we are not in the footprint editor, don't use footprint-editor-private items
1408 if( FOOTPRINT* parentFP = boardItem->GetParentFootprint() )
1409 {
1410 if( IsPcbLayer( layer ) && parentFP->GetPrivateLayers().test( layer ) )
1411 continue;
1412 }
1413 }
1414
1415 // The boardItem must be visible and on an active layer
1416 if( view->IsVisible( boardItem ) && ( !isHighContrast || activeLayers.count( layer ) )
1417 && boardItem->ViewGetLOD( layer, view ) < view->GetScale() )
1418 {
1419 items.push_back( boardItem );
1420 }
1421 }
1422
1423 std::sort( items.begin(), items.end(), std::less<>() );
1424 items.erase( std::unique( items.begin(), items.end() ), items.end() );
1425
1426 std::unordered_set<BOARD_ITEM*> skippedItems;
1427
1428 for( BOARD_ITEM* item : aSkip )
1429 {
1430 if( !item )
1431 continue;
1432
1433 skippedItems.insert( item );
1434 item->RunOnChildren(
1435 [&]( BOARD_ITEM* aChild )
1436 {
1437 skippedItems.insert( aChild );
1438 },
1440 }
1441
1442 items.erase( std::remove_if( items.begin(), items.end(),
1443 [&]( BOARD_ITEM* aItem )
1444 {
1445 return skippedItems.contains( aItem );
1446 } ),
1447 items.end() );
1448
1449 return items;
1450}
1451
1452
1454{
1457
1458 // Clang wants this constructor
1460 Item( aItem ),
1461 Geometry( std::move( aSeg ) )
1462 {
1463 }
1464};
1465
1466
1467void PCB_GRID_HELPER::computeAnchors( const std::vector<BOARD_ITEM*>& aItems, const VECTOR2I& aRefPos, bool aFrom,
1468 const PCB_SELECTION_FILTER_OPTIONS* aSelectionFilter, const LSET* aMatchLayers,
1469 bool aForDrag )
1470{
1471 std::vector<PCB_INTERSECTABLE> intersectables;
1472 intersectables.reserve( aItems.size() );
1473
1474 // These could come from a more granular snap mode filter
1475 // But when looking for drag points, we don't want construction geometry
1476 const bool computeIntersections = !aForDrag;
1477 const bool computePointsOnElements = !aForDrag;
1478 const bool excludeGraphics = aSelectionFilter && !aSelectionFilter->graphics;
1479 const bool excludeTracks = aSelectionFilter && !aSelectionFilter->tracks;
1480
1481 const auto itemIsSnappable =
1482 [&]( const BOARD_ITEM& aItem )
1483 {
1484 // If we are filtering by layers, check if the item matches
1485 if( aMatchLayers )
1486 return m_magneticSettings->allLayers || ( ( *aMatchLayers & aItem.GetLayerSet() ).any() );
1487
1488 return true;
1489 };
1490
1491 const auto processItem =
1492 [&]( BOARD_ITEM& item )
1493 {
1494 // Don't even process the item if it doesn't match the layers
1495 if( !itemIsSnappable( item ) )
1496 return;
1497
1498 // First, add all the key points of the item itself
1499 computeAnchors( &item, aRefPos, aFrom, aSelectionFilter );
1500
1501 // If we are computing intersections, construct the relevant intersectables
1502 // Points on elements also use the intersectables.
1503 if( computeIntersections || computePointsOnElements )
1504 {
1505 std::optional<INTERSECTABLE_GEOM> intersectableGeom;
1506
1507 if( !excludeGraphics
1508 && ( item.Type() == PCB_SHAPE_T || item.Type() == PCB_REFERENCE_IMAGE_T ) )
1509 {
1510 intersectableGeom = GetBoardIntersectable( item );
1511 }
1512 else if( !excludeTracks && ( item.Type() == PCB_TRACE_T || item.Type() == PCB_ARC_T ) )
1513 {
1514 intersectableGeom = GetBoardIntersectable( item );
1515 }
1516
1517 if( intersectableGeom )
1518 intersectables.emplace_back( &item, *intersectableGeom );
1519 }
1520 };
1521
1522 for( BOARD_ITEM* item : aItems )
1523 {
1524 processItem( *item );
1525 }
1526
1527 for( const CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH& batch : getSnapManager().GetConstructionItems() )
1528 {
1529 for( const CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM& constructionItem : batch )
1530 {
1531 BOARD_ITEM* involvedItem = static_cast<BOARD_ITEM*>( constructionItem.Item );
1532
1533 for( const CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM::DRAWABLE_ENTRY& drawable : constructionItem.Constructions )
1534 {
1535 std::visit(
1536 [&]( const auto& visited )
1537 {
1538 using ItemType = std::decay_t<decltype( visited )>;
1539
1540 if constexpr( std::is_same_v<ItemType, LINE>
1541 || std::is_same_v<ItemType, CIRCLE>
1542 || std::is_same_v<ItemType, HALF_LINE>
1543 || std::is_same_v<ItemType, SHAPE_ARC> )
1544 {
1545 intersectables.emplace_back( involvedItem, visited );
1546 }
1547 else if constexpr( std::is_same_v<ItemType, VECTOR2I> )
1548 {
1549 // Add any free-floating points as snap points.
1550 addAnchor( visited, SNAPPABLE | CONSTRUCTED, involvedItem, POINT_TYPE::PT_NONE );
1551 }
1552 },
1553 drawable.Drawable );
1554 }
1555 }
1556 }
1557
1558 // Now, add all the intersections between the items
1559 // This is obviously quadratic, so performance may be a concern for large selections
1560 // But, so far up to ~20k comparisons seems not to be an issue with run times in the ms range
1561 // and it's usually only a handful of items.
1562
1563 if( computeIntersections )
1564 {
1565 for( std::size_t ii = 0; ii < intersectables.size(); ++ii )
1566 {
1567 const PCB_INTERSECTABLE& intersectableA = intersectables[ii];
1568
1569 for( std::size_t jj = ii + 1; jj < intersectables.size(); ++jj )
1570 {
1571 const PCB_INTERSECTABLE& intersectableB = intersectables[jj];
1572
1573 // An item and its own extension will often have intersections (as they are on top of each other),
1574 // but they not useful points to snap to
1575 if( intersectableA.Item == intersectableB.Item )
1576 continue;
1577
1578 std::vector<VECTOR2I> intersections;
1579 const INTERSECTION_VISITOR visitor{ intersectableA.Geometry, intersections };
1580
1581 std::visit( visitor, intersectableB.Geometry );
1582
1583 // For each intersection, add an intersection snap anchor
1584 for( const VECTOR2I& intersection : intersections )
1585 {
1586 std::vector<EDA_ITEM*> items = {
1587 intersectableA.Item,
1588 intersectableB.Item,
1589 };
1590 addAnchor( intersection, SNAPPABLE | CONSTRUCTED, std::move( items ),
1592 }
1593 }
1594 }
1595 }
1596
1597 // The intersectables can also be used for fall-back snapping to "point on line"
1598 // snaps if no other snap is found
1600
1601 if( computePointsOnElements )
1602 {
1603 // For the moment, it's trivial to make a NEARABLE from an INTERSECTABLE,
1604 // because all INTERSECTABLEs are also NEARABLEs.
1605 for( const PCB_INTERSECTABLE& intersectable : intersectables )
1606 {
1607 std::visit(
1608 [&]( const auto& geom )
1609 {
1610 NEARABLE_GEOM nearable( geom );
1611 m_pointOnLineCandidates.emplace_back( nearable );
1612 },
1613 intersectable.Geometry );
1614 }
1615 }
1616}
1617
1618
1619// Padstacks report a set of "unique" layers, which may each represent one or more
1620// "real" layers. This function takes a unique layer and checks if it applies to the
1621// given "real" layer.
1622static bool PadstackUniqueLayerAppliesToLayer( const PADSTACK& aPadStack, PCB_LAYER_ID aPadstackUniqueLayer,
1623 const PCB_LAYER_ID aRealLayer )
1624{
1625 switch( aPadStack.Mode() )
1626 {
1628 {
1629 // Normal mode padstacks are the same on every layer, so they'll apply to any
1630 // "real" copper layer.
1631 return IsCopperLayer( aRealLayer );
1632 }
1634 {
1635 switch( aPadstackUniqueLayer )
1636 {
1637 case F_Cu:
1638 case B_Cu:
1639 // The outer-layer uhique layers only apply to those exact "real" layers
1640 return aPadstackUniqueLayer == aRealLayer;
1642 // But the inner layers apply to any inner layer
1643 return IsInnerCopperLayer( aRealLayer );
1644 default:
1645 wxFAIL_MSG( wxString::Format( "Unexpected padstack unique layer %d in FRONT_INNER_BACK mode",
1646 aPadstackUniqueLayer ) );
1647 break;
1648 }
1649 break;
1650 }
1652 {
1653 // Custom modes are unique per layer, so it's 1:1
1654 return aRealLayer == aPadstackUniqueLayer;
1655 }
1656 }
1657
1658 return false;
1659};
1660
1661
1662std::vector<PCB_GRID_HELPER::ANCHOR_SPEC> PCB_GRID_HELPER::GetArcAnchors( const PCB_ARC& aArc,
1663 bool aFrom )
1664{
1665 std::vector<ANCHOR_SPEC> anchors;
1666
1667 // The stored midpoint is grid-aligned when the arc is; expose it alongside the endpoints so
1668 // BestDragOrigin picks a grid-aligned corner as the drag/paste reference.
1669 anchors.push_back( { aArc.GetMid(), CORNER | SNAPPABLE, POINT_TYPE::PT_MID } );
1670
1671 // The derived geometric center is rarely grid-aligned. It stays available as a drag origin for
1672 // other items (aFrom=false) but is never offered as this arc's own origin, which was the cause
1673 // of pasted arcs landing off grid.
1674 if( !aFrom )
1675 anchors.push_back( { aArc.GetCenter(), ORIGIN, POINT_TYPE::PT_CENTER } );
1676
1677 return anchors;
1678}
1679
1680
1681void PCB_GRID_HELPER::computeAnchors( BOARD_ITEM* aItem, const VECTOR2I& aRefPos, bool aFrom,
1682 const PCB_SELECTION_FILTER_OPTIONS* aSelectionFilter )
1683{
1684 KIGFX::VIEW* view = m_toolMgr->GetView();
1685 RENDER_SETTINGS* settings = view->GetPainter()->GetSettings();
1686 const std::set<int>& activeLayers = settings->GetHighContrastLayers();
1687 const PCB_LAYER_ID activeHighContrastPrimaryLayer = settings->GetPrimaryHighContrastLayer();
1688 bool isHighContrast = settings->GetHighContrast();
1689
1690 const auto checkVisibility =
1691 [&]( const BOARD_ITEM* item )
1692 {
1693 // New moved items don't yet have view flags so VIEW will call them invisible
1694 if( !view->IsVisible( item ) && !item->IsMoving() )
1695 return false;
1696
1697 bool onActiveLayer = !isHighContrast;
1698 bool isLODVisible = false;
1699
1700 for( PCB_LAYER_ID layer : item->GetLayerSet() )
1701 {
1702 if( !onActiveLayer && activeLayers.count( layer ) )
1703 onActiveLayer = true;
1704
1705 if( !isLODVisible && item->ViewGetLOD( layer, view ) < view->GetScale() )
1706 isLODVisible = true;
1707
1708 if( onActiveLayer && isLODVisible )
1709 return true;
1710 }
1711
1712 return false;
1713 };
1714
1715 // As defaults, these are probably reasonable to avoid spamming key points
1716 const KIGEOM::OVAL_KEY_POINT_FLAGS ovalKeyPointFlags = KIGEOM::OVAL_CENTER
1720
1721 auto handlePadShape =
1722 [&]( PAD* aPad, PCB_LAYER_ID aLayer )
1723 {
1725
1727 if( aFrom )
1728 return;
1729
1730 switch( aPad->GetShape( aLayer ) )
1731 {
1732 case PAD_SHAPE::CIRCLE:
1733 {
1734 const CIRCLE circle( aPad->ShapePos( aLayer ), aPad->GetSizeX() / 2 );
1735
1736 for( const TYPED_POINT2I& pt : KIGEOM::GetCircleKeyPoints( circle, false ) )
1737 addAnchor( pt.m_point, OUTLINE | SNAPPABLE, aPad, pt.m_types );
1738
1739 break;
1740 }
1741 case PAD_SHAPE::OVAL:
1742 {
1744 aPad->GetSize( aLayer ), aPad->GetPosition(), aPad->GetOrientation() );
1745
1746 for( const TYPED_POINT2I& pt : KIGEOM::GetOvalKeyPoints( oval, ovalKeyPointFlags ) )
1747 addAnchor( pt.m_point, OUTLINE | SNAPPABLE, aPad, pt.m_types );
1748
1749 break;
1750 }
1755 {
1756 VECTOR2I half_size( aPad->GetSize( aLayer ) / 2 );
1757 VECTOR2I trap_delta( 0, 0 );
1758
1759 if( aPad->GetShape( aLayer ) == PAD_SHAPE::TRAPEZOID )
1760 trap_delta = aPad->GetDelta( aLayer ) / 2;
1761
1762 SHAPE_LINE_CHAIN corners;
1763
1764 corners.Append( -half_size.x - trap_delta.y, half_size.y + trap_delta.x );
1765 corners.Append( half_size.x + trap_delta.y, half_size.y - trap_delta.x );
1766 corners.Append( half_size.x - trap_delta.y, -half_size.y + trap_delta.x );
1767 corners.Append( -half_size.x + trap_delta.y, -half_size.y - trap_delta.x );
1768 corners.SetClosed( true );
1769
1770 corners.Rotate( aPad->GetOrientation() );
1771 corners.Move( aPad->ShapePos( aLayer ) );
1772
1773 for( std::size_t ii = 0; ii < corners.GetSegmentCount(); ++ii )
1774 {
1775 const SEG& seg = corners.GetSegment( ii );
1778
1779 if( ii == corners.GetSegmentCount() - 1 )
1781 }
1782
1783 break;
1784 }
1785
1786 default:
1787 {
1788 const auto& outline = aPad->GetEffectivePolygon( aLayer, ERROR_INSIDE );
1789
1790 if( !outline->IsEmpty() )
1791 {
1792 for( const VECTOR2I& pt : outline->Outline( 0 ).CPoints() )
1793 addAnchor( pt, OUTLINE | SNAPPABLE, aPad );
1794 }
1795
1796 break;
1797 }
1798 }
1799
1800 if( aPad->HasHole() )
1801 {
1802 // Holes are at the pad centre (it's the shape that may be offset)
1803 const VECTOR2I hole_pos = aPad->GetPosition();
1804 const VECTOR2I hole_size = aPad->GetDrillSize();
1805
1806 std::vector<TYPED_POINT2I> snap_pts;
1807
1808 if( hole_size.x == hole_size.y )
1809 {
1810 // Circle
1811 const CIRCLE circle( hole_pos, hole_size.x / 2 );
1812 snap_pts = KIGEOM::GetCircleKeyPoints( circle, true );
1813 }
1814 else
1815 {
1816 // Oval
1817
1818 // For now there's no way to have an off-angle hole, so this is the
1819 // same as the pad. In future, this may not be true:
1820 // https://gitlab.com/kicad/code/kicad/-/issues/4124
1821 const SHAPE_SEGMENT oval =
1822 SHAPE_SEGMENT::BySizeAndCenter( hole_size, hole_pos, aPad->GetOrientation() );
1823 snap_pts = KIGEOM::GetOvalKeyPoints( oval, ovalKeyPointFlags );
1824 }
1825
1826 for( const TYPED_POINT2I& snap_pt : snap_pts )
1827 addAnchor( snap_pt.m_point, OUTLINE | SNAPPABLE, aPad, snap_pt.m_types );
1828 }
1829 };
1830
1831 const auto addRectPoints =
1832 [&]( const BOX2I& aBox, EDA_ITEM& aRelatedItem )
1833 {
1834 const VECTOR2I topRight( aBox.GetRight(), aBox.GetTop() );
1835 const VECTOR2I bottomLeft( aBox.GetLeft(), aBox.GetBottom() );
1836
1837 const SEG first( aBox.GetOrigin(), topRight );
1838 const SEG second( topRight, aBox.GetEnd() );
1839 const SEG third( aBox.GetEnd(), bottomLeft );
1840 const SEG fourth( bottomLeft, aBox.GetOrigin() );
1841
1842 const int snapFlags = CORNER | SNAPPABLE;
1843
1844 addAnchor( aBox.GetCenter(), snapFlags, &aRelatedItem, POINT_TYPE::PT_CENTER );
1845
1846 addAnchor( first.A, snapFlags, &aRelatedItem, POINT_TYPE::PT_CORNER );
1847 addAnchor( first.Center(), snapFlags, &aRelatedItem, POINT_TYPE::PT_MID );
1848 addAnchor( second.A, snapFlags, &aRelatedItem, POINT_TYPE::PT_CORNER );
1849 addAnchor( second.Center(), snapFlags, &aRelatedItem, POINT_TYPE::PT_MID );
1850 addAnchor( third.A, snapFlags, &aRelatedItem, POINT_TYPE::PT_CORNER );
1851 addAnchor( third.Center(), snapFlags, &aRelatedItem, POINT_TYPE::PT_MID );
1852 addAnchor( fourth.A, snapFlags, &aRelatedItem, POINT_TYPE::PT_CORNER );
1853 addAnchor( fourth.Center(), snapFlags, &aRelatedItem, POINT_TYPE::PT_MID );
1854 };
1855
1856 const auto handleShape =
1857 [&]( PCB_SHAPE* shape )
1858 {
1859 VECTOR2I start = shape->GetStart();
1860 VECTOR2I end = shape->GetEnd();
1861
1862 switch( shape->GetShape() )
1863 {
1864 case SHAPE_T::CIRCLE:
1865 {
1866 const int r = ( start - end ).EuclideanNorm();
1867
1868 addAnchor( start, ORIGIN | SNAPPABLE, shape, POINT_TYPE::PT_CENTER );
1869
1870 addAnchor( start + VECTOR2I( -r, 0 ), OUTLINE | SNAPPABLE, shape, POINT_TYPE::PT_QUADRANT );
1871 addAnchor( start + VECTOR2I( r, 0 ), OUTLINE | SNAPPABLE, shape, POINT_TYPE::PT_QUADRANT );
1872 addAnchor( start + VECTOR2I( 0, -r ), OUTLINE | SNAPPABLE, shape, POINT_TYPE::PT_QUADRANT );
1873 addAnchor( start + VECTOR2I( 0, r ), OUTLINE | SNAPPABLE, shape, POINT_TYPE::PT_QUADRANT );
1874 break;
1875 }
1876
1877 case SHAPE_T::ARC:
1878 addAnchor( shape->GetStart(), CORNER | SNAPPABLE, shape, POINT_TYPE::PT_END );
1879 addAnchor( shape->GetEnd(), CORNER | SNAPPABLE, shape, POINT_TYPE::PT_END );
1880 addAnchor( shape->GetArcMid(), CORNER | SNAPPABLE, shape, POINT_TYPE::PT_MID );
1882 break;
1883
1884 case SHAPE_T::RECTANGLE:
1885 {
1886 addRectPoints( BOX2I::ByCorners( start, end ), *shape );
1887 break;
1888 }
1889
1890 case SHAPE_T::SEGMENT:
1891 addAnchor( start, CORNER | SNAPPABLE, shape, POINT_TYPE::PT_END );
1893 addAnchor( shape->GetCenter(), CORNER | SNAPPABLE, shape, POINT_TYPE::PT_MID );
1894 break;
1895
1896 case SHAPE_T::POLY:
1897 {
1899 lc.SetClosed( true );
1900 for( const VECTOR2I& p : shape->GetPolyPoints() )
1901 {
1903 lc.Append( p );
1904 }
1905
1906 addAnchor( lc.NearestPoint( aRefPos ), OUTLINE, aItem );
1907 break;
1908 }
1909
1910 case SHAPE_T::ELLIPSE:
1911 {
1912 VECTOR2I center = shape->GetEllipseCenter();
1913 int majorR = shape->GetEllipseMajorRadius();
1914 int minorR = shape->GetEllipseMinorRadius();
1915 EDA_ANGLE rot = shape->GetEllipseRotation();
1916 VECTOR2I majorEnd( KiROUND( majorR * rot.Cos() ), KiROUND( majorR * rot.Sin() ) );
1917 VECTOR2I minorEnd( KiROUND( -minorR * rot.Sin() ), KiROUND( minorR * rot.Cos() ) );
1918
1920 addAnchor( center + majorEnd, OUTLINE | SNAPPABLE, shape, POINT_TYPE::PT_QUADRANT );
1921 addAnchor( center - majorEnd, OUTLINE | SNAPPABLE, shape, POINT_TYPE::PT_QUADRANT );
1922 addAnchor( center + minorEnd, OUTLINE | SNAPPABLE, shape, POINT_TYPE::PT_QUADRANT );
1923 addAnchor( center - minorEnd, OUTLINE | SNAPPABLE, shape, POINT_TYPE::PT_QUADRANT );
1924 break;
1925 }
1926
1928 {
1929 addAnchor( shape->GetStart(), CORNER | SNAPPABLE, shape, POINT_TYPE::PT_END );
1930 addAnchor( shape->GetEnd(), CORNER | SNAPPABLE, shape, POINT_TYPE::PT_END );
1932 break;
1933 }
1934
1935 case SHAPE_T::BEZIER:
1936 addAnchor( start, CORNER | SNAPPABLE, shape, POINT_TYPE::PT_END );
1939
1940 default:
1941 addAnchor( shape->GetPosition(), ORIGIN | SNAPPABLE, shape );
1942 break;
1943 }
1944 };
1945
1946 switch( aItem->Type() )
1947 {
1948 case PCB_FOOTPRINT_T:
1949 {
1950 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem );
1951 bool footprintVisible = checkVisibility( footprint );
1952
1953 for( PAD* pad : footprint->Pads() )
1954 {
1955 if( aFrom )
1956 {
1957 if( aSelectionFilter && !aSelectionFilter->pads )
1958 continue;
1959 }
1960 else
1961 {
1963 continue;
1964 }
1965
1966 if( !checkVisibility( pad ) )
1967 continue;
1968
1969 if( !pad->GetBoundingBox().Contains( aRefPos ) )
1970 continue;
1971
1972 pad->Padstack().ForEachUniqueLayer(
1973 [&]( PCB_LAYER_ID aLayer )
1974 {
1975 if( !isHighContrast
1976 || PadstackUniqueLayerAppliesToLayer( pad->Padstack(), aLayer,
1977 activeHighContrastPrimaryLayer ) )
1978 {
1979 handlePadShape( pad, aLayer );
1980 }
1981 } );
1982 }
1983
1984 // Points are also pick-up points
1985 for( const PCB_POINT* pt : footprint->Points() )
1986 {
1987 if( aSelectionFilter && !aSelectionFilter->points )
1988 continue;
1989
1990 if( !checkVisibility( pt ) )
1991 continue;
1992
1993 addAnchor( pt->GetPosition(), ORIGIN | SNAPPABLE, footprint, POINT_TYPE::PT_CENTER );
1994 }
1995
1996 // When computing drag origins (aFrom=true), always proceed to add the footprint
1997 // position anchor regardless of the visibility state. The footprint is already
1998 // selected, so its anchor must be reachable as a drag point even if the active layer
1999 // or zoom level causes checkVisibility to return false. Snapping TO an external
2000 // footprint (aFrom=false) should still respect visibility.
2001 if( !footprintVisible && !aFrom )
2002 break;
2003
2004 if( aFrom && aSelectionFilter && !aSelectionFilter->footprints )
2005 break;
2006
2007 // Snap to the footprint origin so that move operations keep the part aligned to
2008 // the grid regardless of anchor layer visibility, but not when the footprint's
2009 // side is hidden.
2010 int fpRenderLayer = ( footprint->GetLayer() == F_Cu ) ? LAYER_FOOTPRINTS_FR
2011 : ( footprint->GetLayer() == B_Cu ) ? LAYER_FOOTPRINTS_BK
2012 : LAYER_ANCHOR;
2013
2014 if( !view->IsLayerVisible( fpRenderLayer ) )
2015 break;
2016
2017 VECTOR2I position = footprint->GetPosition();
2018 VECTOR2I center = footprint->GetBoundingBox( false ).Centre();
2019 VECTOR2I grid( GetGrid() );
2020
2021 addAnchor( position, ORIGIN | SNAPPABLE, footprint, POINT_TYPE::PT_CENTER );
2022
2023 if( ( center - position ).SquaredEuclideanNorm() > grid.SquaredEuclideanNorm() )
2025
2026 break;
2027 }
2028
2029 case PCB_PAD_T:
2030 if( aFrom )
2031 {
2032 if( aSelectionFilter && !aSelectionFilter->pads )
2033 break;
2034 }
2035 else
2036 {
2038 break;
2039 }
2040
2041 if( checkVisibility( aItem ) )
2042 {
2043 PAD* pad = static_cast<PAD*>( aItem );
2044
2045 pad->Padstack().ForEachUniqueLayer(
2046 [&]( PCB_LAYER_ID aLayer )
2047 {
2048 if( !isHighContrast
2049 || PadstackUniqueLayerAppliesToLayer( pad->Padstack(), aLayer,
2050 activeHighContrastPrimaryLayer ) )
2051 {
2052 handlePadShape( pad, aLayer );
2053 }
2054 } );
2055 }
2056
2057 break;
2058
2059 case PCB_TEXTBOX_T:
2060 if( aFrom )
2061 {
2062 if( aSelectionFilter && !aSelectionFilter->text )
2063 break;
2064 }
2065 else
2066 {
2067 if( !m_magneticSettings->graphics )
2068 break;
2069 }
2070
2071 if( checkVisibility( aItem ) )
2072 handleShape( static_cast<PCB_SHAPE*>( aItem ) );
2073
2074 break;
2075
2076 case PCB_TABLE_T:
2077 if( aFrom )
2078 {
2079 if( aSelectionFilter && !aSelectionFilter->text )
2080 break;
2081 }
2082 else
2083 {
2084 if( !m_magneticSettings->graphics )
2085 break;
2086 }
2087
2088 if( checkVisibility( aItem ) )
2089 {
2090 PCB_TABLE* table = static_cast<PCB_TABLE*>( aItem );
2091
2092 EDA_ANGLE drawAngle = table->GetCell( 0, 0 )->GetDrawRotation();
2093 VECTOR2I topLeft = table->GetCell( 0, 0 )->GetCornersInSequence( drawAngle )[0];
2094 VECTOR2I bottomLeft =
2095 table->GetCell( table->GetRowCount() - 1, 0 )->GetCornersInSequence( drawAngle )[3];
2096 VECTOR2I topRight = table->GetCell( 0, table->GetColCount() - 1 )->GetCornersInSequence( drawAngle )[1];
2097 VECTOR2I bottomRight = table->GetCell( table->GetRowCount() - 1, table->GetColCount() - 1 )
2098 ->GetCornersInSequence( drawAngle )[2];
2099
2104
2105 addAnchor( table->GetCenter(), ORIGIN, table, POINT_TYPE::PT_MID );
2106 }
2107
2108 break;
2109
2110 case PCB_SHAPE_T:
2111 if( aFrom )
2112 {
2113 if( aSelectionFilter && !aSelectionFilter->graphics )
2114 break;
2115 }
2116 else
2117 {
2118 if( !m_magneticSettings->graphics )
2119 break;
2120 }
2121
2122 if( checkVisibility( aItem ) )
2123 handleShape( static_cast<PCB_SHAPE*>( aItem ) );
2124
2125 break;
2126
2127 case PCB_TRACE_T:
2128 case PCB_ARC_T:
2129 if( aFrom )
2130 {
2131 if( aSelectionFilter && !aSelectionFilter->tracks )
2132 break;
2133 }
2134 else
2135 {
2137 break;
2138 }
2139
2140 if( checkVisibility( aItem ) )
2141 {
2142 PCB_TRACK* track = static_cast<PCB_TRACK*>( aItem );
2143
2144 addAnchor( track->GetStart(), CORNER | SNAPPABLE, track, POINT_TYPE::PT_END );
2145 addAnchor( track->GetEnd(), CORNER | SNAPPABLE, track, POINT_TYPE::PT_END );
2146
2147 if( aItem->Type() == PCB_ARC_T )
2148 {
2149 PCB_ARC* arc = static_cast<PCB_ARC*>( aItem );
2150
2151 for( const ANCHOR_SPEC& spec : GetArcAnchors( *arc, aFrom ) )
2152 addAnchor( spec.pos, spec.flags, arc, spec.pointType );
2153 }
2154 else
2155 {
2156 addAnchor( track->GetCenter(), ORIGIN, track, POINT_TYPE::PT_MID );
2157 }
2158 }
2159
2160 break;
2161
2162 case PCB_MARKER_T:
2163 case PCB_TARGET_T:
2165 break;
2166
2167 case PCB_POINT_T:
2168 if( aSelectionFilter && !aSelectionFilter->points )
2169 break;
2170
2171 if( checkVisibility( aItem ) )
2173
2174 break;
2175
2176 case PCB_VIA_T:
2177 if( aFrom )
2178 {
2179 if( aSelectionFilter && !aSelectionFilter->vias )
2180 break;
2181 }
2182 else
2183 {
2185 break;
2186 }
2187
2188 if( checkVisibility( aItem ) )
2190
2191 break;
2192
2193 case PCB_ZONE_T:
2194 if( aFrom && aSelectionFilter && !aSelectionFilter->zones )
2195 break;
2196
2197 if( checkVisibility( aItem ) )
2198 {
2199 const SHAPE_POLY_SET* outline = static_cast<const ZONE*>( aItem )->Outline();
2200
2202 lc.SetClosed( true );
2203
2204 for( auto iter = outline->CIterateWithHoles(); iter; iter++ )
2205 {
2206 addAnchor( *iter, CORNER | SNAPPABLE, aItem, POINT_TYPE::PT_CORNER );
2207 lc.Append( *iter );
2208 }
2209
2210 addAnchor( lc.NearestPoint( aRefPos ), OUTLINE, aItem );
2211 }
2212
2213 break;
2214
2215 case PCB_DIM_ALIGNED_T:
2217 if( aFrom && aSelectionFilter && !aSelectionFilter->dimensions )
2218 break;
2219
2220 if( checkVisibility( aItem ) )
2221 {
2222 PCB_DIM_ALIGNED* dim = static_cast<PCB_DIM_ALIGNED*>( aItem );
2223 addAnchor( dim->GetCrossbarStart(), CORNER | SNAPPABLE, dim );
2224 addAnchor( dim->GetCrossbarEnd(), CORNER | SNAPPABLE, dim );
2225 addAnchor( dim->GetStart(), CORNER | SNAPPABLE, dim );
2226 addAnchor( dim->GetEnd(), CORNER | SNAPPABLE, dim );
2227 }
2228
2229 break;
2230
2231 case PCB_DIM_CENTER_T:
2232 if( aFrom && aSelectionFilter && !aSelectionFilter->dimensions )
2233 break;
2234
2235 if( checkVisibility( aItem ) )
2236 {
2237 PCB_DIM_CENTER* dim = static_cast<PCB_DIM_CENTER*>( aItem );
2238 addAnchor( dim->GetStart(), CORNER | SNAPPABLE, dim );
2239 addAnchor( dim->GetEnd(), CORNER | SNAPPABLE, dim );
2240
2241 VECTOR2I start( dim->GetStart() );
2242 VECTOR2I radial( dim->GetEnd() - dim->GetStart() );
2243
2244 for( int i = 0; i < 2; i++ )
2245 {
2246 RotatePoint( radial, -ANGLE_90 );
2247 addAnchor( start + radial, CORNER | SNAPPABLE, dim );
2248 }
2249 }
2250
2251 break;
2252
2253 case PCB_DIM_RADIAL_T:
2254 if( aFrom && aSelectionFilter && !aSelectionFilter->dimensions )
2255 break;
2256
2257 if( checkVisibility( aItem ) )
2258 {
2259 PCB_DIM_RADIAL* radialDim = static_cast<PCB_DIM_RADIAL*>( aItem );
2260 addAnchor( radialDim->GetStart(), CORNER | SNAPPABLE, radialDim );
2261 addAnchor( radialDim->GetEnd(), CORNER | SNAPPABLE, radialDim );
2262 addAnchor( radialDim->GetKnee(), CORNER | SNAPPABLE, radialDim );
2263 addAnchor( radialDim->GetTextPos(), CORNER | SNAPPABLE, radialDim );
2264 }
2265
2266 break;
2267
2268 case PCB_DIM_LEADER_T:
2269 if( aFrom && aSelectionFilter && !aSelectionFilter->dimensions )
2270 break;
2271
2272 if( checkVisibility( aItem ) )
2273 {
2274 PCB_DIM_LEADER* leader = static_cast<PCB_DIM_LEADER*>( aItem );
2275 addAnchor( leader->GetStart(), CORNER | SNAPPABLE, leader );
2276 addAnchor( leader->GetEnd(), CORNER | SNAPPABLE, leader );
2277 addAnchor( leader->GetTextPos(), CORNER | SNAPPABLE, leader );
2278 }
2279
2280 break;
2281
2282 case PCB_FIELD_T:
2283 case PCB_TEXT_T:
2284 if( aFrom && aSelectionFilter && !aSelectionFilter->text )
2285 break;
2286
2287 if( checkVisibility( aItem ) )
2288 addAnchor( aItem->GetPosition(), ORIGIN, aItem );
2289
2290 break;
2291
2292 case PCB_BARCODE_T:
2293 if( aFrom && aSelectionFilter && !aSelectionFilter->otherItems )
2294 break;
2295
2296 if( checkVisibility( aItem ) )
2297 {
2298 PCB_BARCODE* barcode = static_cast<PCB_BARCODE*>( aItem );
2299 const BOX2I bbox = barcode->GetSymbolPoly().BBox();
2300
2301 addAnchor( aItem->GetPosition(), ORIGIN, barcode, POINT_TYPE::PT_CENTER );
2302 addRectPoints( bbox, *barcode );
2303 }
2304
2305 break;
2306
2307 case PCB_GROUP_T:
2308 for( BOARD_ITEM* item : static_cast<PCB_GROUP*>( aItem )->GetBoardItems() )
2309 {
2310 if( checkVisibility( item ) )
2311 computeAnchors( item, aRefPos, aFrom, nullptr );
2312 }
2313
2314 break;
2315
2317 if( aFrom && aSelectionFilter && !aSelectionFilter->graphics )
2318 break;
2319
2320 if( checkVisibility( aItem ) )
2321 {
2322 PCB_REFERENCE_IMAGE* image = static_cast<PCB_REFERENCE_IMAGE*>( aItem );
2323 const REFERENCE_IMAGE& refImg = image->GetReferenceImage();
2324 const BOX2I bbox = refImg.GetBoundingBox();
2325
2326 addRectPoints( bbox, *image );
2327
2328 if( refImg.GetTransformOriginOffset() != VECTOR2I( 0, 0 ) )
2329 {
2330 addAnchor( image->GetPosition() + refImg.GetTransformOriginOffset(), ORIGIN,
2332 }
2333 }
2334
2335 break;
2336
2337 default:
2338 break;
2339 }
2340}
2341
2342
2344{
2345 // Do this all in squared distances as we only care about relative distances
2347
2348 ecoord minDist = std::numeric_limits<ecoord>::max();
2349 std::vector<ANCHOR*> anchorsAtMinDistance;
2350
2351 for( ANCHOR& anchor : m_anchors )
2352 {
2353 // There is no need to filter by layers here, as the items are already filtered
2354 // by layer (if needed) when the anchors are computed.
2355 if( ( aFlags & anchor.flags ) != aFlags )
2356 continue;
2357
2358 if( !anchorsAtMinDistance.empty() && anchor.pos == anchorsAtMinDistance.front()->pos )
2359 {
2360 // Same distance as the previous best anchor
2361 anchorsAtMinDistance.push_back( &anchor );
2362 }
2363 else
2364 {
2365 const double dist = anchor.pos.SquaredDistance( aPos );
2366
2367 if( dist < minDist )
2368 {
2369 // New minimum distance
2370 minDist = dist;
2371 anchorsAtMinDistance.clear();
2372 anchorsAtMinDistance.push_back( &anchor );
2373 }
2374 }
2375 }
2376
2377 // Check that any involved real items are 'active'
2378 // (i.e. the user has moused over a key point previously)
2379 // If any are not real (e.g. snap lines), they are allowed to be involved
2380 //
2381 // This is an area most likely to be controversial/need tuning,
2382 // as some users will think it's fiddly; without 'activation', others will
2383 // think the snaps are intrusive.
2384 SNAP_MANAGER& snapManager = getSnapManager();
2385
2386 auto noRealItemsInAnchorAreInvolved =
2387 [&]( ANCHOR* aAnchor ) -> bool
2388 {
2389 // If no extension snaps are enabled, don't inhibit
2390 static const bool haveExtensions = ADVANCED_CFG::GetCfg().m_EnableExtensionSnaps;
2391
2392 if( !haveExtensions )
2393 return false;
2394
2395 // If the anchor is not constructed, it may be involved (because it is one
2396 // of the nearest anchors). The items will only be activated later, but don't
2397 // discard the anchor yet.
2398 const bool anchorIsConstructed = aAnchor->flags & ANCHOR_FLAGS::CONSTRUCTED;
2399
2400 if( !anchorIsConstructed )
2401 return false;
2402
2403 bool allRealAreInvolved = snapManager.GetConstructionManager().InvolvesAllGivenRealItems( aAnchor->items );
2404 return !allRealAreInvolved;
2405 };
2406
2407 // Trim out items that aren't involved
2408 std::erase_if( anchorsAtMinDistance, noRealItemsInAnchorAreInvolved );
2409
2410 // More than one anchor can be at the same distance, for example
2411 // two lines end-to-end each have the same endpoint anchor.
2412 // So, check which one has an involved item that's closest to the origin,
2413 // and use that one (which allows the user to choose which items
2414 // gets extended - it's the one nearest the cursor)
2415 ecoord minDistToItem = std::numeric_limits<ecoord>::max();
2416 ANCHOR* best = nullptr;
2417
2418 // One of the anchors at the minimum distance
2419 for( ANCHOR* const anchor : anchorsAtMinDistance )
2420 {
2421 ecoord distToNearestItem = std::numeric_limits<ecoord>::max();
2422
2423 for( EDA_ITEM* const item : anchor->items )
2424 {
2425 if( !item || !item->IsBOARD_ITEM() )
2426 continue;
2427
2428 std::optional<ecoord> distToThisItem =
2429 FindSquareDistanceToItem( static_cast<const BOARD_ITEM&>( *item ), aPos );
2430
2431 if( distToThisItem )
2432 distToNearestItem = std::min( distToNearestItem, *distToThisItem );
2433 }
2434
2435 // If the item doesn't have any special min-dist handler,
2436 // just use the distance to the anchor
2437 distToNearestItem = std::min( distToNearestItem, minDist );
2438
2439 if( distToNearestItem < minDistToItem )
2440 {
2441 minDistToItem = distToNearestItem;
2442 best = anchor;
2443 }
2444 }
2445
2446 return best;
2447}
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
constexpr BOX2I BOX2ISafe(const BOX2D &aInput)
Definition box2.h:925
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:83
virtual VECTOR2I GetCenter() const
This defaults to the center of the bounding box if not overridden.
Definition board_item.h:137
FOOTPRINT * GetParentFootprint() const
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition board_item.h:315
virtual void RunOnChildren(const std::function< void(BOARD_ITEM *)> &aFunction, RECURSE_MODE aMode) const
Invoke a function on all children.
Definition board_item.h:233
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:235
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
bool IsFootprintHolder() const
Find out if the board is being used to hold a single footprint for editing/viewing.
Definition board.h:403
void AddListener(BOARD_LISTENER *aListener)
Add a listener to the board to receive calls whenever something on the board has been modified.
Definition board.cpp:3707
void RemoveListener(BOARD_LISTENER *aListener)
Remove the specified listener.
Definition board.cpp:3714
constexpr BOX2< Vec > Intersect(const BOX2< Vec > &aRect)
Definition box2.h:343
static constexpr BOX2< VECTOR2I > ByCorners(const VECTOR2I &aCorner1, const VECTOR2I &aCorner2)
Definition box2.h:66
constexpr const Vec GetEnd() const
Definition box2.h:208
constexpr size_type GetWidth() const
Definition box2.h:210
constexpr Vec Centre() const
Definition box2.h:93
constexpr const Vec GetCenter() const
Definition box2.h:226
constexpr size_type GetHeight() const
Definition box2.h:211
constexpr coord_type GetLeft() const
Definition box2.h:224
constexpr const Vec & GetOrigin() const
Definition box2.h:206
constexpr coord_type GetRight() const
Definition box2.h:213
constexpr coord_type GetTop() const
Definition box2.h:225
constexpr void Offset(coord_type dx, coord_type dy)
Definition box2.h:255
constexpr coord_type GetBottom() const
Definition box2.h:218
Represent basic circle geometry with utility geometry functions.
Definition circle.h:33
void ProposeConstructionItems(std::unique_ptr< CONSTRUCTION_ITEM_BATCH > aBatch, bool aIsPersistent)
Add a batch of construction items to the helper.
void CancelProposal()
Cancel outstanding proposals for new geometry.
void Clear()
Clear all construction items.
std::vector< CONSTRUCTION_ITEM > CONSTRUCTION_ITEM_BATCH
bool InvolvesAllGivenRealItems(const std::vector< EDA_ITEM * > &aItems) const
Check if all 'real' (non-null = constructed) the items in the batch are in the list of items currentl...
double Sin() const
Definition eda_angle.h:178
double Cos() const
Definition eda_angle.h:197
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:96
virtual VECTOR2I GetPosition() const
Definition eda_item.h:282
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:135
const KIID m_Uuid
Definition eda_item.h:531
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
virtual bool HitTest(const VECTOR2I &aPosition, int aAccuracy=0) const
Test if aPosition is inside or on the boundary of this item.
Definition eda_item.h:243
bool IsMoving() const
Definition eda_item.h:130
int GetEllipseMinorRadius() const
Definition eda_shape.h:310
const VECTOR2I & GetEllipseCenter() const
Definition eda_shape.h:292
int GetEllipseMajorRadius() const
Definition eda_shape.h:301
std::vector< VECTOR2I > GetPolyPoints() const
Duplicate the polygon outlines into a flat list of VECTOR2I points.
EDA_ANGLE GetEllipseRotation() const
Definition eda_shape.h:319
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:185
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:240
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:190
VECTOR2I GetArcMid() const
PCB_POINTS & Points()
Definition footprint.h:390
std::deque< PAD * > & Pads()
Definition footprint.h:375
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:420
VECTOR2I GetPosition() const override
Definition footprint.h:406
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
void retainAcceptedSnaps(const SNAP_RESULT &aResult)
Record which snaps the resolver accepted so they can be re-biased on the next resolve,...
std::optional< VECTOR2I > SnapToConstructionLines(const VECTOR2I &aPoint, const VECTOR2I &aNearestGrid, const VECTOR2D &aGrid, double aSnapRange) const
void addAnchor(const VECTOR2I &aPos, int aFlags, EDA_ITEM *aItem, int aPointTypes=POINT_TYPE::PT_NONE)
SNAP_MANAGER & getSnapManager()
VECTOR2I m_skipPoint
bool m_enableGrid
SNAP_RANGES computeSnapRanges(bool aClampToVisibleGrid) const
Compute the snap thresholds.
void emitAngleBranchCandidates(std::vector< SNAP_CANDIDATE > &aCandidates, const VECTOR2I &aOrigin, double aSnapScale) const
Emit the angle-restriction snap candidates (the two branches bracketing the cursor angle) into the cu...
void showConstructionGeometry(bool aShow)
SNAP_RESOLVER::FEASIBILITY_CALLBACK m_feasibilityCallback
std::vector< SEG > m_stationarySelfSegments
TOOL_MANAGER * m_toolMgr
SNAP_RESOLVER::TRACE_CALLBACK snapTraceCallback(const SNAP_SOURCE_CONTEXT &aContext) const
VECTOR2I GetGrid() const
bool m_enableSnapLine
bool m_enableSnap
VECTOR2I GetOrigin() const
std::optional< SNAP_STABLE_ID > m_retainedAngleBranch
void clearAnchors()
std::optional< ANCHOR > m_snapItem
SNAP_REFERENCE_PREFERENCE m_layoutReferencePreference
KIGFX::ANCHOR_DEBUG * enableAndGetAnchorDebug()
Enable the anchor debug if permitted and return it.
KIGFX::SNAP_INDICATOR m_viewSnapPoint
void updateSnapPoint(const TYPED_POINT2I &aPoint)
KIGFX::ORIGIN_VIEWITEM m_viewAxis
bool m_pointEditProfile
std::vector< SNAP_STABLE_ID > m_stickySnapIds
void setLayoutReference(const VECTOR2I &aPoint, const std::optional< BOX2I > &aBounds, bool aAnchorPoint)
Record the classified drag reference and trace it.
void emitSelfAndGridCandidates(std::vector< SNAP_CANDIDATE > &aCandidates, const SNAP_SOURCE_CONTEXT &aContext, const VECTOR2I &aOrigin, const VECTOR2I &aNearestGrid, double aSnapScale, int aSnapRange, bool aUseGrid) const
Emit the point editor's unchanged self-geometry and the independent grid axes.
void applySnapResultGuides(const SNAP_RESULT &aResult)
std::vector< ANCHOR > m_anchors
View item to draw debug items for anchors.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
COLOR4D WithAlpha(double aAlpha) const
Return a color with the same color, but the given alpha.
Definition color4d.h:308
COLOR4D Brightened(double aFactor) const
Return a color that is brighter by a given factor, without modifying object.
Definition color4d.h:265
virtual RENDER_SETTINGS * GetSettings()=0
Return a pointer to current settings that are going to be used when drawing items.
Container for all the knowledge about how graphical objects are drawn on any output surface/device.
const std::set< int > GetHighContrastLayers() const
Returns the set of currently high-contrast layers.
PCB_LAYER_ID GetPrimaryHighContrastLayer() const
Return the board layer which is in high-contrast mode.
const COLOR4D & GetLayerColor(int aLayer) const
Return the color used to draw a layer.
bool IsBOARD_ITEM() const
Definition view_item.h:98
virtual double ViewGetLOD(int aLayer, const VIEW *aView) const
Return the level of detail (LOD) of the item.
Definition view_item.h:151
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition view.h:63
double GetScale() const
Definition view.h:281
virtual void Add(VIEW_ITEM *aItem, int aDrawPriority=-1)
Add a VIEW_ITEM to the view.
Definition view.cpp:300
virtual void Remove(VIEW_ITEM *aItem)
Remove a VIEW_ITEM from the view.
Definition view.cpp:404
int Query(const BOX2I &aRect, std::vector< LAYER_ITEM_PAIR > &aResult) const
Find all visible items that touch or are within the rectangle aRect.
Definition view.cpp:487
bool IsLayerVisible(int aLayer) const
Return information about visibility of a particular layer.
Definition view.h:427
PAINTER * GetPainter() const
Return the painter object used by the view for drawing #VIEW_ITEMS.
Definition view.h:225
bool IsVisible(const VIEW_ITEM *aItem) const
Return information if the item is visible (or not).
Definition view.cpp:1805
void SetVisible(VIEW_ITEM *aItem, bool aIsVisible=true)
Set the item visibility.
Definition view.cpp:1756
Definition line.h:32
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllLayersMask()
Definition lset.cpp:637
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:157
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:171
@ CUSTOM
Shapes can be defined on arbitrary layers.
Definition padstack.h:173
@ FRONT_INNER_BACK
Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu)
Definition padstack.h:172
MODE Mode() const
Definition padstack.h:335
static constexpr PCB_LAYER_ID INNER_LAYERS
! The layer identifier to use for "inner layers" on top/inner/bottom padstacks
Definition padstack.h:180
Definition pad.h:61
int GetSizeX() const
Definition pad.cpp:311
const VECTOR2I & GetDelta(PCB_LAYER_ID aLayer) const
Definition pad.h:302
VECTOR2I GetPosition() const override
Definition pad.cpp:245
VECTOR2I GetDrillSize() const
Definition pad.h:315
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition pad.h:202
VECTOR2I GetSize(PCB_LAYER_ID aLayer) const
Definition pad.cpp:287
EDA_ANGLE GetOrientation() const
Return the rotation angle of the pad.
Definition pad.cpp:1723
const std::shared_ptr< SHAPE_POLY_SET > & GetEffectivePolygon(PCB_LAYER_ID aLayer, ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Definition pad.cpp:1194
bool HasHole() const override
Definition pad.h:113
VECTOR2I ShapePos(PCB_LAYER_ID aLayer) const
Definition pad.cpp:1831
const VECTOR2I & GetMid() const
Definition pcb_track.h:286
virtual VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_track.h:293
const SHAPE_POLY_SET & GetSymbolPoly() const
Access the cached polygon for the barcode symbol only (no text, no margins/knockout).
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
PCB_SCREEN * GetScreen() const override
Return a pointer to a BASE_SCREEN or one of its derivatives.
virtual VECTOR2I GetEnd() const
virtual VECTOR2I GetStart() const
The dimension's origin is the first feature point for the dimension.
For better understanding of the points that make a dimension:
const VECTOR2I & GetCrossbarStart() const
const VECTOR2I & GetCrossbarEnd() const
Mark the center of a circle or arc with a cross shape.
A leader is a dimension-like object pointing to a specific point.
A radial dimension indicates either the radius or diameter of an arc or circle.
VECTOR2I GetKnee() const
std::vector< NEARABLE_GEOM > m_pointOnLineCandidates
void OnBoardItemRemoved(BOARD &aBoard, BOARD_ITEM *aRemovedItem) override
std::vector< BOARD_ITEM * > queryVisible(std::initializer_list< BOX2I > aAreas, const std::vector< BOARD_ITEM * > &aSkip) const
static std::vector< ANCHOR_SPEC > GetArcAnchors(const PCB_ARC &aArc, bool aFrom)
Return the snap/drag anchor points that a track arc contributes.
SNAP_RESULT ResolveSnap(const VECTOR2I &aOrigin, BOARD_ITEM *aReferenceItem, GRID_HELPER_GRIDS aGrid=GRID_HELPER_GRIDS::GRID_CURRENT)
Chooses the "best" snap anchor around the given point, optionally taking layers from the reference it...
SNAP_INFERENCE_SETTINGS snapInferenceSettings() const
Snap inference settings for whichever editor owns this helper.
~PCB_GRID_HELPER() override
VECTOR2I AlignToArc(const VECTOR2I &aPoint, const SHAPE_ARC &aSeg)
VECTOR2I SnapToPad(const VECTOR2I &aMousePos, std::deque< PAD * > &aPads)
void OnBoardItemsRemoved(BOARD &aBoard, std::vector< BOARD_ITEM * > &aBoardItems) override
BOARD_ITEM * GetSnapped() const
Function GetSnapped If the PCB_GRID_HELPER has highlighted a snap point (target shown),...
VECTOR2D GetGridSize(GRID_HELPER_GRIDS aGrid) const override
Return the size of the specified grid.
VECTOR2I BestDragOrigin(const VECTOR2I &aMousePos, std::vector< BOARD_ITEM * > &aItem, GRID_HELPER_GRIDS aGrid=GRID_HELPER_GRIDS::GRID_CURRENT, const PCB_SELECTION_FILTER_OPTIONS *aSelectionFilter=nullptr)
bool editingInsideFootprint() const
True when the footprint's own contents are the layout objects.
void AddConstructionItems(std::vector< BOARD_ITEM * > aItems, bool aExtensionOnly, bool aIsPersistent)
Add construction geometry for a set of board items.
ANCHOR * nearestAnchor(const VECTOR2I &aPos, int aFlags)
Find the nearest anchor point to the given position with matching flags.
static BOX2I layoutBounds(const BOARD_ITEM &aItem)
MAGNETIC_SETTINGS * m_magneticSettings
GRID_HELPER_GRIDS GetItemGrid(const EDA_ITEM *aItem) const override
Get the coarsest grid that applies to an item.
VECTOR2I AlignToSegment(const VECTOR2I &aPoint, const SEG &aSeg)
virtual VECTOR2I Align(const VECTOR2I &aPoint, GRID_HELPER_GRIDS aGrid) const
void computeAnchors(const std::vector< BOARD_ITEM * > &aItems, const VECTOR2I &aRefPos, bool aFrom, const PCB_SELECTION_FILTER_OPTIONS *aSelectionFilter, const LSET *aLayers, bool aForDrag)
computeAnchors inserts the local anchor points in to the grid helper for the specified container of b...
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
A PCB_POINT is a 0-dimensional point that is used to mark a position on a PCB, or more usually a foot...
Definition pcb_point.h:39
Object to handle a bitmap image that can be inserted in a PCB.
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
REFERENCE_IMAGE & GetReferenceImage()
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:78
VECTOR2I GetPosition() const override
Definition pcb_shape.h:76
VECTOR2I GetTextPos() const override
Definition pcb_text.cpp:445
const VECTOR2I & GetStart() const
Definition pcb_track.h:93
const VECTOR2I & GetEnd() const
Definition pcb_track.h:90
A REFERENCE_IMAGE is a wrapper around a BITMAP_IMAGE that is displayed in an editor as a reference fo...
VECTOR2I GetTransformOriginOffset() const
Get the center of scaling, etc, relative to the image center (GetPosition()).
VECTOR2I GetPosition() const
BOX2I GetBoundingBox() const
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
ecoord SquaredDistance(const SEG &aSeg) const
Definition seg.cpp:76
VECTOR2I::extended_type ecoord
Definition seg.h:40
VECTOR2I B
Definition seg.h:46
VECTOR2I Center() const
Definition seg.h:375
OPT_VECTOR2I IntersectLines(const SEG &aSeg) const
Compute the intersection point of lines passing through ends of (this) and aSeg.
Definition seg.h:216
const VECTOR2I & GetP1() const
Definition shape_arc.h:115
int IntersectLine(const SEG &aSeg, std::vector< VECTOR2I > *aIpsBuffer) const
Find intersection points between this arc and aSeg, treating aSeg as an infinite line.
const VECTOR2I & GetP0() const
Definition shape_arc.h:114
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void Move(const VECTOR2I &aVector) override
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
virtual const SEG GetSegment(int aIndex) const override
const VECTOR2I NearestPoint(const VECTOR2I &aP, bool aAllowInternalShapePoints=true) const
Find a point on the line chain that is closest to point aP.
virtual size_t GetSegmentCount() const override
Represent a set of closed polygons.
CONST_ITERATOR CIterateWithHoles(int aOutline) const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
static SHAPE_SEGMENT BySizeAndCenter(const VECTOR2I &aSize, const VECTOR2I &aCenter, const EDA_ANGLE &aRotation)
std::vector< SNAP_CANDIDATE > CollectObjectGeometry(const SNAP_SOURCE_CONTEXT &aContext, int aRadius) const
std::vector< SNAP_CANDIDATE > CollectEqualSpacing(const SNAP_SOURCE_CONTEXT &aContext, int aRadius) const
void AddBounds(SNAP_OBJECT_BOUNDS aBounds)
std::vector< SNAP_CANDIDATE > CollectTangentNormal(const SNAP_SOURCE_CONTEXT &aContext, int aRadius, bool aTangentEnabled, bool aNormalEnabled) const
void AddPath(SNAP_OBJECT_PATH aPath)
void AddAlignmentPoint(SNAP_ALIGNMENT_POINT aPoint)
std::vector< SNAP_CANDIDATE > CollectAlignment(const SNAP_SOURCE_CONTEXT &aContext, int aRadius) const
OPT_VECTOR2I GetNearestSnapLinePoint(const VECTOR2I &aCursor, const VECTOR2I &aNearestGrid, std::optional< int > aDistToNearest, int snapRange, const VECTOR2D &aGridSize=VECTOR2D(0, 0), const VECTOR2I &aGridOrigin=VECTOR2I(0, 0)) const
If the snap line is active, return the best snap point that is closest to the cursor.
void SetSnappedAnchor(const VECTOR2I &aAnchorPos)
Inform this manager that an anchor snap has been made.
void ClearSnapLine()
Clear the snap line origin and end points.
void SetSnapLineOrigin(const VECTOR2I &aOrigin)
The snap point is a special point that is located at the last point the cursor snapped to.
void SetSnapLineEnd(const OPT_VECTOR2I &aSnapPoint)
Set the end point of the snap line.
A SNAP_MANAGER glues together the snap line manager and construction manager., along with some other ...
SNAP_LINE_MANAGER & GetSnapLineManager()
CONSTRUCTION_MANAGER & GetConstructionManager()
const std::vector< VECTOR2I > & GetReferenceOnlyPoints() const
void SetReferenceOnlyPoints(std::vector< VECTOR2I > aPoints)
Set the reference-only points - these are points that are not snapped to, but can still be used for c...
void SetDimensionBrackets(std::vector< SEG > aBrackets)
Master controller class:
EDA_ITEM * GetModel() const
Define a general 2D-vector/point.
Definition vector2d.h:67
constexpr extended_type SquaredDistance(const VECTOR2< T > &aVector) const
Compute the squared distance between two vectors.
Definition vector2d.h:557
static constexpr extended_type ECOORD_MAX
Definition vector2d.h:72
VECTOR2_TRAITS< int32_t >::extended_type extended_type
Definition vector2d.h:69
Handle a list of polygons defining a copper zone.
Definition zone.h:70
A type-safe container of any type.
Definition ki_any.h:92
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ RECURSE
Definition eda_item.h:49
@ ELLIPSE
Definition eda_shape.h:52
@ SEGMENT
Definition eda_shape.h:46
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:47
@ ELLIPSE_ARC
Definition eda_shape.h:53
SNAP_TARGET_ID SnapTargetId(const KIID &aId)
The snap identity of a document item.
Definition grid_helper.h:49
GRID_HELPER_GRIDS
Definition grid_helper.h:55
@ GRID_VIAS
Definition grid_helper.h:61
@ GRID_TEXT
Definition grid_helper.h:62
@ GRID_CURRENT
Definition grid_helper.h:57
@ GRID_GRAPHICS
Definition grid_helper.h:63
@ GRID_CONNECTABLE
Definition grid_helper.h:59
@ GRID_WIRES
Definition grid_helper.h:60
bool m_ExtensionSnapActivateOnHover
If extension snaps are enabled, 'activate' items on hover, even if not near a snap point.
bool m_EnableExtensionSnaps
Enable snap anchors based on item line extensions.
const wxChar *const traceSnap
Flag to enable snap/grid helper debug tracing.
std::variant< LINE, HALF_LINE, SEG, CIRCLE, SHAPE_ARC, BOX2I > INTERSECTABLE_GEOM
A variant type that can hold any of the supported geometry types for intersection calculations.
bool IsPcbLayer(int aLayer)
Test whether a layer is a valid layer for Pcbnew.
Definition layer_ids.h:672
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:683
@ LAYER_FOOTPRINTS_FR
Show footprints on front.
Definition layer_ids.h:255
@ LAYER_AUX_ITEMS
Auxiliary items (guides, rule, etc).
Definition layer_ids.h:279
@ LAYER_FOOTPRINTS_BK
Show footprints on back.
Definition layer_ids.h:256
@ LAYER_ANCHOR
Anchor of items having an anchor point (texts, footprints).
Definition layer_ids.h:244
bool IsInnerCopperLayer(int aLayerId)
Test whether a layer is an inner (In1_Cu to In30_Cu) copper layer.
Definition layer_ids.h:705
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ B_Cu
Definition layer_ids.h:61
@ F_Cu
Definition layer_ids.h:60
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:79
std::vector< TYPED_POINT2I > GetCircleKeyPoints(const CIRCLE &aCircle, bool aIncludeCenter)
Get key points of an CIRCLE.
std::vector< TYPED_POINT2I > GetOvalKeyPoints(const SHAPE_SEGMENT &aOval, OVAL_KEY_POINT_FLAGS aFlags)
Get a list of interesting points on an oval (rectangle with semicircular end caps)
Definition oval.cpp:46
std::array< SEG, 4 > BoxToSegs(const BOX2I &aBox)
Decompose a BOX2 into four segments.
@ OVAL_CAP_TIPS
Definition oval.h:45
@ OVAL_SIDE_MIDPOINTS
Definition oval.h:47
@ OVAL_CARDINAL_EXTREMES
Definition oval.h:49
@ OVAL_CENTER
Definition oval.h:44
unsigned int OVAL_KEY_POINT_FLAGS
Definition oval.h:53
@ GEOMETRY
Position or shape has changed.
Definition view_item.h:51
STL namespace.
VECTOR2I GetNearestPoint(const NEARABLE_GEOM &aGeom, const VECTOR2I &aPt)
Get the nearest point on a geometry to a given point.
Definition nearest.cpp:54
std::variant< LINE, HALF_LINE, SEG, CIRCLE, SHAPE_ARC, BOX2I, VECTOR2I > NEARABLE_GEOM
A variant type that can hold any of the supported geometry types for nearest point calculations.
Definition nearest.h:39
@ CHAMFERED_RECT
Definition padstack.h:60
@ ROUNDRECT
Definition padstack.h:57
@ TRAPEZOID
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:54
BARCODE class definition.
static bool PadstackUniqueLayerAppliesToLayer(const PADSTACK &aPadStack, PCB_LAYER_ID aPadstackUniqueLayer, const PCB_LAYER_ID aRealLayer)
Class to handle a set of BOARD_ITEMs.
@ PT_INTERSECTION
The point is an intersection of two (or more) items.
Definition point_types.h:63
@ PT_CENTER
The point is the center of something.
Definition point_types.h:42
@ PT_CORNER
The point is a corner of a polygon, rectangle, etc (you may want to infer PT_END from this)
Definition point_types.h:59
@ PT_NONE
No specific point type.
Definition point_types.h:38
@ PT_QUADRANT
The point is on a quadrant of a circle (N, E, S, W points).
Definition point_types.h:54
@ PT_END
The point is at the end of a segment, arc, etc.
Definition point_types.h:46
@ PT_MID
The point is at the middle of a segment, arc, etc.
Definition point_types.h:50
@ PT_ON_ELEMENT
The point is somewhere on another element, but not some specific point.
Definition point_types.h:68
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:35
VECTOR2I::extended_type ecoord
Utility functions for working with shapes.
SNAP_FRAME_OUTPUT< Payload > ResolveSnapFrame(SNAP_FRAME_INPUT< Payload > aInput)
Definition snap_frame.h:60
SNAP_STABLE_ID MakePointSnapId(SNAP_ID_KIND aKind, const VECTOR2I &aPoint, int aFeatureIndex=0)
SNAP_STABLE_ID MakeCompositeSnapId(SNAP_ID_KIND aKind, const std::vector< SNAP_TARGET_ID > &aTargets, int aFeatureIndex=0)
SNAP_STABLE_ID MakeDerivedSnapId(SNAP_ID_KIND aKind, const SNAP_STABLE_ID &aSource, int aFeatureIndex=0, int aSolutionBranch=0)
SNAP_ID_KIND
KIGFX::CONSTRUCTION_GEOM::DRAWABLE Drawable
Items to be used for the construction of "virtual" anchors, for example, when snapping to a point inv...
std::vector< EDA_ITEM * > items
Items that are associated with this anchor (can be more than one, e.g.
double Distance(const VECTOR2I &aP) const
World-space snap thresholds derived from the screen-space snap radius.
int range
Snap radius, optionally clamped to the visible grid.
int in
Distance at which a candidate is picked up.
double scale
SNAP_SCREEN_RADIUS in world units.
int out
Distance at which a held candidate is released.
double rankingHysteresis
Resolver ranking stickiness, as a fraction of the radius.
A visitor that visits INTERSECTABLE_GEOM variant objects with another (which is held as state: m_othe...
A single anchor point contributed by an item, before it is registered with the helper.
PCB_INTERSECTABLE(BOARD_ITEM *aItem, INTERSECTABLE_GEOM aSeg)
INTERSECTABLE_GEOM Geometry
This file contains data structures that are saved in the project file or project local settings file ...
bool otherItems
Anything not fitting one of the above categories.
bool graphics
Graphic lines, shapes, polygons.
bool footprints
Allow selecting entire footprints.
bool text
Text (free or attached to a footprint)
static SNAP_CANDIDATE Point(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, const VECTOR2I &aPoint, double aResidual)
std::optional< SNAP_STABLE_ID > retainedId
Definition snap_frame.h:34
SNAP_RESOLVER::TRACE_CALLBACK trace
Definition snap_frame.h:39
SNAP_RESOLVER::FEASIBILITY_CALLBACK feasibility
Definition snap_frame.h:38
std::map< SNAP_STABLE_ID, Payload > presentation
Definition snap_frame.h:35
SNAP_SOURCE_CONTEXT context
Definition snap_frame.h:32
std::vector< SNAP_CANDIDATE > candidates
Definition snap_frame.h:33
double rankingHysteresis
Definition snap_frame.h:37
std::vector< SNAP_STABLE_ID > stickyIds
Definition snap_frame.h:36
std::optional< SNAP_FRAME_PRESENTATION< Payload > > presentation
Definition snap_frame.h:55
SNAP_RESULT result
Definition snap_frame.h:54
std::optional< VECTOR2I > stationarySourceLeg
std::optional< BOX2I > movingBounds
std::optional< VECTOR2I > movingReferencePoint
std::optional< SNAP_STABLE_ID > movingItem
std::vector< SNAP_STABLE_ID > stationarySelfFeatures
SNAP_EDITOR_PROFILE profile
SNAP_REFERENCE_PREFERENCE referencePreference
SNAP_TARGET_ID target
VECTOR2I center
VECTOR2I end
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:225
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:81
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:99
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:96
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:90
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:97
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:104
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:86
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:101
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:85
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:82
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:83
@ PCB_MARKER_T
class PCB_MARKER, a marker used to show something
Definition typeinfo.h:92
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:94
@ PCB_TARGET_T
class PCB_TARGET, a target (graphic item)
Definition typeinfo.h:100
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:79
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:95
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:80
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:91
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:93
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition typeinfo.h:87
@ PCB_POINT_T
class PCB_POINT, a 0-dimensional point
Definition typeinfo.h:106
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:89
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:98
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682