KiCad PCB EDA Suite
Loading...
Searching...
No Matches
connectivity_algo.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) 2016-2018 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Tomasz Wlostowski <[email protected]>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
23
24#include <algorithm>
25#include <future>
26#include <limits>
27#include <mutex>
28#include <ranges>
29
31#include <core/union_find.h>
32#include <progress_reporter.h>
34#include <board.h>
35#include <board_commit.h>
36#include <thread_pool.h>
37#include <footprint.h>
38#include <pad.h>
39#include <pcb_shape.h>
40#include <pcb_track.h>
41
42#include <wx/log.h>
43
44#ifdef PROFILE
45#include <core/profile.h>
46#endif
47
48
50{
51 bool anythingDeleted = false;
52 markItemNetAsDirty( aItem );
53
54 switch( aItem->Type() )
55 {
56 case PCB_FOOTPRINT_T:
57 for( PAD* pad : static_cast<FOOTPRINT*>( aItem )->Pads() )
58 {
59 if( m_itemMap.find( pad ) != m_itemMap.end() ) // prevent double deletion
60 {
61 m_itemMap[pad].MarkItemsAsInvalid();
62 m_itemMap.erase( pad );
63 anythingDeleted = true;
64 }
65 }
66
67 m_itemList.SetDirty( true );
68 break;
69
70 case PCB_PAD_T:
71 case PCB_TRACE_T:
72 case PCB_ARC_T:
73 case PCB_VIA_T:
74 case PCB_ZONE_T:
75 case PCB_SHAPE_T:
76 if( m_itemMap.find( aItem ) != m_itemMap.end() ) // prevent double deletion
77 {
78 m_itemMap[aItem].MarkItemsAsInvalid();
79 m_itemMap.erase ( aItem );
80 m_itemList.SetDirty( true );
81 anythingDeleted = true;
82 }
83 break;
84
85 default:
86 return false;
87 }
88
89
90 // Once we delete an item, it may connect between lists, so mark both as potentially invalid
91 if( anythingDeleted )
92 m_itemList.SetHasInvalid( true );
93
94 return true;
95}
96
97
99{
100 if( aItem->IsConnected() )
101 {
102 const BOARD_CONNECTED_ITEM* citem = static_cast<const BOARD_CONNECTED_ITEM*>( aItem );
103 MarkNetAsDirty( citem->GetNetCode() );
104 }
105 else
106 {
107 if( aItem->Type() == PCB_FOOTPRINT_T )
108 {
109 const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( aItem );
110
111 for( PAD* pad : footprint->Pads() )
112 MarkNetAsDirty( pad->GetNetCode() );
113 }
114 }
115}
116
117
119{
120 auto alreadyAdded =
121 [this]( BOARD_ITEM* item )
122 {
123 auto it = m_itemMap.find( item );
124
125 if( it == m_itemMap.end() )
126 return false;
127
128 // Don't be fooled by an empty ITEM_MAP_ENTRY auto-created by operator[].
129 return !it->second.GetItems().empty();
130 };
131
132 switch( aItem->Type() )
133 {
134 case PCB_NETINFO_T:
135 MarkNetAsDirty( static_cast<NETINFO_ITEM*>( aItem )->GetNetCode() );
136 break;
137
138 case PCB_FOOTPRINT_T:
139 {
140 if( static_cast<FOOTPRINT*>( aItem )->GetAttributes() & FP_JUST_ADDED )
141 return false;
142
143 for( PAD* pad : static_cast<FOOTPRINT*>( aItem )->Pads() )
144 {
145 if( !pad->IsOnCopperLayer() )
146 continue;
147
148 if( alreadyAdded( pad ) )
149 return false;
150
151 add( m_itemList, pad );
152 }
153
154 break;
155 }
156
157 case PCB_PAD_T:
158 {
159 if( !aItem->IsOnCopperLayer() )
160 return false;
161
162 if( FOOTPRINT* fp = aItem->GetParentFootprint() )
163 {
164 if( fp->GetAttributes() & FP_JUST_ADDED )
165 return false;
166 }
167
168 if( alreadyAdded( aItem ) )
169 return false;
170
171 add( m_itemList, static_cast<PAD*>( aItem ) );
172 break;
173 }
174
175 case PCB_TRACE_T:
176 if( alreadyAdded( aItem ) )
177 return false;
178
179 add( m_itemList, static_cast<PCB_TRACK*>( aItem ) );
180 break;
181
182 case PCB_ARC_T:
183 if( alreadyAdded( aItem ) )
184 return false;
185
186 add( m_itemList, static_cast<PCB_ARC*>( aItem ) );
187 break;
188
189 case PCB_VIA_T:
190 if( alreadyAdded( aItem ) )
191 return false;
192
193 add( m_itemList, static_cast<PCB_VIA*>( aItem ) );
194 break;
195
196 case PCB_SHAPE_T:
197 if( !aItem->IsOnCopperLayer() )
198 return false;
199
200 if( alreadyAdded( aItem ) )
201 return false;
202
203 if( !IsCopperLayer( aItem->GetLayer() ) )
204 return false;
205
206 add( m_itemList, static_cast<PCB_SHAPE*>( aItem ) );
207 break;
208
209 case PCB_ZONE_T:
210 {
211 if( !aItem->IsOnCopperLayer() )
212 return false;
213
214 ZONE* zone = static_cast<ZONE*>( aItem );
215
216 if( alreadyAdded( aItem ) )
217 return false;
218
219 m_itemMap[zone] = ITEM_MAP_ENTRY();
220
221 // Don't check for connections on layers that only exist in the zone but
222 // were disabled in the board
223 BOARD* board = zone->GetBoard();
224 LSET layerset = board->GetEnabledLayers() & zone->GetLayerSet();
225
226 layerset.RunOnLayers(
227 [&]( PCB_LAYER_ID layer )
228 {
229 for( CN_ITEM* zitem : m_itemList.Add( zone, layer ) )
230 m_itemMap[zone].Link( zitem );
231 } );
232
233 break;
234 }
235
236 default:
237 return false;
238 }
239
240 markItemNetAsDirty( aItem );
241
242 return true;
243}
244
245
247{
248 for( CN_ITEM* item : m_itemList )
249 item->RemoveInvalidRefs();
250}
251
252
254{
255 std::lock_guard lock( m_mutex );
256#ifdef PROFILE
257 PROF_TIMER garbage_collection( "garbage-collection" );
258#endif
259 std::vector<CN_ITEM*> garbage;
260 garbage.reserve( 1024 );
261
262 m_parentConnectivityData->RemoveInvalidRefs();
263
264 if( m_isLocal )
265 m_globalConnectivityData->RemoveInvalidRefs();
266
267 m_itemList.RemoveInvalidItems( garbage );
268
269 for( CN_ITEM* item : garbage )
270 delete item;
271
272#ifdef PROFILE
273 garbage_collection.Show();
274 PROF_TIMER search_basic( "search-basic" );
275#endif
276
278 std::vector<CN_ITEM*> dirtyItems;
279 std::copy_if( m_itemList.begin(), m_itemList.end(), std::back_inserter( dirtyItems ),
280 [] ( CN_ITEM* aItem )
281 {
282 return aItem->Dirty();
283 } );
284
286 {
287 m_progressReporter->SetMaxProgress( dirtyItems.size() );
288
289 if( !m_progressReporter->KeepRefreshing() )
290 return;
291 }
292
293 if( m_itemList.IsDirty() )
294 {
295 // Collect deferred net code changes to avoid data races in parallel search.
296 // Vias connected to zones have their net codes updated after all parallel work
297 // completes, but only if the via has no higher-priority connections (tracks, pads).
298 std::vector<std::pair<CN_ITEM*, int>> deferredNetCodes;
299 std::mutex deferredNetCodesMutex;
300
301 // One task per item made the queue and its futures cost more than the searches they
302 // carried, so hand the pool blocks of items instead.
303 auto returns = tp.submit_loop( size_t( 0 ), dirtyItems.size(),
304 [&dirtyItems, this, &deferredNetCodes, &deferredNetCodesMutex]( const size_t ii )
305 {
306 if( m_progressReporter && m_progressReporter->IsCancelled() )
307 return;
308
309 CN_VISITOR visitor( dirtyItems[ii], &deferredNetCodes, &deferredNetCodesMutex );
310 m_itemList.FindNearby( dirtyItems[ii], visitor );
311
312 if( m_progressReporter )
313 m_progressReporter->AdvanceProgress();
314 } );
315
316 // Here we balance returns with a 250ms timeout to allow UI updating
317 while( !returns.wait_for( std::chrono::milliseconds( 250 ) ) )
318 {
320 m_progressReporter->KeepRefreshing();
321 }
322
323 // Apply deferred zone net changes, but only for vias that have no non-zone
324 // connections. Tracks and pads take priority over zones for net assignment;
325 // cluster-based propagation will handle those vias.
326 //
327 // A single via can touch zones of several different nets (e.g. a through via
328 // crossing a GND plane and a power plane). The order in which those candidate
329 // nets are collected depends on the parallel search and is not stable across
330 // connectivity rebuilds, so we must not simply pick the first one: doing so makes
331 // the via's net flip arbitrarily on every rebuild (i.e. on every undo/redo).
332 // Instead, if the via's existing net matches any zone it touches, we keep it.
333 // This preserves a deliberately-assigned net and only falls back to a
334 // deterministic choice (lowest net code) when the current net no longer touches
335 // any zone.
336 std::sort( deferredNetCodes.begin(), deferredNetCodes.end(),
337 []( const auto& a, const auto& b ) { return a.first < b.first; } );
338
339 for( auto it = deferredNetCodes.begin(); it != deferredNetCodes.end(); )
340 {
341 CN_ITEM* cnItem = it->first;
342
343 // Entries for the same via are contiguous after the sort above.
344 auto groupEnd = it;
345
346 while( groupEnd != deferredNetCodes.end() && groupEnd->first == cnItem )
347 ++groupEnd;
348
349 if( std::ranges::any_of( cnItem->ConnectedItems(),
350 []( const CN_ITEM* c )
351 {
352 return c->Parent()->Type() != PCB_ZONE_T;
353 } ) )
354 {
355 // Connected to a track or pad, so cluster propagation owns the net.
356 it = groupEnd;
357 continue;
358 }
359
360 int existingNet = cnItem->Parent()->GetNetCode();
361 bool keepExisting = false;
362 int bestNet = std::numeric_limits<int>::max();
363
364 for( auto entry = it; entry != groupEnd; ++entry )
365 {
366 if( entry->second == existingNet )
367 {
368 keepExisting = true;
369 break;
370 }
371
372 bestNet = std::min( bestNet, entry->second );
373 }
374
375 if( !keepExisting )
376 cnItem->Parent()->SetNetCode( bestNet );
377
378 it = groupEnd;
379 }
380
382 m_progressReporter->KeepRefreshing();
383 }
384
385#ifdef PROFILE
386 search_basic.Show();
387#endif
388
389 m_itemList.ClearDirtyFlags();
390}
391
392
397
398
400CN_CONNECTIVITY_ALGO::SearchClusters( CLUSTER_SEARCH_MODE aMode, bool aExcludeZones, int aSingleNet )
401{
402 bool withinAnyNet = ( aMode != CSM_PROPAGATE );
403
404 CLUSTERS clusters;
405
406 if( m_itemList.IsDirty() )
408
409 // Numbering stays local rather than stamped on the items because several DRC threads can
410 // be inside this function at once, each with a different view of who takes part
411 std::vector<CN_ITEM*> members;
412 std::vector<int> memberOf( m_itemList.Size(), -1 );
413
414 members.reserve( m_itemList.Size() );
415
416 // aSingleNet restricts which items may seed a cluster, not which may be reached, since
417 // propagation mode crosses nets once started. Hold the net test back for the emit
418 std::vector<bool> selected;
419 selected.reserve( m_itemList.Size() );
420
421 for( CN_ITEM* item : m_itemList )
422 {
423 bool participates = item->Valid()
424 && !( withinAnyNet && item->Net() <= 0 )
425 && !( withinAnyNet && aSingleNet >= 0 && item->Net() != aSingleNet )
426 && !( aExcludeZones && item->Parent()->Type() == PCB_ZONE_T );
427
428 if( participates )
429 {
430 memberOf[item->ListIndex()] = static_cast<int>( members.size() );
431 members.push_back( item );
432 selected.push_back( aSingleNet < 0 || item->Net() == aSingleNet );
433 }
434 }
435
436 if( m_progressReporter && m_progressReporter->IsCancelled() )
437 return CLUSTERS();
438
439 // Every member of a cluster carries the cluster's net, so gating a neighbour on the near
440 // end's net selects the same edges as gating it on the component root's
441 KI_UNION_FIND forest( members.size() );
442
443 // Stays serial although Unite() is lock-free, because DRC enters here from inside a
444 // thread pool task and feeding work back to a pool we already occupy deadlocks it
445 for( size_t ii = 0; ii < members.size(); ++ii )
446 {
447 CN_ITEM* item = members[ii];
448
449 for( CN_ITEM* neighbour : item->ConnectedItems() )
450 {
451 int listIndex = neighbour->ListIndex();
452
453 // An adjacency that outlived its item would otherwise index the map out of range
454 if( listIndex < 0 || listIndex >= (int) memberOf.size() )
455 continue;
456
457 int index = memberOf[listIndex];
458
459 if( index < 0 )
460 continue;
461
462 if( withinAnyNet && neighbour->Net() != item->Net() )
463 continue;
464
465 forest.Unite( ii, static_cast<size_t>( index ) );
466 }
467 }
468
469 if( m_progressReporter && m_progressReporter->IsCancelled() )
470 return CLUSTERS();
471
472 // Index order makes cluster contents a function of the item list alone, so the origin pad
473 // elected on a cluster spanning several nets no longer follows the heap layout
474 std::vector<int> clusterOf( members.size(), -1 );
475 std::vector<bool> clusterSelected;
476
477 for( size_t ii = 0; ii < members.size(); ++ii )
478 {
479 size_t root = forest.FindCompress( ii );
480
481 if( clusterOf[root] < 0 )
482 {
483 clusterOf[root] = static_cast<int>( clusters.size() );
484 clusters.push_back( std::make_shared<CN_CLUSTER>() );
485 clusterSelected.push_back( false );
486 }
487
488 clusters[clusterOf[root]]->Add( members[ii] );
489
490 if( selected[ii] )
491 clusterSelected[clusterOf[root]] = true;
492 }
493
494 // A component reached only from items outside aSingleNet was never a result
495 if( aSingleNet >= 0 )
496 {
497 CLUSTERS keep;
498
499 for( size_t ii = 0; ii < clusters.size(); ++ii )
500 {
501 if( clusterSelected[ii] )
502 keep.push_back( std::move( clusters[ii] ) );
503 }
504
505 clusters = std::move( keep );
506 }
507
508 std::sort( clusters.begin(), clusters.end(),
509 []( const std::shared_ptr<CN_CLUSTER>& a, const std::shared_ptr<CN_CLUSTER>& b )
510 {
511 return a->OriginNet() < b->OriginNet();
512 } );
513
514 return clusters;
515}
516
517
519{
520 // Nothing queries the index until searchConnections(), so index the board in one packed
521 // load rather than run the R*-tree insertion heuristic once per item
523
524 // Generate CN_ZONE_LAYERs for each island on each layer of each zone
525 //
526 std::vector<CN_ZONE_LAYER*> zitems;
527
528 for( ZONE* zone : aBoard->Zones() )
529 {
530 if( zone->IsOnCopperLayer() )
531 {
532 m_itemMap[zone] = ITEM_MAP_ENTRY();
533 markItemNetAsDirty( zone );
534
535 // Don't check for connections on layers that only exist in the zone but
536 // were disabled in the board
537 BOARD* board = zone->GetBoard();
538 LSET layerset = board->GetEnabledLayers() & zone->GetLayerSet() & LSET::AllCuMask();
539
540 layerset.RunOnLayers(
541 [&]( PCB_LAYER_ID layer )
542 {
543 for( int j = 0; j < zone->GetFilledPolysList( layer )->OutlineCount(); j++ )
544 zitems.push_back( new CN_ZONE_LAYER( zone, layer, j ) );
545 } );
546 }
547 }
548
549 // Setup progress metrics
550 //
551 int progressDelta = 50;
552 double size = 0.0;
553
554 size += zitems.size(); // Once for building RTrees
555 size += zitems.size(); // Once for adding to connectivity
556 size += aBoard->Tracks().size();
557 size += aBoard->Drawings().size();
558
559 for( FOOTPRINT* footprint : aBoard->Footprints() )
560 size += footprint->Pads().size();
561
562 size *= 1.5; // Our caller gets the other third of the progress bar
563
564 progressDelta = std::max( progressDelta, (int) size / 4 );
565
566 auto report =
567 [&]( int progress )
568 {
569 if( aReporter && ( progress % progressDelta ) == 0 )
570 {
571 aReporter->SetCurrentProgress( progress / size );
572 aReporter->KeepRefreshing( false );
573 }
574 };
575
576 // Generate RTrees for CN_ZONE_LAYER items (in parallel)
577 //
579 std::vector<std::future<size_t>> returns( zitems.size() );
580
581 auto cache_zones =
582 [aReporter]( CN_ZONE_LAYER* aZoneLayer ) -> size_t
583 {
584 if( aReporter && aReporter->IsCancelled() )
585 return 0;
586
587 aZoneLayer->BuildRTree();
588
589 if( aReporter )
590 aReporter->AdvanceProgress();
591
592 return 1;
593 };
594
595 for( size_t ii = 0; ii < zitems.size(); ++ii )
596 {
597 CN_ZONE_LAYER* ptr = zitems[ii];
598 returns[ii] = tp.submit_task(
599 [cache_zones, ptr] { return cache_zones( ptr ); } );
600 }
601
602 for( const std::future<size_t>& ret : returns )
603 {
604 std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
605
606 while( status != std::future_status::ready )
607 {
608 if( aReporter )
609 aReporter->KeepRefreshing();
610
611 status = ret.wait_for( std::chrono::milliseconds( 250 ) );
612 }
613
614 }
615
616 // Add CN_ZONE_LAYERS, tracks, and pads to connectivity
617 //
618 int ii = zitems.size();
619
620 for( CN_ZONE_LAYER* zitem : zitems )
621 {
622 m_itemList.Add( zitem );
623 m_itemMap[ zitem->Parent() ].Link( zitem );
624 report( ++ii );
625 }
626
627 for( PCB_TRACK* tv : aBoard->Tracks() )
628 {
629 Add( tv );
630 report( ++ii );
631 }
632
633 for( FOOTPRINT* footprint : aBoard->Footprints() )
634 {
635 for( PAD* pad : footprint->Pads() )
636 {
637 Add( pad );
638 report( ++ii );
639 }
640 }
641
642 for( BOARD_ITEM* drawing : aBoard->Drawings() )
643 {
644 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( drawing ) )
645 {
646 if( shape->IsOnCopperLayer() )
647 Add( shape );
648 }
649
650 report( ++ii );
651 }
652
653 if( aReporter )
654 {
655 aReporter->SetCurrentProgress( (double) ii / (double) size );
656 aReporter->KeepRefreshing( false );
657 }
658}
659
660
661void CN_CONNECTIVITY_ALGO::LocalBuild( const std::shared_ptr<CONNECTIVITY_DATA>& aGlobalConnectivity,
662 const std::vector<BOARD_ITEM*>& aLocalItems )
663{
664 m_isLocal = true;
665 m_globalConnectivityData = aGlobalConnectivity;
666
667 for( BOARD_ITEM* item : aLocalItems )
668 {
669 switch( item->Type() )
670 {
671 case PCB_TRACE_T:
672 case PCB_ARC_T:
673 case PCB_VIA_T:
674 case PCB_PAD_T:
675 case PCB_FOOTPRINT_T:
676 case PCB_SHAPE_T:
677 Add( item );
678 break;
679
680 default:
681 break;
682 }
683 }
684}
685
686
688{
689 for( const std::shared_ptr<CN_CLUSTER>& cluster : m_connClusters )
690 {
691 if( cluster->IsConflicting() )
692 {
693 // Conflicting pads in cluster: we don't know the user's intent so best to do
694 // nothing.
695 wxLogTrace( wxT( "CN" ), wxT( "Conflicting pads in cluster %p; skipping propagation" ),
696 cluster.get() );
697 }
698 else if( cluster->HasValidNet() )
699 {
700 // Propagate from the origin (will be a pad if there are any, or another item if
701 // there are no pads).
702 int n_changed = 0;
703
704 for( CN_ITEM* item : *cluster )
705 {
706 if( item->Valid() && item->CanChangeNet()
707 && item->Parent()->GetNetCode() != cluster->OriginNet() )
708 {
709 MarkNetAsDirty( item->Parent()->GetNetCode() );
710 MarkNetAsDirty( cluster->OriginNet() );
711
712 if( aCommit )
713 aCommit->Modify( item->Parent() );
714
715 item->Parent()->SetNetCode( cluster->OriginNet() );
716 n_changed++;
717 }
718 }
719
720 if( n_changed )
721 {
722 wxLogTrace( wxT( "CN" ), wxT( "Cluster %p: net: %d %s" ),
723 cluster.get(),
724 cluster->OriginNet(),
725 (const char*) cluster->OriginNetName().c_str() );
726 }
727 else
728 {
729 wxLogTrace( wxT( "CN" ), wxT( "Cluster %p: no changeable items to propagate to" ),
730 cluster.get() );
731 }
732 }
733 else
734 {
735 wxLogTrace( wxT( "CN" ), wxT( "Cluster %p: connected to unused net" ),
736 cluster.get() );
737 }
738 }
739}
740
741
748
749
750void CN_CONNECTIVITY_ALGO::FillIsolatedIslandsMap( std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>>& aMap,
751 bool aConnectivityAlreadyRebuilt )
752{
753 int progressDelta = 50;
754 int ii = 0;
755
756 progressDelta = std::max( progressDelta, (int) aMap.size() / 4 );
757
758 if( !aConnectivityAlreadyRebuilt )
759 {
760 for( const auto& [ zone, islands ] : aMap )
761 {
762 Remove( zone );
763 Add( zone );
764 ii++;
765
766 if( m_progressReporter && ( ii % progressDelta ) == 0 )
767 {
768 m_progressReporter->SetCurrentProgress( (double) ii / (double) aMap.size() );
769 m_progressReporter->KeepRefreshing( false );
770 }
771
772 if( m_progressReporter && m_progressReporter->IsCancelled() )
773 return;
774 }
775 }
776
778
779 // Bucket the zone items in one pass. A search per zone layer is O(zone layers x items).
780 // Keep the cluster and item order, which sets the recorded outline order.
781 struct ZONE_CLUSTER_ITEM
782 {
783 CN_ZONE_LAYER* m_item;
784 bool m_orphaned;
785 };
786
787 std::unordered_map<const BOARD_ITEM*, std::map<PCB_LAYER_ID, std::vector<ZONE_CLUSTER_ITEM>>>
788 zoneItems;
789
790 for( const auto& [ zone, zoneIslands ] : aMap )
791 zoneItems[zone];
792
793 for( const std::shared_ptr<CN_CLUSTER>& cluster : m_connClusters )
794 {
795 const bool orphaned = cluster->IsOrphaned();
796
797 for( CN_ITEM* item : *cluster )
798 {
799 auto it = zoneItems.find( item->Parent() );
800
801 if( it != zoneItems.end() )
802 {
803 it->second[item->GetBoardLayer()].push_back(
804 { static_cast<CN_ZONE_LAYER*>( item ), orphaned } );
805 }
806 }
807 }
808
809 for( auto& [ zone, zoneIslands ] : aMap )
810 {
811 const auto& layerItems = zoneItems[zone];
812
813 for( auto& [ layer, layerIslands ] : zoneIslands )
814 {
815 if( zone->GetFilledPolysList( layer )->IsEmpty() )
816 continue;
817
818 auto layerIt = layerItems.find( layer );
819 bool notInConnectivity = layerIt == layerItems.end();
820
821 if( !notInConnectivity )
822 {
823 for( const ZONE_CLUSTER_ITEM& entry : layerIt->second )
824 {
825 if( entry.m_orphaned )
826 layerIslands.m_IsolatedOutlines.push_back( entry.m_item->SubpolyIndex() );
827 else if( entry.m_item->HasSingleConnection() )
828 layerIslands.m_SingleConnectionOutlines.push_back( entry.m_item->SubpolyIndex() );
829 }
830 }
831
832 // Non-copper zones (silk, mask, etc.) are never added to the connectivity graph,
833 // so notInConnectivity is always true for them. Without the IsCopperLayer guard
834 // outline 0 of every non-copper multi-island fill gets dropped on every refill
835 // (issue 24089).
836 if( notInConnectivity && IsCopperLayer( layer ) )
837 layerIslands.m_IsolatedOutlines.push_back( 0 );
838 }
839 }
840}
841
842
848
849
851{
852 if( aNet < 0 )
853 return;
854
855 if( (int) m_dirtyNets.size() <= aNet )
856 {
857 int lastNet = m_dirtyNets.size() - 1;
858
859 if( lastNet < 0 )
860 lastNet = 0;
861
862 m_dirtyNets.resize( aNet + 1 );
863
864 for( int i = lastNet; i < aNet + 1; i++ )
865 m_dirtyNets[i] = true;
866 }
867
868 m_dirtyNets[aNet] = true;
869}
870
871
873{
874 PCB_LAYER_ID layer = aZoneLayer->GetLayer();
875 BOARD_CONNECTED_ITEM* item = aItem->Parent();
876
877 if( !item->IsOnLayer( layer ) )
878 return;
879
880 auto connect =
881 [&]()
882 {
883 // We don't propagate nets from zones, so via-zone net changes are deferred
884 // and applied only if the via has no higher-priority connections (tracks, pads).
885 if( aItem->Parent()->Type() == PCB_VIA_T && aItem->CanChangeNet() )
886 {
887 std::lock_guard<std::mutex> lock( *m_deferredNetCodesMutex );
888 m_deferredNetCodes->emplace_back( aItem, aZoneLayer->Net() );
889 }
890
891 aZoneLayer->Connect( aItem );
892 aItem->Connect( aZoneLayer );
893 };
894
895 // Try quick checks first...
896 if( item->Type() == PCB_PAD_T )
897 {
898 PAD* pad = static_cast<PAD*>( item );
899
900 if( pad->ConditionallyFlashed( layer )
901 && pad->GetZoneLayerOverride( layer ) == ZLO_FORCE_NO_ZONE_CONNECTION )
902 {
903 return;
904 }
905
906 // Don't connect zones to pads on backdrilled or post-machined layers
907 if( pad->IsBackdrilledOrPostMachined( layer ) )
908 return;
909 }
910 else if( item->Type() == PCB_VIA_T )
911 {
912 PCB_VIA* via = static_cast<PCB_VIA*>( item );
913
914 if( via->ConditionallyFlashed( layer )
915 && via->GetZoneLayerOverride( layer ) == ZLO_FORCE_NO_ZONE_CONNECTION )
916 {
917 return;
918 }
919
920 // Don't connect zones to vias on backdrilled or post-machined layers
921 if( via->IsBackdrilledOrPostMachined( layer ) )
922 return;
923 }
924
925 for( int i = 0; i < aItem->AnchorCount(); ++i )
926 {
927 if( aZoneLayer->ContainsPoint( aItem->GetAnchor( i ) ) )
928 {
929 connect();
930 return;
931 }
932 }
933
934 if( item->Type() == PCB_VIA_T || item->Type() == PCB_PAD_T )
935 {
936 // As long as the pad/via crosses the zone layer, check for the full effective shape
937 // We check for the overlapping layers above
938 if( aZoneLayer->Collide( item->GetEffectiveShape( layer, FLASHING::ALWAYS_FLASHED ).get() ) )
939 connect();
940
941 return;
942 }
943
944 if( aZoneLayer->Collide( item->GetEffectiveShape( layer ).get() ) )
945 connect();
946}
947
949{
950 // CN_ZONE_LAYER now caches its own copy of the outline, so we just check if it's non-empty.
951 if( !aZoneLayerA->HasValidOutline() || !aZoneLayerB->HasValidOutline() )
952 return;
953
954 const BOX2I& boxA = aZoneLayerA->BBox();
955 const BOX2I& boxB = aZoneLayerB->BBox();
956
957 PCB_LAYER_ID layer = aZoneLayerA->GetLayer();
958
959 if( aZoneLayerB->GetLayer() != layer )
960 return;
961
962 if( !boxA.Intersects( boxB ) )
963 return;
964
965 const SHAPE_LINE_CHAIN& outlineA = aZoneLayerA->GetOutline();
966
967 for( int i = 0; i < outlineA.PointCount(); i++ )
968 {
969 const VECTOR2I& pt = outlineA.CPoint( i );
970
971 if( !boxB.Contains( pt ) )
972 continue;
973
974 if( aZoneLayerB->ContainsPoint( pt ) )
975 {
976 aZoneLayerA->Connect( aZoneLayerB );
977 aZoneLayerB->Connect( aZoneLayerA );
978 return;
979 }
980 }
981
982 const SHAPE_LINE_CHAIN& outlineB = aZoneLayerB->GetOutline();
983
984 for( int i = 0; i < outlineB.PointCount(); i++ )
985 {
986 const VECTOR2I& pt = outlineB.CPoint( i );
987
988 if( !boxA.Contains( pt ) )
989 continue;
990
991 if( aZoneLayerA->ContainsPoint( pt ) )
992 {
993 aZoneLayerA->Connect( aZoneLayerB );
994 aZoneLayerB->Connect( aZoneLayerA );
995 return;
996 }
997 }
998}
999
1000
1002{
1003 const BOARD_CONNECTED_ITEM* parentA = aCandidate->Parent();
1004 const BOARD_CONNECTED_ITEM* parentB = m_item->Parent();
1005
1006 if( !aCandidate->Valid() || !m_item->Valid() )
1007 return true;
1008
1009 if( parentA == parentB )
1010 return true;
1011
1012 // Don't connect items in different nets that can't be changed
1013 if( !aCandidate->CanChangeNet() && !m_item->CanChangeNet() && aCandidate->Net() != m_item->Net() )
1014 return true;
1015
1016 // If both m_item and aCandidate are marked dirty, they will both be searched
1017 // Since we are reciprocal in our connection, we arbitrarily pick one of the connections
1018 // to conduct the expensive search
1019 if( aCandidate->Dirty() && aCandidate < m_item )
1020 return true;
1021
1022 // We should handle zone-zone connection separately
1023 if ( parentA->Type() == PCB_ZONE_T && parentB->Type() == PCB_ZONE_T )
1024 {
1026 static_cast<CN_ZONE_LAYER*>( aCandidate ) );
1027 return true;
1028 }
1029
1030 if( parentA->Type() == PCB_ZONE_T )
1031 {
1032 checkZoneItemConnection( static_cast<CN_ZONE_LAYER*>( aCandidate ), m_item );
1033 return true;
1034 }
1035
1036 if( parentB->Type() == PCB_ZONE_T )
1037 {
1038 checkZoneItemConnection( static_cast<CN_ZONE_LAYER*>( m_item ), aCandidate );
1039 return true;
1040 }
1041
1042 LSET commonLayers = parentA->GetLayerSet() & parentB->GetLayerSet();
1043
1044 if( const BOARD* board = parentA->GetBoard() )
1045 commonLayers &= board->GetEnabledLayers();
1046
1047 for( PCB_LAYER_ID layer : commonLayers )
1048 {
1051
1052 if( parentA->Type() == PCB_PAD_T )
1053 {
1054 if( !static_cast<const PAD*>( parentA )->ConditionallyFlashed( layer ) )
1055 flashingA = FLASHING::ALWAYS_FLASHED;
1056 }
1057 else if( parentA->Type() == PCB_VIA_T )
1058 {
1059 if( !static_cast<const PCB_VIA*>( parentA )->ConditionallyFlashed( layer ) )
1060 flashingA = FLASHING::ALWAYS_FLASHED;
1061 }
1062
1063 if( parentB->Type() == PCB_PAD_T )
1064 {
1065 if( !static_cast<const PAD*>( parentB )->ConditionallyFlashed( layer ) )
1066 flashingB = FLASHING::ALWAYS_FLASHED;
1067 }
1068 else if( parentB->Type() == PCB_VIA_T )
1069 {
1070 if( !static_cast<const PCB_VIA*>( parentB )->ConditionallyFlashed( layer ) )
1071 flashingB = FLASHING::ALWAYS_FLASHED;
1072 }
1073
1074 if( parentA->GetEffectiveShape( layer, flashingA )->Collide(
1075 parentB->GetEffectiveShape( layer, flashingB ).get() ) )
1076 {
1077 m_item->Connect( aCandidate );
1078 aCandidate->Connect( m_item );
1079 return true;
1080 }
1081 }
1082
1083 return true;
1084};
1085
1086
1088{
1089 m_ratsnestClusters.clear();
1090 m_connClusters.clear();
1091 m_itemMap.clear();
1092 m_itemList.Clear();
1093
1094}
1095
1100
1101
1103{
1104 // Map of footprint -> map of pad number -> list of CN_ITEMs for pads with that number
1105 std::map<FOOTPRINT*, std::map<wxString, std::vector<CN_ITEM*>>> padsByFootprint;
1106
1107 for( CN_ITEM* item : m_itemList )
1108 {
1109 if( !item->Valid() || item->Parent()->Type() != PCB_PAD_T )
1110 continue;
1111
1112 auto pad = static_cast<const PAD*>( item->Parent() );
1113
1114 FOOTPRINT* fp = pad->GetParentFootprint();
1115
1116 padsByFootprint[fp][ pad->GetNumber() ].emplace_back( item );
1117 }
1118
1119 for( auto& [footprint, padsMap] : padsByFootprint )
1120 {
1121 if( footprint->GetDuplicatePadNumbersAreJumpers() )
1122 {
1123 for( const std::vector<CN_ITEM*>& padsList : padsMap | std::views::values )
1124 {
1125 for( size_t i = 0; i < padsList.size(); ++i )
1126 {
1127 for( size_t j = 1; j < padsList.size(); ++j )
1128 {
1129 padsList[i]->Connect( padsList[j] );
1130 padsList[j]->Connect( padsList[i] );
1131 }
1132 }
1133 }
1134 }
1135
1136 for( const std::set<wxString>& group : footprint->JumperPadGroups() )
1137 {
1138 std::vector<CN_ITEM*> toConnect;
1139
1140 for( const wxString& padNumber : group )
1141 std::ranges::copy( padsMap[padNumber], std::back_inserter( toConnect ) );
1142
1143 for( size_t i = 0; i < toConnect.size(); ++i )
1144 {
1145 for( size_t j = 1; j < toConnect.size(); ++j )
1146 {
1147 toConnect[i]->Connect( toConnect[j] );
1148 toConnect[j]->Connect( toConnect[i] );
1149 }
1150 }
1151 }
1152 }
1153}
int index
@ ZLO_FORCE_NO_ZONE_CONNECTION
Definition board_item.h:75
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
virtual bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual bool IsConnected() const
Returns information if the object is derived from BOARD_CONNECTED_ITEM.
Definition board_item.h:172
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
virtual bool IsOnLayer(PCB_LAYER_ID aLayer) const
Test to see if this object is on the given layer.
Definition board_item.h:408
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
FOOTPRINT * GetParentFootprint() const
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition board_item.h:346
virtual bool IsOnCopperLayer() const
Definition board_item.h:189
virtual std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, FLASHING aFlash=FLASHING::DEFAULT, DRC_CONSTRAINT_T aUsage=NULL_CONSTRAINT) const
Some pad shapes can be complex (rounded/chamfered rectangle), even without considering custom shapes.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
const ZONES & Zones() const
Definition board.h:467
const FOOTPRINTS & Footprints() const
Definition board.h:463
const TRACKS & Tracks() const
Definition board.h:461
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1183
const DRAWINGS & Drawings() const
Definition board.h:465
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:165
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:308
void FillIsolatedIslandsMap(std::map< ZONE *, std::map< PCB_LAYER_ID, ISOLATED_ISLANDS > > &aMap, bool aConnectivityAlreadyRebuilt)
Fill in the isolated islands map with copper islands that are not connected to a net.
bool Remove(BOARD_ITEM *aItem)
CONNECTIVITY_DATA * m_parentConnectivityData
void add(Container &c, BItem brditem)
PROGRESS_REPORTER * m_progressReporter
std::vector< std::shared_ptr< CN_CLUSTER > > m_connClusters
void propagateConnections(BOARD_COMMIT *aCommit=nullptr)
const CLUSTERS & GetClusters()
void LocalBuild(const std::shared_ptr< CONNECTIVITY_DATA > &aGlobalConnectivity, const std::vector< BOARD_ITEM * > &aLocalItems)
const CLUSTERS SearchClusters(CLUSTER_SEARCH_MODE aMode, bool aExcludeZones, int aSingleNet)
void markItemNetAsDirty(const BOARD_ITEM *aItem)
std::vector< std::shared_ptr< CN_CLUSTER > > m_ratsnestClusters
void PropagateNets(BOARD_COMMIT *aCommit=nullptr)
Propagate nets from pads to other items in clusters.
std::shared_ptr< CONNECTIVITY_DATA > m_globalConnectivityData
std::vector< bool > m_dirtyNets
std::unordered_map< const BOARD_ITEM *, ITEM_MAP_ENTRY > m_itemMap
void SetProgressReporter(PROGRESS_REPORTER *aReporter)
std::vector< std::shared_ptr< CN_CLUSTER > > CLUSTERS
void Build(BOARD *aBoard, PROGRESS_REPORTER *aReporter=nullptr)
bool Add(BOARD_ITEM *aItem)
CN_ITEM represents a BOARD_CONNETED_ITEM in the connectivity system (ie: a pad, track/arc/via,...
void Connect(CN_ITEM *b)
const BOX2I & BBox()
virtual int AnchorCount() const
const std::vector< CN_ITEM * > & ConnectedItems() const
int Net() const
bool Valid() const
virtual const VECTOR2I GetAnchor(int n) const
bool CanChangeNet() const
bool Dirty() const
BOARD_CONNECTED_ITEM * Parent() const
Hold bulk mode for a scope.
void checkZoneItemConnection(CN_ZONE_LAYER *aZoneLayer, CN_ITEM *aItem)
CN_ITEM * m_item
The item we are looking for connections to.
void checkZoneZoneConnection(CN_ZONE_LAYER *aZoneLayerA, CN_ZONE_LAYER *aZoneLayerB)
std::vector< std::pair< CN_ITEM *, int > > * m_deferredNetCodes
Deferred net code changes collected during parallel connectivity search.
std::mutex * m_deferredNetCodesMutex
bool operator()(CN_ITEM *aCandidate)
const SHAPE_LINE_CHAIN & GetOutline() const
PCB_LAYER_ID GetLayer() const
bool Collide(SHAPE *aRefShape) const
bool ContainsPoint(const VECTOR2I &p) const
bool HasValidOutline() const
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
std::deque< PAD * > & Pads()
Definition footprint.h:404
Lock-free disjoint-set over a dense range of indices.
Definition union_find.h:48
size_t FindCompress(size_t aX)
Shorten the path from aX to its root so that later queries walk less of it.
Definition union_find.h:150
bool Unite(size_t aA, size_t aB)
Merge the components that hold aA and aB.
Definition union_find.h:82
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
void RunOnLayers(const std::function< void(PCB_LAYER_ID)> &aFunction) const
Execute a function on each layer of the LSET.
Definition lset.h:263
Handle the data for a net.
Definition netinfo.h:50
Definition pad.h:61
A small class to help profiling.
Definition profile.h:46
void Show(std::ostream &aStream=std::cerr)
Print the elapsed time (in a suitable unit) to a stream.
Definition profile.h:103
A progress reporter interface for use in multi-threaded environments.
virtual bool IsCancelled() const =0
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void AdvanceProgress()=0
Increment the progress bar length (inside the current virtual zone).
virtual void SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition shape.h:179
Handle a list of polygons defining a copper zone.
Definition zone.h:70
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:133
@ FP_JUST_ADDED
Definition footprint.h:90
a few functions useful in geometry calculations.
FLASHING
Enum used during connectivity building to ensure we do not query connectivity while building the data...
Definition layer_ids.h:180
@ NEVER_FLASHED
Never flashed for connectivity.
Definition layer_ids.h:183
@ ALWAYS_FLASHED
Always flashed for connectivity.
Definition layer_ids.h:182
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:27
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:100
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ PCB_NETINFO_T
class NETINFO_ITEM, a description of a net
Definition typeinfo.h:102
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683