KiCad PCB EDA Suite
Loading...
Searching...
No Matches
zone_utils.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "zone_utils.h"
21
22#include <board.h>
23#include <footprint.h>
24#include <pad.h>
25#include <pcb_track.h>
26#include <thread_pool.h>
27#include <zone.h>
29
30#include <algorithm>
31#include <cmath>
32#include <future>
33#include <optional>
34#include <unordered_map>
35#include <unordered_set>
36
37
38static bool RuleAreasHaveSameProps( const ZONE& a, const ZONE& b )
39{
40 // This function is only used to compare rule areas, so we can assume that both a and b are rule areas
41 wxASSERT( a.GetIsRuleArea() && b.GetIsRuleArea() );
42
48}
49
50
51std::vector<std::unique_ptr<ZONE>> MergeZonesWithSameOutline( std::vector<std::unique_ptr<ZONE>>&& aZones )
52{
53 const auto polygonsAreMergeable = []( const SHAPE_POLY_SET::POLYGON& a, const SHAPE_POLY_SET::POLYGON& b ) -> bool
54 {
55 if( a.size() != b.size() )
56 return false;
57
58 // NOTE: this assumes the polygons have their line chains in the same order
59 // But that is not actually required for same geometry (i.e. mergeability)
60 for( size_t lineChainId = 0; lineChainId < a.size(); lineChainId++ )
61 {
62 const SHAPE_LINE_CHAIN& chainA = a[lineChainId];
63 const SHAPE_LINE_CHAIN& chainB = b[lineChainId];
64
65 // Note: this assumes the polygons are either already simplified or that it's
66 // OK to not merge even if they would be the same after simplification.
67 if( chainA.PointCount() != chainB.PointCount() || chainA.BBox() != chainB.BBox()
68 || !chainA.CompareGeometry( chainB ) )
69 {
70 // Different geometry, can't merge
71 return false;
72 }
73 }
74
75 return true;
76 };
77
78 const auto zonesAreMergeable = [&]( const ZONE& a, const ZONE& b ) -> bool
79 {
80 // Can't merge rule areas with zone fills
81 if( a.GetIsRuleArea() != b.GetIsRuleArea() )
82 return false;
83
84 if( a.GetIsRuleArea() )
85 {
86 if( !RuleAreasHaveSameProps( a, b ) )
87 return false;
88 }
89 else
90 {
91 // We could also check clearances and so on
92 if( a.GetNetCode() != b.GetNetCode() )
93 return false;
94 }
95
96 const SHAPE_POLY_SET* polySetA = a.Outline();
97 const SHAPE_POLY_SET* polySetB = b.Outline();
98
99 if( polySetA->OutlineCount() != polySetB->OutlineCount() )
100 return false;
101
102 if( polySetA->OutlineCount() == 0 )
103 {
104 // both have no outline, so they are the same, but we must not
105 // derefence them, as they are empty
106 return true;
107 }
108
109 // REVIEW: this assumes the zones only have a single polygon in the
110 const SHAPE_POLY_SET::POLYGON& polyA = polySetA->CPolygon( 0 );
111 const SHAPE_POLY_SET::POLYGON& polyB = polySetB->CPolygon( 0 );
112
113 return polygonsAreMergeable( polyA, polyB );
114 };
115
116 std::vector<std::unique_ptr<ZONE>> deduplicatedZones;
117
118 // Map of zone indexes that we have already merged into a prior zone
119 std::vector<bool> merged( aZones.size(), false );
120
121 for( size_t i = 0; i < aZones.size(); i++ )
122 {
123 // This one has already been subsumed into a prior zone, so skip it
124 // and it will be dropped at the end.
125 if( merged[i] )
126 continue;
127
128 ZONE& primary = *aZones[i];
129 LSET layers = primary.GetLayerSet();
130 std::unordered_map<PCB_LAYER_ID, SHAPE_POLY_SET> mergedFills;
131
132 for( size_t j = i + 1; j < aZones.size(); j++ )
133 {
134 // This zone has already been subsumed by a prior zone, so it
135 // cannot be merged into another primary
136 if( merged[j] )
137 continue;
138
139 ZONE& candidate = *aZones[j];
140 bool canMerge = zonesAreMergeable( primary, candidate );
141
142 if( canMerge )
143 {
144 for( PCB_LAYER_ID layer : candidate.GetLayerSet() )
145 {
146 if( SHAPE_POLY_SET* fill = candidate.GetFill( layer ) )
147 mergedFills[layer] = *fill;
148 }
149
150 layers |= candidate.GetLayerSet();
151 merged[j] = true;
152 }
153 }
154
155 if( layers != primary.GetLayerSet() )
156 {
157 for( PCB_LAYER_ID layer : primary.GetLayerSet() )
158 {
159 if( SHAPE_POLY_SET* fill = primary.GetFill( layer ) )
160 mergedFills[layer] = *fill;
161 }
162
163 primary.SetLayerSet( layers );
164
165 for( const auto& [layer, fill] : mergedFills )
166 primary.SetFilledPolysList( layer, fill );
167
168 primary.SetNeedRefill( false );
169 primary.SetIsFilled( true );
170 }
171
172 // Keep this zone - it's a primary (may or may not have had other zones merged into it)
173 deduplicatedZones.push_back( std::move( aZones[i] ) );
174 }
175
176 return deduplicatedZones;
177}
178
179
180namespace
181{
182
183struct ZONE_OVERLAP_PAIR
184{
185 ZONE* zoneA;
186 ZONE* zoneB;
187 LSET sharedLayers;
188};
189
190
191struct ZONE_PRIORITY_EDGE
192{
193 ZONE* higher;
194 ZONE* lower;
195 int countDiff;
196 bool fromArea;
197};
198
199} // namespace
200
201
202static std::vector<ZONE_OVERLAP_PAIR> findOverlappingPairs( BOARD* aBoard )
203{
204 std::vector<ZONE_OVERLAP_PAIR> pairs;
205 const ZONES& zones = aBoard->Zones();
206
207 for( size_t i = 0; i < zones.size(); i++ )
208 {
209 ZONE* a = zones[i];
210
211 if( a->GetIsRuleArea() || a->IsTeardropArea() || !a->IsOnCopperLayer() )
212 continue;
213
214 BOX2I bboxA = a->GetBoundingBox();
215
216 for( size_t j = i + 1; j < zones.size(); j++ )
217 {
218 ZONE* b = zones[j];
219
220 if( b->GetIsRuleArea() || b->IsTeardropArea() || !b->IsOnCopperLayer() )
221 continue;
222
223 LSET shared = a->GetLayerSet() & b->GetLayerSet();
224 shared &= LSET::AllCuMask();
225
226 if( shared.none() )
227 continue;
228
229 if( !b->GetBoundingBox().Intersects( bboxA ) )
230 continue;
231
232 SHAPE_POLY_SET aOutline = a->GetBoardOutline();
233 SHAPE_POLY_SET bOutline = b->GetBoardOutline();
234
235 bool overlaps = aOutline.Collide( &bOutline )
236 || ( bOutline.TotalVertices() > 0 && aOutline.Contains( bOutline.CVertex( 0 ) ) )
237 || ( aOutline.TotalVertices() > 0 && bOutline.Contains( aOutline.CVertex( 0 ) ) );
238
239 if( overlaps )
240 pairs.push_back( { a, b, shared } );
241 }
242 }
243
244 return pairs;
245}
246
247
248static std::optional<ZONE_PRIORITY_EDGE> computeConstraint( const ZONE_OVERLAP_PAIR& aPair,
249 BOARD* aBoard )
250{
251 SHAPE_POLY_SET polyA = aPair.zoneA->GetBoardOutline();
252 SHAPE_POLY_SET polyB = aPair.zoneB->GetBoardOutline();
253 polyA.ClearArcs();
254 polyB.ClearArcs();
255
256 SHAPE_POLY_SET intersection;
257 intersection.BooleanIntersection( polyA, polyB );
258
259 if( intersection.IsEmpty() )
260 return std::nullopt;
261
262 intersection.BuildBBoxCaches();
263
264 int netCodeA = aPair.zoneA->GetNetCode();
265 int netCodeB = aPair.zoneB->GetNetCode();
266
267 // Same-net overlapping zones are cooperative, not competitive. Priority
268 // between them is meaningless to the fill engine. Return no constraint
269 // here; AutoAssignZonePriorities() groups them to the same priority level.
270 if( netCodeA == netCodeB )
271 return std::nullopt;
272
273 int countA = 0;
274 int countB = 0;
275
276 auto countIfInOverlap = [&]( const VECTOR2I& aPos, int aNetCode, PCB_LAYER_ID aLayer )
277 {
278 if( !aPair.sharedLayers.test( aLayer ) )
279 return;
280
281 if( intersection.Contains( aPos ) )
282 {
283 if( aNetCode == netCodeA )
284 countA++;
285 else if( aNetCode == netCodeB )
286 countB++;
287 }
288 };
289
290 for( FOOTPRINT* fp : aBoard->Footprints() )
291 {
292 for( PAD* pad : fp->Pads() )
293 {
294 for( PCB_LAYER_ID layer : aPair.sharedLayers.Seq() )
295 {
296 if( pad->IsOnLayer( layer ) )
297 {
298 countIfInOverlap( pad->GetPosition(), pad->GetNetCode(), layer );
299 break;
300 }
301 }
302 }
303 }
304
305 for( PCB_TRACK* track : aBoard->Tracks() )
306 {
307 if( track->Type() != PCB_VIA_T )
308 continue;
309
310 PCB_VIA* via = static_cast<PCB_VIA*>( track );
311
312 for( PCB_LAYER_ID layer : aPair.sharedLayers.Seq() )
313 {
314 if( via->IsOnLayer( layer ) )
315 {
316 countIfInOverlap( via->GetPosition(), via->GetNetCode(), layer );
317 break;
318 }
319 }
320 }
321
322 if( countA == 0 && countB == 0 )
323 {
324 double areaA = aPair.zoneA->GetBoardOutline().Area();
325 double areaB = aPair.zoneB->GetBoardOutline().Area();
326
327 if( areaA == areaB )
328 return std::nullopt;
329
330 ZONE* higher = ( areaA < areaB ) ? aPair.zoneA : aPair.zoneB;
331 ZONE* lower = ( higher == aPair.zoneA ) ? aPair.zoneB : aPair.zoneA;
332 return ZONE_PRIORITY_EDGE{ higher, lower, 0, true };
333 }
334
335 int maxCount = std::max( countA, countB );
336 int diff = std::abs( countA - countB );
337 double ratio = static_cast<double>( diff ) / maxCount;
338
339 constexpr double SIMILARITY_THRESHOLD = 0.20;
340
341 if( ratio < SIMILARITY_THRESHOLD )
342 {
343 double areaA = aPair.zoneA->GetBoardOutline().Area();
344 double areaB = aPair.zoneB->GetBoardOutline().Area();
345
346 if( areaA == areaB )
347 return std::nullopt;
348
349 ZONE* higher = ( areaA < areaB ) ? aPair.zoneA : aPair.zoneB;
350 ZONE* lower = ( higher == aPair.zoneA ) ? aPair.zoneB : aPair.zoneA;
351 return ZONE_PRIORITY_EDGE{ higher, lower, diff, true };
352 }
353
354 ZONE* higher = ( countA > countB ) ? aPair.zoneA : aPair.zoneB;
355 ZONE* lower = ( higher == aPair.zoneA ) ? aPair.zoneB : aPair.zoneA;
356 return ZONE_PRIORITY_EDGE{ higher, lower, diff, false };
357}
358
359
360static void assignPrioritiesFromGraph( const std::vector<ZONE_PRIORITY_EDGE>& aEdges,
361 std::vector<ZONE*>& aAllZones )
362{
363 std::unordered_map<ZONE*, std::vector<ZONE*>> adj;
364 std::unordered_map<ZONE*, int> inDegree;
365 std::unordered_set<ZONE*> inGraph;
366
367 for( ZONE* z : aAllZones )
368 {
369 inDegree[z] = 0;
370 inGraph.insert( z );
371 }
372
373 // Sort edges so area-based (weakest) come first, then by ascending countDiff
374 std::vector<ZONE_PRIORITY_EDGE> sortedEdges = aEdges;
375
376 std::sort( sortedEdges.begin(), sortedEdges.end(),
377 []( const ZONE_PRIORITY_EDGE& a, const ZONE_PRIORITY_EDGE& b )
378 {
379 if( a.fromArea != b.fromArea )
380 return a.fromArea;
381
382 return a.countDiff < b.countDiff;
383 } );
384
385 for( const ZONE_PRIORITY_EDGE& edge : sortedEdges )
386 {
387 adj[edge.higher].push_back( edge.lower );
388 inDegree[edge.lower]++;
389 }
390
391 // Kahn's algorithm: sources (in-degree 0) have nothing constraining them to be lower,
392 // so they are the highest-priority zones. Process them first.
393 std::vector<ZONE*> queue;
394
395 for( ZONE* z : aAllZones )
396 {
397 if( inDegree[z] == 0 )
398 queue.push_back( z );
399 }
400
401 std::sort( queue.begin(), queue.end(),
402 []( const ZONE* a, const ZONE* b )
403 {
404 return a->GetAssignedPriority() < b->GetAssignedPriority();
405 } );
406
407 std::vector<ZONE*> topoOrder;
408 topoOrder.reserve( aAllZones.size() );
409
410 while( !queue.empty() )
411 {
412 ZONE* current = queue.front();
413 queue.erase( queue.begin() );
414 topoOrder.push_back( current );
415
416 auto& neighbors = adj[current];
417
418 std::sort( neighbors.begin(), neighbors.end(),
419 []( const ZONE* a, const ZONE* b )
420 {
421 return a->GetAssignedPriority() < b->GetAssignedPriority();
422 } );
423
424 for( ZONE* neighbor : neighbors )
425 {
426 inDegree[neighbor]--;
427
428 if( inDegree[neighbor] == 0 )
429 queue.push_back( neighbor );
430 }
431
432 std::sort( queue.begin(), queue.end(),
433 []( const ZONE* a, const ZONE* b )
434 {
435 return a->GetAssignedPriority() < b->GetAssignedPriority();
436 } );
437 }
438
439 // Zones stuck in cycles get appended sorted by their current priority
440 if( topoOrder.size() < aAllZones.size() )
441 {
442 std::unordered_set<ZONE*> ordered( topoOrder.begin(), topoOrder.end() );
443 std::vector<ZONE*> remaining;
444
445 for( ZONE* z : aAllZones )
446 {
447 if( ordered.find( z ) == ordered.end() )
448 remaining.push_back( z );
449 }
450
451 std::sort( remaining.begin(), remaining.end(),
452 []( const ZONE* a, const ZONE* b )
453 {
454 return a->GetAssignedPriority() < b->GetAssignedPriority();
455 } );
456
457 for( ZONE* z : remaining )
458 topoOrder.push_back( z );
459 }
460
461 // topoOrder[0] is the highest-priority zone (source node). Assign descending values.
462 for( size_t i = 0; i < topoOrder.size(); i++ )
463 topoOrder[i]->SetAssignedPriority( static_cast<unsigned>( topoOrder.size() - 1 - i ) );
464}
465
466
467static ZONE* ufFind( std::unordered_map<ZONE*, ZONE*>& aParent, ZONE* aZone )
468{
469 ZONE*& parent = aParent[aZone];
470
471 if( parent != aZone )
472 parent = ufFind( aParent, parent );
473
474 return parent;
475}
476
477
478static void ufUnion( std::unordered_map<ZONE*, ZONE*>& aParent,
479 std::unordered_map<ZONE*, int>& aRank,
480 ZONE* aA, ZONE* aB )
481{
482 ZONE* rootA = ufFind( aParent, aA );
483 ZONE* rootB = ufFind( aParent, aB );
484
485 if( rootA == rootB )
486 return;
487
488 if( aRank[rootA] < aRank[rootB] )
489 std::swap( rootA, rootB );
490
491 aParent[rootB] = rootA;
492
493 if( aRank[rootA] == aRank[rootB] )
494 aRank[rootA]++;
495}
496
497
499{
500 std::vector<ZONE*> eligibleZones;
501
502 for( ZONE* zone : aBoard->Zones() )
503 {
504 if( !zone->GetIsRuleArea() && !zone->IsTeardropArea() && zone->IsOnCopperLayer() )
505 eligibleZones.push_back( zone );
506 }
507
508 if( eligibleZones.size() < 2 )
509 return false;
510
511 std::unordered_map<ZONE*, unsigned> originalPriorities;
512
513 for( ZONE* z : eligibleZones )
514 originalPriorities[z] = z->GetAssignedPriority();
515
516 std::vector<ZONE_OVERLAP_PAIR> pairs = findOverlappingPairs( aBoard );
517
518 if( pairs.empty() )
519 return false;
520
521 // Build equivalence classes for same-net overlapping zones. These zones
522 // are cooperative and must share the same priority after assignment.
523 std::unordered_map<ZONE*, ZONE*> ufParent;
524 std::unordered_map<ZONE*, int> ufRank;
525
526 for( ZONE* z : eligibleZones )
527 {
528 ufParent[z] = z;
529 ufRank[z] = 0;
530 }
531
532 for( const ZONE_OVERLAP_PAIR& pair : pairs )
533 {
534 if( pair.zoneA->GetNetCode() == pair.zoneB->GetNetCode() )
535 ufUnion( ufParent, ufRank, pair.zoneA, pair.zoneB );
536 }
537
539 std::vector<std::future<std::optional<ZONE_PRIORITY_EDGE>>> futures;
540 futures.reserve( pairs.size() );
541
542 for( const ZONE_OVERLAP_PAIR& pair : pairs )
543 {
544 if( pair.zoneA->GetNetCode() == pair.zoneB->GetNetCode() )
545 continue;
546
547 futures.emplace_back( tp.submit_task(
548 [&pair, aBoard]()
549 {
550 return computeConstraint( pair, aBoard );
551 } ) );
552 }
553
554 std::vector<ZONE_PRIORITY_EDGE> edges;
555
556 for( auto& future : futures )
557 {
558 std::optional<ZONE_PRIORITY_EDGE> result = future.get();
559
560 if( result.has_value() )
561 edges.push_back( result.value() );
562 }
563
564 if( !edges.empty() )
565 assignPrioritiesFromGraph( edges, eligibleZones );
566
567 // Equalize priorities within each same-net equivalence class. Each group
568 // gets the maximum priority of any member so ordering constraints from
569 // different-net edges propagate to the whole group.
570 std::unordered_map<ZONE*, unsigned> groupMax;
571
572 for( ZONE* z : eligibleZones )
573 {
574 ZONE* root = ufFind( ufParent, z );
575 unsigned pri = z->GetAssignedPriority();
576 auto& maxPri = groupMax[root];
577
578 if( pri > maxPri )
579 maxPri = pri;
580 }
581
582 for( ZONE* z : eligibleZones )
583 {
584 ZONE* root = ufFind( ufParent, z );
585 z->SetAssignedPriority( groupMax[root] );
586 }
587
588 for( ZONE* z : eligibleZones )
589 {
590 if( z->GetAssignedPriority() != originalPriorities[z] )
591 return true;
592 }
593
594 return false;
595}
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
const ZONES & Zones() const
Definition board.h:424
const FOOTPRINTS & Footprints() const
Definition board.h:420
const TRACKS & Tracks() const
Definition board.h:418
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:307
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
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition lset.cpp:309
Definition pad.h:61
A progress reporter interface for use in multi-threaded environments.
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.
bool CompareGeometry(const SHAPE_LINE_CHAIN &aOther, bool aCyclicalCompare=false, int aEpsilon=0) const
Compare this line chain with another one.
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Represent a set of closed polygons.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
double Area()
Return the area of this poly set.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
int TotalVertices() const
Return total number of vertices stored in the set.
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
void BuildBBoxCaches() const
Construct BBoxCaches for Contains(), below.
const VECTOR2I & CVertex(int aIndex, int aOutline, int aHole) const
Return the index-th vertex in a given hole outline within a given outline.
int OutlineCount() const
Return the number of outlines in the set.
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
const POLYGON & CPolygon(int aIndex) const
Handle a list of polygons defining a copper zone.
Definition zone.h:70
void SetNeedRefill(bool aNeedRefill)
Definition zone.h:310
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:811
bool GetDoNotAllowVias() const
Definition zone.h:822
bool GetDoNotAllowPads() const
Definition zone.h:824
const BOX2I GetBoundingBox() const override
Definition zone.cpp:737
bool GetDoNotAllowTracks() const
Definition zone.h:823
SHAPE_POLY_SET * GetFill(PCB_LAYER_ID aLayer)
Definition zone.h:704
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition zone.h:726
SHAPE_POLY_SET GetBoardOutline() const
Definition zone.cpp:835
void SetIsFilled(bool isFilled)
Definition zone.h:307
void SetLayerSet(const LSET &aLayerSet) override
Definition zone.cpp:624
bool IsTeardropArea() const
Definition zone.h:786
bool GetDoNotAllowFootprints() const
Definition zone.h:825
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:133
bool GetDoNotAllowZoneFills() const
Definition zone.h:821
bool IsOnCopperLayer() const override
Definition zone.cpp:574
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
std::vector< ZONE * > ZONES
wxString result
Test unit parsing edge cases and error handling.
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_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:90
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
static void assignPrioritiesFromGraph(const std::vector< ZONE_PRIORITY_EDGE > &aEdges, std::vector< ZONE * > &aAllZones)
static ZONE * ufFind(std::unordered_map< ZONE *, ZONE * > &aParent, ZONE *aZone)
static bool RuleAreasHaveSameProps(const ZONE &a, const ZONE &b)
std::vector< std::unique_ptr< ZONE > > MergeZonesWithSameOutline(std::vector< std::unique_ptr< ZONE > > &&aZones)
Merges zones with identical outlines and nets on different layers into single multi-layer zones.
static void ufUnion(std::unordered_map< ZONE *, ZONE * > &aParent, std::unordered_map< ZONE *, int > &aRank, ZONE *aA, ZONE *aB)
static std::vector< ZONE_OVERLAP_PAIR > findOverlappingPairs(BOARD *aBoard)
bool AutoAssignZonePriorities(BOARD *aBoard, PROGRESS_REPORTER *aReporter)
Automatically assign zone priorities based on connectivity analysis of overlapping regions.
static std::optional< ZONE_PRIORITY_EDGE > computeConstraint(const ZONE_OVERLAP_PAIR &aPair, BOARD *aBoard)