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