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 <chrono>
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 // Extra blocks let the pool balance islands with different fill sizes
580 auto returns = tp.submit_loop( size_t( 0 ), zitems.size(),
581 [aReporter, &zitems]( const size_t ii )
582 {
583 try
584 {
585 if( aReporter && aReporter->IsCancelled() )
586 return;
587
588 zitems[ii]->BuildRTree();
589
590 if( aReporter )
591 aReporter->AdvanceProgress();
592 }
593 catch( ... )
594 {
595 // Preserve per-island failure isolation within a shared task
596 }
597 }, 4 * tp.get_thread_count() );
598
599 try
600 {
601 while( !returns.wait_for( std::chrono::milliseconds( 250 ) ) )
602 {
603 if( aReporter )
604 aReporter->KeepRefreshing();
605 }
606 }
607 catch( ... )
608 {
609 // Workers must release zitems before this frame unwinds
610 returns.wait();
611 throw;
612 }
613
614 // Add CN_ZONE_LAYERS, tracks, and pads to connectivity
615 //
616 int ii = zitems.size();
617
618 for( CN_ZONE_LAYER* zitem : zitems )
619 {
620 m_itemList.Add( zitem );
621 m_itemMap[ zitem->Parent() ].Link( zitem );
622 report( ++ii );
623 }
624
625 for( PCB_TRACK* tv : aBoard->Tracks() )
626 {
627 Add( tv );
628 report( ++ii );
629 }
630
631 for( FOOTPRINT* footprint : aBoard->Footprints() )
632 {
633 for( PAD* pad : footprint->Pads() )
634 {
635 Add( pad );
636 report( ++ii );
637 }
638 }
639
640 for( BOARD_ITEM* drawing : aBoard->Drawings() )
641 {
642 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( drawing ) )
643 {
644 if( shape->IsOnCopperLayer() )
645 Add( shape );
646 }
647
648 report( ++ii );
649 }
650
651 if( aReporter )
652 {
653 aReporter->SetCurrentProgress( (double) ii / (double) size );
654 aReporter->KeepRefreshing( false );
655 }
656}
657
658
659void CN_CONNECTIVITY_ALGO::LocalBuild( const std::shared_ptr<CONNECTIVITY_DATA>& aGlobalConnectivity,
660 const std::vector<BOARD_ITEM*>& aLocalItems )
661{
662 m_isLocal = true;
663 m_globalConnectivityData = aGlobalConnectivity;
664
665 for( BOARD_ITEM* item : aLocalItems )
666 {
667 switch( item->Type() )
668 {
669 case PCB_TRACE_T:
670 case PCB_ARC_T:
671 case PCB_VIA_T:
672 case PCB_PAD_T:
673 case PCB_FOOTPRINT_T:
674 case PCB_SHAPE_T:
675 Add( item );
676 break;
677
678 default:
679 break;
680 }
681 }
682}
683
684
686{
687 for( const std::shared_ptr<CN_CLUSTER>& cluster : m_connClusters )
688 {
689 if( cluster->IsConflicting() )
690 {
691 // Conflicting pads in cluster: we don't know the user's intent so best to do
692 // nothing.
693 wxLogTrace( wxT( "CN" ), wxT( "Conflicting pads in cluster %p; skipping propagation" ),
694 cluster.get() );
695 }
696 else if( cluster->HasValidNet() )
697 {
698 // Propagate from the origin (will be a pad if there are any, or another item if
699 // there are no pads).
700 int n_changed = 0;
701
702 for( CN_ITEM* item : *cluster )
703 {
704 if( item->Valid() && item->CanChangeNet()
705 && item->Parent()->GetNetCode() != cluster->OriginNet() )
706 {
707 MarkNetAsDirty( item->Parent()->GetNetCode() );
708 MarkNetAsDirty( cluster->OriginNet() );
709
710 if( aCommit )
711 aCommit->Modify( item->Parent() );
712
713 item->Parent()->SetNetCode( cluster->OriginNet() );
714 n_changed++;
715 }
716 }
717
718 if( n_changed )
719 {
720 wxLogTrace( wxT( "CN" ), wxT( "Cluster %p: net: %d %s" ),
721 cluster.get(),
722 cluster->OriginNet(),
723 (const char*) cluster->OriginNetName().c_str() );
724 }
725 else
726 {
727 wxLogTrace( wxT( "CN" ), wxT( "Cluster %p: no changeable items to propagate to" ),
728 cluster.get() );
729 }
730 }
731 else
732 {
733 wxLogTrace( wxT( "CN" ), wxT( "Cluster %p: connected to unused net" ),
734 cluster.get() );
735 }
736 }
737}
738
739
746
747
748void CN_CONNECTIVITY_ALGO::FillIsolatedIslandsMap( std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>>& aMap,
749 bool aConnectivityAlreadyRebuilt )
750{
751 int progressDelta = 50;
752 int ii = 0;
753
754 progressDelta = std::max( progressDelta, (int) aMap.size() / 4 );
755
756 if( !aConnectivityAlreadyRebuilt )
757 {
758 for( const auto& [ zone, islands ] : aMap )
759 {
760 Remove( zone );
761 Add( zone );
762 ii++;
763
764 if( m_progressReporter && ( ii % progressDelta ) == 0 )
765 {
766 m_progressReporter->SetCurrentProgress( (double) ii / (double) aMap.size() );
767 m_progressReporter->KeepRefreshing( false );
768 }
769
770 if( m_progressReporter && m_progressReporter->IsCancelled() )
771 return;
772 }
773 }
774
776
777 // Bucket the zone items in one pass. A search per zone layer is O(zone layers x items).
778 // Keep the cluster and item order, which sets the recorded outline order.
779 struct ZONE_CLUSTER_ITEM
780 {
781 CN_ZONE_LAYER* m_item;
782 bool m_orphaned;
783 };
784
785 std::unordered_map<const BOARD_ITEM*, std::map<PCB_LAYER_ID, std::vector<ZONE_CLUSTER_ITEM>>>
786 zoneItems;
787
788 for( const auto& [ zone, zoneIslands ] : aMap )
789 zoneItems[zone];
790
791 for( const std::shared_ptr<CN_CLUSTER>& cluster : m_connClusters )
792 {
793 const bool orphaned = cluster->IsOrphaned();
794
795 for( CN_ITEM* item : *cluster )
796 {
797 auto it = zoneItems.find( item->Parent() );
798
799 if( it != zoneItems.end() )
800 {
801 it->second[item->GetBoardLayer()].push_back(
802 { static_cast<CN_ZONE_LAYER*>( item ), orphaned } );
803 }
804 }
805 }
806
807 for( auto& [ zone, zoneIslands ] : aMap )
808 {
809 const auto& layerItems = zoneItems[zone];
810
811 for( auto& [ layer, layerIslands ] : zoneIslands )
812 {
813 if( zone->GetFilledPolysList( layer )->IsEmpty() )
814 continue;
815
816 auto layerIt = layerItems.find( layer );
817 bool notInConnectivity = layerIt == layerItems.end();
818
819 if( !notInConnectivity )
820 {
821 for( const ZONE_CLUSTER_ITEM& entry : layerIt->second )
822 {
823 if( entry.m_orphaned )
824 layerIslands.m_IsolatedOutlines.push_back( entry.m_item->SubpolyIndex() );
825 else if( entry.m_item->HasSingleConnection() )
826 layerIslands.m_SingleConnectionOutlines.push_back( entry.m_item->SubpolyIndex() );
827 }
828 }
829
830 // Non-copper zones (silk, mask, etc.) are never added to the connectivity graph,
831 // so notInConnectivity is always true for them. Without the IsCopperLayer guard
832 // outline 0 of every non-copper multi-island fill gets dropped on every refill
833 // (issue 24089).
834 if( notInConnectivity && IsCopperLayer( layer ) )
835 layerIslands.m_IsolatedOutlines.push_back( 0 );
836 }
837 }
838}
839
840
846
847
849{
850 if( aNet < 0 )
851 return;
852
853 if( (int) m_dirtyNets.size() <= aNet )
854 {
855 int lastNet = m_dirtyNets.size() - 1;
856
857 if( lastNet < 0 )
858 lastNet = 0;
859
860 m_dirtyNets.resize( aNet + 1 );
861
862 for( int i = lastNet; i < aNet + 1; i++ )
863 m_dirtyNets[i] = true;
864 }
865
866 m_dirtyNets[aNet] = true;
867}
868
869
871{
872 PCB_LAYER_ID layer = aZoneLayer->GetLayer();
873 BOARD_CONNECTED_ITEM* item = aItem->Parent();
874
875 if( !item->IsOnLayer( layer ) )
876 return;
877
878 auto connect =
879 [&]()
880 {
881 // We don't propagate nets from zones, so via-zone net changes are deferred
882 // and applied only if the via has no higher-priority connections (tracks, pads).
883 if( aItem->Parent()->Type() == PCB_VIA_T && aItem->CanChangeNet() )
884 {
885 std::lock_guard<std::mutex> lock( *m_deferredNetCodesMutex );
886 m_deferredNetCodes->emplace_back( aItem, aZoneLayer->Net() );
887 }
888
889 aZoneLayer->Connect( aItem );
890 aItem->Connect( aZoneLayer );
891 };
892
893 // Try quick checks first...
894 if( item->Type() == PCB_PAD_T )
895 {
896 PAD* pad = static_cast<PAD*>( item );
897
898 if( pad->ConditionallyFlashed( layer )
899 && pad->GetZoneLayerOverride( layer ) == ZLO_FORCE_NO_ZONE_CONNECTION )
900 {
901 return;
902 }
903
904 // Don't connect zones to pads on backdrilled or post-machined layers
905 if( pad->IsBackdrilledOrPostMachined( layer ) )
906 return;
907 }
908 else if( item->Type() == PCB_VIA_T )
909 {
910 PCB_VIA* via = static_cast<PCB_VIA*>( item );
911
912 if( via->ConditionallyFlashed( layer )
913 && via->GetZoneLayerOverride( layer ) == ZLO_FORCE_NO_ZONE_CONNECTION )
914 {
915 return;
916 }
917
918 // Don't connect zones to vias on backdrilled or post-machined layers
919 if( via->IsBackdrilledOrPostMachined( layer ) )
920 return;
921 }
922
923 for( int i = 0; i < aItem->AnchorCount(); ++i )
924 {
925 if( aZoneLayer->ContainsPoint( aItem->GetAnchor( i ) ) )
926 {
927 connect();
928 return;
929 }
930 }
931
932 if( item->Type() == PCB_VIA_T || item->Type() == PCB_PAD_T )
933 {
934 // As long as the pad/via crosses the zone layer, check for the full effective shape
935 // We check for the overlapping layers above
936 if( aZoneLayer->Collide( item->GetEffectiveShape( layer, FLASHING::ALWAYS_FLASHED ).get() ) )
937 connect();
938
939 return;
940 }
941
942 if( aZoneLayer->Collide( item->GetEffectiveShape( layer ).get() ) )
943 connect();
944}
945
947{
948 // CN_ZONE_LAYER now caches its own copy of the outline, so we just check if it's non-empty.
949 if( !aZoneLayerA->HasValidOutline() || !aZoneLayerB->HasValidOutline() )
950 return;
951
952 const BOX2I& boxA = aZoneLayerA->BBox();
953 const BOX2I& boxB = aZoneLayerB->BBox();
954
955 PCB_LAYER_ID layer = aZoneLayerA->GetLayer();
956
957 if( aZoneLayerB->GetLayer() != layer )
958 return;
959
960 if( !boxA.Intersects( boxB ) )
961 return;
962
963 const SHAPE_LINE_CHAIN& outlineA = aZoneLayerA->GetOutline();
964
965 for( int i = 0; i < outlineA.PointCount(); i++ )
966 {
967 const VECTOR2I& pt = outlineA.CPoint( i );
968
969 if( !boxB.Contains( pt ) )
970 continue;
971
972 if( aZoneLayerB->ContainsPoint( pt ) )
973 {
974 aZoneLayerA->Connect( aZoneLayerB );
975 aZoneLayerB->Connect( aZoneLayerA );
976 return;
977 }
978 }
979
980 const SHAPE_LINE_CHAIN& outlineB = aZoneLayerB->GetOutline();
981
982 for( int i = 0; i < outlineB.PointCount(); i++ )
983 {
984 const VECTOR2I& pt = outlineB.CPoint( i );
985
986 if( !boxA.Contains( pt ) )
987 continue;
988
989 if( aZoneLayerA->ContainsPoint( pt ) )
990 {
991 aZoneLayerA->Connect( aZoneLayerB );
992 aZoneLayerB->Connect( aZoneLayerA );
993 return;
994 }
995 }
996}
997
998
1000{
1001 bool alwaysFlashed = false;
1002
1003 if( aItem->Type() == PCB_PAD_T )
1004 alwaysFlashed = !static_cast<const PAD*>( aItem )->ConditionallyFlashed( aLayer );
1005 else if( aItem->Type() == PCB_VIA_T )
1006 alwaysFlashed = !static_cast<const PCB_VIA*>( aItem )->ConditionallyFlashed( aLayer );
1007
1008 return alwaysFlashed ? FLASHING::ALWAYS_FLASHED : FLASHING::NEVER_FLASHED;
1009}
1010
1011
1013{
1014 std::shared_ptr<SHAPE> shapeA = aItemA->GetEffectiveShape( aLayer, flashingFor( aItemA, aLayer ) );
1015 std::shared_ptr<SHAPE> shapeB = aItemB->GetEffectiveShape( aLayer, flashingFor( aItemB, aLayer ) );
1016
1017 return shapeA->Collide( shapeB.get() );
1018}
1019
1020
1022{
1023 const BOARD_CONNECTED_ITEM* parentA = aCandidate->Parent();
1024 const BOARD_CONNECTED_ITEM* parentB = m_item->Parent();
1025
1026 if( !aCandidate->Valid() || !m_item->Valid() )
1027 return true;
1028
1029 if( parentA == parentB )
1030 return true;
1031
1032 // Don't connect items in different nets that can't be changed
1033 if( !aCandidate->CanChangeNet() && !m_item->CanChangeNet() && aCandidate->Net() != m_item->Net() )
1034 return true;
1035
1036 // If both m_item and aCandidate are marked dirty, they will both be searched
1037 // Since we are reciprocal in our connection, we arbitrarily pick one of the connections
1038 // to conduct the expensive search
1039 if( aCandidate->Dirty() && aCandidate < m_item )
1040 return true;
1041
1042 // We should handle zone-zone connection separately
1043 if ( parentA->Type() == PCB_ZONE_T && parentB->Type() == PCB_ZONE_T )
1044 {
1046 static_cast<CN_ZONE_LAYER*>( aCandidate ) );
1047 return true;
1048 }
1049
1050 if( parentA->Type() == PCB_ZONE_T )
1051 {
1052 checkZoneItemConnection( static_cast<CN_ZONE_LAYER*>( aCandidate ), m_item );
1053 return true;
1054 }
1055
1056 if( parentB->Type() == PCB_ZONE_T )
1057 {
1058 checkZoneItemConnection( static_cast<CN_ZONE_LAYER*>( m_item ), aCandidate );
1059 return true;
1060 }
1061
1062 LSET commonLayers = parentA->GetLayerSet() & parentB->GetLayerSet();
1063
1064 if( const BOARD* board = parentA->GetBoard() )
1065 commonLayers &= board->GetEnabledLayers();
1066
1067 for( PCB_LAYER_ID layer : commonLayers )
1068 {
1069 if( ItemsTouchOnLayer( parentA, parentB, layer ) )
1070 {
1071 m_item->Connect( aCandidate );
1072 aCandidate->Connect( m_item );
1073 return true;
1074 }
1075 }
1076
1077 return true;
1078};
1079
1080
1082{
1083 m_ratsnestClusters.clear();
1084 m_connClusters.clear();
1085 m_itemMap.clear();
1086 m_itemList.Clear();
1087
1088}
1089
1094
1095
1097{
1098 // Map of footprint -> map of pad number -> list of CN_ITEMs for pads with that number
1099 std::map<FOOTPRINT*, std::map<wxString, std::vector<CN_ITEM*>>> padsByFootprint;
1100
1101 for( CN_ITEM* item : m_itemList )
1102 {
1103 if( !item->Valid() || item->Parent()->Type() != PCB_PAD_T )
1104 continue;
1105
1106 auto pad = static_cast<const PAD*>( item->Parent() );
1107
1108 FOOTPRINT* fp = pad->GetParentFootprint();
1109
1110 padsByFootprint[fp][ pad->GetNumber() ].emplace_back( item );
1111 }
1112
1113 for( auto& [footprint, padsMap] : padsByFootprint )
1114 {
1115 if( footprint->GetDuplicatePadNumbersAreJumpers() )
1116 {
1117 for( const std::vector<CN_ITEM*>& padsList : padsMap | std::views::values )
1118 {
1119 for( size_t i = 0; i < padsList.size(); ++i )
1120 {
1121 for( size_t j = 1; j < padsList.size(); ++j )
1122 {
1123 padsList[i]->Connect( padsList[j] );
1124 padsList[j]->Connect( padsList[i] );
1125 }
1126 }
1127 }
1128 }
1129
1130 for( const JUMPER_GROUP& group : footprint->JumperPadGroups().GetAll() )
1131 {
1132 std::vector<CN_ITEM*> toConnect;
1133
1134 for( const wxString& padNumber : group.GetNames() )
1135 std::ranges::copy( padsMap[padNumber], std::back_inserter( toConnect ) );
1136
1137 for( size_t i = 0; i < toConnect.size(); ++i )
1138 {
1139 for( size_t j = 1; j < toConnect.size(); ++j )
1140 {
1141 toConnect[i]->Connect( toConnect[j] );
1142 toConnect[j]->Connect( toConnect[i] );
1143 }
1144 }
1145 }
1146 }
1147}
int index
@ ZLO_FORCE_NO_ZONE_CONNECTION
Definition board_item.h:75
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
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:409
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:410
const ZONES & Zones() const
Definition board.h:468
const FOOTPRINTS & Footprints() const
Definition board.h:464
const TRACKS & Tracks() const
Definition board.h:462
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1185
const DRAWINGS & Drawings() const
Definition board.h:466
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_CONNECTED_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)
Represents a single outline of a zone fill on a particular layer.
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:405
Represents a group of jumper pins or pads, keyed by name.
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:275
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:104
A progress reporter interface for use in multi-threaded environments.
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
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.
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
bool ItemsTouchOnLayer(const BOARD_CONNECTED_ITEM *aItemA, const BOARD_CONNECTED_ITEM *aItemB, PCB_LAYER_ID aLayer)
Test whether two items have copper in contact on aLayer.
static FLASHING flashingFor(const BOARD_CONNECTED_ITEM *aItem, PCB_LAYER_ID aLayer)
bool ItemsTouchOnLayer(const BOARD_CONNECTED_ITEM *aItemA, const BOARD_CONNECTED_ITEM *aItemB, PCB_LAYER_ID aLayer)
Test whether two items have copper in contact on aLayer.
@ FP_JUST_ADDED
Definition footprint.h:91
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:708