KiCad PCB EDA Suite
Loading...
Searching...
No Matches
zone_filler.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2014-2017 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Tomasz Włostowski <[email protected]>
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26#include <future>
27#include <set>
28#include <core/kicad_algo.h>
29#include <advanced_config.h>
30#include <board.h>
31#include <core/profile.h>
32
38static const wxChar traceZoneFiller[] = wxT( "KICAD_ZONE_FILLER" );
40#include <zone.h>
41#include <footprint.h>
42#include <pad.h>
43#include <pcb_target.h>
44#include <pcb_track.h>
45#include <pcb_text.h>
46#include <pcb_textbox.h>
47#include <pcb_tablecell.h>
48#include <pcb_table.h>
49#include <pcb_dimension.h>
52#include <board_commit.h>
53#include <progress_reporter.h>
57#include <geometry/vertex_set.h>
58#include <kidialog.h>
59#include <thread_pool.h>
60#include <math/util.h> // for KiROUND
61#include "zone_filler.h"
62#include "project.h"
64#include "pcb_barcode.h"
65
66// Helper classes for connect_nearby_polys
68{
69public:
70 RESULTS( int aOutline1, int aOutline2, int aVertex1, int aVertex2 ) :
71 m_outline1( aOutline1 ), m_outline2( aOutline2 ),
72 m_vertex1( aVertex1 ), m_vertex2( aVertex2 )
73 {
74 }
75
76 bool operator<( const RESULTS& aOther ) const
77 {
78 if( m_outline1 != aOther.m_outline1 )
79 return m_outline1 < aOther.m_outline1;
80 if( m_outline2 != aOther.m_outline2 )
81 return m_outline2 < aOther.m_outline2;
82 if( m_vertex1 != aOther.m_vertex1 )
83 return m_vertex1 < aOther.m_vertex1;
84 return m_vertex2 < aOther.m_vertex2;
85 }
86
91};
92
94{
95public:
96 VERTEX_CONNECTOR( const BOX2I& aBBox, const SHAPE_POLY_SET& aPolys, int aDist ) : VERTEX_SET( 0 )
97 {
98 SetBoundingBox( aBBox );
99 VERTEX* tail = nullptr;
100
101 for( int i = 0; i < aPolys.OutlineCount(); i++ )
102 tail = createList( aPolys.Outline( i ), tail, (void*)( intptr_t )( i ) );
103
104 if( tail )
105 tail->updateList();
106 m_dist = aDist;
107 }
108
109 VERTEX* getPoint( VERTEX* aPt ) const
110 {
111 // z-order range for the current point ± limit bounding box
112 const uint32_t maxZ = zOrder( aPt->x + m_dist, aPt->y + m_dist );
113 const uint32_t minZ = zOrder( aPt->x - m_dist, aPt->y - m_dist );
114 const SEG::ecoord limit2 = SEG::Square( m_dist );
115
116 // first look for points in increasing z-order
117 SEG::ecoord min_dist = std::numeric_limits<SEG::ecoord>::max();
118 VERTEX* retval = nullptr;
119
120 auto check_pt = [&]( VERTEX* p )
121 {
122 VECTOR2D diff( p->x - aPt->x, p->y - aPt->y );
123 SEG::ecoord dist2 = diff.SquaredEuclideanNorm();
124
125 if( dist2 > 0 && dist2 < limit2 && dist2 < min_dist && p->isEar( true ) )
126 {
127 min_dist = dist2;
128 retval = p;
129 }
130 };
131
132 VERTEX* p = aPt->nextZ;
133
134 while( p && p->z <= maxZ )
135 {
136 check_pt( p );
137 p = p->nextZ;
138 }
139
140 p = aPt->prevZ;
141
142 while( p && p->z >= minZ )
143 {
144 check_pt( p );
145 p = p->prevZ;
146 }
147
148 return retval;
149 }
150
152 {
153 if( m_vertices.empty() )
154 return;
155
156 VERTEX* p = m_vertices.front().next;
157 std::set<VERTEX*> visited;
158
159 while( p != &m_vertices.front() )
160 {
161 // Skip points that are concave
162 if( !p->isEar() )
163 {
164 p = p->next;
165 continue;
166 }
167
168 VERTEX* q = nullptr;
169
170 if( ( visited.empty() || !visited.contains( p ) ) && ( q = getPoint( p ) ) )
171 {
172 visited.insert( p );
173
174 if( !visited.contains( q ) &&
175 m_results.emplace( (intptr_t) p->GetUserData(), (intptr_t) q->GetUserData(),
176 p->i, q->i ).second )
177 {
178 // We don't want to connect multiple points in the same vicinity, so skip
179 // 2 points before and after each point and match.
180 visited.insert( p->prev );
181 visited.insert( p->prev->prev );
182 visited.insert( p->next );
183 visited.insert( p->next->next );
184
185 visited.insert( q->prev );
186 visited.insert( q->prev->prev );
187 visited.insert( q->next );
188 visited.insert( q->next->next );
189
190 visited.insert( q );
191 }
192 }
193
194 p = p->next;
195 }
196 }
197
198 std::set<RESULTS> GetResults() const
199 {
200 return m_results;
201 }
202
203private:
204 std::set<RESULTS> m_results;
206};
207
208
210 m_board( aBoard ),
211 m_brdOutlinesValid( false ),
212 m_commit( aCommit ),
213 m_progressReporter( nullptr ),
215{
216 m_maxError = aBoard->GetDesignSettings().m_MaxError;
217
218 // To enable add "DebugZoneFiller=1" to kicad_advanced settings file.
220}
221
222
226
227
229{
230 m_progressReporter = aReporter;
231 wxASSERT_MSG( m_commit, wxT( "ZONE_FILLER must have a valid commit to call SetProgressReporter" ) );
232}
233
234
245bool ZONE_FILLER::Fill( const std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aParent )
246{
247 std::lock_guard<KISPINLOCK> lock( m_board->GetConnectivity()->GetLock() );
248
249 std::vector<std::pair<ZONE*, PCB_LAYER_ID>> toFill;
250 std::map<std::pair<ZONE*, PCB_LAYER_ID>, HASH_128> oldFillHashes;
251 std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>> isolatedIslandsMap;
252
253 std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_board->GetConnectivity();
254
255 // Ensure that multiple threads don't attempt to initialize the advanced cfg global at the same
256 // time.
258
259 // Rebuild (from scratch, ignoring dirty flags) just in case. This really needs to be reliable.
260 connectivity->ClearRatsnest();
261 connectivity->Build( m_board, m_progressReporter );
262
263 m_worstClearance = m_board->GetMaxClearanceValue();
264
266 {
267 m_progressReporter->Report( aCheck ? _( "Checking zone fills..." )
268 : _( "Building zone fills..." ) );
269 m_progressReporter->SetMaxProgress( aZones.size() );
270 m_progressReporter->KeepRefreshing();
271 }
272
273 // The board outlines is used to clip solid areas inside the board (when outlines are valid)
274 m_boardOutline.RemoveAllContours();
275 m_brdOutlinesValid = m_board->GetBoardPolygonOutlines( m_boardOutline, true );
276
277 // Update and cache zone bounding boxes and pad effective shapes so that we don't have to
278 // make them thread-safe.
279 //
280 for( ZONE* zone : m_board->Zones() )
281 zone->CacheBoundingBox();
282
283 for( FOOTPRINT* footprint : m_board->Footprints() )
284 {
285 for( PAD* pad : footprint->Pads() )
286 {
287 if( pad->IsDirty() )
288 {
289 pad->BuildEffectiveShapes();
290 pad->BuildEffectivePolygon( ERROR_OUTSIDE );
291 }
292 }
293
294 for( ZONE* zone : footprint->Zones() )
295 zone->CacheBoundingBox();
296
297 // Rules may depend on insideCourtyard() or other expressions
298 footprint->BuildCourtyardCaches();
299 footprint->BuildNetTieCache();
300 }
301
302 LSET boardCuMask = LSET::AllCuMask( m_board->GetCopperLayerCount() );
303
304 auto findHighestPriorityZone =
305 [&]( const BOX2I& bbox, PCB_LAYER_ID itemLayer, int netcode,
306 const std::function<bool( const ZONE* )>& testFn ) -> ZONE*
307 {
308 unsigned highestPriority = 0;
309 ZONE* highestPriorityZone = nullptr;
310
311 for( ZONE* zone : m_board->Zones() )
312 {
313 // Rule areas are not filled
314 if( zone->GetIsRuleArea() )
315 continue;
316
317 if( zone->GetAssignedPriority() < highestPriority )
318 continue;
319
320 if( !zone->IsOnLayer( itemLayer ) )
321 continue;
322
323 // Degenerate zones will cause trouble; skip them
324 if( zone->GetNumCorners() <= 2 )
325 continue;
326
327 if( !zone->GetBoundingBox().Intersects( bbox ) )
328 continue;
329
330 if( !testFn( zone ) )
331 continue;
332
333 // Prefer highest priority and matching netcode
334 if( zone->GetAssignedPriority() > highestPriority
335 || zone->GetNetCode() == netcode )
336 {
337 highestPriority = zone->GetAssignedPriority();
338 highestPriorityZone = zone;
339 }
340 }
341
342 return highestPriorityZone;
343 };
344
345 auto isInPourKeepoutArea =
346 [&]( const BOX2I& bbox, PCB_LAYER_ID itemLayer, const VECTOR2I& testPoint ) -> bool
347 {
348 for( ZONE* zone : m_board->Zones() )
349 {
350 if( !zone->GetIsRuleArea() )
351 continue;
352
353 if( !zone->HasKeepoutParametersSet() )
354 continue;
355
356 if( !zone->GetDoNotAllowZoneFills() )
357 continue;
358
359 if( !zone->IsOnLayer( itemLayer ) )
360 continue;
361
362 // Degenerate zones will cause trouble; skip them
363 if( zone->GetNumCorners() <= 2 )
364 continue;
365
366 if( !zone->GetBoundingBox().Intersects( bbox ) )
367 continue;
368
369 if( zone->Outline()->Contains( testPoint ) )
370 return true;
371 }
372
373 return false;
374 };
375
376 // Determine state of conditional via flashing
377 // This is now done completely deterministically prior to filling due to the pathological
378 // case presented in https://gitlab.com/kicad/code/kicad/-/issues/12964.
379 for( PCB_TRACK* track : m_board->Tracks() )
380 {
381 if( track->Type() == PCB_VIA_T )
382 {
383 PCB_VIA* via = static_cast<PCB_VIA*>( track );
384 PADSTACK& padstack = via->Padstack();
385
386 via->ClearZoneLayerOverrides();
387
388 if( !via->GetRemoveUnconnected() )
389 continue;
390
391 BOX2I bbox = via->GetBoundingBox();
392 VECTOR2I center = via->GetPosition();
393 int holeRadius = via->GetDrillValue() / 2 + 1;
394 int netcode = via->GetNetCode();
395 LSET layers = via->GetLayerSet() & boardCuMask;
396
397 // Checking if the via hole touches the zone outline
398 auto viaTestFn =
399 [&]( const ZONE* aZone ) -> bool
400 {
401 return aZone->Outline()->Contains( center, -1, holeRadius );
402 };
403
404 for( PCB_LAYER_ID layer : layers )
405 {
406 if( !via->ConditionallyFlashed( layer ) )
407 continue;
408
409 if( isInPourKeepoutArea( bbox, layer, center ) )
410 {
411 via->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
412 }
413 else
414 {
415 ZONE* zone = findHighestPriorityZone( bbox, layer, netcode, viaTestFn );
416
417 if( zone && zone->GetNetCode() == via->GetNetCode()
419 || layer == padstack.Drill().start
420 || layer == padstack.Drill().end ) )
421 {
422 via->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
423 }
424 else
425 {
426 via->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
427 }
428 }
429 }
430 }
431 }
432
433 // Determine state of conditional pad flashing
434 for( FOOTPRINT* footprint : m_board->Footprints() )
435 {
436 for( PAD* pad : footprint->Pads() )
437 {
438 pad->ClearZoneLayerOverrides();
439
440 if( !pad->GetRemoveUnconnected() )
441 continue;
442
443 BOX2I bbox = pad->GetBoundingBox();
444 VECTOR2I center = pad->GetPosition();
445 int netcode = pad->GetNetCode();
446 LSET layers = pad->GetLayerSet() & boardCuMask;
447
448 auto padTestFn =
449 [&]( const ZONE* aZone ) -> bool
450 {
451 return aZone->Outline()->Contains( center );
452 };
453
454 for( PCB_LAYER_ID layer : layers )
455 {
456 if( !pad->ConditionallyFlashed( layer ) )
457 continue;
458
459 if( isInPourKeepoutArea( bbox, layer, center ) )
460 {
461 pad->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
462 }
463 else
464 {
465 ZONE* zone = findHighestPriorityZone( bbox, layer, netcode, padTestFn );
466
467 if( zone && zone->GetNetCode() == pad->GetNetCode() )
468 pad->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
469 else
470 pad->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
471 }
472 }
473 }
474 }
475
476 for( ZONE* zone : aZones )
477 {
478 // Rule areas are not filled
479 if( zone->GetIsRuleArea() )
480 continue;
481
482 // Degenerate zones will cause trouble; skip them
483 if( zone->GetNumCorners() <= 2 )
484 continue;
485
486 if( m_commit )
487 m_commit->Modify( zone );
488
489 // calculate the hash value for filled areas. it will be used later to know if the
490 // current filled areas are up to date
491 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
492 {
493 zone->BuildHashValue( layer );
494 oldFillHashes[ { zone, layer } ] = zone->GetHashValue( layer );
495
496 // Add the zone to the list of zones to test or refill
497 toFill.emplace_back( std::make_pair( zone, layer ) );
498
499 isolatedIslandsMap[ zone ][ layer ] = ISOLATED_ISLANDS();
500 }
501
502 // Remove existing fill first to prevent drawing invalid polygons on some platforms
503 zone->UnFill();
504 }
505
506 auto check_fill_dependency =
507 [&]( ZONE* aZone, PCB_LAYER_ID aLayer, ZONE* aOtherZone ) -> bool
508 {
509 // Check to see if we have to knock-out the filled areas of a higher-priority
510 // zone. If so we have to wait until said zone is filled before we can fill.
511
512 // If the other zone is already filled on the requested layer then we're
513 // good-to-go
514 if( aOtherZone->GetFillFlag( aLayer ) )
515 return false;
516
517 // Even if keepouts exclude copper pours, the exclusion is by outline rather than
518 // filled area, so we're good-to-go here too
519 if( aOtherZone->GetIsRuleArea() )
520 return false;
521
522 // If the other zone is never going to be filled then don't wait for it
523 if( aOtherZone->GetNumCorners() <= 2 )
524 return false;
525
526 // If the zones share no common layers
527 if( !aOtherZone->GetLayerSet().test( aLayer ) )
528 return false;
529
530 if( aZone->HigherPriority( aOtherZone ) )
531 return false;
532
533 // Same-net zones always use outlines to produce determinate results
534 if( aOtherZone->SameNet( aZone ) )
535 return false;
536
537 // A higher priority zone is found: if we intersect and it's not filled yet
538 // then we have to wait.
539 BOX2I inflatedBBox = aZone->GetBoundingBox();
540 inflatedBBox.Inflate( m_worstClearance );
541
542 if( !inflatedBBox.Intersects( aOtherZone->GetBoundingBox() ) )
543 return false;
544
545 return aZone->Outline()->Collide( aOtherZone->Outline(), m_worstClearance );
546 };
547
548 auto fill_lambda =
549 [&]( std::pair<ZONE*, PCB_LAYER_ID> aFillItem ) -> int
550 {
551 PCB_LAYER_ID layer = aFillItem.second;
552 ZONE* zone = aFillItem.first;
553 bool canFill = true;
554
555 // Check for any fill dependencies. If our zone needs to be clipped by
556 // another zone then we can't fill until that zone is filled.
557 for( ZONE* otherZone : aZones )
558 {
559 if( otherZone == zone )
560 continue;
561
562 if( check_fill_dependency( zone, layer, otherZone ) )
563 {
564 canFill = false;
565 break;
566 }
567 }
568
569 if( m_progressReporter && m_progressReporter->IsCancelled() )
570 return 0;
571
572 if( !canFill )
573 return 0;
574
575 // Now we're ready to fill.
576 {
577 std::unique_lock<std::mutex> zoneLock( zone->GetLock(), std::try_to_lock );
578
579 if( !zoneLock.owns_lock() )
580 return 0;
581
582 SHAPE_POLY_SET fillPolys;
583
584 if( !fillSingleZone( zone, layer, fillPolys ) )
585 return 0;
586
587 zone->SetFilledPolysList( layer, fillPolys );
588 }
589
591 m_progressReporter->AdvanceProgress();
592
593 return 1;
594 };
595
596 auto tesselate_lambda =
597 [&]( std::pair<ZONE*, PCB_LAYER_ID> aFillItem ) -> int
598 {
599 if( m_progressReporter && m_progressReporter->IsCancelled() )
600 return 0;
601
602 PCB_LAYER_ID layer = aFillItem.second;
603 ZONE* zone = aFillItem.first;
604
605 {
606 std::unique_lock<std::mutex> zoneLock( zone->GetLock(), std::try_to_lock );
607
608 if( !zoneLock.owns_lock() )
609 return 0;
610
611 zone->CacheTriangulation( layer );
612 zone->SetFillFlag( layer, true );
613 }
614
615 return 1;
616 };
617
618 // Calculate the copper fills (NB: this is multi-threaded)
619 //
620 std::vector<std::pair<std::future<int>, int>> returns;
621 returns.reserve( toFill.size() );
622 size_t finished = 0;
623 bool cancelled = false;
624
626
627 for( const std::pair<ZONE*, PCB_LAYER_ID>& fillItem : toFill )
628 {
629 returns.emplace_back( std::make_pair( tp.submit_task(
630 [&, fillItem]()
631 {
632 return fill_lambda( fillItem );
633 } ), 0 ) );
634 }
635
636 while( !cancelled && finished != 2 * toFill.size() )
637 {
638 for( size_t ii = 0; ii < returns.size(); ++ii )
639 {
640 auto& ret = returns[ii];
641
642 if( ret.second > 1 )
643 continue;
644
645 std::future_status status = ret.first.wait_for( std::chrono::seconds( 0 ) );
646
647 if( status == std::future_status::ready )
648 {
649 if( ret.first.get() ) // lambda completed
650 {
651 ++finished;
652 ret.second++; // go to next step
653 }
654
655 if( !cancelled )
656 {
657 // Queue the next step (will re-queue the existing step if it didn't complete)
658 if( ret.second == 0 )
659 {
660 returns[ii].first = tp.submit_task(
661 [&, idx = ii]()
662 {
663 return fill_lambda( toFill[idx] );
664 } );
665 }
666 else if( ret.second == 1 )
667 {
668 returns[ii].first = tp.submit_task(
669 [&, idx = ii]()
670 {
671 return tesselate_lambda( toFill[idx] );
672 } );
673 }
674 }
675 }
676 }
677
678 std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
679
680
682 {
683 m_progressReporter->KeepRefreshing();
684
685 if( m_progressReporter->IsCancelled() )
686 cancelled = true;
687 }
688 }
689
690 // Make sure that all futures have finished.
691 // This can happen when the user cancels the above operation
692 for( auto& ret : returns )
693 {
694 if( ret.first.valid() )
695 {
696 std::future_status status = ret.first.wait_for( std::chrono::seconds( 0 ) );
697
698 while( status != std::future_status::ready )
699 {
701 m_progressReporter->KeepRefreshing();
702
703 status = ret.first.wait_for( std::chrono::milliseconds( 100 ) );
704 }
705 }
706 }
707
708 // Now update the connectivity to check for isolated copper islands
709 // (NB: FindIsolatedCopperIslands() is multi-threaded)
710 //
712 {
713 if( m_progressReporter->IsCancelled() )
714 return false;
715
716 m_progressReporter->AdvancePhase();
717 m_progressReporter->Report( _( "Removing isolated copper islands..." ) );
718 m_progressReporter->KeepRefreshing();
719 }
720
721 connectivity->SetProgressReporter( m_progressReporter );
722 connectivity->FillIsolatedIslandsMap( isolatedIslandsMap );
723 connectivity->SetProgressReporter( nullptr );
724
725 if( m_progressReporter && m_progressReporter->IsCancelled() )
726 return false;
727
728 for( ZONE* zone : aZones )
729 {
730 // Keepout zones are not filled
731 if( zone->GetIsRuleArea() )
732 continue;
733
734 zone->SetIsFilled( true );
735 }
736
737 // Now remove isolated copper islands according to the isolated islands strategy assigned
738 // by the user (always, never, below-certain-size).
739 //
740 // Track zones that had islands removed for potential iterative refill
741 std::set<ZONE*> zonesWithRemovedIslands;
742
743 for( const auto& [ zone, zoneIslands ] : isolatedIslandsMap )
744 {
745 // If *all* the polygons are islands, do not remove any of them
746 bool allIslands = true;
747
748 for( const auto& [ layer, layerIslands ] : zoneIslands )
749 {
750 if( layerIslands.m_IsolatedOutlines.size()
751 != static_cast<size_t>( zone->GetFilledPolysList( layer )->OutlineCount() ) )
752 {
753 allIslands = false;
754 break;
755 }
756 }
757
758 if( allIslands )
759 continue;
760
761 for( const auto& [ layer, layerIslands ] : zoneIslands )
762 {
763 if( m_debugZoneFiller && LSET::InternalCuMask().Contains( layer ) )
764 continue;
765
766 if( layerIslands.m_IsolatedOutlines.empty() )
767 continue;
768
769 std::vector<int> islands = layerIslands.m_IsolatedOutlines;
770
771 // The list of polygons to delete must be explored from last to first in list,
772 // to allow deleting a polygon from list without breaking the remaining of the list
773 std::sort( islands.begin(), islands.end(), std::greater<int>() );
774
775 std::shared_ptr<SHAPE_POLY_SET> poly = zone->GetFilledPolysList( layer );
776 long long int minArea = zone->GetMinIslandArea();
777 ISLAND_REMOVAL_MODE mode = zone->GetIslandRemovalMode();
778
779 wxLogTrace( traceZoneFiller, wxT( "Zone %s layer %d: %zu islands to process, mode=%d, poly has %d outlines, area %.0f" ),
780 zone->GetNetname(), static_cast<int>( layer ), islands.size(),
781 static_cast<int>( mode ), poly->OutlineCount(), poly->Area() );
782
783 for( int idx : islands )
784 {
785 SHAPE_LINE_CHAIN& outline = poly->Outline( idx );
786
787 if( mode == ISLAND_REMOVAL_MODE::ALWAYS )
788 {
789 wxLogTrace( traceZoneFiller, wxT( "Removing island %d from zone %s (ALWAYS mode)" ),
790 idx, zone->GetNetname() );
791 poly->DeletePolygonAndTriangulationData( idx, false );
792 zonesWithRemovedIslands.insert( zone );
793 }
794 else if ( mode == ISLAND_REMOVAL_MODE::AREA && outline.Area( true ) < minArea )
795 {
796 wxLogTrace( traceZoneFiller, wxT( "Removing island %d from zone %s (AREA mode, area=%.0f < min=%.0f)" ),
797 idx, zone->GetNetname(), outline.Area( true ),
798 static_cast<double>( minArea ) );
799 poly->DeletePolygonAndTriangulationData( idx, false );
800 zonesWithRemovedIslands.insert( zone );
801 }
802 else
803 {
804 zone->SetIsIsland( layer, idx );
805 }
806 }
807
808 poly->UpdateTriangulationDataHash();
809 zone->CalculateFilledArea();
810
811 BOX2I bbox = poly->BBox();
812 wxLogTrace( traceZoneFiller, wxT( "After island removal, zone %s: %d outlines, area %.0f, bbox (%d,%d)-(%d,%d)" ),
813 zone->GetNetname(), poly->OutlineCount(), poly->Area(),
814 bbox.GetX(), bbox.GetY(), bbox.GetRight(), bbox.GetBottom() );
815
816 if( m_progressReporter && m_progressReporter->IsCancelled() )
817 return false;
818 }
819 }
820
821 // Iterative refill: If islands were removed from higher-priority zones, lower-priority zones
822 // may need to be refilled to occupy the now-available space (issue 21746).
823 //
824 const bool iterativeRefill = ADVANCED_CFG::GetCfg().m_ZoneFillIterativeRefill;
825
826 if( iterativeRefill && !zonesWithRemovedIslands.empty() )
827 {
828 PROF_TIMER timer;
829
830 wxLogTrace( traceZoneFiller, wxT( "Iterative refill: %zu zones had islands removed, cache size: %zu" ),
831 zonesWithRemovedIslands.size(), m_preKnockoutFillCache.size() );
832
833 // Find lower-priority zones that may need refilling.
834 // A zone needs refilling if it overlaps with a zone that had islands removed
835 // and has lower priority than that zone.
836 std::vector<std::pair<ZONE*, PCB_LAYER_ID>> zonesToRefill;
837
838 for( ZONE* zoneWithIsland : zonesWithRemovedIslands )
839 {
840 BOX2I islandZoneBBox = zoneWithIsland->GetBoundingBox();
841 islandZoneBBox.Inflate( m_worstClearance );
842
843 for( ZONE* zone : aZones )
844 {
845 // Skip the zone that had islands removed
846 if( zone == zoneWithIsland )
847 continue;
848
849 // Skip keepout zones
850 if( zone->GetIsRuleArea() )
851 continue;
852
853 // Only refill zones with lower priority than the zone that had islands removed
854 if( !zoneWithIsland->HigherPriority( zone ) )
855 continue;
856
857 // Check for layer overlap
858 LSET commonLayers = zone->GetLayerSet() & zoneWithIsland->GetLayerSet();
859
860 if( commonLayers.none() )
861 continue;
862
863 // Check for bounding box overlap
864 if( !zone->GetBoundingBox().Intersects( islandZoneBBox ) )
865 continue;
866
867 // Add zone/layer pairs for refilling
868 for( PCB_LAYER_ID layer : commonLayers )
869 {
870 auto fillItem = std::make_pair( zone, layer );
871
872 if( std::find( zonesToRefill.begin(), zonesToRefill.end(), fillItem ) == zonesToRefill.end() )
873 {
874 zonesToRefill.push_back( fillItem );
875 }
876 }
877 }
878 }
879
880 if( !zonesToRefill.empty() )
881 {
882 wxLogTrace( traceZoneFiller, wxT( "Iterative refill: refilling %zu zone/layer pairs" ),
883 zonesToRefill.size() );
884
886 {
887 m_progressReporter->AdvancePhase();
888 m_progressReporter->Report( _( "Refilling zones after island removal..." ) );
889 m_progressReporter->KeepRefreshing();
890 }
891
892 // Refill using cached pre-knockout fills - much faster than full refill
893 // since we only need to re-apply the higher-priority zone knockout
894 auto cached_refill_lambda =
895 [&]( const std::pair<ZONE*, PCB_LAYER_ID>& aFillItem ) -> int
896 {
897 ZONE* zone = aFillItem.first;
898 PCB_LAYER_ID layer = aFillItem.second;
899 SHAPE_POLY_SET fillPolys;
900
901 if( !refillZoneFromCache( zone, layer, fillPolys ) )
902 return 0;
903
904 wxLogTrace( traceZoneFiller,
905 wxT( "Cached refill for zone %s: %d outlines, area %.0f" ),
906 zone->GetNetname(), fillPolys.OutlineCount(), fillPolys.Area() );
907
908 zone->SetFilledPolysList( layer, fillPolys );
909 zone->SetFillFlag( layer, true );
910 return 1;
911 };
912
913 std::vector<std::pair<std::future<int>, int>> refillReturns;
914 refillReturns.reserve( zonesToRefill.size() );
915 size_t refillFinished = 0;
916
917 for( const auto& fillItem : zonesToRefill )
918 {
919 refillReturns.emplace_back( std::make_pair( tp.submit_task(
920 [&, fillItem]()
921 {
922 return cached_refill_lambda( fillItem );
923 } ), 0 ) );
924 }
925
926 while( !cancelled && refillFinished != 2 * zonesToRefill.size() )
927 {
928 for( size_t ii = 0; ii < refillReturns.size(); ++ii )
929 {
930 auto& ret = refillReturns[ii];
931
932 if( ret.second > 1 )
933 continue;
934
935 std::future_status status = ret.first.wait_for( std::chrono::seconds( 0 ) );
936
937 if( status == std::future_status::ready )
938 {
939 if( ret.first.get() )
940 {
941 ++refillFinished;
942 ret.second++;
943 }
944
945 if( !cancelled )
946 {
947 if( ret.second == 0 )
948 {
949 refillReturns[ii].first = tp.submit_task(
950 [&, idx = ii]()
951 {
952 return cached_refill_lambda( zonesToRefill[idx] );
953 } );
954 }
955 else if( ret.second == 1 )
956 {
957 refillReturns[ii].first = tp.submit_task(
958 [&, idx = ii]()
959 {
960 return tesselate_lambda( zonesToRefill[idx] );
961 } );
962 }
963 }
964 }
965 }
966
967 std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
968
970 {
971 m_progressReporter->KeepRefreshing();
972
973 if( m_progressReporter->IsCancelled() )
974 cancelled = true;
975 }
976 }
977
978 // Wait for all refill tasks to complete
979 for( auto& ret : refillReturns )
980 {
981 if( ret.first.valid() )
982 {
983 std::future_status status = ret.first.wait_for( std::chrono::seconds( 0 ) );
984
985 while( status != std::future_status::ready )
986 {
988 m_progressReporter->KeepRefreshing();
989
990 status = ret.first.wait_for( std::chrono::milliseconds( 100 ) );
991 }
992 }
993 }
994
995 // Re-run island detection for refilled zones
996 std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>> refillIslandsMap;
997 std::set<ZONE*> refillZones;
998
999 for( const auto& [zone, layer] : zonesToRefill )
1000 refillZones.insert( zone );
1001
1002 for( ZONE* zone : refillZones )
1003 refillIslandsMap[zone] = std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>();
1004
1005 connectivity->FillIsolatedIslandsMap( refillIslandsMap );
1006
1007 // Remove islands from refilled zones
1008 for( const auto& [ zone, zoneIslands ] : refillIslandsMap )
1009 {
1010 bool allIslands = true;
1011
1012 for( const auto& [ layer, layerIslands ] : zoneIslands )
1013 {
1014 if( layerIslands.m_IsolatedOutlines.size()
1015 != static_cast<size_t>( zone->GetFilledPolysList( layer )->OutlineCount() ) )
1016 {
1017 allIslands = false;
1018 break;
1019 }
1020 }
1021
1022 if( allIslands )
1023 continue;
1024
1025 for( const auto& [ layer, layerIslands ] : zoneIslands )
1026 {
1027 if( m_debugZoneFiller && LSET::InternalCuMask().Contains( layer ) )
1028 continue;
1029
1030 if( layerIslands.m_IsolatedOutlines.empty() )
1031 continue;
1032
1033 std::vector<int> islands = layerIslands.m_IsolatedOutlines;
1034 std::sort( islands.begin(), islands.end(), std::greater<int>() );
1035
1036 std::shared_ptr<SHAPE_POLY_SET> poly = zone->GetFilledPolysList( layer );
1037 long long int minArea = zone->GetMinIslandArea();
1038 ISLAND_REMOVAL_MODE mode = zone->GetIslandRemovalMode();
1039
1040 for( int idx : islands )
1041 {
1042 SHAPE_LINE_CHAIN& outline = poly->Outline( idx );
1043
1044 if( mode == ISLAND_REMOVAL_MODE::ALWAYS )
1045 poly->DeletePolygonAndTriangulationData( idx, false );
1046 else if( mode == ISLAND_REMOVAL_MODE::AREA && outline.Area( true ) < minArea )
1047 poly->DeletePolygonAndTriangulationData( idx, false );
1048 else
1049 zone->SetIsIsland( layer, idx );
1050 }
1051
1052 poly->UpdateTriangulationDataHash();
1053 zone->CalculateFilledArea();
1054 }
1055 }
1056
1057 wxLogTrace( traceZoneFiller, wxT( "Iterative refill completed in %0.3f ms" ),
1058 timer.msecs() );
1059 }
1060 }
1061
1062 // Now remove islands which are either outside the board edge or fail to meet the minimum
1063 // area requirements
1064 using island_check_return = std::vector<std::pair<std::shared_ptr<SHAPE_POLY_SET>, int>>;
1065
1066 std::vector<std::pair<std::shared_ptr<SHAPE_POLY_SET>, double>> polys_to_check;
1067
1068 // rough estimate to save re-allocation time
1069 polys_to_check.reserve( m_board->GetCopperLayerCount() * aZones.size() );
1070
1071 for( ZONE* zone : aZones )
1072 {
1073 // Don't check for connections on layers that only exist in the zone but
1074 // were disabled in the board
1075 BOARD* board = zone->GetBoard();
1076 LSET zoneCopperLayers = zone->GetLayerSet() & LSET::AllCuMask( board->GetCopperLayerCount() );
1077
1078 // Min-thickness is the web thickness. On the other hand, a blob min-thickness by
1079 // min-thickness is not useful. Since there's no obvious definition of web vs. blob, we
1080 // arbitrarily choose "at least 3X the area".
1081 double minArea = (double) zone->GetMinThickness() * zone->GetMinThickness() * 3;
1082
1083 for( PCB_LAYER_ID layer : zoneCopperLayers )
1084 {
1085 if( m_debugZoneFiller && LSET::InternalCuMask().Contains( layer ) )
1086 continue;
1087
1088 polys_to_check.emplace_back( zone->GetFilledPolysList( layer ), minArea );
1089 }
1090 }
1091
1092 auto island_lambda =
1093 [&]( int aStart, int aEnd ) -> island_check_return
1094 {
1095 island_check_return retval;
1096
1097 for( int ii = aStart; ii < aEnd && !cancelled; ++ii )
1098 {
1099 auto [poly, minArea] = polys_to_check[ii];
1100
1101 for( int jj = poly->OutlineCount() - 1; jj >= 0; jj-- )
1102 {
1103 SHAPE_POLY_SET island;
1104 SHAPE_POLY_SET intersection;
1105 const SHAPE_LINE_CHAIN& test_poly = poly->Polygon( jj ).front();
1106 double island_area = test_poly.Area();
1107
1108 if( island_area < minArea )
1109 continue;
1110
1111
1112 island.AddOutline( test_poly );
1113 intersection.BooleanIntersection( m_boardOutline, island );
1114
1115 // Nominally, all of these areas should be either inside or outside the
1116 // board outline. So this test should be able to just compare areas (if
1117 // they are equal, you are inside). But in practice, we sometimes have
1118 // slight overlap at the edges, so testing against half-size area acts as
1119 // a fail-safe.
1120 if( intersection.Area() < island_area / 2.0 )
1121 retval.emplace_back( poly, jj );
1122 }
1123 }
1124
1125 return retval;
1126 };
1127
1128 auto island_returns = tp.submit_blocks( 0, polys_to_check.size(), island_lambda );
1129 cancelled = false;
1130
1131 // Allow island removal threads to finish
1132 for( size_t ii = 0; ii < island_returns.size(); ++ii )
1133 {
1134 std::future<island_check_return>& ret = island_returns[ii];
1135
1136 if( ret.valid() )
1137 {
1138 std::future_status status = ret.wait_for( std::chrono::seconds( 0 ) );
1139
1140 while( status != std::future_status::ready )
1141 {
1142 if( m_progressReporter )
1143 {
1144 m_progressReporter->KeepRefreshing();
1145
1146 if( m_progressReporter->IsCancelled() )
1147 cancelled = true;
1148 }
1149
1150 status = ret.wait_for( std::chrono::milliseconds( 100 ) );
1151 }
1152 }
1153 }
1154
1155 if( cancelled )
1156 return false;
1157
1158 for( size_t ii = 0; ii < island_returns.size(); ++ii )
1159 {
1160 std::future<island_check_return>& ret = island_returns[ii];
1161
1162 if( ret.valid() )
1163 {
1164 for( auto& action_item : ret.get() )
1165 action_item.first->DeletePolygonAndTriangulationData( action_item.second, true );
1166 }
1167 }
1168
1169 for( ZONE* zone : aZones )
1170 zone->CalculateFilledArea();
1171
1172 // Second pass: Re-evaluate via flashing based on actual filled polygons.
1173 // The first pass (before filling) marks vias as ZLO_FORCE_FLASHED if they're within the
1174 // zone outline. However, if the fill doesn't actually reach the via (due to obstacles like
1175 // tracks), we should not flash the via. See https://gitlab.com/kicad/code/kicad/-/issues/22010
1176 for( PCB_TRACK* track : m_board->Tracks() )
1177 {
1178 if( track->Type() != PCB_VIA_T )
1179 continue;
1180
1181 PCB_VIA* via = static_cast<PCB_VIA*>( track );
1182 VECTOR2I center = via->GetPosition();
1183 int holeRadius = via->GetDrillValue() / 2;
1184 int netcode = via->GetNetCode();
1185 LSET layers = via->GetLayerSet() & boardCuMask;
1186
1187 for( PCB_LAYER_ID layer : layers )
1188 {
1189 if( via->GetZoneLayerOverride( layer ) != ZLO_FORCE_FLASHED )
1190 continue;
1191
1192 bool zoneReachesVia = false;
1193
1194 for( ZONE* zone : m_board->Zones() )
1195 {
1196 if( zone->GetIsRuleArea() )
1197 continue;
1198
1199 if( zone->GetNetCode() != netcode )
1200 continue;
1201
1202 if( !zone->IsOnLayer( layer ) )
1203 continue;
1204
1205 if( !zone->HasFilledPolysForLayer( layer ) )
1206 continue;
1207
1208 const std::shared_ptr<SHAPE_POLY_SET>& fill = zone->GetFilledPolysList( layer );
1209
1210 if( fill->IsEmpty() )
1211 continue;
1212
1213 // Check if the filled zone reaches the via hole. Use holeRadius as reach distance
1214 // to match the pre-fill check logic.
1215 if( fill->Contains( center, -1, holeRadius ) )
1216 {
1217 zoneReachesVia = true;
1218 break;
1219 }
1220 }
1221
1222 if( !zoneReachesVia )
1223 via->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
1224 }
1225 }
1226
1227 // Same logic for pads
1228 for( FOOTPRINT* footprint : m_board->Footprints() )
1229 {
1230 for( PAD* pad : footprint->Pads() )
1231 {
1232 VECTOR2I center = pad->GetPosition();
1233 int netcode = pad->GetNetCode();
1234 LSET layers = pad->GetLayerSet() & boardCuMask;
1235
1236 for( PCB_LAYER_ID layer : layers )
1237 {
1238 if( pad->GetZoneLayerOverride( layer ) != ZLO_FORCE_FLASHED )
1239 continue;
1240
1241 bool zoneReachesPad = false;
1242
1243 for( ZONE* zone : m_board->Zones() )
1244 {
1245 if( zone->GetIsRuleArea() )
1246 continue;
1247
1248 if( zone->GetNetCode() != netcode )
1249 continue;
1250
1251 if( !zone->IsOnLayer( layer ) )
1252 continue;
1253
1254 if( !zone->HasFilledPolysForLayer( layer ) )
1255 continue;
1256
1257 const std::shared_ptr<SHAPE_POLY_SET>& fill = zone->GetFilledPolysList( layer );
1258
1259 if( fill->IsEmpty() )
1260 continue;
1261
1262 if( fill->Contains( center ) )
1263 {
1264 zoneReachesPad = true;
1265 break;
1266 }
1267 }
1268
1269 if( !zoneReachesPad )
1270 pad->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
1271 }
1272 }
1273 }
1274
1275 if( aCheck )
1276 {
1277 bool outOfDate = false;
1278
1279 for( ZONE* zone : aZones )
1280 {
1281 // Keepout zones are not filled
1282 if( zone->GetIsRuleArea() )
1283 continue;
1284
1285 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
1286 {
1287 zone->BuildHashValue( layer );
1288
1289 if( oldFillHashes[ { zone, layer } ] != zone->GetHashValue( layer ) )
1290 outOfDate = true;
1291 }
1292 }
1293
1294 if( ( m_board->GetProject()
1295 && m_board->GetProject()->GetLocalSettings().m_PrototypeZoneFill ) )
1296 {
1297 KIDIALOG dlg( aParent, _( "Prototype zone fill enabled. Disable setting and refill?" ), _( "Confirmation" ),
1298 wxOK | wxCANCEL | wxICON_WARNING );
1299 dlg.SetOKCancelLabels( _( "Disable and refill" ), _( "Continue without Refill" ) );
1300 dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
1301
1302 if( dlg.ShowModal() == wxID_OK )
1303 {
1304 m_board->GetProject()->GetLocalSettings().m_PrototypeZoneFill = false;
1305 }
1306 else if( !outOfDate )
1307 {
1308 return false;
1309 }
1310 }
1311
1312 if( outOfDate )
1313 {
1314 KIDIALOG dlg( aParent, _( "Zone fills are out-of-date. Refill?" ), _( "Confirmation" ),
1315 wxOK | wxCANCEL | wxICON_WARNING );
1316 dlg.SetOKCancelLabels( _( "Refill" ), _( "Continue without Refill" ) );
1317 dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
1318
1319 if( dlg.ShowModal() == wxID_CANCEL )
1320 return false;
1321 }
1322 else
1323 {
1324 // No need to commit something that hasn't changed (and committing will set
1325 // the modified flag).
1326 return false;
1327 }
1328 }
1329
1330 if( m_progressReporter )
1331 {
1332 if( m_progressReporter->IsCancelled() )
1333 return false;
1334
1335 m_progressReporter->AdvancePhase();
1336 m_progressReporter->KeepRefreshing();
1337 }
1338
1339 return true;
1340}
1341
1342
1347void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles )
1348{
1349 if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
1350 {
1351 PAD* pad = static_cast<PAD*>( aItem );
1352 SHAPE_POLY_SET poly;
1353 pad->TransformShapeToPolygon( poly, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
1354
1355 // the pad shape in zone can be its convex hull or the shape itself
1356 if( pad->GetCustomShapeInZoneOpt() == CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL )
1357 {
1358 std::vector<VECTOR2I> convex_hull;
1359 BuildConvexHull( convex_hull, poly );
1360
1361 aHoles.NewOutline();
1362
1363 for( const VECTOR2I& pt : convex_hull )
1364 aHoles.Append( pt );
1365 }
1366 else
1367 {
1368 aHoles.Append( poly );
1369 }
1370 }
1371 else
1372 {
1373 aItem->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
1374 }
1375}
1376
1377
1381void ZONE_FILLER::addHoleKnockout( PAD* aPad, int aGap, SHAPE_POLY_SET& aHoles )
1382{
1383 aPad->TransformHoleToPolygon( aHoles, aGap, m_maxError, ERROR_OUTSIDE );
1384}
1385
1386
1388{
1389 int minorAxis = 0;
1390
1391 if( aItem->Type() == PCB_PAD_T )
1392 {
1393 PAD* pad = static_cast<PAD*>( aItem );
1394 VECTOR2I padSize = pad->GetSize( aLayer );
1395
1396 minorAxis = std::min( padSize.x, padSize.y );
1397 }
1398 else if( aItem->Type() == PCB_VIA_T )
1399 {
1400 PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
1401
1402 minorAxis = via->GetWidth( aLayer );
1403 }
1404
1405 return ( aZone->GetHatchGap() - aZone->GetHatchThickness() - minorAxis ) / 2;
1406}
1407
1408
1413void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap,
1414 bool aIgnoreLineWidth, SHAPE_POLY_SET& aHoles )
1415{
1416 switch( aItem->Type() )
1417 {
1418 case PCB_FIELD_T:
1419 case PCB_TEXT_T:
1420 {
1421 PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
1422
1423 if( text->IsVisible() )
1424 {
1425 if( text->IsKnockout() )
1426 {
1427 // Knockout text should only leave holes where the text is, not where the copper fill
1428 // around it would be.
1429 PCB_TEXT textCopy = *text;
1430 textCopy.SetIsKnockout( false );
1431 textCopy.TransformTextToPolySet( aHoles, 0, m_maxError, ERROR_INSIDE );
1432 }
1433 else
1434 {
1435 text->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
1436 }
1437 }
1438
1439 break;
1440 }
1441
1442 case PCB_TEXTBOX_T:
1443 case PCB_TABLE_T:
1444 case PCB_SHAPE_T:
1445 case PCB_TARGET_T:
1446 aItem->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE, aIgnoreLineWidth );
1447 break;
1448
1449 case PCB_BARCODE_T:
1450 {
1451 PCB_BARCODE* barcode = static_cast<PCB_BARCODE*>( aItem );
1452 barcode->GetBoundingHull( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
1453 break;
1454 }
1455
1456 case PCB_DIM_ALIGNED_T:
1457 case PCB_DIM_LEADER_T:
1458 case PCB_DIM_CENTER_T:
1459 case PCB_DIM_RADIAL_T:
1461 {
1462 PCB_DIMENSION_BASE* dim = static_cast<PCB_DIMENSION_BASE*>( aItem );
1463
1464 dim->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE, false );
1465 dim->PCB_TEXT::TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
1466 break;
1467 }
1468
1469 default:
1470 break;
1471 }
1472}
1473
1474
1480 SHAPE_POLY_SET& aFill,
1481 std::vector<BOARD_ITEM*>& aThermalConnectionPads,
1482 std::vector<PAD*>& aNoConnectionPads )
1483{
1484 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
1485 ZONE_CONNECTION connection;
1486 DRC_CONSTRAINT constraint;
1487 int padClearance;
1488 std::shared_ptr<SHAPE> padShape;
1489 int holeClearance;
1490 SHAPE_POLY_SET holes;
1491
1492 for( FOOTPRINT* footprint : m_board->Footprints() )
1493 {
1494 for( PAD* pad : footprint->Pads() )
1495 {
1496 if( !pad->IsOnLayer( aLayer ) )
1497 continue;
1498
1499 BOX2I padBBox = pad->GetBoundingBox();
1500 padBBox.Inflate( m_worstClearance );
1501
1502 if( !padBBox.Intersects( aZone->GetBoundingBox() ) )
1503 continue;
1504
1505 bool noConnection = pad->GetNetCode() != aZone->GetNetCode();
1506
1507 if( !aZone->IsTeardropArea() )
1508 {
1509 if( aZone->GetNetCode() == 0
1510 || pad->GetZoneLayerOverride( aLayer ) == ZLO_FORCE_NO_ZONE_CONNECTION )
1511 {
1512 noConnection = true;
1513 }
1514 }
1515
1516 // Check if the pad is backdrilled or post-machined on this layer
1517 if( pad->IsBackdrilledOrPostMachined( aLayer ) )
1518 noConnection = true;
1519
1520 if( noConnection )
1521 {
1522 // collect these for knockout in buildCopperItemClearances()
1523 aNoConnectionPads.push_back( pad );
1524 continue;
1525 }
1526
1527 // For hatch zones, respect the zone connection type just like solid zones
1528 // Pads with THERMAL connection get thermal rings; FULL connections get no knockout;
1529 // NONE connections get handled later in buildCopperItemClearances.
1531 {
1532 constraint = bds.m_DRCEngine->EvalZoneConnection( pad, aZone, aLayer );
1533 connection = constraint.m_ZoneConnection;
1534
1535 if( connection == ZONE_CONNECTION::THERMAL && !pad->CanFlashLayer( aLayer ) )
1536 connection = ZONE_CONNECTION::NONE;
1537
1538 switch( connection )
1539 {
1541 {
1542 padShape = pad->GetEffectiveShape( aLayer, FLASHING::ALWAYS_FLASHED );
1543
1544 if( aFill.Collide( padShape.get(), 0 ) )
1545 {
1546 // Get the thermal relief gap
1548 aZone, aLayer );
1549 int thermalGap = constraint.GetValue().Min();
1550
1551 // Knock out the thermal gap only - the thermal ring will be added separately
1552 aThermalConnectionPads.push_back( pad );
1553 addKnockout( pad, aLayer, thermalGap, holes );
1554 }
1555
1556 break;
1557 }
1558
1560 // Will be handled by buildCopperItemClearances
1561 aNoConnectionPads.push_back( pad );
1562 break;
1563
1565 default:
1566 // No knockout - pad connects directly to the hatch
1567 break;
1568 }
1569
1570 continue;
1571 }
1572
1573 if( aZone->IsTeardropArea() )
1574 {
1575 connection = ZONE_CONNECTION::FULL;
1576 }
1577 else
1578 {
1579 constraint = bds.m_DRCEngine->EvalZoneConnection( pad, aZone, aLayer );
1580 connection = constraint.m_ZoneConnection;
1581 }
1582
1583 if( connection == ZONE_CONNECTION::THERMAL && !pad->CanFlashLayer( aLayer ) )
1584 connection = ZONE_CONNECTION::NONE;
1585
1586 switch( connection )
1587 {
1589 padShape = pad->GetEffectiveShape( aLayer, FLASHING::ALWAYS_FLASHED );
1590
1591 if( aFill.Collide( padShape.get(), 0 ) )
1592 {
1593 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
1594 padClearance = constraint.GetValue().Min();
1595
1596 aThermalConnectionPads.push_back( pad );
1597 addKnockout( pad, aLayer, padClearance, holes );
1598 }
1599
1600 break;
1601
1603 constraint = bds.m_DRCEngine->EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, pad, aZone, aLayer );
1604
1605 if( constraint.GetValue().Min() > aZone->GetLocalClearance().value() )
1606 padClearance = constraint.GetValue().Min();
1607 else
1608 padClearance = aZone->GetLocalClearance().value();
1609
1610 if( pad->FlashLayer( aLayer ) )
1611 {
1612 addKnockout( pad, aLayer, padClearance, holes );
1613 }
1614 else if( pad->GetDrillSize().x > 0 )
1615 {
1616 constraint = bds.m_DRCEngine->EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, pad, aZone, aLayer );
1617
1618 if( constraint.GetValue().Min() > padClearance )
1619 holeClearance = constraint.GetValue().Min();
1620 else
1621 holeClearance = padClearance;
1622
1623 pad->TransformHoleToPolygon( holes, holeClearance, m_maxError, ERROR_OUTSIDE );
1624 }
1625
1626 break;
1627
1628 default:
1629 // No knockout
1630 continue;
1631 }
1632 }
1633 }
1634
1635 // For hatch zones, vias also get proper thermal treatment. They always use thermal connection
1636 // since vias don't have zone connection settings like pads do.
1638 {
1639 for( PCB_TRACK* track : m_board->Tracks() )
1640 {
1641 if( track->Type() == PCB_VIA_T )
1642 {
1643 PCB_VIA* via = static_cast<PCB_VIA*>( track );
1644
1645 if( !via->IsOnLayer( aLayer ) )
1646 continue;
1647
1648 BOX2I viaBBox = via->GetBoundingBox();
1649 viaBBox.Inflate( m_worstClearance );
1650
1651 if( !viaBBox.Intersects( aZone->GetBoundingBox() ) )
1652 continue;
1653
1654 bool noConnection = via->GetNetCode() != aZone->GetNetCode()
1655 || ( via->Padstack().UnconnectedLayerMode() == UNCONNECTED_LAYER_MODE::START_END_ONLY
1656 && aLayer != via->Padstack().Drill().start
1657 && aLayer != via->Padstack().Drill().end );
1658
1659 // Check if this layer is affected by backdrill or post-machining
1660 if( via->IsBackdrilledOrPostMachined( aLayer ) )
1661 {
1662 noConnection = true;
1663
1664 // Add knockout for backdrill/post-machining hole
1665 int pmSize = 0;
1666 int bdSize = 0;
1667
1668 const PADSTACK::POST_MACHINING_PROPS& frontPM = via->Padstack().FrontPostMachining();
1669 const PADSTACK::POST_MACHINING_PROPS& backPM = via->Padstack().BackPostMachining();
1670
1673 {
1674 pmSize = std::max( pmSize, frontPM.size );
1675 }
1676
1679 {
1680 pmSize = std::max( pmSize, backPM.size );
1681 }
1682
1683 const PADSTACK::DRILL_PROPS& secDrill = via->Padstack().SecondaryDrill();
1684
1685 if( secDrill.start != UNDEFINED_LAYER && secDrill.end != UNDEFINED_LAYER )
1686 bdSize = secDrill.size.x;
1687
1688 int knockoutSize = std::max( pmSize, bdSize );
1689
1690 if( knockoutSize > 0 )
1691 {
1692 int clearance = aZone->GetLocalClearance().value_or( 0 );
1693
1694 TransformCircleToPolygon( holes, via->GetPosition(), knockoutSize / 2 + clearance,
1696 }
1697 }
1698
1699 if( noConnection )
1700 continue;
1701
1702 // Use proper thermal gap from DRC constraints
1703 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, via, aZone, aLayer );
1704 int thermalGap = constraint.GetValue().Min();
1705
1706 aThermalConnectionPads.push_back( via );
1707 addKnockout( via, aLayer, thermalGap, holes );
1708 }
1709 }
1710 }
1711
1712 aFill.BooleanSubtract( holes );
1713}
1714
1715
1721 const std::vector<PAD*>& aNoConnectionPads,
1722 SHAPE_POLY_SET& aHoles,
1723 bool aIncludeZoneClearances )
1724{
1725 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
1726 long ticker = 0;
1727
1728 auto checkForCancel =
1729 [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool
1730 {
1731 return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled();
1732 };
1733
1734 // A small extra clearance to be sure actual track clearances are not smaller than
1735 // requested clearance due to many approximations in calculations, like arc to segment
1736 // approx, rounding issues, etc.
1737 BOX2I zone_boundingbox = aZone->GetBoundingBox();
1738 int extra_margin = pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_ExtraClearance );
1739
1740 // Items outside the zone bounding box are skipped, so it needs to be inflated by the
1741 // largest clearance value found in the netclasses and rules
1742 zone_boundingbox.Inflate( m_worstClearance + extra_margin );
1743
1744 auto evalRulesForItems =
1745 [&bds]( DRC_CONSTRAINT_T aConstraint, const BOARD_ITEM* a, const BOARD_ITEM* b,
1746 PCB_LAYER_ID aEvalLayer ) -> int
1747 {
1748 DRC_CONSTRAINT c = bds.m_DRCEngine->EvalRules( aConstraint, a, b, aEvalLayer );
1749
1750 if( c.IsNull() )
1751 return -1;
1752 else
1753 return c.GetValue().Min();
1754 };
1755
1756 // Add non-connected pad clearances
1757 //
1758 auto knockoutPadClearance =
1759 [&]( PAD* aPad )
1760 {
1761 int init_gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone, aPad, aLayer );
1762 int gap = init_gap;
1763 bool hasHole = aPad->GetDrillSize().x > 0;
1764 bool flashLayer = aPad->FlashLayer( aLayer );
1765 bool platedHole = hasHole && aPad->GetAttribute() == PAD_ATTRIB::PTH;
1766
1767 if( flashLayer || platedHole )
1768 {
1769 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, aPad, aLayer ) );
1770 }
1771
1772 if( flashLayer && gap >= 0 )
1773 addKnockout( aPad, aLayer, gap + extra_margin, aHoles );
1774
1775 if( hasHole )
1776 {
1777 // NPTH do not need copper clearance gaps to their holes
1778 if( aPad->GetAttribute() == PAD_ATTRIB::NPTH )
1779 gap = init_gap;
1780
1781 gap = std::max( gap, evalRulesForItems( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, aZone, aPad, aLayer ) );
1782
1783 gap = std::max( gap, evalRulesForItems( HOLE_CLEARANCE_CONSTRAINT, aZone, aPad, aLayer ) );
1784
1785 if( gap >= 0 )
1786 addHoleKnockout( aPad, gap + extra_margin, aHoles );
1787 }
1788
1789 // Handle backdrill and post-machining knockouts
1790 if( aPad->IsBackdrilledOrPostMachined( aLayer ) )
1791 {
1792 int pmSize = 0;
1793 int bdSize = 0;
1794
1795 const PADSTACK::POST_MACHINING_PROPS& frontPM = aPad->Padstack().FrontPostMachining();
1796 const PADSTACK::POST_MACHINING_PROPS& backPM = aPad->Padstack().BackPostMachining();
1797
1800 {
1801 pmSize = std::max( pmSize, frontPM.size );
1802 }
1803
1806 {
1807 pmSize = std::max( pmSize, backPM.size );
1808 }
1809
1810 const PADSTACK::DRILL_PROPS& secDrill = aPad->Padstack().SecondaryDrill();
1811
1812 if( secDrill.start != UNDEFINED_LAYER && secDrill.end != UNDEFINED_LAYER )
1813 bdSize = secDrill.size.x;
1814
1815 int knockoutSize = std::max( pmSize, bdSize );
1816
1817 if( knockoutSize > 0 )
1818 {
1819 int clearance = std::max( gap, 0 ) + extra_margin;
1820
1821 TransformCircleToPolygon( aHoles, aPad->GetPosition(), knockoutSize / 2 + clearance,
1823 }
1824 }
1825 };
1826
1827 for( PAD* pad : aNoConnectionPads )
1828 {
1829 if( checkForCancel( m_progressReporter ) )
1830 return;
1831
1832 knockoutPadClearance( pad );
1833 }
1834
1835 // Add non-connected track clearances
1836 //
1837 auto knockoutTrackClearance =
1838 [&]( PCB_TRACK* aTrack )
1839 {
1840 if( aTrack->GetBoundingBox().Intersects( zone_boundingbox ) )
1841 {
1842 bool sameNet = aTrack->GetNetCode() == aZone->GetNetCode();
1843
1844 if( !aZone->IsTeardropArea() && aZone->GetNetCode() == 0 )
1845 sameNet = false;
1846
1847 int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone, aTrack, aLayer );
1848
1849 if( aTrack->Type() == PCB_VIA_T )
1850 {
1851 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
1852
1853 if( via->GetZoneLayerOverride( aLayer ) == ZLO_FORCE_NO_ZONE_CONNECTION )
1854 sameNet = false;
1855 }
1856
1857 if( !sameNet )
1858 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, aTrack, aLayer ) );
1859
1860 if( aTrack->Type() == PCB_VIA_T )
1861 {
1862 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
1863
1864 if( via->FlashLayer( aLayer ) && gap > 0 )
1865 {
1866 via->TransformShapeToPolygon( aHoles, aLayer, gap + extra_margin, m_maxError,
1867 ERROR_OUTSIDE );
1868 }
1869
1870 gap = std::max( gap, evalRulesForItems( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, aZone, via,
1871 aLayer ) );
1872
1873 if( !sameNet )
1874 gap = std::max( gap, evalRulesForItems( HOLE_CLEARANCE_CONSTRAINT, aZone, via, aLayer ) );
1875
1876 if( gap >= 0 )
1877 {
1878 int radius = via->GetDrillValue() / 2;
1879
1880 TransformCircleToPolygon( aHoles, via->GetPosition(), radius + gap + extra_margin,
1882 }
1883
1884 // Handle backdrill and post-machining knockouts
1885 if( via->IsBackdrilledOrPostMachined( aLayer ) )
1886 {
1887 int pmSize = 0;
1888 int bdSize = 0;
1889
1890 const PADSTACK::POST_MACHINING_PROPS& frontPM = via->Padstack().FrontPostMachining();
1891 const PADSTACK::POST_MACHINING_PROPS& backPM = via->Padstack().BackPostMachining();
1892
1895 {
1896 pmSize = std::max( pmSize, frontPM.size );
1897 }
1898
1901 {
1902 pmSize = std::max( pmSize, backPM.size );
1903 }
1904
1905 const PADSTACK::DRILL_PROPS& secDrill = via->Padstack().SecondaryDrill();
1906
1907 if( secDrill.start != UNDEFINED_LAYER && secDrill.end != UNDEFINED_LAYER )
1908 bdSize = secDrill.size.x;
1909
1910 int knockoutSize = std::max( pmSize, bdSize );
1911
1912 if( knockoutSize > 0 )
1913 {
1914 int clearance = std::max( gap, 0 ) + extra_margin;
1915
1916 TransformCircleToPolygon( aHoles, via->GetPosition(), knockoutSize / 2 + clearance,
1918 }
1919 }
1920 }
1921 else
1922 {
1923 if( gap >= 0 )
1924 {
1925 aTrack->TransformShapeToPolygon( aHoles, aLayer, gap + extra_margin, m_maxError,
1926 ERROR_OUTSIDE );
1927 }
1928 }
1929 }
1930 };
1931
1932 for( PCB_TRACK* track : m_board->Tracks() )
1933 {
1934 if( !track->IsOnLayer( aLayer ) )
1935 continue;
1936
1937 if( checkForCancel( m_progressReporter ) )
1938 return;
1939
1940 knockoutTrackClearance( track );
1941 }
1942
1943 // Add graphic item clearances.
1944 //
1945 auto knockoutGraphicClearance =
1946 [&]( BOARD_ITEM* aItem )
1947 {
1948 int shapeNet = -1;
1949
1950 if( aItem->Type() == PCB_SHAPE_T )
1951 shapeNet = static_cast<PCB_SHAPE*>( aItem )->GetNetCode();
1952
1953 bool sameNet = shapeNet == aZone->GetNetCode();
1954
1955 if( !aZone->IsTeardropArea() && aZone->GetNetCode() == 0 )
1956 sameNet = false;
1957
1958 // A item on the Edge_Cuts or Margin is always seen as on any layer:
1959 if( aItem->IsOnLayer( aLayer )
1960 || aItem->IsOnLayer( Edge_Cuts )
1961 || aItem->IsOnLayer( Margin ) )
1962 {
1963 if( aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
1964 {
1965 bool ignoreLineWidths = false;
1966 int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone, aItem, aLayer );
1967
1968 if( aItem->IsOnLayer( aLayer ) && !sameNet )
1969 {
1970 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, aItem, aLayer ) );
1971 }
1972 else if( aItem->IsOnLayer( Edge_Cuts ) )
1973 {
1974 gap = std::max( gap, evalRulesForItems( EDGE_CLEARANCE_CONSTRAINT, aZone, aItem, aLayer ) );
1975 ignoreLineWidths = true;
1976 }
1977 else if( aItem->IsOnLayer( Margin ) )
1978 {
1979 gap = std::max( gap, evalRulesForItems( EDGE_CLEARANCE_CONSTRAINT, aZone, aItem, aLayer ) );
1980 }
1981
1982 if( gap >= 0 )
1983 {
1984 gap += extra_margin;
1985 addKnockout( aItem, aLayer, gap, ignoreLineWidths, aHoles );
1986 }
1987 }
1988 }
1989 };
1990
1991 auto knockoutCourtyardClearance =
1992 [&]( FOOTPRINT* aFootprint )
1993 {
1994 if( aFootprint->GetBoundingBox().Intersects( zone_boundingbox ) )
1995 {
1996 int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone, aFootprint, aLayer );
1997
1998 if( gap == 0 )
1999 {
2000 aHoles.Append( aFootprint->GetCourtyard( aLayer ) );
2001 }
2002 else if( gap > 0 )
2003 {
2004 SHAPE_POLY_SET hole = aFootprint->GetCourtyard( aLayer );
2006 aHoles.Append( hole );
2007 }
2008 }
2009 };
2010
2011 for( FOOTPRINT* footprint : m_board->Footprints() )
2012 {
2013 knockoutCourtyardClearance( footprint );
2014 knockoutGraphicClearance( &footprint->Reference() );
2015 knockoutGraphicClearance( &footprint->Value() );
2016
2017 std::set<PAD*> allowedNetTiePads;
2018
2019 // Don't knock out holes for graphic items which implement a net-tie to the zone's net
2020 // on the layer being filled.
2021 if( footprint->IsNetTie() )
2022 {
2023 for( PAD* pad : footprint->Pads() )
2024 {
2025 bool sameNet = pad->GetNetCode() == aZone->GetNetCode();
2026
2027 if( !aZone->IsTeardropArea() && aZone->GetNetCode() == 0 )
2028 sameNet = false;
2029
2030 if( sameNet )
2031 {
2032 if( pad->IsOnLayer( aLayer ) )
2033 allowedNetTiePads.insert( pad );
2034
2035 for( PAD* other : footprint->GetNetTiePads( pad ) )
2036 {
2037 if( other->IsOnLayer( aLayer ) )
2038 allowedNetTiePads.insert( other );
2039 }
2040 }
2041 }
2042 }
2043
2044 for( BOARD_ITEM* item : footprint->GraphicalItems() )
2045 {
2046 if( checkForCancel( m_progressReporter ) )
2047 return;
2048
2049 BOX2I itemBBox = item->GetBoundingBox();
2050
2051 if( !zone_boundingbox.Intersects( itemBBox ) )
2052 continue;
2053
2054 bool skipItem = false;
2055
2056 if( item->IsOnLayer( aLayer ) )
2057 {
2058 std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape();
2059
2060 for( PAD* pad : allowedNetTiePads )
2061 {
2062 if( pad->GetBoundingBox().Intersects( itemBBox )
2063 && pad->GetEffectiveShape( aLayer )->Collide( itemShape.get() ) )
2064 {
2065 skipItem = true;
2066 break;
2067 }
2068 }
2069 }
2070
2071 if( !skipItem )
2072 knockoutGraphicClearance( item );
2073 }
2074 }
2075
2076 for( BOARD_ITEM* item : m_board->Drawings() )
2077 {
2078 if( checkForCancel( m_progressReporter ) )
2079 return;
2080
2081 knockoutGraphicClearance( item );
2082 }
2083
2084 // Add non-connected zone clearances
2085 //
2086 auto knockoutZoneClearance =
2087 [&]( ZONE* aKnockout )
2088 {
2089 // If the zones share no common layers
2090 if( !aKnockout->GetLayerSet().test( aLayer ) )
2091 return;
2092
2093 if( aKnockout->GetBoundingBox().Intersects( zone_boundingbox ) )
2094 {
2095 if( aKnockout->GetIsRuleArea() )
2096 {
2097 // Keepouts use outline with no clearance
2098 aKnockout->TransformSmoothedOutlineToPolygon( aHoles, 0, m_maxError, ERROR_OUTSIDE, nullptr );
2099 }
2100 else
2101 {
2102 int gap = std::max( 0, evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone, aKnockout,
2103 aLayer ) );
2104
2105 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, aKnockout, aLayer ) );
2106
2107 SHAPE_POLY_SET poly;
2108 aKnockout->TransformShapeToPolygon( poly, aLayer, gap + extra_margin, m_maxError,
2109 ERROR_OUTSIDE );
2110 aHoles.Append( poly );
2111 }
2112 }
2113 };
2114
2115 if( aIncludeZoneClearances )
2116 {
2117 for( ZONE* otherZone : m_board->Zones() )
2118 {
2119 if( checkForCancel( m_progressReporter ) )
2120 return;
2121
2122 // Only check zones whose bounding box overlaps the max clearance
2123 if( !otherZone->GetBoundingBox().Intersects( zone_boundingbox ) )
2124 continue;
2125
2126 // Negative clearance permits zones to short
2127 if( evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, otherZone, aLayer ) < 0 )
2128 continue;
2129
2130 if( otherZone->GetIsRuleArea() )
2131 {
2132 if( otherZone->GetDoNotAllowZoneFills() && !aZone->IsTeardropArea() )
2133 knockoutZoneClearance( otherZone );
2134 }
2135 else if( otherZone->HigherPriority( aZone ) )
2136 {
2137 if( !otherZone->SameNet( aZone ) )
2138 knockoutZoneClearance( otherZone );
2139 }
2140 }
2141
2142 for( FOOTPRINT* footprint : m_board->Footprints() )
2143 {
2144 for( ZONE* otherZone : footprint->Zones() )
2145 {
2146 if( checkForCancel( m_progressReporter ) )
2147 return;
2148
2149 // Only check zones whose bounding box overlaps
2150 if( !otherZone->GetBoundingBox().Intersects( zone_boundingbox ) )
2151 continue;
2152
2153 if( otherZone->GetIsRuleArea() )
2154 {
2155 if( otherZone->GetDoNotAllowZoneFills() && !aZone->IsTeardropArea() )
2156 knockoutZoneClearance( otherZone );
2157 }
2158 else if( otherZone->HigherPriority( aZone ) )
2159 {
2160 if( !otherZone->SameNet( aZone ) )
2161 knockoutZoneClearance( otherZone );
2162 }
2163 }
2164 }
2165 }
2166
2167 aHoles.Simplify();
2168}
2169
2170
2176 SHAPE_POLY_SET& aHoles )
2177{
2178 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
2179 int extra_margin = pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_ExtraClearance );
2180
2181 BOX2I zone_boundingbox = aZone->GetBoundingBox();
2182 zone_boundingbox.Inflate( m_worstClearance + extra_margin );
2183
2184 auto evalRulesForItems =
2185 [&bds]( DRC_CONSTRAINT_T aConstraint, const BOARD_ITEM* a, const BOARD_ITEM* b,
2186 PCB_LAYER_ID aEvalLayer ) -> int
2187 {
2188 DRC_CONSTRAINT c = bds.m_DRCEngine->EvalRules( aConstraint, a, b, aEvalLayer );
2189
2190 if( c.IsNull() )
2191 return -1;
2192 else
2193 return c.GetValue().Min();
2194 };
2195
2196 auto knockoutZoneClearance =
2197 [&]( ZONE* aKnockout )
2198 {
2199 if( !aKnockout->GetLayerSet().test( aLayer ) )
2200 return;
2201
2202 if( aKnockout->GetBoundingBox().Intersects( zone_boundingbox ) )
2203 {
2204 if( aKnockout->GetIsRuleArea() )
2205 {
2206 aKnockout->TransformSmoothedOutlineToPolygon( aHoles, 0, m_maxError,
2207 ERROR_OUTSIDE, nullptr );
2208 }
2209 else
2210 {
2211 int gap = std::max( 0, evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
2212 aZone, aKnockout, aLayer ) );
2213
2214 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT, aZone,
2215 aKnockout, aLayer ) );
2216
2217 SHAPE_POLY_SET poly;
2218 aKnockout->TransformShapeToPolygon( poly, aLayer, gap + extra_margin,
2220 aHoles.Append( poly );
2221 }
2222 }
2223 };
2224
2225 for( ZONE* otherZone : m_board->Zones() )
2226 {
2227 if( !otherZone->GetBoundingBox().Intersects( zone_boundingbox ) )
2228 continue;
2229
2230 if( evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, otherZone, aLayer ) < 0 )
2231 continue;
2232
2233 // Rule areas with do-not-fill are handled by buildCopperItemClearances
2234 if( !otherZone->GetIsRuleArea() && otherZone->HigherPriority( aZone ) )
2235 {
2236 if( !otherZone->SameNet( aZone ) )
2237 knockoutZoneClearance( otherZone );
2238 }
2239 }
2240
2241 for( FOOTPRINT* footprint : m_board->Footprints() )
2242 {
2243 for( ZONE* otherZone : footprint->Zones() )
2244 {
2245 if( !otherZone->GetBoundingBox().Intersects( zone_boundingbox ) )
2246 continue;
2247
2248 if( !otherZone->GetIsRuleArea() && otherZone->HigherPriority( aZone ) )
2249 {
2250 if( !otherZone->SameNet( aZone ) )
2251 knockoutZoneClearance( otherZone );
2252 }
2253 }
2254 }
2255
2256 aHoles.Simplify();
2257}
2258
2259
2265 SHAPE_POLY_SET& aRawFill )
2266{
2267 BOX2I zoneBBox = aZone->GetBoundingBox();
2268
2269 auto knockoutZoneOutline =
2270 [&]( ZONE* aKnockout )
2271 {
2272 // If the zones share no common layers
2273 if( !aKnockout->GetLayerSet().test( aLayer ) )
2274 return;
2275
2276 if( aKnockout->GetBoundingBox().Intersects( zoneBBox ) )
2277 {
2278 // Processing of arc shapes in zones is not yet supported because Clipper
2279 // can't do boolean operations on them. The poly outline must be converted to
2280 // segments first.
2281 SHAPE_POLY_SET outline = aKnockout->Outline()->CloneDropTriangulation();
2282 outline.ClearArcs();
2283
2284 aRawFill.BooleanSubtract( outline );
2285 }
2286 };
2287
2288 for( ZONE* otherZone : m_board->Zones() )
2289 {
2290 // Don't use the `HigherPriority()` check here because we _only_ want to knock out zones
2291 // with explicitly higher priorities, not those with equal priorities
2292 if( otherZone->SameNet( aZone )
2293 && otherZone->GetAssignedPriority() > aZone->GetAssignedPriority() )
2294 {
2295 // Do not remove teardrop area: it is not useful and not good
2296 if( !otherZone->IsTeardropArea() )
2297 knockoutZoneOutline( otherZone );
2298 }
2299 }
2300
2301 for( FOOTPRINT* footprint : m_board->Footprints() )
2302 {
2303 for( ZONE* otherZone : footprint->Zones() )
2304 {
2305 if( otherZone->SameNet( aZone ) && otherZone->HigherPriority( aZone ) )
2306 {
2307 // Do not remove teardrop area: it is not useful and not good
2308 if( !otherZone->IsTeardropArea() )
2309 knockoutZoneOutline( otherZone );
2310 }
2311 }
2312 }
2313}
2314
2315
2316void ZONE_FILLER::connect_nearby_polys( SHAPE_POLY_SET& aPolys, double aDistance )
2317{
2318 if( aPolys.OutlineCount() < 1 )
2319 return;
2320
2321 VERTEX_CONNECTOR vs( aPolys.BBoxFromCaches(), aPolys, aDistance );
2322
2323 vs.FindResults();
2324
2325 // This cannot be a reference because we need to do the comparison below while
2326 // changing the values
2327 std::map<int, std::vector<std::pair<int, VECTOR2I>>> insertion_points;
2328
2329 for( const RESULTS& result : vs.GetResults() )
2330 {
2331 SHAPE_LINE_CHAIN& line1 = aPolys.Outline( result.m_outline1 );
2332 SHAPE_LINE_CHAIN& line2 = aPolys.Outline( result.m_outline2 );
2333
2334 VECTOR2I pt1 = line1.CPoint( result.m_vertex1 );
2335 VECTOR2I pt2 = line2.CPoint( result.m_vertex2 );
2336
2337 // We want to insert the existing point first so that we can place the new point
2338 // between the two points at the same location.
2339 insertion_points[result.m_outline1].push_back( { result.m_vertex1, pt1 } );
2340 insertion_points[result.m_outline1].push_back( { result.m_vertex1, pt2 } );
2341 }
2342
2343 for( auto& [outline, vertices] : insertion_points )
2344 {
2345 SHAPE_LINE_CHAIN& line = aPolys.Outline( outline );
2346
2347 if( vertices.empty() )
2348 continue;
2349
2350 // Stable sort here because we want to make sure that we are inserting pt1 first and
2351 // pt2 second but still sorting the rest of the indices
2352 std::stable_sort( vertices.begin(), vertices.end(),
2353 []( const std::pair<int, VECTOR2I>& a, const std::pair<int, VECTOR2I>& b )
2354 {
2355 return a.first < b.first;
2356 } );
2357
2358 std::vector<VECTOR2I> new_points;
2359 new_points.reserve( line.PointCount() + vertices.size() );
2360
2361 size_t vertex_idx = 0;
2362
2363 for( int i = 0; i < line.PointCount(); ++i )
2364 {
2365 new_points.push_back( line.CPoint( i ) );
2366
2367 // Insert all points that should come after position i
2368 while( vertex_idx < vertices.size() && vertices[vertex_idx].first == i )
2369 {
2370 new_points.push_back( vertices[vertex_idx].second );
2371 vertex_idx++;
2372 }
2373 }
2374
2375 line.Clear();
2376
2377 for( const auto& pt : new_points )
2378 line.Append( pt );
2379 }
2380}
2381
2382
2383#define DUMP_POLYS_TO_COPPER_LAYER( a, b, c ) \
2384 { if( m_debugZoneFiller && aDebugLayer == b ) \
2385 { \
2386 m_board->SetLayerName( b, c ); \
2387 SHAPE_POLY_SET d = a; \
2388 d.Fracture(); \
2389 aFillPolys = d; \
2390 return false; \
2391 } \
2392 }
2393
2394
2395/*
2396 * Note that aSmoothedOutline is larger than the zone where it intersects with other, same-net
2397 * zones. This is to prevent the re-inflation post min-width trimming from createing divots
2398 * between adjacent zones. The final aMaxExtents trimming will remove these areas from the final
2399 * fill.
2400 */
2401bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer,
2402 const SHAPE_POLY_SET& aSmoothedOutline,
2403 const SHAPE_POLY_SET& aMaxExtents, SHAPE_POLY_SET& aFillPolys )
2404{
2405 m_maxError = m_board->GetDesignSettings().m_MaxError;
2406
2407 // Features which are min_width should survive pruning; features that are *less* than
2408 // min_width should not. Therefore we subtract epsilon from the min_width when
2409 // deflating/inflating.
2410 int half_min_width = aZone->GetMinThickness() / 2;
2411 int epsilon = pcbIUScale.mmToIU( 0.001 );
2412
2413 // Solid polygons are deflated and inflated during calculations. Deflating doesn't cause
2414 // issues, but inflate is tricky as it can create excessively long and narrow spikes for
2415 // acute angles.
2416 // ALLOW_ACUTE_CORNERS cannot be used due to the spike problem.
2417 // CHAMFER_ACUTE_CORNERS is tempting, but can still produce spikes in some unusual
2418 // circumstances (https://gitlab.com/kicad/code/kicad/-/issues/5581).
2419 // It's unclear if ROUND_ACUTE_CORNERS would have the same issues, but is currently avoided
2420 // as a "less-safe" option.
2421 // ROUND_ALL_CORNERS produces the uniformly nicest shapes, but also a lot of segments.
2422 // CHAMFER_ALL_CORNERS improves the segment count.
2425
2426 std::vector<BOARD_ITEM*> thermalConnectionPads;
2427 std::vector<PAD*> noConnectionPads;
2428 std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
2429 SHAPE_POLY_SET clearanceHoles;
2430
2431 aFillPolys = aSmoothedOutline;
2432 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In1_Cu, wxT( "smoothed-outline" ) );
2433
2434 if( m_progressReporter && m_progressReporter->IsCancelled() )
2435 return false;
2436
2437 /* -------------------------------------------------------------------------------------
2438 * Knockout thermal reliefs.
2439 */
2440
2441 knockoutThermalReliefs( aZone, aLayer, aFillPolys, thermalConnectionPads, noConnectionPads );
2442 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In2_Cu, wxT( "minus-thermal-reliefs" ) );
2443
2444 if( m_progressReporter && m_progressReporter->IsCancelled() )
2445 return false;
2446
2447 /* -------------------------------------------------------------------------------------
2448 * For hatch zones, add thermal rings around pads with thermal relief.
2449 * The rings are clipped to the zone boundary and provide the connection point
2450 * for the hatch webbing instead of connecting directly to the pad.
2451 */
2452
2454 {
2455 buildHatchZoneThermalRings( aZone, aLayer, aSmoothedOutline, thermalConnectionPads, aFillPolys );
2456 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In2_Cu, wxT( "plus-thermal-rings" ) );
2457 }
2458
2459 if( m_progressReporter && m_progressReporter->IsCancelled() )
2460 return false;
2461
2462 /* -------------------------------------------------------------------------------------
2463 * Knockout electrical clearances.
2464 */
2465
2466 // When iterative refill is enabled, we build zone clearances separately so we can cache
2467 // the fill before zone knockouts are applied (issue 21746).
2468 const bool iterativeRefill = ADVANCED_CFG::GetCfg().m_ZoneFillIterativeRefill;
2469
2470 buildCopperItemClearances( aZone, aLayer, noConnectionPads, clearanceHoles,
2471 !iterativeRefill /* include zone clearances only if not iterative */ );
2472 DUMP_POLYS_TO_COPPER_LAYER( clearanceHoles, In3_Cu, wxT( "clearance-holes" ) );
2473
2474 if( m_progressReporter && m_progressReporter->IsCancelled() )
2475 return false;
2476
2477 /* -------------------------------------------------------------------------------------
2478 * Add thermal relief spokes.
2479 */
2480
2481 buildThermalSpokes( aZone, aLayer, thermalConnectionPads, thermalSpokes );
2482
2483 if( m_progressReporter && m_progressReporter->IsCancelled() )
2484 return false;
2485
2486 // Create a temporary zone that we can hit-test spoke-ends against. It's only temporary
2487 // because the "real" subtract-clearance-holes has to be done after the spokes are added.
2488 static const bool USE_BBOX_CACHES = true;
2489 SHAPE_POLY_SET testAreas = aFillPolys.CloneDropTriangulation();
2490 testAreas.BooleanSubtract( clearanceHoles );
2491 DUMP_POLYS_TO_COPPER_LAYER( testAreas, In4_Cu, wxT( "minus-clearance-holes" ) );
2492
2493 // Prune features that don't meet minimum-width criteria
2494 if( half_min_width - epsilon > epsilon )
2495 {
2496 testAreas.Deflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
2497 DUMP_POLYS_TO_COPPER_LAYER( testAreas, In5_Cu, wxT( "spoke-test-deflated" ) );
2498
2499 testAreas.Inflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
2500 DUMP_POLYS_TO_COPPER_LAYER( testAreas, In6_Cu, wxT( "spoke-test-reinflated" ) );
2501 }
2502
2503 if( m_progressReporter && m_progressReporter->IsCancelled() )
2504 return false;
2505
2506 // Spoke-end-testing is hugely expensive so we generate cached bounding-boxes to speed
2507 // things up a bit.
2508 testAreas.BuildBBoxCaches();
2509 int interval = 0;
2510
2511 SHAPE_POLY_SET debugSpokes;
2512
2513 for( const SHAPE_LINE_CHAIN& spoke : thermalSpokes )
2514 {
2515 const VECTOR2I& testPt = spoke.CPoint( 3 );
2516
2517 // Hit-test against zone body
2518 if( testAreas.Contains( testPt, -1, 1, USE_BBOX_CACHES ) )
2519 {
2520 if( m_debugZoneFiller )
2521 debugSpokes.AddOutline( spoke );
2522
2523 aFillPolys.AddOutline( spoke );
2524 continue;
2525 }
2526
2527 if( interval++ > 400 )
2528 {
2529 if( m_progressReporter && m_progressReporter->IsCancelled() )
2530 return false;
2531
2532 interval = 0;
2533 }
2534
2535 // Hit-test against other spokes
2536 for( const SHAPE_LINE_CHAIN& other : thermalSpokes )
2537 {
2538 // Hit test in both directions to avoid interactions with round-off errors.
2539 // (See https://gitlab.com/kicad/code/kicad/-/issues/13316.)
2540 if( &other != &spoke
2541 && other.PointInside( testPt, 1, USE_BBOX_CACHES )
2542 && spoke.PointInside( other.CPoint( 3 ), 1, USE_BBOX_CACHES ) )
2543 {
2544 if( m_debugZoneFiller )
2545 debugSpokes.AddOutline( spoke );
2546
2547 aFillPolys.AddOutline( spoke );
2548 break;
2549 }
2550 }
2551 }
2552
2553 DUMP_POLYS_TO_COPPER_LAYER( debugSpokes, In7_Cu, wxT( "spokes" ) );
2554
2555 if( m_progressReporter && m_progressReporter->IsCancelled() )
2556 return false;
2557
2558 aFillPolys.BooleanSubtract( clearanceHoles );
2559 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In8_Cu, wxT( "after-spoke-trimming" ) );
2560
2561 /* -------------------------------------------------------------------------------------
2562 * Prune features that don't meet minimum-width criteria
2563 */
2564
2565 if( half_min_width - epsilon > epsilon )
2566 aFillPolys.Deflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
2567
2568 // Min-thickness is the web thickness. On the other hand, a blob min-thickness by
2569 // min-thickness is not useful. Since there's no obvious definition of web vs. blob, we
2570 // arbitrarily choose "at least 2X min-thickness on one axis". (Since we're doing this
2571 // during the deflated state, that means we test for "at least min-thickness".)
2572 for( int ii = aFillPolys.OutlineCount() - 1; ii >= 0; ii-- )
2573 {
2574 std::vector<SHAPE_LINE_CHAIN>& island = aFillPolys.Polygon( ii );
2575 BOX2I islandExtents;
2576
2577 for( const VECTOR2I& pt : island.front().CPoints() )
2578 {
2579 islandExtents.Merge( pt );
2580
2581 if( islandExtents.GetSizeMax() > aZone->GetMinThickness() )
2582 break;
2583 }
2584
2585 if( islandExtents.GetSizeMax() < aZone->GetMinThickness() )
2586 aFillPolys.DeletePolygon( ii );
2587 }
2588
2589 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In9_Cu, wxT( "deflated" ) );
2590
2591 if( m_progressReporter && m_progressReporter->IsCancelled() )
2592 return false;
2593
2594 /* -------------------------------------------------------------------------------------
2595 * Process the hatch pattern (note that we do this while deflated)
2596 */
2597
2599 && ( !m_board->GetProject()
2600 || !m_board->GetProject()->GetLocalSettings().m_PrototypeZoneFill ) )
2601 {
2602 if( !addHatchFillTypeOnZone( aZone, aLayer, aDebugLayer, aFillPolys ) )
2603 return false;
2604 }
2605 else
2606 {
2607 /* ---------------------------------------------------------------------------------
2608 * Connect nearby polygons with zero-width lines in order to ensure correct
2609 * re-inflation.
2610 */
2611 aFillPolys.Fracture();
2612 connect_nearby_polys( aFillPolys, aZone->GetMinThickness() );
2613
2614 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In10_Cu, wxT( "connected-nearby-polys" ) );
2615 }
2616
2617 if( m_progressReporter && m_progressReporter->IsCancelled() )
2618 return false;
2619
2620 /* -------------------------------------------------------------------------------------
2621 * Finish minimum-width pruning by re-inflating
2622 */
2623
2624 if( half_min_width - epsilon > epsilon )
2625 aFillPolys.Inflate( half_min_width - epsilon, cornerStrategy, m_maxError, true );
2626
2627 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In15_Cu, wxT( "after-reinflating" ) );
2628
2629 /* -------------------------------------------------------------------------------------
2630 * Ensure additive changes (thermal stubs and inflating acute corners) do not add copper
2631 * outside the zone boundary, inside the clearance holes, or between otherwise isolated
2632 * islands
2633 */
2634
2635 for( BOARD_ITEM* item : thermalConnectionPads )
2636 {
2637 if( item->Type() == PCB_PAD_T )
2638 addHoleKnockout( static_cast<PAD*>( item ), 0, clearanceHoles );
2639 }
2640
2641 aFillPolys.BooleanIntersection( aMaxExtents );
2642 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In16_Cu, wxT( "after-trim-to-outline" ) );
2643 aFillPolys.BooleanSubtract( clearanceHoles );
2644 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In17_Cu, wxT( "after-trim-to-clearance-holes" ) );
2645
2646 /* -------------------------------------------------------------------------------------
2647 * Lastly give any same-net but higher-priority zones control over their own area.
2648 */
2649
2650 // Cache the pre-knockout fill for iterative refill optimization (issue 21746)
2651 // At this point, the fill has NOT been knocked out by higher-priority zones on different nets
2652 if( iterativeRefill )
2653 {
2654 {
2655 std::lock_guard<std::mutex> lock( m_cacheMutex );
2656 m_preKnockoutFillCache[{ aZone, aLayer }] = aFillPolys;
2657 }
2658
2659 BOX2I cacheBBox = aFillPolys.BBox();
2660
2661 wxLogTrace( traceZoneFiller,
2662 wxT( "Cached pre-knockout fill for zone %s layer %d: %d outlines, area %.0f, "
2663 "bbox (%d,%d)-(%d,%d)" ),
2664 aZone->GetNetname(), static_cast<int>( aLayer ),
2665 aFillPolys.OutlineCount(), aFillPolys.Area(),
2666 cacheBBox.GetX(), cacheBBox.GetY(), cacheBBox.GetRight(), cacheBBox.GetBottom() );
2667
2668 // Now apply zone-to-zone knockouts for different-net zones
2669 SHAPE_POLY_SET zoneClearances;
2670 buildDifferentNetZoneClearances( aZone, aLayer, zoneClearances );
2671
2672 if( zoneClearances.OutlineCount() > 0 )
2673 aFillPolys.BooleanSubtract( zoneClearances );
2674 }
2675
2676 subtractHigherPriorityZones( aZone, aLayer, aFillPolys );
2677 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In18_Cu, wxT( "minus-higher-priority-zones" ) );
2678
2679 aFillPolys.Fracture();
2680 return true;
2681}
2682
2683
2685 const SHAPE_POLY_SET& aSmoothedOutline,
2686 SHAPE_POLY_SET& aFillPolys )
2687{
2688 BOX2I zone_boundingbox = aZone->GetBoundingBox();
2689 SHAPE_POLY_SET clearanceHoles;
2690 long ticker = 0;
2691
2692 auto checkForCancel =
2693 [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool
2694 {
2695 return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled();
2696 };
2697
2698 auto knockoutGraphicItem =
2699 [&]( BOARD_ITEM* aItem )
2700 {
2701 if( aItem->IsKnockout() && aItem->IsOnLayer( aLayer )
2702 && aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
2703 {
2704 addKnockout( aItem, aLayer, 0, true, clearanceHoles );
2705 }
2706 };
2707
2708 for( FOOTPRINT* footprint : m_board->Footprints() )
2709 {
2710 if( checkForCancel( m_progressReporter ) )
2711 return false;
2712
2713 knockoutGraphicItem( &footprint->Reference() );
2714 knockoutGraphicItem( &footprint->Value() );
2715
2716 for( BOARD_ITEM* item : footprint->GraphicalItems() )
2717 knockoutGraphicItem( item );
2718 }
2719
2720 for( BOARD_ITEM* item : m_board->Drawings() )
2721 {
2722 if( checkForCancel( m_progressReporter ) )
2723 return false;
2724
2725 knockoutGraphicItem( item );
2726 }
2727
2728 aFillPolys = aSmoothedOutline;
2729 aFillPolys.BooleanSubtract( clearanceHoles );
2730
2731 auto subtractKeepout =
2732 [&]( ZONE* candidate )
2733 {
2734 if( !candidate->GetIsRuleArea() )
2735 return;
2736
2737 if( !candidate->HasKeepoutParametersSet() )
2738 return;
2739
2740 if( candidate->GetDoNotAllowZoneFills() && candidate->IsOnLayer( aLayer ) )
2741 {
2742 if( candidate->GetBoundingBox().Intersects( zone_boundingbox ) )
2743 {
2744 if( candidate->Outline()->ArcCount() == 0 )
2745 {
2746 aFillPolys.BooleanSubtract( *candidate->Outline() );
2747 }
2748 else
2749 {
2750 SHAPE_POLY_SET keepoutOutline( *candidate->Outline() );
2751 keepoutOutline.ClearArcs();
2752 aFillPolys.BooleanSubtract( keepoutOutline );
2753 }
2754 }
2755 }
2756 };
2757
2758 for( ZONE* keepout : m_board->Zones() )
2759 {
2760 if( checkForCancel( m_progressReporter ) )
2761 return false;
2762
2763 subtractKeepout( keepout );
2764 }
2765
2766 for( FOOTPRINT* footprint : m_board->Footprints() )
2767 {
2768 if( checkForCancel( m_progressReporter ) )
2769 return false;
2770
2771 for( ZONE* keepout : footprint->Zones() )
2772 subtractKeepout( keepout );
2773 }
2774
2775 // Features which are min_width should survive pruning; features that are *less* than
2776 // min_width should not. Therefore we subtract epsilon from the min_width when
2777 // deflating/inflating.
2778 int half_min_width = aZone->GetMinThickness() / 2;
2779 int epsilon = pcbIUScale.mmToIU( 0.001 );
2780
2781 aFillPolys.Deflate( half_min_width - epsilon, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, m_maxError );
2782
2783 // Remove the non filled areas due to the hatch pattern
2785 {
2786 if( !addHatchFillTypeOnZone( aZone, aLayer, aLayer, aFillPolys ) )
2787 return false;
2788 }
2789
2790 // Re-inflate after pruning of areas that don't meet minimum-width criteria
2791 if( half_min_width - epsilon > epsilon )
2792 aFillPolys.Inflate( half_min_width - epsilon, CORNER_STRATEGY::ROUND_ALL_CORNERS, m_maxError );
2793
2794 aFillPolys.Fracture();
2795 return true;
2796}
2797
2798
2799/*
2800 * Build the filled solid areas data from real outlines (stored in m_Poly)
2801 * The solid areas can be more than one on copper layers, and do not have holes
2802 * ( holes are linked by overlapping segments to the main outline)
2803 */
2805{
2806 SHAPE_POLY_SET* boardOutline = m_brdOutlinesValid ? &m_boardOutline : nullptr;
2807 SHAPE_POLY_SET maxExtents;
2808 SHAPE_POLY_SET smoothedPoly;
2809 PCB_LAYER_ID debugLayer = UNDEFINED_LAYER;
2810
2811 if( m_debugZoneFiller && LSET::InternalCuMask().Contains( aLayer ) )
2812 {
2813 debugLayer = aLayer;
2814 aLayer = F_Cu;
2815 }
2816
2817 if( !aZone->BuildSmoothedPoly( maxExtents, aLayer, boardOutline, &smoothedPoly ) )
2818 return false;
2819
2820 if( m_progressReporter && m_progressReporter->IsCancelled() )
2821 return false;
2822
2823 if( aZone->IsOnCopperLayer() )
2824 {
2825 if( fillCopperZone( aZone, aLayer, debugLayer, smoothedPoly, maxExtents, aFillPolys ) )
2826 aZone->SetNeedRefill( false );
2827 }
2828 else
2829 {
2830 if( fillNonCopperZone( aZone, aLayer, smoothedPoly, aFillPolys ) )
2831 aZone->SetNeedRefill( false );
2832 }
2833
2834 return true;
2835}
2836
2837
2842 const std::vector<BOARD_ITEM*>& aSpokedPadsList,
2843 std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
2844{
2845 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
2846 BOX2I zoneBB = aZone->GetBoundingBox();
2847 DRC_CONSTRAINT constraint;
2848 int zone_half_width = aZone->GetMinThickness() / 2;
2849
2851 zone_half_width = aZone->GetHatchThickness() / 2;
2852
2853 zoneBB.Inflate( std::max( bds.GetBiggestClearanceValue(), aZone->GetLocalClearance().value() ) );
2854
2855 // Is a point on the boundary of the polygon inside or outside?
2856 // The boundary may be off by MaxError
2857 int epsilon = bds.m_MaxError;
2858
2859 for( BOARD_ITEM* item : aSpokedPadsList )
2860 {
2861 // We currently only connect to pads, not pad holes
2862 if( !item->IsOnLayer( aLayer ) )
2863 continue;
2864
2865 int thermalReliefGap = 0;
2866 int spoke_w = 0;
2867 PAD* pad = nullptr;
2868 PCB_VIA* via = nullptr;
2869 bool circular = false;
2870
2871 if( item->Type() == PCB_PAD_T )
2872 {
2873 pad = static_cast<PAD*>( item );
2874 VECTOR2I padSize = pad->GetSize( aLayer );
2875
2876 if( pad->GetShape( aLayer) == PAD_SHAPE::CIRCLE
2877 || ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL && padSize.x == padSize.y ) )
2878 {
2879 circular = true;
2880 }
2881 }
2882 else if( item->Type() == PCB_VIA_T )
2883 {
2884 via = static_cast<PCB_VIA*>( item );
2885 circular = true;
2886 }
2887
2888 // For hatch zones, use proper DRC constraints for thermal gap and spoke width,
2889 // just like solid zones. This ensures consistent thermal relief appearance and
2890 // respects pad-specific thermal spoke settings.
2892 {
2893 if( pad )
2894 {
2896 aZone, aLayer );
2897 thermalReliefGap = constraint.GetValue().Min();
2898
2900 aZone, aLayer );
2901 spoke_w = constraint.GetValue().Opt();
2902
2903 int spoke_max_allowed_w = std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y );
2904 spoke_w = std::clamp( spoke_w, constraint.Value().Min(), constraint.Value().Max() );
2905 spoke_w = std::min( spoke_w, spoke_max_allowed_w );
2906
2907 if( spoke_w < aZone->GetMinThickness() )
2908 continue;
2909 }
2910 else if( via )
2911 {
2913 aZone, aLayer );
2914 thermalReliefGap = constraint.GetValue().Min();
2915
2917 aZone, aLayer );
2918 spoke_w = constraint.GetValue().Opt();
2919
2920 spoke_w = std::min( spoke_w, via->GetWidth( aLayer ) );
2921
2922 if( spoke_w < aZone->GetMinThickness() )
2923 continue;
2924 }
2925 else
2926 {
2927 continue;
2928 }
2929 }
2930 else if( pad )
2931 {
2932 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
2933 thermalReliefGap = constraint.GetValue().Min();
2934
2935 constraint = bds.m_DRCEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, aZone, aLayer );
2936 spoke_w = constraint.GetValue().Opt();
2937
2938 // Spoke width should ideally be smaller than the pad minor axis.
2939 // Otherwise the thermal shape is not really a thermal relief,
2940 // and the algo to count the actual number of spokes can fail
2941 int spoke_max_allowed_w = std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y );
2942
2943 spoke_w = std::clamp( spoke_w, constraint.Value().Min(), constraint.Value().Max() );
2944
2945 // ensure the spoke width is smaller than the pad minor size
2946 spoke_w = std::min( spoke_w, spoke_max_allowed_w );
2947
2948 // Cannot create stubs having a width < zone min thickness
2949 if( spoke_w < aZone->GetMinThickness() )
2950 continue;
2951 }
2952 else
2953 {
2954 // We don't currently support via thermal connections *except* in a hatched zone.
2955 continue;
2956 }
2957
2958 int spoke_half_w = spoke_w / 2;
2959
2960 // Quick test here to possibly save us some work
2961 BOX2I itemBB = item->GetBoundingBox();
2962 itemBB.Inflate( thermalReliefGap + epsilon );
2963
2964 if( !( itemBB.Intersects( zoneBB ) ) )
2965 continue;
2966
2967 bool customSpokes = false;
2968
2969 if( pad && pad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
2970 {
2971 for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) )
2972 {
2973 if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
2974 {
2975 customSpokes = true;
2976 break;
2977 }
2978 }
2979 }
2980
2981 // Thermal spokes consist of square-ended segments from the pad center to points just
2982 // outside the thermal relief. The outside end has an extra center point (which must be
2983 // at idx 3) which is used for testing whether or not the spoke connects to copper in the
2984 // parent zone.
2985
2986 auto buildSpokesFromOrigin =
2987 [&]( const BOX2I& box, EDA_ANGLE angle )
2988 {
2989 VECTOR2I center = box.GetCenter();
2990 VECTOR2I half_size = KiROUND( box.GetWidth() / 2.0, box.GetHeight() / 2.0 );
2991
2992 // Function to find intersection of line with box edge
2993 auto intersectBBox =
2994 [&]( const EDA_ANGLE& spokeAngle, VECTOR2I* spoke_side ) -> VECTOR2I
2995 {
2996 double dx = spokeAngle.Cos();
2997 double dy = spokeAngle.Sin();
2998
2999 // Short-circuit the axis cases because they will be degenerate in the
3000 // intersection test
3001 if( dx == 0 )
3002 {
3003 *spoke_side = VECTOR2I( spoke_half_w, 0 );
3004 return KiROUND( 0.0, dy * half_size.y );
3005 }
3006 else if( dy == 0 )
3007 {
3008 *spoke_side = VECTOR2I( 0, spoke_half_w );
3009 return KiROUND( dx * half_size.x, 0.0 );
3010 }
3011
3012 // We are going to intersect with one side or the other. Whichever
3013 // we hit first is the fraction of the spoke length we keep
3014 double dist_x = half_size.x / std::abs( dx );
3015 double dist_y = half_size.y / std::abs( dy );
3016
3017 if( dist_x < dist_y )
3018 {
3019 *spoke_side = KiROUND( 0.0, spoke_half_w / ( ANGLE_90 - spokeAngle ).Sin() );
3020 return KiROUND( dx * dist_x, dy * dist_x );
3021 }
3022 else
3023 {
3024 *spoke_side = KiROUND( spoke_half_w / spokeAngle.Sin(), 0.0 );
3025 return KiROUND( dx * dist_y, dy * dist_y );
3026 }
3027 };
3028
3029 // Precalculate angles for four cardinal directions
3030 const EDA_ANGLE angles[4] = {
3031 EDA_ANGLE( 0.0, DEGREES_T ) + angle, // Right
3032 EDA_ANGLE( 90.0, DEGREES_T ) + angle, // Up
3033 EDA_ANGLE( 180.0, DEGREES_T ) + angle, // Left
3034 EDA_ANGLE( 270.0, DEGREES_T ) + angle // Down
3035 };
3036
3037 // Generate four spokes in cardinal directions
3038 for( const EDA_ANGLE& spokeAngle : angles )
3039 {
3040 VECTOR2I spoke_side;
3041 VECTOR2I intersection = intersectBBox( spokeAngle, &spoke_side );
3042
3043 SHAPE_LINE_CHAIN spoke;
3044 spoke.Append( center + spoke_side );
3045 spoke.Append( center - spoke_side );
3046 spoke.Append( center + intersection - spoke_side );
3047 spoke.Append( center + intersection ); // test pt
3048 spoke.Append( center + intersection + spoke_side );
3049 spoke.SetClosed( true );
3050 aSpokesList.push_back( std::move( spoke ) );
3051 }
3052 };
3053
3054 if( customSpokes )
3055 {
3056 SHAPE_POLY_SET thermalPoly;
3057 SHAPE_LINE_CHAIN thermalOutline;
3058
3059 pad->TransformShapeToPolygon( thermalPoly, aLayer, thermalReliefGap + epsilon, m_maxError, ERROR_OUTSIDE );
3060
3061 if( thermalPoly.OutlineCount() )
3062 thermalOutline = thermalPoly.Outline( 0 );
3063
3064 SHAPE_LINE_CHAIN padOutline = pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Outline( 0 );
3065
3066 auto trimToOutline = [&]( SEG& aSegment )
3067 {
3068 SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
3069
3070 if( padOutline.Intersect( aSegment, intersections ) )
3071 {
3072 intersections.clear();
3073
3074 // Trim the segment to the thermal outline
3075 if( thermalOutline.Intersect( aSegment, intersections ) )
3076 {
3077 aSegment.B = intersections.front().p;
3078 return true;
3079 }
3080 }
3081 return false;
3082 };
3083
3084 for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) )
3085 {
3086 if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
3087 {
3088 SEG seg( primitive->GetStart(), primitive->GetEnd() );
3089 SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
3090
3091 RotatePoint( seg.A, pad->GetOrientation() );
3092 RotatePoint( seg.B, pad->GetOrientation() );
3093 seg.A += pad->ShapePos( aLayer );
3094 seg.B += pad->ShapePos( aLayer );
3095
3096 // Make sure seg.A is the origin
3097 if( !pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Contains( seg.A ) )
3098 {
3099 // Do not create this spoke if neither point is in the pad.
3100 if( !pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Contains( seg.B ) )
3101 continue;
3102
3103 seg.Reverse();
3104 }
3105
3106 // Trim segment to pad and thermal outline polygon.
3107 // If there is no intersection with the pad, don't create the spoke.
3108 if( trimToOutline( seg ) )
3109 {
3110 VECTOR2I direction = ( seg.B - seg.A ).Resize( spoke_half_w );
3111 VECTOR2I offset = direction.Perpendicular().Resize( spoke_half_w );
3112 // Extend the spoke edges by half the spoke width to capture convex pad shapes
3113 // with a maximum of 45 degrees.
3114 SEG segL( seg.A - direction - offset, seg.B + direction - offset );
3115 SEG segR( seg.A - direction + offset, seg.B + direction + offset );
3116
3117 // Only create this spoke if both edges intersect the pad and thermal outline
3118 if( trimToOutline( segL ) && trimToOutline( segR ) )
3119 {
3120 // Extend the spoke by the minimum thickness for the zone to ensure full
3121 // connection width
3122 direction = direction.Resize( aZone->GetMinThickness() );
3123
3124 SHAPE_LINE_CHAIN spoke;
3125
3126 spoke.Append( seg.A + offset );
3127 spoke.Append( seg.A - offset );
3128
3129 spoke.Append( segL.B + direction );
3130 spoke.Append( seg.B + direction ); // test pt at index 3.
3131 spoke.Append( segR.B + direction );
3132
3133 spoke.SetClosed( true );
3134 aSpokesList.push_back( std::move( spoke ) );
3135 }
3136 }
3137 }
3138 }
3139 }
3140 else
3141 {
3142 EDA_ANGLE thermalSpokeAngle;
3143
3144 // Use pad's thermal spoke angle for both solid and hatch zones.
3145 // This ensures custom thermal spoke templates are respected.
3146 if( pad )
3147 thermalSpokeAngle = pad->GetThermalSpokeAngle();
3148
3149 BOX2I spokesBox;
3150 VECTOR2I position;
3151 EDA_ANGLE orientation;
3152
3153 // Since the bounding-box needs to be correclty rotated we use a dummy pad to keep
3154 // from dirtying the real pad's cached shapes.
3155 if( pad )
3156 {
3157 PAD dummy_pad( *pad );
3158 dummy_pad.SetOrientation( ANGLE_0 );
3159
3160 // Spokes are from center of pad shape, not from hole. So the dummy pad has no shape
3161 // offset and is at position 0,0
3162 dummy_pad.SetPosition( VECTOR2I( 0, 0 ) );
3163 dummy_pad.SetOffset( aLayer, VECTOR2I( 0, 0 ) );
3164
3165 spokesBox = dummy_pad.GetBoundingBox( aLayer );
3166 position = pad->ShapePos( aLayer );
3167 orientation = pad->GetOrientation();
3168 }
3169 else if( via )
3170 {
3171 PCB_VIA dummy_via( *via );
3172 dummy_via.SetPosition( VECTOR2I( 0, 0 ) );
3173
3174 spokesBox = dummy_via.GetBoundingBox( aLayer );
3175 position = via->GetPosition();
3176 }
3177
3178 // Add half the zone mininum width to the inflate amount to account for the fact that
3179 // the deflation procedure will shrink the results by half the half the zone min width.
3180 spokesBox.Inflate( thermalReliefGap + epsilon + zone_half_width );
3181
3182 // Yet another wrinkle: the bounding box for circles will overshoot the mark considerably
3183 // when the spokes are near a 45 degree increment. So we build the spokes at 0 degrees
3184 // and then rotate them to the correct position.
3185 if( circular )
3186 {
3187 buildSpokesFromOrigin( spokesBox, ANGLE_0 );
3188
3189 if( thermalSpokeAngle != ANGLE_0 )
3190 {
3191 // Rotate the last four elements of aspokeslist
3192 for( auto it = aSpokesList.rbegin(); it != aSpokesList.rbegin() + 4; ++it )
3193 it->Rotate( thermalSpokeAngle );
3194 }
3195 }
3196 else
3197 {
3198 buildSpokesFromOrigin( spokesBox, thermalSpokeAngle );
3199 }
3200
3201 auto spokeIter = aSpokesList.rbegin();
3202
3203 for( int ii = 0; ii < 4; ++ii, ++spokeIter )
3204 {
3205 spokeIter->Rotate( orientation );
3206 spokeIter->Move( position );
3207 }
3208 }
3209 }
3210
3211 for( size_t ii = 0; ii < aSpokesList.size(); ++ii )
3212 aSpokesList[ii].GenerateBBoxCache();
3213}
3214
3215
3217 const SHAPE_POLY_SET& aSmoothedOutline,
3218 const std::vector<BOARD_ITEM*>& aThermalConnectionPads,
3219 SHAPE_POLY_SET& aFillPolys )
3220{
3221 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
3222 DRC_CONSTRAINT constraint;
3223
3224 for( BOARD_ITEM* item : aThermalConnectionPads )
3225 {
3226 if( !item->IsOnLayer( aLayer ) )
3227 continue;
3228
3229 PAD* pad = nullptr;
3230 PCB_VIA* via = nullptr;
3231 bool isCircular = false;
3232 int thermalGap = 0;
3233 int spokeWidth = 0;
3234 VECTOR2I position;
3235 int padRadius = 0;
3236
3237 if( item->Type() == PCB_PAD_T )
3238 {
3239 pad = static_cast<PAD*>( item );
3240 VECTOR2I padSize = pad->GetSize( aLayer );
3241 position = pad->ShapePos( aLayer );
3242
3243 isCircular = ( pad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE
3244 || ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL && padSize.x == padSize.y ) );
3245
3246 if( isCircular )
3247 padRadius = std::max( padSize.x, padSize.y ) / 2;
3248
3249 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
3250 thermalGap = constraint.GetValue().Min();
3251
3252 constraint = bds.m_DRCEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, aZone, aLayer );
3253 spokeWidth = constraint.GetValue().Opt();
3254
3255 // Clamp spoke width to pad size
3256 int spokeMaxWidth = std::min( padSize.x, padSize.y );
3257 spokeWidth = std::min( spokeWidth, spokeMaxWidth );
3258 }
3259 else if( item->Type() == PCB_VIA_T )
3260 {
3261 via = static_cast<PCB_VIA*>( item );
3262 position = via->GetPosition();
3263 isCircular = true;
3264 padRadius = via->GetWidth( aLayer ) / 2;
3265
3266 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, via, aZone, aLayer );
3267 thermalGap = constraint.GetValue().Min();
3268
3269 constraint = bds.m_DRCEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, via, aZone, aLayer );
3270 spokeWidth = constraint.GetValue().Opt();
3271
3272 // Clamp spoke width to via diameter
3273 spokeWidth = std::min( spokeWidth, padRadius * 2 );
3274 }
3275 else
3276 {
3277 continue;
3278 }
3279
3280 // Don't create a ring if spoke width is too small
3281 if( spokeWidth < aZone->GetMinThickness() )
3282 continue;
3283
3284 SHAPE_POLY_SET thermalRing;
3285
3286 if( isCircular )
3287 {
3288 // For circular pads/vias: create an arc ring
3289 // Ring inner radius = pad radius + thermal gap
3290 // Ring width = spoke width
3291 int ringInnerRadius = padRadius + thermalGap;
3292 int ringWidth = spokeWidth;
3293
3294 TransformRingToPolygon( thermalRing, position, ringInnerRadius + ringWidth / 2,
3295 ringWidth, m_maxError, ERROR_OUTSIDE );
3296 }
3297 else
3298 {
3299 // For non-circular pads: create ring by inflating pad to outer radius,
3300 // then subtracting pad inflated to inner radius
3301 SHAPE_POLY_SET outerShape;
3302 SHAPE_POLY_SET innerShape;
3303
3304 // Outer ring edge = pad + thermal gap + spoke width
3305 pad->TransformShapeToPolygon( outerShape, aLayer, thermalGap + spokeWidth,
3307
3308 // Inner ring edge = pad + thermal gap (this is already knocked out)
3309 pad->TransformShapeToPolygon( innerShape, aLayer, thermalGap,
3311
3312 thermalRing = outerShape;
3313 thermalRing.BooleanSubtract( innerShape );
3314 }
3315
3316 // Clip the thermal ring to the zone boundary so it doesn't overflow
3317 thermalRing.BooleanIntersection( aSmoothedOutline );
3318
3319 // Add the thermal ring to the fill
3320 aFillPolys.BooleanAdd( thermalRing );
3321 }
3322}
3323
3324
3326 PCB_LAYER_ID aDebugLayer, SHAPE_POLY_SET& aFillPolys )
3327{
3328 // Build grid:
3329
3330 // obviously line thickness must be > zone min thickness.
3331 // It can happens if a board file was edited by hand by a python script
3332 // Use 1 micron margin to be *sure* there is no issue in Gerber files
3333 // (Gbr file unit = 1 or 10 nm) due to some truncation in coordinates or calculations
3334 // This margin also avoid problems due to rounding coordinates in next calculations
3335 // that can create incorrect polygons
3336 int thickness = std::max( aZone->GetHatchThickness(),
3337 aZone->GetMinThickness() + pcbIUScale.mmToIU( 0.001 ) );
3338
3339 int gridsize = thickness + aZone->GetHatchGap();
3340 int maxError = m_board->GetDesignSettings().m_MaxError;
3341
3342 SHAPE_POLY_SET filledPolys = aFillPolys.CloneDropTriangulation();
3343 // Use a area that contains the rotated bbox by orientation, and after rotate the result
3344 // by -orientation.
3345 if( !aZone->GetHatchOrientation().IsZero() )
3346 filledPolys.Rotate( - aZone->GetHatchOrientation() );
3347
3348 BOX2I bbox = filledPolys.BBox( 0 );
3349
3350 // Build hole shape
3351 // the hole size is aZone->GetHatchGap(), but because the outline thickness
3352 // is aZone->GetMinThickness(), the hole shape size must be larger
3353 SHAPE_LINE_CHAIN hole_base;
3354 int hole_size = aZone->GetHatchGap() + aZone->GetMinThickness();
3355 VECTOR2I corner( 0, 0 );;
3356 hole_base.Append( corner );
3357 corner.x += hole_size;
3358 hole_base.Append( corner );
3359 corner.y += hole_size;
3360 hole_base.Append( corner );
3361 corner.x = 0;
3362 hole_base.Append( corner );
3363 hole_base.SetClosed( true );
3364
3365 // Calculate minimal area of a grid hole.
3366 // All holes smaller than a threshold will be removed
3367 double minimal_hole_area = hole_base.Area() * aZone->GetHatchHoleMinArea();
3368
3369 // Now convert this hole to a smoothed shape:
3370 if( aZone->GetHatchSmoothingLevel() > 0 )
3371 {
3372 // the actual size of chamfer, or rounded corner radius is the half size
3373 // of the HatchFillTypeGap scaled by aZone->GetHatchSmoothingValue()
3374 // aZone->GetHatchSmoothingValue() = 1.0 is the max value for the chamfer or the
3375 // radius of corner (radius = half size of the hole)
3376 int smooth_value = KiROUND( aZone->GetHatchGap()
3377 * aZone->GetHatchSmoothingValue() / 2 );
3378
3379 // Minimal optimization:
3380 // make smoothing only for reasonable smooth values, to avoid a lot of useless segments
3381 // and if the smooth value is small, use chamfer even if fillet is requested
3382 #define SMOOTH_MIN_VAL_MM 0.02
3383 #define SMOOTH_SMALL_VAL_MM 0.04
3384
3385 if( smooth_value > pcbIUScale.mmToIU( SMOOTH_MIN_VAL_MM ) )
3386 {
3387 SHAPE_POLY_SET smooth_hole;
3388 smooth_hole.AddOutline( hole_base );
3389 int smooth_level = aZone->GetHatchSmoothingLevel();
3390
3391 if( smooth_value < pcbIUScale.mmToIU( SMOOTH_SMALL_VAL_MM ) && smooth_level > 1 )
3392 smooth_level = 1;
3393
3394 // Use a larger smooth_value to compensate the outline tickness
3395 // (chamfer is not visible is smooth value < outline thickess)
3396 smooth_value += aZone->GetMinThickness() / 2;
3397
3398 // smooth_value cannot be bigger than the half size oh the hole:
3399 smooth_value = std::min( smooth_value, aZone->GetHatchGap() / 2 );
3400
3401 // the error to approximate a circle by segments when smoothing corners by a arc
3402 maxError = std::max( maxError * 2, smooth_value / 20 );
3403
3404 switch( smooth_level )
3405 {
3406 case 1:
3407 // Chamfer() uses the distance from a corner to create a end point
3408 // for the chamfer.
3409 hole_base = smooth_hole.Chamfer( smooth_value ).Outline( 0 );
3410 break;
3411
3412 default:
3413 if( aZone->GetHatchSmoothingLevel() > 2 )
3414 maxError /= 2; // Force better smoothing
3415
3416 hole_base = smooth_hole.Fillet( smooth_value, maxError ).Outline( 0 );
3417 break;
3418
3419 case 0:
3420 break;
3421 };
3422 }
3423 }
3424
3425 // Build holes
3426 SHAPE_POLY_SET holes;
3427
3428 auto& defaultOffsets = m_board->GetDesignSettings().m_ZoneLayerProperties;
3429 auto& localOffsets = aZone->LayerProperties();
3430
3431 VECTOR2I offset = defaultOffsets[aLayer].hatching_offset.value_or( VECTOR2I() );
3432
3433 if( localOffsets.contains( aLayer ) && localOffsets.at( aLayer ).hatching_offset.has_value() )
3434 offset = localOffsets.at( aLayer ).hatching_offset.value();
3435
3436 int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize;
3437 int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize;
3438
3439
3440 for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize )
3441 {
3442 for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize )
3443 {
3444 // Generate hole
3445 SHAPE_LINE_CHAIN hole( hole_base );
3446 hole.Move( VECTOR2I( xx, yy ) );
3447
3448 if( !aZone->GetHatchOrientation().IsZero() )
3449 {
3450 hole.Rotate( aZone->GetHatchOrientation() );
3451 }
3452
3453 hole.Move( VECTOR2I( offset.x % gridsize, offset.y % gridsize ) );
3454
3455 holes.AddOutline( hole );
3456 }
3457 }
3458
3459 holes.ClearArcs();
3460
3461 DUMP_POLYS_TO_COPPER_LAYER( holes, In10_Cu, wxT( "hatch-holes" ) );
3462
3463 int deflated_thickness = aZone->GetHatchThickness() - aZone->GetMinThickness();
3464
3465 // Don't let thickness drop below maxError * 2 or it might not get reinflated.
3466 deflated_thickness = std::max( deflated_thickness, maxError * 2 );
3467
3468 // The fill has already been deflated to ensure GetMinThickness() so we just have to
3469 // account for anything beyond that.
3470 SHAPE_POLY_SET deflatedFilledPolys = aFillPolys.CloneDropTriangulation();
3471 deflatedFilledPolys.ClearArcs();
3472 deflatedFilledPolys.Deflate( deflated_thickness, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
3473 holes.BooleanIntersection( deflatedFilledPolys );
3474 DUMP_POLYS_TO_COPPER_LAYER( holes, In11_Cu, wxT( "fill-clipped-hatch-holes" ) );
3475
3476 SHAPE_POLY_SET deflatedOutline = aZone->Outline()->CloneDropTriangulation();
3477 deflatedOutline.ClearArcs();
3478 deflatedOutline.Deflate( aZone->GetMinThickness(), CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
3479 holes.BooleanIntersection( deflatedOutline );
3480 DUMP_POLYS_TO_COPPER_LAYER( holes, In12_Cu, wxT( "outline-clipped-hatch-holes" ) );
3481
3482 // Now filter truncated holes to avoid small holes in pattern
3483 // It happens for holes near the zone outline
3484 for( int ii = 0; ii < holes.OutlineCount(); )
3485 {
3486 double area = holes.Outline( ii ).Area();
3487
3488 if( area < minimal_hole_area ) // The current hole is too small: remove it
3489 holes.DeletePolygon( ii );
3490 else
3491 ++ii;
3492 }
3493
3494 // create grid. Useto
3495 // generate strictly simple polygons needed by Gerber files and Fracture()
3496 aFillPolys.BooleanSubtract( aFillPolys, holes );
3497 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In14_Cu, wxT( "after-hatching" ) );
3498
3499 return true;
3500}
3501
3502
3504{
3505 auto cacheKey = std::make_pair( static_cast<const ZONE*>( aZone ), aLayer );
3506
3507 {
3508 std::lock_guard<std::mutex> lock( m_cacheMutex );
3509 auto it = m_preKnockoutFillCache.find( cacheKey );
3510
3511 if( it == m_preKnockoutFillCache.end() )
3512 {
3513 wxLogTrace( traceZoneFiller, wxT( "Cache miss for zone %s layer %d (cache size: %zu)" ),
3514 aZone->GetNetname(), static_cast<int>( aLayer ), m_preKnockoutFillCache.size() );
3515 return false;
3516 }
3517
3518 wxLogTrace( traceZoneFiller, wxT( "Cache hit for zone %s layer %d: pre-knockout %d outlines" ),
3519 aZone->GetNetname(), static_cast<int>( aLayer ), it->second.OutlineCount() );
3520
3521 // Restore the cached pre-knockout fill
3522 aFillPolys = it->second;
3523 }
3524
3525 // Subtract the FILLED area of higher-priority zones (with clearance for different nets).
3526 // For same-net zones: subtract the filled area directly
3527 // For different-net zones: subtract the filled area with appropriate clearance
3528 // This replaces the original knockout logic to use actual fills after island removal.
3529 BOX2I zoneBBox = aZone->GetBoundingBox();
3530 int zone_clearance = aZone->GetLocalClearance().value();
3531 int board_clearance = m_board->GetDesignSettings().m_MinClearance;
3532
3533 for( ZONE* otherZone : m_board->Zones() )
3534 {
3535 if( otherZone == aZone )
3536 continue;
3537
3538 if( !otherZone->GetLayerSet().test( aLayer ) )
3539 continue;
3540
3541 if( otherZone->IsTeardropArea() )
3542 continue;
3543
3544 if( !otherZone->HigherPriority( aZone ) )
3545 continue;
3546
3547 if( !otherZone->GetBoundingBox().Intersects( zoneBBox ) )
3548 continue;
3549
3550 std::shared_ptr<SHAPE_POLY_SET> otherFill = otherZone->GetFilledPolysList( aLayer );
3551
3552 if( !otherFill || otherFill->OutlineCount() == 0 )
3553 continue;
3554
3555 if( otherZone->SameNet( aZone ) )
3556 {
3557 // Same net: subtract filled area directly
3558 aFillPolys.BooleanSubtract( *otherFill );
3559 }
3560 else
3561 {
3562 // Different net: subtract filled area with clearance
3563 int clearance = std::max( zone_clearance, otherZone->GetLocalClearance().value() );
3564 clearance = std::max( clearance, board_clearance );
3565
3566 SHAPE_POLY_SET inflatedFill = *otherFill;
3568 aFillPolys.BooleanSubtract( inflatedFill );
3569 }
3570 }
3571
3572 for( FOOTPRINT* footprint : m_board->Footprints() )
3573 {
3574 for( ZONE* otherZone : footprint->Zones() )
3575 {
3576 if( !otherZone->GetLayerSet().test( aLayer ) )
3577 continue;
3578
3579 if( otherZone->IsTeardropArea() )
3580 continue;
3581
3582 if( !otherZone->HigherPriority( aZone ) )
3583 continue;
3584
3585 if( !otherZone->GetBoundingBox().Intersects( zoneBBox ) )
3586 continue;
3587
3588 std::shared_ptr<SHAPE_POLY_SET> otherFill = otherZone->GetFilledPolysList( aLayer );
3589
3590 if( !otherFill || otherFill->OutlineCount() == 0 )
3591 continue;
3592
3593 if( otherZone->SameNet( aZone ) )
3594 {
3595 aFillPolys.BooleanSubtract( *otherFill );
3596 }
3597 else
3598 {
3599 int clearance = std::max( zone_clearance, otherZone->GetLocalClearance().value() );
3600 clearance = std::max( clearance, board_clearance );
3601
3602 SHAPE_POLY_SET inflatedFill = *otherFill;
3604 aFillPolys.BooleanSubtract( inflatedFill );
3605 }
3606 }
3607 }
3608
3609 aFillPolys.Fracture();
3610
3611 return true;
3612}
@ ERROR_OUTSIDE
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
@ ZLO_FORCE_NO_ZONE_CONNECTION
Definition board_item.h:74
@ ZLO_FORCE_FLASHED
Definition board_item.h:73
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
Container for design settings for a BOARD object.
std::shared_ptr< DRC_ENGINE > m_DRCEngine
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:83
virtual void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the item shape to a closed polygon.
virtual void SetIsKnockout(bool aKnockout)
Definition board_item.h:324
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
int GetCopperLayerCount() const
Definition board.cpp:918
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1081
constexpr int GetSizeMax() const
Definition box2.h:235
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:558
constexpr coord_type GetY() const
Definition box2.h:208
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr coord_type GetX() const
Definition box2.h:207
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr const Vec GetCenter() const
Definition box2.h:230
constexpr size_type GetHeight() const
Definition box2.h:215
constexpr coord_type GetRight() const
Definition box2.h:217
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:311
constexpr coord_type GetBottom() const
Definition box2.h:222
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition commit.h:72
MINOPTMAX< int > & Value()
Definition drc_rule.h:187
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:186
ZONE_CONNECTION m_ZoneConnection
Definition drc_rule.h:230
bool IsNull() const
Definition drc_rule.h:181
DRC_CONSTRAINT EvalRules(DRC_CONSTRAINT_T aConstraintType, const BOARD_ITEM *a, const BOARD_ITEM *b, PCB_LAYER_ID aLayer, REPORTER *aReporter=nullptr)
DRC_CONSTRAINT EvalZoneConnection(const BOARD_ITEM *a, const BOARD_ITEM *b, PCB_LAYER_ID aLayer, REPORTER *aReporter=nullptr)
double Sin() const
Definition eda_angle.h:178
bool IsZero() const
Definition eda_angle.h:136
double Cos() const
Definition eda_angle.h:197
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
Helper class to create more flexible dialogs, including 'do not show again' checkbox handling.
Definition kidialog.h:42
void DoNotShowCheckbox(wxString file, int line)
Shows the 'do not show again' checkbox.
Definition kidialog.cpp:55
bool SetOKCancelLabels(const ButtonLabel &ok, const ButtonLabel &cancel) override
Definition kidialog.h:52
int ShowModal() override
Definition kidialog.cpp:93
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static LSET AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:591
static const LSET & InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Definition lset.cpp:560
T Min() const
Definition minoptmax.h:33
T Max() const
Definition minoptmax.h:34
T Opt() const
Definition minoptmax.h:35
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:157
UNCONNECTED_LAYER_MODE UnconnectedLayerMode() const
Definition padstack.h:356
DRILL_PROPS & Drill()
Definition padstack.h:341
Definition pad.h:55
const BOX2I GetBoundingBox() const override
The bounding box is cached, so this will be efficient most of the time.
Definition pad.cpp:1300
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition pad.h:196
void SetOffset(PCB_LAYER_ID aLayer, const VECTOR2I &aOffset)
Definition pad.h:323
void SetPosition(const VECTOR2I &aPos) override
Definition pad.h:203
void SetOrientation(const EDA_ANGLE &aAngle)
Set the rotation angle of the pad.
Definition pad.cpp:1391
bool TransformHoleToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Build the corner list of the polygonal drill shape in the board coordinate system.
Definition pad.cpp:2462
void GetBoundingHull(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Abstract dimension API.
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool aIgnoreLineWidth=false) const override
Convert the item shape to a closed polygon.
void TransformTextToPolySet(SHAPE_POLY_SET &aBuffer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc) const
Function TransformTextToPolySet Convert the text to a polygonSet describing the actual character stro...
Definition pcb_text.cpp:567
void SetPosition(const VECTOR2I &aPoint) override
Definition pcb_track.h:615
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
A small class to help profiling.
Definition profile.h:49
double msecs(bool aSinceLast=false)
Definition profile.h:149
A progress reporter interface for use in multi-threaded environments.
int m_vertex2
RESULTS(int aOutline1, int aOutline2, int aVertex1, int aVertex2)
int m_outline2
int m_outline1
int m_vertex1
bool operator<(const RESULTS &aOther) const
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I::extended_type ecoord
Definition seg.h:44
VECTOR2I B
Definition seg.h:50
static SEG::ecoord Square(int a)
Definition seg.h:123
void Reverse()
Definition seg.h:368
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void Move(const VECTOR2I &aVector) override
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int Intersect(const SEG &aSeg, INTERSECTIONS &aIp) const
Find all intersection points between our line chain and the segment aSeg.
int PointCount() const
Return the number of points (vertices) in this line chain.
void Clear()
Remove all points from the line chain.
double Area(bool aAbsolute=true) const
Return the area of this chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
std::vector< INTERSECTION > INTERSECTIONS
Represent a set of closed polygons.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
SHAPE_POLY_SET Chamfer(int aDistance)
Return a chamfered version of the polygon set.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
void DeletePolygon(int aIdx)
Delete aIdx-th polygon from the set.
double Area()
Return the area of this poly set.
void Fracture()
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
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,...
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
void Deflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError)
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
void BuildBBoxCaches() const
Construct BBoxCaches for Contains(), below.
int OutlineCount() const
Return the number of outlines in the set.
SHAPE_POLY_SET Fillet(int aRadius, int aErrorMax)
Return a filleted version of the polygon 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.
SHAPE_POLY_SET CloneDropTriangulation() const
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const BOX2I BBoxFromCaches() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:307
constexpr VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition vector2d.h:314
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:385
VERTEX * getPoint(VERTEX *aPt) const
std::set< RESULTS > GetResults() const
VERTEX_CONNECTOR(const BOX2I &aBBox, const SHAPE_POLY_SET &aPolys, int aDist)
std::set< RESULTS > m_results
std::deque< VERTEX > m_vertices
Definition vertex_set.h:343
friend class VERTEX
Definition vertex_set.h:255
VERTEX * createList(const SHAPE_LINE_CHAIN &points, VERTEX *aTail=nullptr, void *aUserData=nullptr)
Create a list of vertices from a line chain.
void SetBoundingBox(const BOX2I &aBBox)
VERTEX_SET(int aSimplificationLevel)
Definition vertex_set.h:258
uint32_t zOrder(const double aX, const double aY) const
Note that while the inputs are doubles, these are scaled by the size of the bounding box to fit into ...
const double x
Definition vertex_set.h:235
VERTEX * next
Definition vertex_set.h:241
VERTEX * prevZ
Definition vertex_set.h:247
void updateList()
After inserting or changing nodes, this function should be called to remove duplicate vertices and en...
Definition vertex_set.h:121
VERTEX * nextZ
Definition vertex_set.h:248
VERTEX * prev
Definition vertex_set.h:240
const int i
Definition vertex_set.h:234
void * GetUserData() const
Definition vertex_set.h:79
uint32_t z
Definition vertex_set.h:244
bool isEar(bool aMatchUserData=false) const
Check whether the given vertex is in the middle of an ear.
const double y
Definition vertex_set.h:236
COMMIT * m_commit
bool refillZoneFromCache(ZONE *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aFillPolys)
Refill a zone from cached pre-knockout fill.
void buildCopperItemClearances(const ZONE *aZone, PCB_LAYER_ID aLayer, const std::vector< PAD * > &aNoConnectionPads, SHAPE_POLY_SET &aHoles, bool aIncludeZoneClearances=true)
Removes clearance from the shape for copper items which share the zone's layer but are not connected ...
int m_worstClearance
bool m_debugZoneFiller
void connect_nearby_polys(SHAPE_POLY_SET &aPolys, double aDistance)
Create strands of zero-width between elements of SHAPE_POLY_SET that are within aDistance of each oth...
void buildThermalSpokes(const ZONE *box, PCB_LAYER_ID aLayer, const std::vector< BOARD_ITEM * > &aSpokedPadsList, std::deque< SHAPE_LINE_CHAIN > &aSpokes)
Function buildThermalSpokes Constructs a list of all thermal spokes for the given zone.
void buildDifferentNetZoneClearances(const ZONE *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aHoles)
Build clearance knockout holes for higher-priority zones on different nets.
ZONE_FILLER(BOARD *aBoard, COMMIT *aCommit)
void subtractHigherPriorityZones(const ZONE *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aRawFill)
Removes the outlines of higher-proirity zones with the same net.
void knockoutThermalReliefs(const ZONE *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aFill, std::vector< BOARD_ITEM * > &aThermalConnectionPads, std::vector< PAD * > &aNoConnectionPads)
Removes thermal reliefs from the shape for any pads connected to the zone.
void addKnockout(BOARD_ITEM *aItem, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET &aHoles)
Add a knockout for a pad or via.
SHAPE_POLY_SET m_boardOutline
void buildHatchZoneThermalRings(const ZONE *aZone, PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aSmoothedOutline, const std::vector< BOARD_ITEM * > &aThermalConnectionPads, SHAPE_POLY_SET &aFillPolys)
Build thermal rings for pads in hatch zones.
std::map< std::pair< const ZONE *, PCB_LAYER_ID >, SHAPE_POLY_SET > m_preKnockoutFillCache
bool m_brdOutlinesValid
bool addHatchFillTypeOnZone(const ZONE *aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer, SHAPE_POLY_SET &aFillPolys)
for zones having the ZONE_FILL_MODE::ZONE_FILL_MODE::HATCH_PATTERN, create a grid pattern in filled a...
void SetProgressReporter(PROGRESS_REPORTER *aReporter)
std::mutex m_cacheMutex
BOARD * m_board
PROGRESS_REPORTER * m_progressReporter
bool fillCopperZone(const ZONE *aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer, const SHAPE_POLY_SET &aSmoothedOutline, const SHAPE_POLY_SET &aMaxExtents, SHAPE_POLY_SET &aFillPolys)
Function fillCopperZone Add non copper areas polygons (pads and tracks with clearance) to a filled co...
void addHoleKnockout(PAD *aPad, int aGap, SHAPE_POLY_SET &aHoles)
Add a knockout for a pad's hole.
bool fillNonCopperZone(const ZONE *candidate, PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aSmoothedOutline, SHAPE_POLY_SET &aFillPolys)
bool fillSingleZone(ZONE *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aFillPolys)
Build the filled solid areas polygons from zone outlines (stored in m_Poly) The solid areas can be mo...
bool Fill(const std::vector< ZONE * > &aZones, bool aCheck=false, wxWindow *aParent=nullptr)
Fills the given list of zones.
Handle a list of polygons defining a copper zone.
Definition zone.h:74
void SetNeedRefill(bool aNeedRefill)
Definition zone.h:292
std::optional< int > GetLocalClearance() const override
Definition zone.cpp:806
ZONE_LAYER_PROPERTIES & LayerProperties(PCB_LAYER_ID aLayer)
Definition zone.h:145
void CacheTriangulation(PCB_LAYER_ID aLayer=UNDEFINED_LAYER)
Create a list of triangles that "fill" the solid areas used for instance to draw these solid areas on...
Definition zone.cpp:1305
const BOX2I GetBoundingBox() const override
Definition zone.cpp:613
SHAPE_POLY_SET * Outline()
Definition zone.h:331
void SetFillFlag(PCB_LAYER_ID aLayer, bool aFlag)
Definition zone.h:286
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition zone.h:617
int GetMinThickness() const
Definition zone.h:297
bool HigherPriority(const ZONE *aOther) const
Definition zone.cpp:434
int GetHatchThickness() const
Definition zone.h:306
double GetHatchHoleMinArea() const
Definition zone.h:321
bool IsTeardropArea() const
Definition zone.h:676
EDA_ANGLE GetHatchOrientation() const
Definition zone.h:312
bool BuildSmoothedPoly(SHAPE_POLY_SET &aSmoothedPoly, PCB_LAYER_ID aLayer, SHAPE_POLY_SET *aBoardOutline, SHAPE_POLY_SET *aSmoothedPolyWithApron=nullptr) const
Definition zone.cpp:1369
ZONE_FILL_MODE GetFillMode() const
Definition zone.h:220
int GetHatchGap() const
Definition zone.h:309
double GetHatchSmoothingValue() const
Definition zone.h:318
int GetHatchSmoothingLevel() const
Definition zone.h:315
bool IsOnCopperLayer() const override
Definition zone.cpp:511
std::mutex & GetLock()
Definition zone.h:276
unsigned GetAssignedPriority() const
Definition zone.h:126
void TransformRingToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aCentre, int aRadius, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arcs to multiple straight segments.
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
void BuildConvexHull(std::vector< VECTOR2I > &aResult, const std::vector< VECTOR2I > &aPoly)
Calculate the convex hull of a list of points in counter-clockwise order.
CORNER_STRATEGY
define how inflate transform build inflated polygon
@ CHAMFER_ALL_CORNERS
All angles are chamfered.
@ ROUND_ALL_CORNERS
All angles are rounded.
DRC_CONSTRAINT_T
Definition drc_rule.h:47
@ EDGE_CLEARANCE_CONSTRAINT
Definition drc_rule.h:53
@ PHYSICAL_HOLE_CLEARANCE_CONSTRAINT
Definition drc_rule.h:78
@ CLEARANCE_CONSTRAINT
Definition drc_rule.h:49
@ THERMAL_SPOKE_WIDTH_CONSTRAINT
Definition drc_rule.h:64
@ THERMAL_RELIEF_GAP_CONSTRAINT
Definition drc_rule.h:63
@ HOLE_CLEARANCE_CONSTRAINT
Definition drc_rule.h:51
@ PHYSICAL_CLEARANCE_CONSTRAINT
Definition drc_rule.h:77
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ DEGREES_T
Definition eda_angle.h:31
@ SEGMENT
Definition eda_shape.h:45
a few functions useful in geometry calculations.
bool m_ZoneFillIterativeRefill
Enable iterative zone filling to handle isolated islands in higher priority zones.
bool m_DebugZoneFiller
A mode that dumps the various stages of a F_Cu fill into In1_Cu through In9_Cu.
static const wxChar traceZoneFiller[]
Trace mask for zone filler timing information.
@ ALWAYS_FLASHED
Always flashed for connectivity.
Definition layer_ids.h:186
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ In11_Cu
Definition layer_ids.h:76
@ In17_Cu
Definition layer_ids.h:82
@ Edge_Cuts
Definition layer_ids.h:112
@ In9_Cu
Definition layer_ids.h:74
@ In7_Cu
Definition layer_ids.h:72
@ In15_Cu
Definition layer_ids.h:80
@ In2_Cu
Definition layer_ids.h:67
@ In10_Cu
Definition layer_ids.h:75
@ Margin
Definition layer_ids.h:113
@ In4_Cu
Definition layer_ids.h:69
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ In16_Cu
Definition layer_ids.h:81
@ In1_Cu
Definition layer_ids.h:66
@ In8_Cu
Definition layer_ids.h:73
@ In14_Cu
Definition layer_ids.h:79
@ In12_Cu
Definition layer_ids.h:77
@ In6_Cu
Definition layer_ids.h:71
@ In5_Cu
Definition layer_ids.h:70
@ In3_Cu
Definition layer_ids.h:68
@ F_Cu
Definition layer_ids.h:64
@ In18_Cu
Definition layer_ids.h:83
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ PTH
Plated through hole pad.
Definition padstack.h:98
BARCODE class definition.
const double epsilon
A storage class for 128-bit hash value.
Definition hash_128.h:36
A struct recording the isolated and single-pad islands within a zone.
Definition zone.h:61
! The properties of a padstack drill. Drill position is always the pad position (origin).
Definition padstack.h:261
PCB_LAYER_ID start
Definition padstack.h:264
PCB_LAYER_ID end
Definition padstack.h:265
VECTOR2I size
Drill diameter (x == y) or slot dimensions (x != y)
Definition padstack.h:262
std::optional< PAD_DRILL_POST_MACHINING_MODE > mode
Definition padstack.h:275
VECTOR2I center
int radius
int clearance
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:31
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:88
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:106
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:103
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:104
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:93
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:92
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:90
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:101
@ PCB_TARGET_T
class PCB_TARGET, a target (graphic item)
Definition typeinfo.h:107
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:102
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition typeinfo.h:94
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:105
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694
#define SMOOTH_MIN_VAL_MM
int getHatchFillThermalClearance(const ZONE *aZone, BOARD_ITEM *aItem, PCB_LAYER_ID aLayer)
#define DUMP_POLYS_TO_COPPER_LAYER(a, b, c)
#define SMOOTH_SMALL_VAL_MM
ISLAND_REMOVAL_MODE
Whether or not to remove isolated islands from a zone.
ZONE_CONNECTION
How pads are covered by copper in zone.
Definition zones.h:47
@ THERMAL
Use thermal relief for pads.
Definition zones.h:50
@ NONE
Pads are not covered.
Definition zones.h:49
@ FULL
pads are covered by copper
Definition zones.h:51