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 // For internal copper layers, GetCourtyard( aLayer ) always returns the
2223 // front courtyard because IsBackLayer() is false for all internal layers.
2224 // Use the footprint's own layer to select the correct courtyard instead.
2225 PCB_LAYER_ID courtyardSide = IsInnerCopperLayer( aLayer ) ? aFootprint->GetLayer() : aLayer;
2226
2227 if( gap == 0 )
2228 {
2229 aHoles.Append( aFootprint->GetCourtyard( courtyardSide ) );
2230 }
2231 else if( gap > 0 )
2232 {
2233 SHAPE_POLY_SET hole = aFootprint->GetCourtyard( courtyardSide );
2235 aHoles.Append( hole );
2236 }
2237 }
2238 };
2239
2240 for( FOOTPRINT* footprint : m_board->Footprints() )
2241 {
2242 knockoutCourtyardClearance( footprint );
2243 knockoutGraphicClearance( &footprint->Reference() );
2244 knockoutGraphicClearance( &footprint->Value() );
2245
2246 std::set<PAD*> allowedNetTiePads;
2247
2248 // Don't knock out holes for graphic items which implement a net-tie to the zone's net
2249 // on the layer being filled.
2250 if( footprint->IsNetTie() )
2251 {
2252 for( PAD* pad : footprint->Pads() )
2253 {
2254 bool sameNet = pad->GetNetCode() == aZone->GetNetCode();
2255
2256 if( !aZone->IsTeardropArea() && aZone->GetNetCode() == 0 )
2257 sameNet = false;
2258
2259 if( sameNet )
2260 {
2261 if( pad->IsOnLayer( aLayer ) )
2262 allowedNetTiePads.insert( pad );
2263
2264 for( PAD* other : footprint->GetNetTiePads( pad ) )
2265 {
2266 if( other->IsOnLayer( aLayer ) )
2267 allowedNetTiePads.insert( other );
2268 }
2269 }
2270 }
2271 }
2272
2273 for( BOARD_ITEM* item : footprint->GraphicalItems() )
2274 {
2275 if( checkForCancel( m_progressReporter ) )
2276 return;
2277
2278 BOX2I itemBBox = item->GetBoundingBox();
2279
2280 if( !zone_boundingbox.Intersects( itemBBox ) )
2281 continue;
2282
2283 bool skipItem = false;
2284
2285 if( item->IsOnLayer( aLayer ) )
2286 {
2287 std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape();
2288
2289 for( PAD* pad : allowedNetTiePads )
2290 {
2291 if( pad->GetBoundingBox().Intersects( itemBBox )
2292 && pad->GetEffectiveShape( aLayer )->Collide( itemShape.get() ) )
2293 {
2294 skipItem = true;
2295 break;
2296 }
2297 }
2298 }
2299
2300 if( !skipItem )
2301 knockoutGraphicClearance( item );
2302 }
2303 }
2304
2305 for( BOARD_ITEM* item : m_board->Drawings() )
2306 {
2307 if( checkForCancel( m_progressReporter ) )
2308 return;
2309
2310 knockoutGraphicClearance( item );
2311 }
2312
2313 // Add non-connected zone clearances
2314 //
2315 auto knockoutZoneClearance =
2316 [&]( ZONE* aKnockout )
2317 {
2318 // If the zones share no common layers
2319 if( !aKnockout->GetLayerSet().test( aLayer ) )
2320 return;
2321
2322 if( aKnockout->GetBoundingBox().Intersects( zone_boundingbox ) )
2323 {
2324 if( aKnockout->GetIsRuleArea() )
2325 {
2326 if( aKnockout->GetDoNotAllowZoneFills() && !aZone->IsTeardropArea() )
2327 {
2328 // Keepouts use outline with no clearance
2329 aKnockout->TransformSmoothedOutlineToPolygon( aHoles, 0, m_maxError, ERROR_OUTSIDE,
2330 nullptr );
2331 }
2332 }
2333 else
2334 {
2335 if( aKnockout->HigherPriority( aZone ) && !aKnockout->SameNet( aZone ) )
2336 {
2337 int gap = std::max( 0, evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone, aKnockout,
2338 aLayer ) );
2339
2340 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, aKnockout, aLayer ) );
2341
2342 // Negative clearance permits zones to short
2343 if( gap < 0 )
2344 return;
2345
2346 SHAPE_POLY_SET poly;
2347 aKnockout->TransformShapeToPolygon( poly, aLayer, gap + extra_margin, m_maxError,
2348 ERROR_OUTSIDE );
2349 aHoles.Append( poly );
2350 }
2351 }
2352 }
2353 };
2354
2355 if( aIncludeZoneClearances )
2356 {
2357 for( ZONE* otherZone : m_board->Zones() )
2358 {
2359 if( checkForCancel( m_progressReporter ) )
2360 return;
2361
2362 knockoutZoneClearance( otherZone );
2363 }
2364
2365 for( FOOTPRINT* footprint : m_board->Footprints() )
2366 {
2367 for( ZONE* otherZone : footprint->Zones() )
2368 {
2369 if( checkForCancel( m_progressReporter ) )
2370 return;
2371
2372 knockoutZoneClearance( otherZone );
2373 }
2374 }
2375 }
2376
2377 aHoles.Simplify();
2378}
2379
2380
2386{
2387 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
2388 int extra_margin = pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_ExtraClearance );
2389
2390 BOX2I zone_boundingbox = aZone->GetBoundingBox();
2391 zone_boundingbox.Inflate( m_worstClearance + extra_margin );
2392
2393 auto evalRulesForItems =
2394 [&bds]( DRC_CONSTRAINT_T aConstraint, const BOARD_ITEM* a, const BOARD_ITEM* b,
2395 PCB_LAYER_ID aEvalLayer ) -> int
2396 {
2397 DRC_CONSTRAINT c = bds.m_DRCEngine->EvalRules( aConstraint, a, b, aEvalLayer );
2398
2399 if( c.IsNull() )
2400 return -1;
2401 else
2402 return c.GetValue().Min();
2403 };
2404
2405 // Keepout zones (rule areas) are excluded here because they are subtracted earlier in the
2406 // fill process, before the deflate/inflate min-width cycle. Subtracting them here would
2407 // trigger a second deflate/inflate pass that creates artifacts along curved keepout
2408 // boundaries (issue 23515).
2409 auto knockoutZoneClearance =
2410 [&]( ZONE* aKnockout )
2411 {
2412 if( aKnockout->GetIsRuleArea() )
2413 return;
2414
2415 if( !aKnockout->GetLayerSet().test( aLayer ) )
2416 return;
2417
2418 if( aKnockout->GetBoundingBox().Intersects( zone_boundingbox ) )
2419 {
2420 if( aKnockout->HigherPriority( aZone ) && !aKnockout->SameNet( aZone ) )
2421 {
2422 int gap = std::max( 0, evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
2423 aZone, aKnockout, aLayer ) );
2424
2425 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT, aZone,
2426 aKnockout, aLayer ) );
2427
2428 if( gap < 0 )
2429 return;
2430
2431 SHAPE_POLY_SET poly;
2432 aKnockout->TransformShapeToPolygon( poly, aLayer, gap + extra_margin,
2434 aHoles.Append( poly );
2435 }
2436 }
2437 };
2438
2439 forEachBoardAndFootprintZone( m_board, knockoutZoneClearance );
2440
2441 aHoles.Simplify();
2442}
2443
2444
2450 SHAPE_POLY_SET& aRawFill )
2451{
2452 BOX2I zoneBBox = aZone->GetBoundingBox();
2453 SHAPE_POLY_SET knockouts;
2454
2455 auto collectZoneOutline =
2456 [&]( ZONE* aKnockout )
2457 {
2458 if( !aKnockout->GetLayerSet().test( aLayer ) )
2459 return;
2460
2461 if( aKnockout->GetBoundingBox().Intersects( zoneBBox ) )
2462 appendZoneOutlineWithoutArcs( aKnockout, knockouts );
2463 };
2464
2465 forEachBoardAndFootprintZone(
2466 m_board,
2467 [&]( ZONE* otherZone )
2468 {
2469 // Don't use `HigherPriority()` here because we only want explicitly-higher
2470 // priorities, not equal-priority zones.
2471 bool higherPrioritySameNet =
2472 otherZone->SameNet( aZone )
2473 && otherZone->GetAssignedPriority() > aZone->GetAssignedPriority();
2474
2475 if( higherPrioritySameNet && !otherZone->IsTeardropArea() )
2476 collectZoneOutline( otherZone );
2477 } );
2478
2479 if( knockouts.OutlineCount() > 0 )
2480 aRawFill.BooleanSubtract( knockouts );
2481}
2482
2483
2484void ZONE_FILLER::connect_nearby_polys( SHAPE_POLY_SET& aPolys, double aDistance )
2485{
2486 if( aPolys.OutlineCount() < 1 )
2487 return;
2488
2489 VERTEX_CONNECTOR vs( aPolys.BBoxFromCaches(), aPolys, aDistance );
2490
2491 vs.FindResults();
2492
2493 // This cannot be a reference because we need to do the comparison below while
2494 // changing the values
2495 std::map<int, std::vector<std::pair<int, VECTOR2I>>> insertion_points;
2496
2497 for( const RESULTS& result : vs.GetResults() )
2498 {
2499 SHAPE_LINE_CHAIN& line1 = aPolys.Outline( result.m_outline1 );
2500 SHAPE_LINE_CHAIN& line2 = aPolys.Outline( result.m_outline2 );
2501
2502 VECTOR2I pt1 = line1.CPoint( result.m_vertex1 );
2503 VECTOR2I pt2 = line2.CPoint( result.m_vertex2 );
2504
2505 // We want to insert the existing point first so that we can place the new point
2506 // between the two points at the same location.
2507 insertion_points[result.m_outline1].push_back( { result.m_vertex1, pt1 } );
2508 insertion_points[result.m_outline1].push_back( { result.m_vertex1, pt2 } );
2509 }
2510
2511 for( auto& [outline, vertices] : insertion_points )
2512 {
2513 SHAPE_LINE_CHAIN& line = aPolys.Outline( outline );
2514
2515 // Stable sort here because we want to make sure that we are inserting pt1 first and
2516 // pt2 second but still sorting the rest of the indices from highest to lowest.
2517 // This allows us to insert into the existing polygon without modifying the future
2518 // insertion points.
2519 std::stable_sort( vertices.begin(), vertices.end(),
2520 []( const std::pair<int, VECTOR2I>& a, const std::pair<int, VECTOR2I>& b )
2521 {
2522 return a.first > b.first;
2523 } );
2524
2525 for( const auto& [vertex, pt] : vertices )
2526 line.Insert( vertex + 1, pt );
2527 }
2528}
2529
2530
2532{
2533 int half_min_width = aZone->GetMinThickness() / 2;
2534 int epsilon = pcbIUScale.mmToIU( 0.001 );
2535
2536 if( half_min_width - epsilon <= epsilon )
2537 return;
2538
2539 SHAPE_POLY_SET preDeflate = aFillPolys.CloneDropTriangulation();
2540
2541 aFillPolys.Deflate( half_min_width - epsilon, CORNER_STRATEGY::CHAMFER_ALL_CORNERS,
2542 m_maxError );
2543
2544 aFillPolys.Fracture();
2545 connect_nearby_polys( aFillPolys, aZone->GetMinThickness() );
2546
2547 for( int ii = aFillPolys.OutlineCount() - 1; ii >= 0; ii-- )
2548 {
2549 std::vector<SHAPE_LINE_CHAIN>& island = aFillPolys.Polygon( ii );
2550 BOX2I islandExtents;
2551
2552 for( const VECTOR2I& pt : island.front().CPoints() )
2553 {
2554 islandExtents.Merge( pt );
2555
2556 if( islandExtents.GetSizeMax() > aZone->GetMinThickness() )
2557 break;
2558 }
2559
2560 if( islandExtents.GetSizeMax() < aZone->GetMinThickness() )
2561 aFillPolys.DeletePolygon( ii );
2562 }
2563
2564 aFillPolys.Inflate( half_min_width - epsilon, CORNER_STRATEGY::ROUND_ALL_CORNERS, m_maxError,
2565 true );
2566 aFillPolys.BooleanIntersection( preDeflate );
2567}
2568
2569
2570#define DUMP_POLYS_TO_COPPER_LAYER( a, b, c ) \
2571 { if( m_debugZoneFiller && aDebugLayer == b ) \
2572 { \
2573 m_board->SetLayerName( b, c ); \
2574 SHAPE_POLY_SET d = a; \
2575 d.Fracture(); \
2576 aFillPolys = d; \
2577 return false; \
2578 } \
2579 }
2580
2581
2582/*
2583 * Note that aSmoothedOutline is larger than the zone where it intersects with other, same-net
2584 * zones. This is to prevent the re-inflation post min-width trimming from createing divots
2585 * between adjacent zones. The final aMaxExtents trimming will remove these areas from the final
2586 * fill.
2587 */
2588bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer,
2589 const SHAPE_POLY_SET& aSmoothedOutline,
2590 const SHAPE_POLY_SET& aMaxExtents, SHAPE_POLY_SET& aFillPolys )
2591{
2592 // m_maxError is initialized in the constructor. Don't reassign here to avoid data races
2593 // when multiple threads call this function concurrently.
2594
2595 // Features which are min_width should survive pruning; features that are *less* than
2596 // min_width should not. Therefore we subtract epsilon from the min_width when
2597 // deflating/inflating.
2598 int half_min_width = aZone->GetMinThickness() / 2;
2599 int epsilon = pcbIUScale.mmToIU( 0.001 );
2600
2601 // Solid polygons are deflated and inflated during calculations. Deflating doesn't cause
2602 // issues, but inflate is tricky as it can create excessively long and narrow spikes for
2603 // acute angles.
2604 // ALLOW_ACUTE_CORNERS cannot be used due to the spike problem.
2605 // CHAMFER_ACUTE_CORNERS is tempting, but can still produce spikes in some unusual
2606 // circumstances (https://gitlab.com/kicad/code/kicad/-/issues/5581).
2607 // It's unclear if ROUND_ACUTE_CORNERS would have the same issues, but is currently avoided
2608 // as a "less-safe" option.
2609 // ROUND_ALL_CORNERS produces the uniformly nicest shapes, but also a lot of segments.
2610 // CHAMFER_ALL_CORNERS improves the segment count.
2613
2614 std::vector<BOARD_ITEM*> thermalConnectionPads;
2615 std::vector<PAD*> noConnectionPads;
2616 std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
2617 SHAPE_POLY_SET clearanceHoles;
2618
2619 aFillPolys = aSmoothedOutline;
2620 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In1_Cu, wxT( "smoothed-outline" ) );
2621
2622 if( m_progressReporter && m_progressReporter->IsCancelled() )
2623 return false;
2624
2625 /* -------------------------------------------------------------------------------------
2626 * Knockout thermal reliefs.
2627 */
2628
2629 knockoutThermalReliefs( aZone, aLayer, aFillPolys, thermalConnectionPads, noConnectionPads );
2630 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In2_Cu, wxT( "minus-thermal-reliefs" ) );
2631
2632 if( m_progressReporter && m_progressReporter->IsCancelled() )
2633 return false;
2634
2635 /* -------------------------------------------------------------------------------------
2636 * For hatch zones, add thermal rings around pads with thermal relief.
2637 * The rings are clipped to the zone boundary and provide the connection point
2638 * for the hatch webbing instead of connecting directly to the pad.
2639 */
2640
2641 SHAPE_POLY_SET thermalRings;
2642
2644 {
2645 buildHatchZoneThermalRings( aZone, aLayer, aSmoothedOutline, thermalConnectionPads,
2646 aFillPolys, thermalRings );
2647 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In2_Cu, wxT( "plus-thermal-rings" ) );
2648 }
2649
2650 if( m_progressReporter && m_progressReporter->IsCancelled() )
2651 return false;
2652
2653 /* -------------------------------------------------------------------------------------
2654 * Knockout electrical clearances.
2655 */
2656
2657 // When iterative refill is enabled, we build zone-to-zone clearances separately so we can
2658 // cache the fill before zone knockouts are applied (issue 21746). Keepout zones are always
2659 // included in clearanceHoles regardless of the iterative refill setting so they are
2660 // subtracted before the deflate/inflate min-width cycle. Subtracting keepouts after that
2661 // cycle and running a second deflate/inflate pass creates artifacts along curved keepout
2662 // boundaries (issue 23515).
2663 const bool iterativeRefill = ADVANCED_CFG::GetCfg().m_ZoneFillIterativeRefill;
2664
2665 buildCopperItemClearances( aZone, aLayer, noConnectionPads, clearanceHoles,
2666 !iterativeRefill /* include zone clearances only if not iterative */ );
2667
2668 if( iterativeRefill )
2669 {
2670 BOX2I zone_boundingbox = aZone->GetBoundingBox();
2671 bool addedKeepoutHoles = false;
2672
2673 auto collectKeepoutHoles =
2674 [&]( ZONE* candidate )
2675 {
2676 if( aZone->IsTeardropArea() )
2677 return;
2678
2679 if( !isZoneFillKeepout( candidate, aLayer, zone_boundingbox ) )
2680 return;
2681
2682 candidate->TransformSmoothedOutlineToPolygon( clearanceHoles, 0, m_maxError,
2683 ERROR_OUTSIDE, nullptr );
2684 addedKeepoutHoles = true;
2685 };
2686
2687 forEachBoardAndFootprintZone( m_board, collectKeepoutHoles );
2688
2689 if( addedKeepoutHoles )
2690 clearanceHoles.Simplify();
2691 }
2692
2693 DUMP_POLYS_TO_COPPER_LAYER( clearanceHoles, In3_Cu, wxT( "clearance-holes" ) );
2694
2695 if( m_progressReporter && m_progressReporter->IsCancelled() )
2696 return false;
2697
2698 /* -------------------------------------------------------------------------------------
2699 * Add thermal relief spokes.
2700 */
2701
2702 buildThermalSpokes( aZone, aLayer, thermalConnectionPads, thermalSpokes );
2703
2704 if( m_progressReporter && m_progressReporter->IsCancelled() )
2705 return false;
2706
2707 // Create a temporary zone that we can hit-test spoke-ends against. It's only temporary
2708 // because the "real" subtract-clearance-holes has to be done after the spokes are added.
2709 SHAPE_POLY_SET testAreas = aFillPolys.CloneDropTriangulation();
2710 testAreas.BooleanSubtract( clearanceHoles );
2711
2712 // When iterative refill is enabled, zone-to-zone clearances are not included in
2713 // clearanceHoles (they're applied later to allow pre-knockout caching). But we still
2714 // need to account for them when testing spoke endpoints, otherwise spokes will be kept
2715 // that point into areas that will be knocked out by higher-priority zones.
2716 SHAPE_POLY_SET zoneClearances;
2717
2718 if( iterativeRefill )
2719 {
2720 buildDifferentNetZoneClearances( aZone, aLayer, zoneClearances );
2721
2722 if( zoneClearances.OutlineCount() > 0 )
2723 testAreas.BooleanSubtract( zoneClearances );
2724 }
2725
2726 DUMP_POLYS_TO_COPPER_LAYER( testAreas, In4_Cu, wxT( "minus-clearance-holes" ) );
2727
2728 // Prune features that don't meet minimum-width criteria
2729 if( half_min_width - epsilon > epsilon )
2730 {
2731 testAreas.Deflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
2732 DUMP_POLYS_TO_COPPER_LAYER( testAreas, In5_Cu, wxT( "spoke-test-deflated" ) );
2733
2734 testAreas.Inflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
2735 DUMP_POLYS_TO_COPPER_LAYER( testAreas, In6_Cu, wxT( "spoke-test-reinflated" ) );
2736 }
2737
2738 if( m_progressReporter && m_progressReporter->IsCancelled() )
2739 return false;
2740
2741 // Build a Y-stripe spatial index for O(sqrt(V)) spoke endpoint containment queries
2742 // instead of O(V) brute-force ray-casting with bbox caches.
2743 POLY_YSTRIPES_INDEX spokeTestIndex;
2744 spokeTestIndex.Build( testAreas );
2745 int interval = 0;
2746
2747 SHAPE_POLY_SET debugSpokes;
2748
2749 for( const SHAPE_LINE_CHAIN& spoke : thermalSpokes )
2750 {
2751 const VECTOR2I& testPt = spoke.CPoint( 3 );
2752
2753 // Hit-test against zone body
2754 if( spokeTestIndex.Contains( testPt, 1 ) )
2755 {
2756 if( m_debugZoneFiller )
2757 debugSpokes.AddOutline( spoke );
2758
2759 aFillPolys.AddOutline( spoke );
2760 continue;
2761 }
2762
2763 if( interval++ > 400 )
2764 {
2765 if( m_progressReporter && m_progressReporter->IsCancelled() )
2766 return false;
2767
2768 interval = 0;
2769 }
2770
2771 // Hit-test against other spokes
2772 for( const SHAPE_LINE_CHAIN& other : thermalSpokes )
2773 {
2774 // Hit test in both directions to avoid interactions with round-off errors.
2775 // (See https://gitlab.com/kicad/code/kicad/-/issues/13316.)
2776 if( &other != &spoke
2777 && other.PointInside( testPt, 1 )
2778 && spoke.PointInside( other.CPoint( 3 ), 1 ) )
2779 {
2780 if( m_debugZoneFiller )
2781 debugSpokes.AddOutline( spoke );
2782
2783 aFillPolys.AddOutline( spoke );
2784 break;
2785 }
2786 }
2787 }
2788
2789 DUMP_POLYS_TO_COPPER_LAYER( debugSpokes, In7_Cu, wxT( "spokes" ) );
2790
2791 if( m_progressReporter && m_progressReporter->IsCancelled() )
2792 return false;
2793
2794 aFillPolys.BooleanSubtract( clearanceHoles );
2795 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In8_Cu, wxT( "after-spoke-trimming" ) );
2796
2797 /* -------------------------------------------------------------------------------------
2798 * Prune features that don't meet minimum-width criteria
2799 */
2800
2801 if( half_min_width - epsilon > epsilon )
2802 {
2803 aFillPolys.Deflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
2804
2805 // Also deflate thermal rings to match, for correct hatch hole notching
2806 if( thermalRings.OutlineCount() > 0 )
2807 thermalRings.Deflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
2808 }
2809
2810 // Min-thickness is the web thickness. On the other hand, a blob min-thickness by
2811 // min-thickness is not useful. Since there's no obvious definition of web vs. blob, we
2812 // arbitrarily choose "at least 2X min-thickness on one axis". (Since we're doing this
2813 // during the deflated state, that means we test for "at least min-thickness".)
2814 for( int ii = aFillPolys.OutlineCount() - 1; ii >= 0; ii-- )
2815 {
2816 std::vector<SHAPE_LINE_CHAIN>& island = aFillPolys.Polygon( ii );
2817 BOX2I islandExtents;
2818
2819 for( const VECTOR2I& pt : island.front().CPoints() )
2820 {
2821 islandExtents.Merge( pt );
2822
2823 if( islandExtents.GetSizeMax() > aZone->GetMinThickness() )
2824 break;
2825 }
2826
2827 if( islandExtents.GetSizeMax() < aZone->GetMinThickness() )
2828 aFillPolys.DeletePolygon( ii );
2829 }
2830
2831 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In9_Cu, wxT( "deflated" ) );
2832
2833 if( m_progressReporter && m_progressReporter->IsCancelled() )
2834 return false;
2835
2836 /* -------------------------------------------------------------------------------------
2837 * Process the hatch pattern (note that we do this while deflated)
2838 */
2839
2841 && ( !m_board->GetProject()
2842 || !m_board->GetProject()->GetLocalSettings().m_PrototypeZoneFill ) )
2843 {
2844 // Combine thermal rings with clearance holes (non-connected pad clearances) so that
2845 // the hatch hole-dropping logic considers both types of rings
2846 SHAPE_POLY_SET ringsToProtect = thermalRings;
2847 ringsToProtect.BooleanAdd( clearanceHoles );
2848
2849 if( !addHatchFillTypeOnZone( aZone, aLayer, aDebugLayer, aFillPolys, ringsToProtect ) )
2850 return false;
2851 }
2852 else
2853 {
2854 /* ---------------------------------------------------------------------------------
2855 * Connect nearby polygons with zero-width lines in order to ensure correct
2856 * re-inflation.
2857 */
2858 aFillPolys.Fracture();
2859 connect_nearby_polys( aFillPolys, aZone->GetMinThickness() );
2860
2861 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In10_Cu, wxT( "connected-nearby-polys" ) );
2862 }
2863
2864 if( m_progressReporter && m_progressReporter->IsCancelled() )
2865 return false;
2866
2867 /* -------------------------------------------------------------------------------------
2868 * Finish minimum-width pruning by re-inflating
2869 */
2870
2871 if( half_min_width - epsilon > epsilon )
2872 aFillPolys.Inflate( half_min_width - epsilon, cornerStrategy, m_maxError, true );
2873
2874 // The deflation/inflation process can leave notches in the outline. Remove these by
2875 // doing a union with the original ring
2876 aFillPolys.BooleanAdd( thermalRings );
2877
2878 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In15_Cu, wxT( "after-reinflating" ) );
2879
2880 /* -------------------------------------------------------------------------------------
2881 * Ensure additive changes (thermal stubs and inflating acute corners) do not add copper
2882 * outside the zone boundary, inside the clearance holes, or between otherwise isolated
2883 * islands
2884 */
2885
2886 for( BOARD_ITEM* item : thermalConnectionPads )
2887 {
2888 if( item->Type() == PCB_PAD_T )
2889 addHoleKnockout( static_cast<PAD*>( item ), 0, clearanceHoles );
2890 }
2891
2892 aFillPolys.BooleanIntersection( aMaxExtents );
2893 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In16_Cu, wxT( "after-trim-to-outline" ) );
2894 aFillPolys.BooleanSubtract( clearanceHoles );
2895 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In17_Cu, wxT( "after-trim-to-clearance-holes" ) );
2896
2897 // Cache the pre-knockout fill for iterative refill optimization (issue 21746).
2898 // The cache stores the fill BEFORE zone-to-zone knockouts so the iterative refill can
2899 // reclaim space when higher-priority zones have islands removed.
2900 bool knockoutsApplied = false;
2901
2902 if( iterativeRefill )
2903 {
2904 {
2905 std::lock_guard<std::mutex> lock( m_cacheMutex );
2906 m_preKnockoutFillCache[{ aZone, aLayer }] = aFillPolys;
2907 }
2908
2909 // Reuse the zone clearances already computed for spoke endpoint testing
2910 if( zoneClearances.OutlineCount() > 0 )
2911 {
2912 aFillPolys.BooleanSubtract( zoneClearances );
2913 knockoutsApplied = true;
2914 }
2915 }
2916
2917 /* -------------------------------------------------------------------------------------
2918 * Re-prune minimum-width violations introduced by different-net zone knockouts.
2919 *
2920 * This must run BEFORE subtracting same-net higher-priority zones. At this point the
2921 * fill still extends into overlapping same-net zone areas, which provides a natural
2922 * buffer that prevents the deflate/inflate cycle from creating divots at same-net
2923 * zone boundaries (the same role aSmoothedOutline plays in the initial min-width pass).
2924 */
2925
2926 if( knockoutsApplied )
2927 postKnockoutMinWidthPrune( aZone, aFillPolys );
2928
2929 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In18_Cu, wxT( "after-post-knockout-min-width" ) );
2930
2931 /* -------------------------------------------------------------------------------------
2932 * Lastly give any same-net but higher-priority zones control over their own area.
2933 */
2934
2935 subtractHigherPriorityZones( aZone, aLayer, aFillPolys );
2936 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In19_Cu, wxT( "minus-higher-priority-zones" ) );
2937
2938 aFillPolys.Fracture();
2939 return true;
2940}
2941
2942
2944 const SHAPE_POLY_SET& aSmoothedOutline,
2945 SHAPE_POLY_SET& aFillPolys )
2946{
2947 BOX2I zone_boundingbox = aZone->GetBoundingBox();
2948 SHAPE_POLY_SET clearanceHoles;
2949 long ticker = 0;
2950
2951 auto checkForCancel =
2952 [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool
2953 {
2954 return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled();
2955 };
2956
2957 auto knockoutGraphicItem =
2958 [&]( BOARD_ITEM* aItem )
2959 {
2960 if( aItem->IsKnockout() && aItem->IsOnLayer( aLayer )
2961 && aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
2962 {
2963 addKnockout( aItem, aLayer, 0, true, clearanceHoles );
2964 }
2965 };
2966
2967 for( FOOTPRINT* footprint : m_board->Footprints() )
2968 {
2969 if( checkForCancel( m_progressReporter ) )
2970 return false;
2971
2972 knockoutGraphicItem( &footprint->Reference() );
2973 knockoutGraphicItem( &footprint->Value() );
2974
2975 for( BOARD_ITEM* item : footprint->GraphicalItems() )
2976 knockoutGraphicItem( item );
2977 }
2978
2979 for( BOARD_ITEM* item : m_board->Drawings() )
2980 {
2981 if( checkForCancel( m_progressReporter ) )
2982 return false;
2983
2984 knockoutGraphicItem( item );
2985 }
2986
2987 aFillPolys = aSmoothedOutline;
2988 aFillPolys.BooleanSubtract( clearanceHoles );
2989
2990 SHAPE_POLY_SET keepoutHoles;
2991
2992 auto collectKeepout =
2993 [&]( ZONE* candidate )
2994 {
2995 if( !isZoneFillKeepout( candidate, aLayer, zone_boundingbox ) )
2996 return;
2997
2998 appendZoneOutlineWithoutArcs( candidate, keepoutHoles );
2999 };
3000
3001 bool cancelledKeepoutScan = false;
3002
3003 forEachBoardAndFootprintZone(
3004 m_board,
3005 [&]( ZONE* keepout )
3006 {
3007 if( cancelledKeepoutScan )
3008 return;
3009
3010 if( checkForCancel( m_progressReporter ) )
3011 {
3012 cancelledKeepoutScan = true;
3013 return;
3014 }
3015
3016 collectKeepout( keepout );
3017 } );
3018
3019 if( cancelledKeepoutScan )
3020 return false;
3021
3022 if( keepoutHoles.OutlineCount() > 0 )
3023 aFillPolys.BooleanSubtract( keepoutHoles );
3024
3025 // Features which are min_width should survive pruning; features that are *less* than
3026 // min_width should not. Therefore we subtract epsilon from the min_width when
3027 // deflating/inflating.
3028 int half_min_width = aZone->GetMinThickness() / 2;
3029 int epsilon = pcbIUScale.mmToIU( 0.001 );
3030
3031 aFillPolys.Deflate( half_min_width - epsilon, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, m_maxError );
3032
3033 // Remove the non filled areas due to the hatch pattern
3035 {
3036 SHAPE_POLY_SET noThermalRings; // Non-copper zones have no thermal reliefs
3037
3038 if( !addHatchFillTypeOnZone( aZone, aLayer, aLayer, aFillPolys, noThermalRings ) )
3039 return false;
3040 }
3041
3042 // Re-inflate after pruning of areas that don't meet minimum-width criteria
3043 if( half_min_width - epsilon > epsilon )
3044 aFillPolys.Inflate( half_min_width - epsilon, CORNER_STRATEGY::ROUND_ALL_CORNERS, m_maxError );
3045
3046 aFillPolys.Fracture();
3047 return true;
3048}
3049
3050
3051/*
3052 * Build the filled solid areas data from real outlines (stored in m_Poly)
3053 * The solid areas can be more than one on copper layers, and do not have holes
3054 * ( holes are linked by overlapping segments to the main outline)
3055 */
3057{
3058 SHAPE_POLY_SET* boardOutline = m_brdOutlinesValid ? &m_boardOutline : nullptr;
3059 SHAPE_POLY_SET maxExtents;
3060 SHAPE_POLY_SET smoothedPoly;
3061 PCB_LAYER_ID debugLayer = UNDEFINED_LAYER;
3062
3063 if( m_debugZoneFiller && LSET::InternalCuMask().Contains( aLayer ) )
3064 {
3065 debugLayer = aLayer;
3066 aLayer = F_Cu;
3067 }
3068
3069 if( !aZone->BuildSmoothedPoly( maxExtents, aLayer, boardOutline, &smoothedPoly ) )
3070 return false;
3071
3072 if( m_progressReporter && m_progressReporter->IsCancelled() )
3073 return false;
3074
3075 if( aZone->IsOnCopperLayer() )
3076 {
3077 if( fillCopperZone( aZone, aLayer, debugLayer, smoothedPoly, maxExtents, aFillPolys ) )
3078 aZone->SetNeedRefill( false );
3079 }
3080 else
3081 {
3082 if( fillNonCopperZone( aZone, aLayer, smoothedPoly, aFillPolys ) )
3083 aZone->SetNeedRefill( false );
3084 }
3085
3086 return true;
3087}
3088
3089
3094 const std::vector<BOARD_ITEM*>& aSpokedPadsList,
3095 std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
3096{
3097 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
3098 BOX2I zoneBB = aZone->GetBoundingBox();
3099 DRC_CONSTRAINT constraint;
3100 int zone_half_width = aZone->GetMinThickness() / 2;
3101
3103 zone_half_width = aZone->GetHatchThickness() / 2;
3104
3105 zoneBB.Inflate( std::max( bds.GetBiggestClearanceValue(), aZone->GetLocalClearance().value() ) );
3106
3107 // Is a point on the boundary of the polygon inside or outside?
3108 // The boundary may be off by MaxError
3109 int epsilon = bds.m_MaxError;
3110
3111 for( BOARD_ITEM* item : aSpokedPadsList )
3112 {
3113 // We currently only connect to pads, not pad holes
3114 if( !item->IsOnLayer( aLayer ) )
3115 continue;
3116
3117 int thermalReliefGap = 0;
3118 int spoke_w = 0;
3119 PAD* pad = nullptr;
3120 PCB_VIA* via = nullptr;
3121 bool circular = false;
3122
3123 if( item->Type() == PCB_PAD_T )
3124 {
3125 pad = static_cast<PAD*>( item );
3126 VECTOR2I padSize = pad->GetSize( aLayer );
3127
3128 if( pad->GetShape( aLayer) == PAD_SHAPE::CIRCLE
3129 || ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL && padSize.x == padSize.y ) )
3130 {
3131 circular = true;
3132 }
3133 }
3134 else if( item->Type() == PCB_VIA_T )
3135 {
3136 via = static_cast<PCB_VIA*>( item );
3137 circular = true;
3138 }
3139
3140 // For hatch zones, use proper DRC constraints for thermal gap and spoke width,
3141 // just like solid zones. This ensures consistent thermal relief appearance and
3142 // respects pad-specific thermal spoke settings.
3144 {
3145 if( pad )
3146 {
3148 aZone, aLayer );
3149 thermalReliefGap = constraint.GetValue().Min();
3150
3152 aZone, aLayer );
3153 spoke_w = constraint.GetValue().Opt();
3154
3155 int spoke_max_allowed_w = std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y );
3156 spoke_w = std::clamp( spoke_w, constraint.Value().Min(), constraint.Value().Max() );
3157 spoke_w = std::min( spoke_w, spoke_max_allowed_w );
3158
3159 if( spoke_w < aZone->GetMinThickness() )
3160 continue;
3161 }
3162 else if( via )
3163 {
3165 aZone, aLayer );
3166 thermalReliefGap = constraint.GetValue().Min();
3167
3169 aZone, aLayer );
3170 spoke_w = constraint.GetValue().Opt();
3171
3172 spoke_w = std::min( spoke_w, via->GetWidth( aLayer ) );
3173
3174 if( spoke_w < aZone->GetMinThickness() )
3175 continue;
3176 }
3177 else
3178 {
3179 continue;
3180 }
3181 }
3182 else if( pad )
3183 {
3184 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
3185 thermalReliefGap = constraint.GetValue().Min();
3186
3187 constraint = bds.m_DRCEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, aZone, aLayer );
3188 spoke_w = constraint.GetValue().Opt();
3189
3190 // Spoke width should ideally be smaller than the pad minor axis.
3191 // Otherwise the thermal shape is not really a thermal relief,
3192 // and the algo to count the actual number of spokes can fail
3193 int spoke_max_allowed_w = std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y );
3194
3195 spoke_w = std::clamp( spoke_w, constraint.Value().Min(), constraint.Value().Max() );
3196
3197 // ensure the spoke width is smaller than the pad minor size
3198 spoke_w = std::min( spoke_w, spoke_max_allowed_w );
3199
3200 // Cannot create stubs having a width < zone min thickness
3201 if( spoke_w < aZone->GetMinThickness() )
3202 continue;
3203 }
3204 else
3205 {
3206 // We don't currently support via thermal connections *except* in a hatched zone.
3207 continue;
3208 }
3209
3210 int spoke_half_w = spoke_w / 2;
3211
3212 // Quick test here to possibly save us some work
3213 BOX2I itemBB = item->GetBoundingBox();
3214 itemBB.Inflate( thermalReliefGap + epsilon );
3215
3216 if( !( itemBB.Intersects( zoneBB ) ) )
3217 continue;
3218
3219 bool customSpokes = false;
3220
3221 if( pad && pad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
3222 {
3223 for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) )
3224 {
3225 if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
3226 {
3227 customSpokes = true;
3228 break;
3229 }
3230 }
3231 }
3232
3233 // Thermal spokes consist of square-ended segments from the pad center to points just
3234 // outside the thermal relief. The outside end has an extra center point (which must be
3235 // at idx 3) which is used for testing whether or not the spoke connects to copper in the
3236 // parent zone.
3237
3238 auto buildSpokesFromOrigin =
3239 [&]( const BOX2I& box, EDA_ANGLE angle )
3240 {
3241 VECTOR2I center = box.GetCenter();
3242 VECTOR2I half_size = KiROUND( box.GetWidth() / 2.0, box.GetHeight() / 2.0 );
3243
3244 // Function to find intersection of line with box edge
3245 auto intersectBBox =
3246 [&]( const EDA_ANGLE& spokeAngle, VECTOR2I* spoke_side ) -> VECTOR2I
3247 {
3248 double dx = spokeAngle.Cos();
3249 double dy = spokeAngle.Sin();
3250
3251 // Short-circuit the axis cases because they will be degenerate in the
3252 // intersection test
3253 if( dx == 0 )
3254 {
3255 *spoke_side = VECTOR2I( spoke_half_w, 0 );
3256 return KiROUND( 0.0, dy * half_size.y );
3257 }
3258 else if( dy == 0 )
3259 {
3260 *spoke_side = VECTOR2I( 0, spoke_half_w );
3261 return KiROUND( dx * half_size.x, 0.0 );
3262 }
3263
3264 // We are going to intersect with one side or the other. Whichever
3265 // we hit first is the fraction of the spoke length we keep
3266 double dist_x = half_size.x / std::abs( dx );
3267 double dist_y = half_size.y / std::abs( dy );
3268
3269 if( dist_x < dist_y )
3270 {
3271 *spoke_side = KiROUND( 0.0, spoke_half_w / ( ANGLE_90 - spokeAngle ).Sin() );
3272 return KiROUND( dx * dist_x, dy * dist_x );
3273 }
3274 else
3275 {
3276 *spoke_side = KiROUND( spoke_half_w / spokeAngle.Sin(), 0.0 );
3277 return KiROUND( dx * dist_y, dy * dist_y );
3278 }
3279 };
3280
3281 // Precalculate angles for four cardinal directions
3282 const EDA_ANGLE angles[4] = {
3283 EDA_ANGLE( 0.0, DEGREES_T ) + angle, // Right
3284 EDA_ANGLE( 90.0, DEGREES_T ) + angle, // Up
3285 EDA_ANGLE( 180.0, DEGREES_T ) + angle, // Left
3286 EDA_ANGLE( 270.0, DEGREES_T ) + angle // Down
3287 };
3288
3289 // Generate four spokes in cardinal directions
3290 for( const EDA_ANGLE& spokeAngle : angles )
3291 {
3292 VECTOR2I spoke_side;
3293 VECTOR2I intersection = intersectBBox( spokeAngle, &spoke_side );
3294
3295 SHAPE_LINE_CHAIN spoke;
3296 spoke.Append( center + spoke_side );
3297 spoke.Append( center - spoke_side );
3298 spoke.Append( center + intersection - spoke_side );
3299 spoke.Append( center + intersection ); // test pt
3300 spoke.Append( center + intersection + spoke_side );
3301 spoke.SetClosed( true );
3302 aSpokesList.push_back( std::move( spoke ) );
3303 }
3304 };
3305
3306 if( customSpokes )
3307 {
3308 SHAPE_POLY_SET thermalPoly;
3309 SHAPE_LINE_CHAIN thermalOutline;
3310
3311 pad->TransformShapeToPolygon( thermalPoly, aLayer, thermalReliefGap + epsilon, m_maxError, ERROR_OUTSIDE );
3312
3313 if( thermalPoly.OutlineCount() )
3314 thermalOutline = thermalPoly.Outline( 0 );
3315
3316 SHAPE_LINE_CHAIN padOutline = pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Outline( 0 );
3317
3318 auto trimToOutline = [&]( SEG& aSegment )
3319 {
3320 SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
3321
3322 if( padOutline.Intersect( aSegment, intersections ) )
3323 {
3324 intersections.clear();
3325
3326 // Trim the segment to the thermal outline
3327 if( thermalOutline.Intersect( aSegment, intersections ) )
3328 {
3329 aSegment.B = intersections.front().p;
3330 return true;
3331 }
3332 }
3333 return false;
3334 };
3335
3336 for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) )
3337 {
3338 if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
3339 {
3340 SEG seg( primitive->GetStart(), primitive->GetEnd() );
3341 SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
3342
3343 RotatePoint( seg.A, pad->GetOrientation() );
3344 RotatePoint( seg.B, pad->GetOrientation() );
3345 seg.A += pad->ShapePos( aLayer );
3346 seg.B += pad->ShapePos( aLayer );
3347
3348 // Make sure seg.A is the origin
3349 if( !pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Contains( seg.A ) )
3350 {
3351 // Do not create this spoke if neither point is in the pad.
3352 if( !pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Contains( seg.B ) )
3353 continue;
3354
3355 seg.Reverse();
3356 }
3357
3358 // Trim segment to pad and thermal outline polygon.
3359 // If there is no intersection with the pad, don't create the spoke.
3360 if( trimToOutline( seg ) )
3361 {
3362 VECTOR2I direction = ( seg.B - seg.A ).Resize( spoke_half_w );
3363 VECTOR2I offset = direction.Perpendicular().Resize( spoke_half_w );
3364 // Extend the spoke edges by half the spoke width to capture convex pad shapes
3365 // with a maximum of 45 degrees.
3366 SEG segL( seg.A - direction - offset, seg.B + direction - offset );
3367 SEG segR( seg.A - direction + offset, seg.B + direction + offset );
3368
3369 // Only create this spoke if both edges intersect the pad and thermal outline
3370 if( trimToOutline( segL ) && trimToOutline( segR ) )
3371 {
3372 // Extend the spoke by the minimum thickness for the zone to ensure full
3373 // connection width
3374 direction = direction.Resize( aZone->GetMinThickness() );
3375
3376 SHAPE_LINE_CHAIN spoke;
3377
3378 spoke.Append( seg.A + offset );
3379 spoke.Append( seg.A - offset );
3380
3381 spoke.Append( segL.B + direction );
3382 spoke.Append( seg.B + direction ); // test pt at index 3.
3383 spoke.Append( segR.B + direction );
3384
3385 spoke.SetClosed( true );
3386 aSpokesList.push_back( std::move( spoke ) );
3387 }
3388 }
3389 }
3390 }
3391 }
3392 else
3393 {
3394 EDA_ANGLE thermalSpokeAngle;
3395
3396 // Use pad's thermal spoke angle for both solid and hatch zones.
3397 // This ensures custom thermal spoke templates are respected.
3398 if( pad )
3399 thermalSpokeAngle = pad->GetThermalSpokeAngle();
3400
3401 BOX2I spokesBox;
3402 VECTOR2I position;
3403 EDA_ANGLE orientation;
3404
3405 // Since the bounding-box needs to be correclty rotated we use a dummy pad to keep
3406 // from dirtying the real pad's cached shapes.
3407 if( pad )
3408 {
3409 PAD dummy_pad( *pad );
3410 dummy_pad.SetOrientation( ANGLE_0 );
3411
3412 // Spokes are from center of pad shape, not from hole. So the dummy pad has no shape
3413 // offset and is at position 0,0
3414 dummy_pad.SetPosition( VECTOR2I( 0, 0 ) );
3415 dummy_pad.SetOffset( aLayer, VECTOR2I( 0, 0 ) );
3416
3417 spokesBox = dummy_pad.GetBoundingBox( aLayer );
3418 position = pad->ShapePos( aLayer );
3419 orientation = pad->GetOrientation();
3420 }
3421 else if( via )
3422 {
3423 PCB_VIA dummy_via( *via );
3424 dummy_via.SetPosition( VECTOR2I( 0, 0 ) );
3425
3426 spokesBox = dummy_via.GetBoundingBox( aLayer );
3427 position = via->GetPosition();
3428 }
3429
3430 // Add half the zone mininum width to the inflate amount to account for the fact that
3431 // the deflation procedure will shrink the results by half the half the zone min width.
3432 spokesBox.Inflate( thermalReliefGap + epsilon + zone_half_width );
3433
3434 // Yet another wrinkle: the bounding box for circles will overshoot the mark considerably
3435 // when the spokes are near a 45 degree increment. So we build the spokes at 0 degrees
3436 // and then rotate them to the correct position.
3437 if( circular )
3438 {
3439 buildSpokesFromOrigin( spokesBox, ANGLE_0 );
3440
3441 if( thermalSpokeAngle != ANGLE_0 )
3442 {
3443 // Rotate the last four elements of aspokeslist
3444 for( auto it = aSpokesList.rbegin(); it != aSpokesList.rbegin() + 4; ++it )
3445 it->Rotate( thermalSpokeAngle );
3446 }
3447 }
3448 else
3449 {
3450 buildSpokesFromOrigin( spokesBox, thermalSpokeAngle );
3451 }
3452
3453 auto spokeIter = aSpokesList.rbegin();
3454
3455 for( int ii = 0; ii < 4; ++ii, ++spokeIter )
3456 {
3457 spokeIter->Rotate( orientation );
3458 spokeIter->Move( position );
3459 }
3460 }
3461 }
3462
3463 for( size_t ii = 0; ii < aSpokesList.size(); ++ii )
3464 aSpokesList[ii].GenerateBBoxCache();
3465}
3466
3467
3469 const SHAPE_POLY_SET& aSmoothedOutline,
3470 const std::vector<BOARD_ITEM*>& aThermalConnectionPads,
3471 SHAPE_POLY_SET& aFillPolys,
3472 SHAPE_POLY_SET& aThermalRings )
3473{
3474 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
3475 DRC_CONSTRAINT constraint;
3476
3477 for( BOARD_ITEM* item : aThermalConnectionPads )
3478 {
3479 if( !item->IsOnLayer( aLayer ) )
3480 continue;
3481
3482 PAD* pad = nullptr;
3483 PCB_VIA* via = nullptr;
3484 bool isCircular = false;
3485 int thermalGap = 0;
3486 int spokeWidth = 0;
3487 VECTOR2I position;
3488 int padRadius = 0;
3489
3490 if( item->Type() == PCB_PAD_T )
3491 {
3492 pad = static_cast<PAD*>( item );
3493 VECTOR2I padSize = pad->GetSize( aLayer );
3494 position = pad->ShapePos( aLayer );
3495
3496 isCircular = ( pad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE
3497 || ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL && padSize.x == padSize.y ) );
3498
3499 if( isCircular )
3500 padRadius = std::max( padSize.x, padSize.y ) / 2;
3501
3502 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
3503 thermalGap = constraint.GetValue().Min();
3504
3505 constraint = bds.m_DRCEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, aZone, aLayer );
3506 spokeWidth = constraint.GetValue().Opt();
3507
3508 // Clamp spoke width to pad size
3509 int spokeMaxWidth = std::min( padSize.x, padSize.y );
3510 spokeWidth = std::min( spokeWidth, spokeMaxWidth );
3511 }
3512 else if( item->Type() == PCB_VIA_T )
3513 {
3514 via = static_cast<PCB_VIA*>( item );
3515 position = via->GetPosition();
3516 isCircular = true;
3517 padRadius = via->GetWidth( aLayer ) / 2;
3518
3519 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, via, aZone, aLayer );
3520 thermalGap = constraint.GetValue().Min();
3521
3522 constraint = bds.m_DRCEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, via, aZone, aLayer );
3523 spokeWidth = constraint.GetValue().Opt();
3524
3525 // Clamp spoke width to via diameter
3526 spokeWidth = std::min( spokeWidth, padRadius * 2 );
3527 }
3528 else
3529 {
3530 continue;
3531 }
3532
3533 // Don't create a ring if spoke width is too small
3534 if( spokeWidth < aZone->GetMinThickness() )
3535 continue;
3536
3537 SHAPE_POLY_SET thermalRing;
3538
3539 if( isCircular )
3540 {
3541 // For circular pads/vias: create an arc ring
3542 // Ring inner radius = pad radius + thermal gap
3543 // Ring width = spoke width
3544 int ringInnerRadius = padRadius + thermalGap;
3545 int ringWidth = spokeWidth;
3546
3547 TransformRingToPolygon( thermalRing, position, ringInnerRadius + ringWidth / 2,
3548 ringWidth, m_maxError, ERROR_OUTSIDE );
3549 }
3550 else
3551 {
3552 // For non-circular pads: create ring by inflating pad to outer radius,
3553 // then subtracting pad inflated to inner radius
3554 SHAPE_POLY_SET outerShape;
3555 SHAPE_POLY_SET innerShape;
3556
3557 // Outer ring edge = pad + thermal gap + spoke width
3558 pad->TransformShapeToPolygon( outerShape, aLayer, thermalGap + spokeWidth,
3560
3561 // Inner ring edge = pad + thermal gap (this is already knocked out)
3562 pad->TransformShapeToPolygon( innerShape, aLayer, thermalGap,
3564
3565 thermalRing = outerShape;
3566 thermalRing.BooleanSubtract( innerShape );
3567 }
3568
3569 // Clip the thermal ring to the zone boundary so it doesn't overflow
3570 thermalRing.BooleanIntersection( aSmoothedOutline );
3571
3572 // Add the thermal ring to the fill
3573 aFillPolys.BooleanAdd( thermalRing );
3574
3575 // Also collect thermal rings for hatch hole notching to ensure connectivity
3576 aThermalRings.BooleanAdd( thermalRing );
3577 }
3578}
3579
3580
3582 PCB_LAYER_ID aDebugLayer, SHAPE_POLY_SET& aFillPolys,
3583 const SHAPE_POLY_SET& aThermalRings )
3584{
3585 // Build grid:
3586
3587 // obviously line thickness must be > zone min thickness.
3588 // It can happens if a board file was edited by hand by a python script
3589 // Use 1 micron margin to be *sure* there is no issue in Gerber files
3590 // (Gbr file unit = 1 or 10 nm) due to some truncation in coordinates or calculations
3591 // This margin also avoid problems due to rounding coordinates in next calculations
3592 // that can create incorrect polygons
3593 int thickness = std::max( aZone->GetHatchThickness(),
3594 aZone->GetMinThickness() + pcbIUScale.mmToIU( 0.001 ) );
3595
3596 int gridsize = thickness + aZone->GetHatchGap();
3597 int maxError = m_board->GetDesignSettings().m_MaxError;
3598
3599 SHAPE_POLY_SET filledPolys = aFillPolys.CloneDropTriangulation();
3600 // Use a area that contains the rotated bbox by orientation, and after rotate the result
3601 // by -orientation.
3602 if( !aZone->GetHatchOrientation().IsZero() )
3603 filledPolys.Rotate( - aZone->GetHatchOrientation() );
3604
3605 BOX2I bbox = filledPolys.BBox( 0 );
3606
3607 // Build hole shape
3608 // the hole size is aZone->GetHatchGap(), but because the outline thickness
3609 // is aZone->GetMinThickness(), the hole shape size must be larger
3610 SHAPE_LINE_CHAIN hole_base;
3611 int hole_size = aZone->GetHatchGap() + aZone->GetMinThickness();
3612 VECTOR2I corner( 0, 0 );;
3613 hole_base.Append( corner );
3614 corner.x += hole_size;
3615 hole_base.Append( corner );
3616 corner.y += hole_size;
3617 hole_base.Append( corner );
3618 corner.x = 0;
3619 hole_base.Append( corner );
3620 hole_base.SetClosed( true );
3621
3622 // Calculate minimal area of a grid hole.
3623 // All holes smaller than a threshold will be removed
3624 double minimal_hole_area = hole_base.Area() * aZone->GetHatchHoleMinArea();
3625
3626 // Now convert this hole to a smoothed shape:
3627 if( aZone->GetHatchSmoothingLevel() > 0 )
3628 {
3629 // the actual size of chamfer, or rounded corner radius is the half size
3630 // of the HatchFillTypeGap scaled by aZone->GetHatchSmoothingValue()
3631 // aZone->GetHatchSmoothingValue() = 1.0 is the max value for the chamfer or the
3632 // radius of corner (radius = half size of the hole)
3633 int smooth_value = KiROUND( aZone->GetHatchGap()
3634 * aZone->GetHatchSmoothingValue() / 2 );
3635
3636 // Minimal optimization:
3637 // make smoothing only for reasonable smooth values, to avoid a lot of useless segments
3638 // and if the smooth value is small, use chamfer even if fillet is requested
3639 #define SMOOTH_MIN_VAL_MM 0.02
3640 #define SMOOTH_SMALL_VAL_MM 0.04
3641
3642 if( smooth_value > pcbIUScale.mmToIU( SMOOTH_MIN_VAL_MM ) )
3643 {
3644 SHAPE_POLY_SET smooth_hole;
3645 smooth_hole.AddOutline( hole_base );
3646 int smooth_level = aZone->GetHatchSmoothingLevel();
3647
3648 if( smooth_value < pcbIUScale.mmToIU( SMOOTH_SMALL_VAL_MM ) && smooth_level > 1 )
3649 smooth_level = 1;
3650
3651 // Use a larger smooth_value to compensate the outline tickness
3652 // (chamfer is not visible is smooth value < outline thickess)
3653 smooth_value += aZone->GetMinThickness() / 2;
3654
3655 // smooth_value cannot be bigger than the half size oh the hole:
3656 smooth_value = std::min( smooth_value, aZone->GetHatchGap() / 2 );
3657
3658 // the error to approximate a circle by segments when smoothing corners by a arc
3659 maxError = std::max( maxError * 2, smooth_value / 20 );
3660
3661 switch( smooth_level )
3662 {
3663 case 1:
3664 // Chamfer() uses the distance from a corner to create a end point
3665 // for the chamfer.
3666 hole_base = smooth_hole.Chamfer( smooth_value ).Outline( 0 );
3667 break;
3668
3669 default:
3670 if( aZone->GetHatchSmoothingLevel() > 2 )
3671 maxError /= 2; // Force better smoothing
3672
3673 hole_base = smooth_hole.Fillet( smooth_value, maxError ).Outline( 0 );
3674 break;
3675
3676 case 0:
3677 break;
3678 };
3679 }
3680 }
3681
3682 // Build holes
3683 SHAPE_POLY_SET holes;
3684
3685 auto& defaultOffsets = m_board->GetDesignSettings().m_ZoneLayerProperties;
3686 auto& localOffsets = aZone->LayerProperties();
3687
3688 VECTOR2I offset = defaultOffsets[aLayer].hatching_offset.value_or( VECTOR2I() );
3689
3690 if( localOffsets.contains( aLayer ) && localOffsets.at( aLayer ).hatching_offset.has_value() )
3691 offset = localOffsets.at( aLayer ).hatching_offset.value();
3692
3693 int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize;
3694 int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize;
3695
3696
3697 for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize )
3698 {
3699 for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize )
3700 {
3701 // Generate hole
3702 SHAPE_LINE_CHAIN hole( hole_base );
3703 hole.Move( VECTOR2I( xx, yy ) );
3704
3705 if( !aZone->GetHatchOrientation().IsZero() )
3706 {
3707 hole.Rotate( aZone->GetHatchOrientation() );
3708 }
3709
3710 hole.Move( VECTOR2I( offset.x % gridsize, offset.y % gridsize ) );
3711
3712 holes.AddOutline( hole );
3713 }
3714 }
3715
3716 holes.ClearArcs();
3717
3718 DUMP_POLYS_TO_COPPER_LAYER( holes, In10_Cu, wxT( "hatch-holes" ) );
3719
3720 int deflated_thickness = aZone->GetHatchThickness() - aZone->GetMinThickness();
3721
3722 // Don't let thickness drop below maxError * 2 or it might not get reinflated.
3723 deflated_thickness = std::max( deflated_thickness, maxError * 2 );
3724
3725 // The fill has already been deflated to ensure GetMinThickness() so we just have to
3726 // account for anything beyond that.
3727 SHAPE_POLY_SET deflatedFilledPolys = aFillPolys.CloneDropTriangulation();
3728 deflatedFilledPolys.ClearArcs();
3729 deflatedFilledPolys.Deflate( deflated_thickness, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
3730 holes.BooleanIntersection( deflatedFilledPolys );
3731 DUMP_POLYS_TO_COPPER_LAYER( holes, In11_Cu, wxT( "fill-clipped-hatch-holes" ) );
3732
3733 SHAPE_POLY_SET deflatedOutline = aZone->Outline()->CloneDropTriangulation();
3734 deflatedOutline.ClearArcs();
3735 deflatedOutline.Deflate( aZone->GetMinThickness(), CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
3736 holes.BooleanIntersection( deflatedOutline );
3737 DUMP_POLYS_TO_COPPER_LAYER( holes, In12_Cu, wxT( "outline-clipped-hatch-holes" ) );
3738
3739 // Now filter truncated holes to avoid small holes in pattern
3740 // It happens for holes near the zone outline
3741 for( int ii = 0; ii < holes.OutlineCount(); )
3742 {
3743 double area = holes.Outline( ii ).Area();
3744
3745 if( area < minimal_hole_area ) // The current hole is too small: remove it
3746 holes.DeletePolygon( ii );
3747 else
3748 ++ii;
3749 }
3750
3751 // Drop any holes that completely enclose a thermal ring to ensure thermal reliefs
3752 // stay connected to the hatch webbing. Only drop holes where the thermal ring is
3753 // entirely inside the hole; partial overlaps are kept to preserve the hatch pattern.
3754 if( aThermalRings.OutlineCount() > 0 )
3755 {
3756 BOX2I thermalBBox = aThermalRings.BBox();
3757
3758 // Iterate through holes (backwards since we may delete)
3759 for( int holeIdx = holes.OutlineCount() - 1; holeIdx >= 0; holeIdx-- )
3760 {
3761 const SHAPE_LINE_CHAIN& hole = holes.Outline( holeIdx );
3762 BOX2I holeBBox = hole.BBox();
3763
3764 // Quick rejection: skip if hole bbox doesn't intersect thermal rings bbox
3765 if( !holeBBox.Intersects( thermalBBox ) )
3766 continue;
3767
3768 // Check if ANY thermal ring is completely enclosed by this hole
3769 for( int ringIdx = 0; ringIdx < aThermalRings.OutlineCount(); ringIdx++ )
3770 {
3771 const SHAPE_LINE_CHAIN& ring = aThermalRings.Outline( ringIdx );
3772 BOX2I ringBBox = ring.BBox();
3773 VECTOR2I ringCenter = ringBBox.Centre();
3774
3775 // Quick rejection: hole bbox must contain ring bbox
3776 if( !holeBBox.Contains( ringBBox ) )
3777 continue;
3778
3779 // Check 1: Is the ring center inside the hole?
3780 if( !hole.PointInside( ringCenter ) )
3781 continue;
3782
3783 // Check 2: Is at least one point on the ring inside the hole?
3784 if( ring.PointCount() == 0 || !hole.PointInside( ring.CPoint( 0 ) ) )
3785 continue;
3786
3787 // Check 3: Does the ring outline NOT intersect the hole outline?
3788 // If there's no intersection, the ring is fully enclosed (not touching edges)
3789 SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
3790 ring.Intersect( hole, intersections );
3791
3792 if( intersections.empty() )
3793 {
3794 // This hole completely encloses a ring - drop it
3795 holes.DeletePolygon( holeIdx );
3796 break; // Move to next hole
3797 }
3798 }
3799 }
3800 }
3801
3802 // create grid. Useto
3803 // generate strictly simple polygons needed by Gerber files and Fracture()
3804 aFillPolys.BooleanSubtract( aFillPolys, holes );
3805 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In14_Cu, wxT( "after-hatching" ) );
3806
3807 return true;
3808}
3809
3810
3812{
3813 auto cacheKey = std::make_pair( static_cast<const ZONE*>( aZone ), aLayer );
3814
3815 {
3816 std::lock_guard<std::mutex> lock( m_cacheMutex );
3817 auto it = m_preKnockoutFillCache.find( cacheKey );
3818
3819 if( it == m_preKnockoutFillCache.end() )
3820 return false;
3821
3822 // Restore the cached pre-knockout fill
3823 aFillPolys = it->second;
3824 }
3825
3826 // Subtract the FILLED area of higher-priority zones (with clearance for different nets).
3827 // For same-net zones: subtract the filled area directly.
3828 // For different-net zones: subtract the filled area with DRC-evaluated clearance plus
3829 // extra_margin and m_maxError to match the margins used in the initial fill. Without these
3830 // margins, polygon approximation error can produce fills that violate clearance (issue 23053).
3831 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
3832 int extra_margin = pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_ExtraClearance );
3833 BOX2I zoneBBox = aZone->GetBoundingBox();
3834 zoneBBox.Inflate( m_worstClearance + extra_margin );
3835
3836 auto evalRulesForItems =
3837 [&bds]( DRC_CONSTRAINT_T aConstraint, const BOARD_ITEM* a, const BOARD_ITEM* b,
3838 PCB_LAYER_ID aEvalLayer ) -> int
3839 {
3840 DRC_CONSTRAINT c = bds.m_DRCEngine->EvalRules( aConstraint, a, b, aEvalLayer );
3841
3842 if( c.IsNull() )
3843 return -1;
3844 else
3845 return c.GetValue().Min();
3846 };
3847
3848 bool knockoutsApplied = false;
3849 SHAPE_POLY_SET diffNetKnockouts;
3850 SHAPE_POLY_SET sameNetKnockouts;
3851
3852 auto collectZoneKnockout =
3853 [&]( ZONE* otherZone )
3854 {
3855 if( otherZone == aZone )
3856 return;
3857
3858 if( !otherZone->GetLayerSet().test( aLayer ) )
3859 return;
3860
3861 if( otherZone->IsTeardropArea() && otherZone->SameNet( aZone ) )
3862 return;
3863
3864 if( !otherZone->HigherPriority( aZone ) )
3865 return;
3866
3867 if( !otherZone->GetBoundingBox().Intersects( zoneBBox ) )
3868 return;
3869
3870 if( !otherZone->HasFilledPolysForLayer( aLayer ) )
3871 return;
3872
3873 std::shared_ptr<SHAPE_POLY_SET> otherFill = otherZone->GetFilledPolysList( aLayer );
3874
3875 if( !otherFill || otherFill->OutlineCount() == 0 )
3876 return;
3877
3878 if( otherZone->SameNet( aZone ) )
3879 {
3880 sameNetKnockouts.Append( *otherFill );
3881 }
3882 else
3883 {
3884 int gap = std::max( 0, evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
3885 aZone, otherZone, aLayer ) );
3886
3887 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT, aZone,
3888 otherZone, aLayer ) );
3889
3890 if( gap < 0 )
3891 return;
3892
3893 SHAPE_POLY_SET inflatedFill = *otherFill;
3894 inflatedFill.Inflate( gap + extra_margin + m_maxError,
3896 diffNetKnockouts.Append( inflatedFill );
3897 knockoutsApplied = true;
3898 }
3899 };
3900
3901 forEachBoardAndFootprintZone( m_board, collectZoneKnockout );
3902
3903 // Keepout zones are not collected here because they are already baked into the cached
3904 // pre-knockout fill. They were subtracted before the initial deflate/inflate min-width
3905 // cycle so the cached fill already reflects keepout boundaries (issue 23515).
3906
3907 // Subtract different-net knockouts first, then re-prune min-width
3908 // violations BEFORE subtracting same-net knockouts. The fill still extends into
3909 // overlapping same-net zone areas at this point, which provides a natural buffer
3910 // that prevents the deflate/inflate cycle from creating divots at same-net
3911 // zone boundaries.
3912 if( diffNetKnockouts.OutlineCount() > 0 )
3913 aFillPolys.BooleanSubtract( diffNetKnockouts );
3914
3915 if( knockoutsApplied )
3916 postKnockoutMinWidthPrune( aZone, aFillPolys );
3917
3918 if( sameNetKnockouts.OutlineCount() > 0 )
3919 aFillPolys.BooleanSubtract( sameNetKnockouts );
3920
3921 aFillPolys.Fracture();
3922
3923 return true;
3924}
int index
@ ERROR_OUTSIDE
@ ERROR_INSIDE
bool operator==(const wxAuiPaneInfo &aLhs, const wxAuiPaneInfo &aRhs)
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:125
@ 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:937
const FOOTPRINTS & Footprints() const
Definition board.h:364
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1101
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:198
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:197
ZONE_CONNECTION m_ZoneConnection
Definition drc_rule.h:243
bool IsNull() const
Definition drc_rule.h:192
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:2573
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:1359
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:845
ZONE_LAYER_PROPERTIES & LayerProperties(PCB_LAYER_ID aLayer)
Definition zone.h:150
const BOX2I GetBoundingBox() const override
Definition zone.cpp:650
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:643
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:1439
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:48
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
bool IsInnerCopperLayer(int aLayerId)
Test whether a layer is an inner (In1_Cu to In30_Cu) copper layer.
Definition layer_ids.h:701
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