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