KiCad PCB EDA Suite
Loading...
Searching...
No Matches
zone_filler.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2014-2017 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Tomasz Włostowski <[email protected]>
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26#include <future>
27#include <core/kicad_algo.h>
28#include <advanced_config.h>
29#include <board.h>
31#include <zone.h>
32#include <footprint.h>
33#include <pad.h>
34#include <pcb_target.h>
35#include <pcb_track.h>
36#include <pcb_text.h>
37#include <pcb_textbox.h>
38#include <pcb_tablecell.h>
39#include <pcb_table.h>
40#include <pcb_dimension.h>
43#include <board_commit.h>
44#include <progress_reporter.h>
48#include <geometry/vertex_set.h>
49#include <kidialog.h>
50#include <thread_pool.h>
51#include <math/util.h> // for KiROUND
52#include "zone_filler.h"
53#include "project.h"
55
56// Helper classes for connect_nearby_polys
58{
59public:
60 RESULTS( int aOutline1, int aOutline2, int aVertex1, int aVertex2 ) :
61 m_outline1( aOutline1 ), m_outline2( aOutline2 ),
62 m_vertex1( aVertex1 ), m_vertex2( aVertex2 )
63 {
64 }
65
66 bool operator<( const RESULTS& aOther ) const
67 {
68 if( m_outline1 != aOther.m_outline1 )
69 return m_outline1 < aOther.m_outline1;
70 if( m_outline2 != aOther.m_outline2 )
71 return m_outline2 < aOther.m_outline2;
72 if( m_vertex1 != aOther.m_vertex1 )
73 return m_vertex1 < aOther.m_vertex1;
74 return m_vertex2 < aOther.m_vertex2;
75 }
76
81};
82
84{
85public:
86 VERTEX_CONNECTOR( const BOX2I& aBBox, const SHAPE_POLY_SET& aPolys, int aDist ) : VERTEX_SET( 0 )
87 {
88 SetBoundingBox( aBBox );
89 VERTEX* tail = nullptr;
90
91 for( int i = 0; i < aPolys.OutlineCount(); i++ )
92 tail = createList( aPolys.Outline( i ), tail, (void*)( intptr_t )( i ) );
93
94 if( tail )
95 tail->updateList();
96 m_dist = aDist;
97 }
98
99 VERTEX* getPoint( VERTEX* aPt ) const
100 {
101 // z-order range for the current point ± limit bounding box
102 const uint32_t maxZ = zOrder( aPt->x + m_dist, aPt->y + m_dist );
103 const uint32_t minZ = zOrder( aPt->x - m_dist, aPt->y - m_dist );
104 const SEG::ecoord limit2 = SEG::Square( m_dist );
105
106 // first look for points in increasing z-order
107 SEG::ecoord min_dist = std::numeric_limits<SEG::ecoord>::max();
108 VERTEX* retval = nullptr;
109
110 auto check_pt = [&]( VERTEX* p )
111 {
112 VECTOR2D diff( p->x - aPt->x, p->y - aPt->y );
113 SEG::ecoord dist2 = diff.SquaredEuclideanNorm();
114
115 if( dist2 > 0 && dist2 < limit2 && dist2 < min_dist && p->isEar( true ) )
116 {
117 min_dist = dist2;
118 retval = p;
119 }
120 };
121
122 VERTEX* p = aPt->nextZ;
123
124 while( p && p->z <= maxZ )
125 {
126 check_pt( p );
127 p = p->nextZ;
128 }
129
130 p = aPt->prevZ;
131
132 while( p && p->z >= minZ )
133 {
134 check_pt( p );
135 p = p->prevZ;
136 }
137
138 return retval;
139 }
140
142 {
143 if( m_vertices.empty() )
144 return;
145
146 VERTEX* p = m_vertices.front().next;
147 std::set<VERTEX*> visited;
148
149 while( p != &m_vertices.front() )
150 {
151 // Skip points that are concave
152 if( !p->isEar() )
153 {
154 p = p->next;
155 continue;
156 }
157
158 VERTEX* q = nullptr;
159
160 if( ( visited.empty() || !visited.contains( p ) ) && ( q = getPoint( p ) ) )
161 {
162 visited.insert( p );
163
164 if( !visited.contains( q ) &&
165 m_results.emplace( (intptr_t) p->GetUserData(), (intptr_t) q->GetUserData(),
166 p->i, q->i ).second )
167 {
168 // We don't want to connect multiple points in the same vicinity, so skip
169 // 2 points before and after each point and match.
170 visited.insert( p->prev );
171 visited.insert( p->prev->prev );
172 visited.insert( p->next );
173 visited.insert( p->next->next );
174
175 visited.insert( q->prev );
176 visited.insert( q->prev->prev );
177 visited.insert( q->next );
178 visited.insert( q->next->next );
179
180 visited.insert( q );
181 }
182 }
183
184 p = p->next;
185 }
186 }
187
188 std::set<RESULTS> GetResults() const
189 {
190 return m_results;
191 }
192
193private:
194 std::set<RESULTS> m_results;
196};
197
198
200 m_board( aBoard ),
201 m_brdOutlinesValid( false ),
202 m_commit( aCommit ),
203 m_progressReporter( nullptr ),
204 m_maxError( ARC_HIGH_DEF ),
205 m_worstClearance( 0 )
206{
207 // To enable add "DebugZoneFiller=1" to kicad_advanced settings file.
209}
210
211
213{
214}
215
216
218{
219 m_progressReporter = aReporter;
220 wxASSERT_MSG( m_commit, wxT( "ZONE_FILLER must have a valid commit to call "
221 "SetProgressReporter" ) );
222}
223
224
235bool ZONE_FILLER::Fill( const std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aParent )
236{
237 std::lock_guard<KISPINLOCK> lock( m_board->GetConnectivity()->GetLock() );
238
239 std::vector<std::pair<ZONE*, PCB_LAYER_ID>> toFill;
240 std::map<std::pair<ZONE*, PCB_LAYER_ID>, HASH_128> oldFillHashes;
241 std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>> isolatedIslandsMap;
242
243 std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_board->GetConnectivity();
244
245 // Rebuild (from scratch, ignoring dirty flags) just in case. This really needs to be reliable.
246 connectivity->ClearRatsnest();
247 connectivity->Build( m_board, m_progressReporter );
248
250
252 {
253 m_progressReporter->Report( aCheck ? _( "Checking zone fills..." )
254 : _( "Building zone fills..." ) );
255 m_progressReporter->SetMaxProgress( aZones.size() );
257 }
258
259 // The board outlines is used to clip solid areas inside the board (when outlines are valid)
262
263 // Update and cache zone bounding boxes and pad effective shapes so that we don't have to
264 // make them thread-safe.
265 //
266 for( ZONE* zone : m_board->Zones() )
267 zone->CacheBoundingBox();
268
269 for( FOOTPRINT* footprint : m_board->Footprints() )
270 {
271 for( PAD* pad : footprint->Pads() )
272 {
273 if( pad->IsDirty() )
274 {
275 pad->BuildEffectiveShapes();
276 pad->BuildEffectivePolygon( ERROR_OUTSIDE );
277 }
278 }
279
280 for( ZONE* zone : footprint->Zones() )
281 zone->CacheBoundingBox();
282
283 // Rules may depend on insideCourtyard() or other expressions
284 footprint->BuildCourtyardCaches();
285 footprint->BuildNetTieCache();
286 }
287
288 LSET boardCuMask = m_board->GetEnabledLayers() & LSET::AllCuMask();
289
290 auto findHighestPriorityZone =
291 [&]( const BOX2I& bbox, PCB_LAYER_ID itemLayer, int netcode,
292 const std::function<bool( const ZONE* )>& testFn ) -> ZONE*
293 {
294 unsigned highestPriority = 0;
295 ZONE* highestPriorityZone = nullptr;
296
297 for( ZONE* zone : m_board->Zones() )
298 {
299 // Rule areas are not filled
300 if( zone->GetIsRuleArea() )
301 continue;
302
303 if( zone->GetAssignedPriority() < highestPriority )
304 continue;
305
306 if( !zone->IsOnLayer( itemLayer ) )
307 continue;
308
309 // Degenerate zones will cause trouble; skip them
310 if( zone->GetNumCorners() <= 2 )
311 continue;
312
313 if( !zone->GetBoundingBox().Intersects( bbox ) )
314 continue;
315
316 if( !testFn( zone ) )
317 continue;
318
319 // Prefer highest priority and matching netcode
320 if( zone->GetAssignedPriority() > highestPriority
321 || zone->GetNetCode() == netcode )
322 {
323 highestPriority = zone->GetAssignedPriority();
324 highestPriorityZone = zone;
325 }
326 }
327
328 return highestPriorityZone;
329 };
330
331 auto isInPourKeepoutArea =
332 [&]( const BOX2I& bbox, PCB_LAYER_ID itemLayer, const VECTOR2I& testPoint ) -> bool
333 {
334 for( ZONE* zone : m_board->Zones() )
335 {
336 if( !zone->GetIsRuleArea() )
337 continue;
338
339 if( !zone->HasKeepoutParametersSet() )
340 continue;
341
342 if( !zone->GetDoNotAllowZoneFills() )
343 continue;
344
345 if( !zone->IsOnLayer( itemLayer ) )
346 continue;
347
348 // Degenerate zones will cause trouble; skip them
349 if( zone->GetNumCorners() <= 2 )
350 continue;
351
352 if( !zone->GetBoundingBox().Intersects( bbox ) )
353 continue;
354
355 if( zone->Outline()->Contains( testPoint ) )
356 return true;
357 }
358
359 return false;
360 };
361
362 // Determine state of conditional via flashing
363 // This is now done completely deterministically prior to filling due to the pathological
364 // case presented in https://gitlab.com/kicad/code/kicad/-/issues/12964.
365 for( PCB_TRACK* track : m_board->Tracks() )
366 {
367 if( track->Type() == PCB_VIA_T )
368 {
369 PCB_VIA* via = static_cast<PCB_VIA*>( track );
370
371 via->ClearZoneLayerOverrides();
372
373 if( !via->GetRemoveUnconnected() )
374 continue;
375
376 BOX2I bbox = via->GetBoundingBox();
377 VECTOR2I center = via->GetPosition();
378 int holeRadius = via->GetDrillValue() / 2 + 1;
379 int netcode = via->GetNetCode();
380 LSET layers = via->GetLayerSet() & boardCuMask;
381
382 // Checking if the via hole touches the zone outline
383 auto viaTestFn =
384 [&]( const ZONE* aZone ) -> bool
385 {
386 return aZone->Outline()->Contains( center, -1, holeRadius );
387 };
388
389 for( PCB_LAYER_ID layer : layers.Seq() )
390 {
391 if( !via->ConditionallyFlashed( layer ) )
392 continue;
393
394 if( isInPourKeepoutArea( bbox, layer, center ) )
395 {
396 via->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
397 }
398 else
399 {
400 ZONE* zone = findHighestPriorityZone( bbox, layer, netcode, viaTestFn );
401
402 if( zone && zone->GetNetCode() == via->GetNetCode() )
403 via->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
404 else
405 via->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
406 }
407 }
408 }
409 }
410
411 // Determine state of conditional pad flashing
412 for( FOOTPRINT* footprint : m_board->Footprints() )
413 {
414 for( PAD* pad : footprint->Pads() )
415 {
416 pad->ClearZoneLayerOverrides();
417
418 if( !pad->GetRemoveUnconnected() )
419 continue;
420
421 BOX2I bbox = pad->GetBoundingBox();
422 VECTOR2I center = pad->GetPosition();
423 int netcode = pad->GetNetCode();
424 LSET layers = pad->GetLayerSet() & boardCuMask;
425
426 auto padTestFn =
427 [&]( const ZONE* aZone ) -> bool
428 {
429 return aZone->Outline()->Contains( center );
430 };
431
432 for( PCB_LAYER_ID layer : layers.Seq() )
433 {
434 if( !pad->ConditionallyFlashed( layer ) )
435 continue;
436
437 if( isInPourKeepoutArea( bbox, layer, center ) )
438 {
439 pad->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
440 }
441 else
442 {
443 ZONE* zone = findHighestPriorityZone( bbox, layer, netcode, padTestFn );
444
445 if( zone && zone->GetNetCode() == pad->GetNetCode() )
446 pad->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
447 else
448 pad->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
449 }
450 }
451 }
452 }
453
454 for( ZONE* zone : aZones )
455 {
456 // Rule areas are not filled
457 if( zone->GetIsRuleArea() )
458 continue;
459
460 // Degenerate zones will cause trouble; skip them
461 if( zone->GetNumCorners() <= 2 )
462 continue;
463
464 if( m_commit )
465 m_commit->Modify( zone );
466
467 // calculate the hash value for filled areas. it will be used later to know if the
468 // current filled areas are up to date
469 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
470 {
471 zone->BuildHashValue( layer );
472 oldFillHashes[ { zone, layer } ] = zone->GetHashValue( layer );
473
474 // Add the zone to the list of zones to test or refill
475 toFill.emplace_back( std::make_pair( zone, layer ) );
476
477 isolatedIslandsMap[ zone ][ layer ] = ISOLATED_ISLANDS();
478 }
479
480 // Remove existing fill first to prevent drawing invalid polygons on some platforms
481 zone->UnFill();
482 }
483
484 auto check_fill_dependency =
485 [&]( ZONE* aZone, PCB_LAYER_ID aLayer, ZONE* aOtherZone ) -> bool
486 {
487 // Check to see if we have to knock-out the filled areas of a higher-priority
488 // zone. If so we have to wait until said zone is filled before we can fill.
489
490 // If the other zone is already filled on the requested layer then we're
491 // good-to-go
492 if( aOtherZone->GetFillFlag( aLayer ) )
493 return false;
494
495 // Even if keepouts exclude copper pours, the exclusion is by outline rather than
496 // filled area, so we're good-to-go here too
497 if( aOtherZone->GetIsRuleArea() )
498 return false;
499
500 // If the other zone is never going to be filled then don't wait for it
501 if( aOtherZone->GetNumCorners() <= 2 )
502 return false;
503
504 // If the zones share no common layers
505 if( !aOtherZone->GetLayerSet().test( aLayer ) )
506 return false;
507
508 if( aZone->HigherPriority( aOtherZone ) )
509 return false;
510
511 // Same-net zones always use outlines to produce determinate results
512 if( aOtherZone->SameNet( aZone ) )
513 return false;
514
515 // A higher priority zone is found: if we intersect and it's not filled yet
516 // then we have to wait.
517 BOX2I inflatedBBox = aZone->GetBoundingBox();
518 inflatedBBox.Inflate( m_worstClearance );
519
520 if( !inflatedBBox.Intersects( aOtherZone->GetBoundingBox() ) )
521 return false;
522
523 return aZone->Outline()->Collide( aOtherZone->Outline(), m_worstClearance );
524 };
525
526 auto fill_lambda =
527 [&]( std::pair<ZONE*, PCB_LAYER_ID> aFillItem ) -> int
528 {
529 PCB_LAYER_ID layer = aFillItem.second;
530 ZONE* zone = aFillItem.first;
531 bool canFill = true;
532
533 // Check for any fill dependencies. If our zone needs to be clipped by
534 // another zone then we can't fill until that zone is filled.
535 for( ZONE* otherZone : aZones )
536 {
537 if( otherZone == zone )
538 continue;
539
540 if( check_fill_dependency( zone, layer, otherZone ) )
541 {
542 canFill = false;
543 break;
544 }
545 }
546
548 return 0;
549
550 if( !canFill )
551 return 0;
552
553 // Now we're ready to fill.
554 {
555 std::unique_lock<std::mutex> zoneLock( zone->GetLock(), std::try_to_lock );
556
557 if( !zoneLock.owns_lock() )
558 return 0;
559
560 SHAPE_POLY_SET fillPolys;
561
562 if( !fillSingleZone( zone, layer, fillPolys ) )
563 return 0;
564
565 zone->SetFilledPolysList( layer, fillPolys );
566 }
567
570
571 return 1;
572 };
573
574 auto tesselate_lambda =
575 [&]( std::pair<ZONE*, PCB_LAYER_ID> aFillItem ) -> int
576 {
578 return 0;
579
580 PCB_LAYER_ID layer = aFillItem.second;
581 ZONE* zone = aFillItem.first;
582
583 {
584 std::unique_lock<std::mutex> zoneLock( zone->GetLock(), std::try_to_lock );
585
586 if( !zoneLock.owns_lock() )
587 return 0;
588
589 zone->CacheTriangulation( layer );
590 zone->SetFillFlag( layer, true );
591 }
592
593 return 1;
594 };
595
596 // Calculate the copper fills (NB: this is multi-threaded)
597 //
598 std::vector<std::pair<std::future<int>, int>> returns;
599 returns.reserve( toFill.size() );
600 size_t finished = 0;
601 bool cancelled = false;
602
604
605 for( const std::pair<ZONE*, PCB_LAYER_ID>& fillItem : toFill )
606 returns.emplace_back( std::make_pair( tp.submit( fill_lambda, fillItem ), 0 ) );
607
608 while( !cancelled && finished != 2 * toFill.size() )
609 {
610 for( size_t ii = 0; ii < returns.size(); ++ii )
611 {
612 auto& ret = returns[ii];
613
614 if( ret.second > 1 )
615 continue;
616
617 std::future_status status = ret.first.wait_for( std::chrono::seconds( 0 ) );
618
619 if( status == std::future_status::ready )
620 {
621 if( ret.first.get() ) // lambda completed
622 {
623 ++finished;
624 ret.second++; // go to next step
625 }
626
627 if( !cancelled )
628 {
629 // Queue the next step (will re-queue the existing step if it didn't complete)
630 if( ret.second == 0 )
631 returns[ii].first = tp.submit( fill_lambda, toFill[ii] );
632 else if( ret.second == 1 )
633 returns[ii].first = tp.submit( tesselate_lambda, toFill[ii] );
634 }
635 }
636 }
637
638 std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
639
640
642 {
644
646 cancelled = true;
647 }
648 }
649
650 // Make sure that all futures have finished.
651 // This can happen when the user cancels the above operation
652 for( auto& ret : returns )
653 {
654 if( ret.first.valid() )
655 {
656 std::future_status status = ret.first.wait_for( std::chrono::seconds( 0 ) );
657
658 while( status != std::future_status::ready )
659 {
662
663 status = ret.first.wait_for( std::chrono::milliseconds( 100 ) );
664 }
665 }
666 }
667
668 // Now update the connectivity to check for isolated copper islands
669 // (NB: FindIsolatedCopperIslands() is multi-threaded)
670 //
672 {
674 return false;
675
677 m_progressReporter->Report( _( "Removing isolated copper islands..." ) );
679 }
680
681 connectivity->SetProgressReporter( m_progressReporter );
682 connectivity->FillIsolatedIslandsMap( isolatedIslandsMap );
683 connectivity->SetProgressReporter( nullptr );
684
686 return false;
687
688 for( ZONE* zone : aZones )
689 {
690 // Keepout zones are not filled
691 if( zone->GetIsRuleArea() )
692 continue;
693
694 zone->SetIsFilled( true );
695 }
696
697 // Now remove isolated copper islands according to the isolated islands strategy assigned
698 // by the user (always, never, below-certain-size).
699 //
700 for( const auto& [ zone, zoneIslands ] : isolatedIslandsMap )
701 {
702 // If *all* the polygons are islands, do not remove any of them
703 bool allIslands = true;
704
705 for( const auto& [ layer, layerIslands ] : zoneIslands )
706 {
707 if( layerIslands.m_IsolatedOutlines.size()
708 != static_cast<size_t>( zone->GetFilledPolysList( layer )->OutlineCount() ) )
709 {
710 allIslands = false;
711 break;
712 }
713 }
714
715 if( allIslands )
716 continue;
717
718 for( const auto& [ layer, layerIslands ] : zoneIslands )
719 {
720 if( m_debugZoneFiller && LSET::InternalCuMask().Contains( layer ) )
721 continue;
722
723 if( layerIslands.m_IsolatedOutlines.empty() )
724 continue;
725
726 std::vector<int> islands = layerIslands.m_IsolatedOutlines;
727
728 // The list of polygons to delete must be explored from last to first in list,
729 // to allow deleting a polygon from list without breaking the remaining of the list
730 std::sort( islands.begin(), islands.end(), std::greater<int>() );
731
732 std::shared_ptr<SHAPE_POLY_SET> poly = zone->GetFilledPolysList( layer );
733 long long int minArea = zone->GetMinIslandArea();
734 ISLAND_REMOVAL_MODE mode = zone->GetIslandRemovalMode();
735
736 for( int idx : islands )
737 {
738 SHAPE_LINE_CHAIN& outline = poly->Outline( idx );
739
740 if( mode == ISLAND_REMOVAL_MODE::ALWAYS )
741 poly->DeletePolygonAndTriangulationData( idx, false );
742 else if ( mode == ISLAND_REMOVAL_MODE::AREA && outline.Area( true ) < minArea )
743 poly->DeletePolygonAndTriangulationData( idx, false );
744 else
745 zone->SetIsIsland( layer, idx );
746 }
747
748 poly->UpdateTriangulationDataHash();
749 zone->CalculateFilledArea();
750
752 return false;
753 }
754 }
755
756 // Now remove islands which are either outside the board edge or fail to meet the minimum
757 // area requirements
758 using island_check_return = std::vector<std::pair<std::shared_ptr<SHAPE_POLY_SET>, int>>;
759
760 std::vector<std::pair<std::shared_ptr<SHAPE_POLY_SET>, double>> polys_to_check;
761
762 // rough estimate to save re-allocation time
763 polys_to_check.reserve( m_board->GetCopperLayerCount() * aZones.size() );
764
765 for( ZONE* zone : aZones )
766 {
767 // Don't check for connections on layers that only exist in the zone but
768 // were disabled in the board
769 BOARD* board = zone->GetBoard();
770 LSET zoneCopperLayers = zone->GetLayerSet() & LSET::AllCuMask() & board->GetEnabledLayers();
771
772 // Min-thickness is the web thickness. On the other hand, a blob min-thickness by
773 // min-thickness is not useful. Since there's no obvious definition of web vs. blob, we
774 // arbitrarily choose "at least 3X the area".
775 double minArea = (double) zone->GetMinThickness() * zone->GetMinThickness() * 3;
776
777 for( PCB_LAYER_ID layer : zoneCopperLayers.Seq() )
778 {
780 continue;
781
782 polys_to_check.emplace_back( zone->GetFilledPolysList( layer ), minArea );
783 }
784 }
785
786 auto island_lambda =
787 [&]( int aStart, int aEnd ) -> island_check_return
788 {
789 island_check_return retval;
790
791 for( int ii = aStart; ii < aEnd && !cancelled; ++ii )
792 {
793 auto [poly, minArea] = polys_to_check[ii];
794
795 for( int jj = poly->OutlineCount() - 1; jj >= 0; jj-- )
796 {
797 SHAPE_POLY_SET island;
798 SHAPE_POLY_SET intersection;
799 const SHAPE_LINE_CHAIN& test_poly = poly->Polygon( jj ).front();
800 double island_area = test_poly.Area();
801
802 if( island_area < minArea )
803 continue;
804
805
806 island.AddOutline( test_poly );
807 intersection.BooleanIntersection( m_boardOutline, island );
808
809 // Nominally, all of these areas should be either inside or outside the
810 // board outline. So this test should be able to just compare areas (if
811 // they are equal, you are inside). But in practice, we sometimes have
812 // slight overlap at the edges, so testing against half-size area acts as
813 // a fail-safe.
814 if( intersection.Area() < island_area / 2.0 )
815 retval.emplace_back( poly, jj );
816 }
817 }
818
819 return retval;
820 };
821
822 auto island_returns = tp.parallelize_loop( 0, polys_to_check.size(), island_lambda );
823 cancelled = false;
824
825 // Allow island removal threads to finish
826 for( size_t ii = 0; ii < island_returns.size(); ++ii )
827 {
828 std::future<island_check_return>& ret = island_returns[ii];
829
830 if( ret.valid() )
831 {
832 std::future_status status = ret.wait_for( std::chrono::seconds( 0 ) );
833
834 while( status != std::future_status::ready )
835 {
837 {
839
841 cancelled = true;
842 }
843
844 status = ret.wait_for( std::chrono::milliseconds( 100 ) );
845 }
846 }
847 }
848
849 if( cancelled )
850 return false;
851
852 for( size_t ii = 0; ii < island_returns.size(); ++ii )
853 {
854 std::future<island_check_return>& ret = island_returns[ii];
855
856 if( ret.valid() )
857 {
858 for( auto& action_item : ret.get() )
859 action_item.first->DeletePolygonAndTriangulationData( action_item.second, true );
860 }
861 }
862
863 for( ZONE* zone : aZones )
864 zone->CalculateFilledArea();
865
866
867 if( aCheck )
868 {
869 bool outOfDate = false;
870
871 for( ZONE* zone : aZones )
872 {
873 // Keepout zones are not filled
874 if( zone->GetIsRuleArea() )
875 continue;
876
877 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
878 {
879 zone->BuildHashValue( layer );
880
881 if( oldFillHashes[ { zone, layer } ] != zone->GetHashValue( layer ) )
882 outOfDate = true;
883 }
884 }
885
886 if( ( m_board->GetProject()
888 {
889 KIDIALOG dlg( aParent, _( "Prototype zone fill enabled. Disable setting and refill?" ),
890 _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
891 dlg.SetOKCancelLabels( _( "Disable and refill" ), _( "Continue without Refill" ) );
892 dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
893
894 if( dlg.ShowModal() == wxID_OK )
895 {
897 }
898 else if( !outOfDate )
899 {
900 return false;
901 }
902 }
903
904 if( outOfDate )
905 {
906 KIDIALOG dlg( aParent, _( "Zone fills are out-of-date. Refill?" ),
907 _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
908 dlg.SetOKCancelLabels( _( "Refill" ), _( "Continue without Refill" ) );
909 dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
910
911 if( dlg.ShowModal() == wxID_CANCEL )
912 return false;
913 }
914 else
915 {
916 // No need to commit something that hasn't changed (and committing will set
917 // the modified flag).
918 return false;
919 }
920 }
921
923 {
925 return false;
926
929 }
930
931 return true;
932}
933
934
939void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles )
940{
941 if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
942 {
943 PAD* pad = static_cast<PAD*>( aItem );
944 SHAPE_POLY_SET poly;
945 pad->TransformShapeToPolygon( poly, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
946
947 // the pad shape in zone can be its convex hull or the shape itself
948 if( pad->GetCustomShapeInZoneOpt() == PADSTACK::CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL )
949 {
950 std::vector<VECTOR2I> convex_hull;
951 BuildConvexHull( convex_hull, poly );
952
953 aHoles.NewOutline();
954
955 for( const VECTOR2I& pt : convex_hull )
956 aHoles.Append( pt );
957 }
958 else
959 aHoles.Append( poly );
960 }
961 else
962 {
963 aItem->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
964 }
965}
966
967
971void ZONE_FILLER::addHoleKnockout( PAD* aPad, int aGap, SHAPE_POLY_SET& aHoles )
972{
973 aPad->TransformHoleToPolygon( aHoles, aGap, m_maxError, ERROR_OUTSIDE );
974}
975
976
977int getHatchFillThermalClearance( const ZONE* aZone, BOARD_ITEM* aItem, PCB_LAYER_ID aLayer )
978{
979 int minorAxis = 0;
980
981 if( aItem->Type() == PCB_PAD_T )
982 {
983 PAD* pad = static_cast<PAD*>( aItem );
984 VECTOR2I padSize = pad->GetSize( aLayer );
985
986 minorAxis = std::min( padSize.x, padSize.y );
987 }
988 else if( aItem->Type() == PCB_VIA_T )
989 {
990 PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
991
992 minorAxis = via->GetWidth( aLayer );
993 }
994
995 return ( aZone->GetHatchGap() - aZone->GetHatchThickness() - minorAxis ) / 2;
996}
997
998
1003void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap,
1004 bool aIgnoreLineWidth, SHAPE_POLY_SET& aHoles )
1005{
1006 switch( aItem->Type() )
1007 {
1008 case PCB_FIELD_T:
1009 case PCB_TEXT_T:
1010 {
1011 PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
1012
1013 if( text->IsVisible() )
1014 {
1015 if( text->IsKnockout() )
1016 {
1017 // Knockout text should only leave holes where the text is, not where the copper fill
1018 // around it would be.
1019 PCB_TEXT textCopy = *text;
1020 textCopy.SetIsKnockout( false );
1021 textCopy.TransformTextToPolySet( aHoles, 0, m_maxError, ERROR_INSIDE );
1022 }
1023 else
1024 {
1025 text->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
1026 }
1027 }
1028
1029 break;
1030 }
1031
1032 case PCB_TEXTBOX_T:
1033 case PCB_TABLE_T:
1034 case PCB_SHAPE_T:
1035 case PCB_TARGET_T:
1036 aItem->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE,
1037 aIgnoreLineWidth );
1038 break;
1039
1040 case PCB_DIM_ALIGNED_T:
1041 case PCB_DIM_LEADER_T:
1042 case PCB_DIM_CENTER_T:
1043 case PCB_DIM_RADIAL_T:
1045 {
1046 PCB_DIMENSION_BASE* dim = static_cast<PCB_DIMENSION_BASE*>( aItem );
1047
1048 dim->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE, false );
1049 dim->PCB_TEXT::TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
1050 break;
1051 }
1052
1053 default:
1054 break;
1055 }
1056}
1057
1058
1064 SHAPE_POLY_SET& aFill,
1065 std::vector<BOARD_ITEM*>& aThermalConnectionPads,
1066 std::vector<PAD*>& aNoConnectionPads )
1067{
1069 ZONE_CONNECTION connection;
1070 DRC_CONSTRAINT constraint;
1071 int padClearance;
1072 std::shared_ptr<SHAPE> padShape;
1073 int holeClearance;
1074 SHAPE_POLY_SET holes;
1075
1076 for( FOOTPRINT* footprint : m_board->Footprints() )
1077 {
1078 for( PAD* pad : footprint->Pads() )
1079 {
1080 if( !pad->IsOnLayer( aLayer ) )
1081 continue;
1082
1083 BOX2I padBBox = pad->GetBoundingBox();
1084 padBBox.Inflate( m_worstClearance );
1085
1086 if( !padBBox.Intersects( aZone->GetBoundingBox() ) )
1087 continue;
1088
1089 bool noConnection = pad->GetNetCode() != aZone->GetNetCode();
1090
1091 if( !aZone->IsTeardropArea() )
1092 {
1093 if( aZone->GetNetCode() == 0
1094 || pad->GetZoneLayerOverride( aLayer ) == ZLO_FORCE_NO_ZONE_CONNECTION )
1095 {
1096 noConnection = true;
1097 }
1098 }
1099
1100 if( noConnection )
1101 {
1102 // collect these for knockout in buildCopperItemClearances()
1103 aNoConnectionPads.push_back( pad );
1104 continue;
1105 }
1106
1107 // We put thermal reliefs on all connected items in a hatch fill zone as a way of
1108 // guaranteeing that they connect to the webbing. (The thermal gap is the hatch
1109 // gap minus the pad/via size, making it impossible for the pad/via to be isolated
1110 // within the center of a hole.)
1111 if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
1112 {
1113 aThermalConnectionPads.push_back( pad );
1114 addKnockout( pad, aLayer, getHatchFillThermalClearance( aZone, pad, aLayer ), holes );
1115 continue;
1116 }
1117
1118 if( aZone->IsTeardropArea() )
1119 {
1120 connection = ZONE_CONNECTION::FULL;
1121 }
1122 else
1123 {
1124 constraint = bds.m_DRCEngine->EvalZoneConnection( pad, aZone, aLayer );
1125 connection = constraint.m_ZoneConnection;
1126 }
1127
1128 if( connection == ZONE_CONNECTION::THERMAL && !pad->CanFlashLayer( aLayer ) )
1129 connection = ZONE_CONNECTION::NONE;
1130
1131 switch( connection )
1132 {
1133 case ZONE_CONNECTION::THERMAL:
1134 padShape = pad->GetEffectiveShape( aLayer, FLASHING::ALWAYS_FLASHED );
1135
1136 if( aFill.Collide( padShape.get(), 0 ) )
1137 {
1138 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad,
1139 aZone, aLayer );
1140 padClearance = constraint.GetValue().Min();
1141
1142 aThermalConnectionPads.push_back( pad );
1143 addKnockout( pad, aLayer, padClearance, holes );
1144 }
1145
1146 break;
1147
1148 case ZONE_CONNECTION::NONE:
1149 constraint = bds.m_DRCEngine->EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, pad,
1150 aZone, aLayer );
1151
1152 if( constraint.GetValue().Min() > aZone->GetLocalClearance().value() )
1153 padClearance = constraint.GetValue().Min();
1154 else
1155 padClearance = aZone->GetLocalClearance().value();
1156
1157 if( pad->FlashLayer( aLayer ) )
1158 {
1159 addKnockout( pad, aLayer, padClearance, holes );
1160 }
1161 else if( pad->GetDrillSize().x > 0 )
1162 {
1163 constraint = bds.m_DRCEngine->EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT,
1164 pad, aZone, aLayer );
1165
1166 if( constraint.GetValue().Min() > padClearance )
1167 holeClearance = constraint.GetValue().Min();
1168 else
1169 holeClearance = padClearance;
1170
1171 pad->TransformHoleToPolygon( holes, holeClearance, m_maxError, ERROR_OUTSIDE );
1172 }
1173
1174 break;
1175
1176 default:
1177 // No knockout
1178 continue;
1179 }
1180 }
1181 }
1182
1183 // We put thermal reliefs on all connected items in a hatch fill zone as a way of guaranteeing
1184 // that they connect to the webbing. (The thermal gap is the hatch gap minus the pad/via size,
1185 // making it impossible for the pad/via to be isolated within the center of a hole.)
1186 if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
1187 {
1188 for( PCB_TRACK* track : m_board->Tracks() )
1189 {
1190 if( track->Type() == PCB_VIA_T )
1191 {
1192 PCB_VIA* via = static_cast<PCB_VIA*>( track );
1193
1194 if( !via->IsOnLayer( aLayer ) )
1195 continue;
1196
1197 BOX2I viaBBox = via->GetBoundingBox();
1198 viaBBox.Inflate( m_worstClearance );
1199
1200 if( !viaBBox.Intersects( aZone->GetBoundingBox() ) )
1201 continue;
1202
1203 bool noConnection = via->GetNetCode() != aZone->GetNetCode();
1204
1205 if( noConnection )
1206 continue;
1207
1208 aThermalConnectionPads.push_back( via );
1209 addKnockout( via, aLayer, getHatchFillThermalClearance( aZone, via, aLayer ), holes );
1210 }
1211 }
1212 }
1213
1214 aFill.BooleanSubtract( holes );
1215}
1216
1217
1223 const std::vector<PAD*>& aNoConnectionPads,
1224 SHAPE_POLY_SET& aHoles )
1225{
1227 long ticker = 0;
1228
1229 auto checkForCancel =
1230 [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool
1231 {
1232 return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled();
1233 };
1234
1235 // A small extra clearance to be sure actual track clearances are not smaller than
1236 // requested clearance due to many approximations in calculations, like arc to segment
1237 // approx, rounding issues, etc.
1238 BOX2I zone_boundingbox = aZone->GetBoundingBox();
1240
1241 // Items outside the zone bounding box are skipped, so it needs to be inflated by the
1242 // largest clearance value found in the netclasses and rules
1243 zone_boundingbox.Inflate( m_worstClearance + extra_margin );
1244
1245 auto evalRulesForItems =
1246 [&bds]( DRC_CONSTRAINT_T aConstraint, const BOARD_ITEM* a, const BOARD_ITEM* b,
1247 PCB_LAYER_ID aEvalLayer ) -> int
1248 {
1249 DRC_CONSTRAINT c = bds.m_DRCEngine->EvalRules( aConstraint, a, b, aEvalLayer );
1250
1251 if( c.IsNull() )
1252 return -1;
1253 else
1254 return c.GetValue().Min();
1255 };
1256
1257 // Add non-connected pad clearances
1258 //
1259 auto knockoutPadClearance =
1260 [&]( PAD* aPad )
1261 {
1262 int init_gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone, aPad, aLayer );
1263 int gap = init_gap;
1264 bool hasHole = aPad->GetDrillSize().x > 0;
1265 bool flashLayer = aPad->FlashLayer( aLayer );
1266 bool platedHole = hasHole && aPad->GetAttribute() == PAD_ATTRIB::PTH;
1267
1268 if( flashLayer || platedHole )
1269 {
1270 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
1271 aZone, aPad, aLayer ) );
1272 }
1273
1274 if( flashLayer && gap >= 0 )
1275 addKnockout( aPad, aLayer, gap + extra_margin, aHoles );
1276
1277 if( hasHole )
1278 {
1279 // NPTH do not need copper clearance gaps to their holes
1280 if( aPad->GetAttribute() == PAD_ATTRIB::NPTH )
1281 gap = init_gap;
1282
1283 gap = std::max( gap, evalRulesForItems( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT,
1284 aZone, aPad, aLayer ) );
1285
1286 gap = std::max( gap, evalRulesForItems( HOLE_CLEARANCE_CONSTRAINT,
1287 aZone, aPad, aLayer ) );
1288
1289 if( gap >= 0 )
1290 addHoleKnockout( aPad, gap + extra_margin, aHoles );
1291 }
1292 };
1293
1294 for( PAD* pad : aNoConnectionPads )
1295 {
1296 if( checkForCancel( m_progressReporter ) )
1297 return;
1298
1299 knockoutPadClearance( pad );
1300 }
1301
1302 // Add non-connected track clearances
1303 //
1304 auto knockoutTrackClearance =
1305 [&]( PCB_TRACK* aTrack )
1306 {
1307 if( aTrack->GetBoundingBox().Intersects( zone_boundingbox ) )
1308 {
1309 bool sameNet = aTrack->GetNetCode() == aZone->GetNetCode();
1310
1311 if( !aZone->IsTeardropArea() && aZone->GetNetCode() == 0 )
1312 sameNet = false;
1313
1314 int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
1315 aZone, aTrack, aLayer );
1316
1317 if( aTrack->Type() == PCB_VIA_T )
1318 {
1319 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
1320
1321 if( via->GetZoneLayerOverride( aLayer ) == ZLO_FORCE_NO_ZONE_CONNECTION )
1322 sameNet = false;
1323 }
1324
1325 if( !sameNet )
1326 {
1327 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
1328 aZone, aTrack, aLayer ) );
1329 }
1330
1331 if( aTrack->Type() == PCB_VIA_T )
1332 {
1333 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
1334
1335 if( via->FlashLayer( aLayer ) && gap > 0 )
1336 {
1337 via->TransformShapeToPolygon( aHoles, aLayer, gap + extra_margin,
1339 }
1340
1341 gap = std::max( gap, evalRulesForItems( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT,
1342 aZone, via, aLayer ) );
1343
1344 if( !sameNet )
1345 {
1346 gap = std::max( gap, evalRulesForItems( HOLE_CLEARANCE_CONSTRAINT,
1347 aZone, via, aLayer ) );
1348 }
1349
1350 if( gap >= 0 )
1351 {
1352 int radius = via->GetDrillValue() / 2;
1353
1354 TransformCircleToPolygon( aHoles, via->GetPosition(),
1355 radius + gap + extra_margin,
1357 }
1358 }
1359 else
1360 {
1361 if( gap >= 0 )
1362 {
1363 aTrack->TransformShapeToPolygon( aHoles, aLayer, gap + extra_margin,
1365 }
1366 }
1367 }
1368 };
1369
1370 for( PCB_TRACK* track : m_board->Tracks() )
1371 {
1372 if( !track->IsOnLayer( aLayer ) )
1373 continue;
1374
1375 if( checkForCancel( m_progressReporter ) )
1376 return;
1377
1378 knockoutTrackClearance( track );
1379 }
1380
1381 // Add graphic item clearances.
1382 //
1383 auto knockoutGraphicClearance =
1384 [&]( BOARD_ITEM* aItem )
1385 {
1386 int shapeNet = -1;
1387
1388 if( aItem->Type() == PCB_SHAPE_T )
1389 shapeNet = static_cast<PCB_SHAPE*>( aItem )->GetNetCode();
1390
1391 bool sameNet = shapeNet == aZone->GetNetCode();
1392
1393 if( !aZone->IsTeardropArea() && aZone->GetNetCode() == 0 )
1394 sameNet = false;
1395
1396 // A item on the Edge_Cuts or Margin is always seen as on any layer:
1397 if( aItem->IsOnLayer( aLayer )
1398 || aItem->IsOnLayer( Edge_Cuts )
1399 || aItem->IsOnLayer( Margin ) )
1400 {
1401 if( aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
1402 {
1403 bool ignoreLineWidths = false;
1404 int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
1405 aZone, aItem, aLayer );
1406
1407 if( aItem->IsOnLayer( aLayer ) && !sameNet )
1408 {
1409 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
1410 aZone, aItem, aLayer ) );
1411 }
1412 else if( aItem->IsOnLayer( Edge_Cuts ) )
1413 {
1414 gap = std::max( gap, evalRulesForItems( EDGE_CLEARANCE_CONSTRAINT,
1415 aZone, aItem, aLayer ) );
1416 ignoreLineWidths = true;
1417 }
1418 else if( aItem->IsOnLayer( Margin ) )
1419 {
1420 gap = std::max( gap, evalRulesForItems( EDGE_CLEARANCE_CONSTRAINT,
1421 aZone, aItem, aLayer ) );
1422 }
1423
1424 if( gap >= 0 )
1425 {
1426 gap += extra_margin;
1427 addKnockout( aItem, aLayer, gap, ignoreLineWidths, aHoles );
1428 }
1429 }
1430 }
1431 };
1432
1433 auto knockoutCourtyardClearance =
1434 [&]( FOOTPRINT* aFootprint )
1435 {
1436 if( aFootprint->GetBoundingBox().Intersects( zone_boundingbox ) )
1437 {
1438 int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone,
1439 aFootprint, aLayer );
1440
1441 if( gap == 0 )
1442 {
1443 aHoles.Append( aFootprint->GetCourtyard( aLayer ) );
1444 }
1445 else if( gap > 0 )
1446 {
1447 SHAPE_POLY_SET hole = aFootprint->GetCourtyard( aLayer );
1448 hole.Inflate( gap, CORNER_STRATEGY::ROUND_ALL_CORNERS, m_maxError );
1449 aHoles.Append( hole );
1450 }
1451 }
1452 };
1453
1454 for( FOOTPRINT* footprint : m_board->Footprints() )
1455 {
1456 knockoutCourtyardClearance( footprint );
1457 knockoutGraphicClearance( &footprint->Reference() );
1458 knockoutGraphicClearance( &footprint->Value() );
1459
1460 std::set<PAD*> allowedNetTiePads;
1461
1462 // Don't knock out holes for graphic items which implement a net-tie to the zone's net
1463 // on the layer being filled.
1464 if( footprint->IsNetTie() )
1465 {
1466 for( PAD* pad : footprint->Pads() )
1467 {
1468 bool sameNet = pad->GetNetCode() == aZone->GetNetCode();
1469
1470 if( !aZone->IsTeardropArea() && aZone->GetNetCode() == 0 )
1471 sameNet = false;
1472
1473 if( sameNet )
1474 {
1475 if( pad->IsOnLayer( aLayer ) )
1476 allowedNetTiePads.insert( pad );
1477
1478 for( PAD* other : footprint->GetNetTiePads( pad ) )
1479 {
1480 if( other->IsOnLayer( aLayer ) )
1481 allowedNetTiePads.insert( other );
1482 }
1483 }
1484 }
1485 }
1486
1487 for( BOARD_ITEM* item : footprint->GraphicalItems() )
1488 {
1489 if( checkForCancel( m_progressReporter ) )
1490 return;
1491
1492 BOX2I itemBBox = item->GetBoundingBox();
1493
1494 if( !zone_boundingbox.Intersects( itemBBox ) )
1495 continue;
1496
1497 bool skipItem = false;
1498
1499 if( item->IsOnLayer( aLayer ) )
1500 {
1501 std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape();
1502
1503 for( PAD* pad : allowedNetTiePads )
1504 {
1505 if( pad->GetBoundingBox().Intersects( itemBBox )
1506 && pad->GetEffectiveShape( aLayer )->Collide( itemShape.get() ) )
1507 {
1508 skipItem = true;
1509 break;
1510 }
1511 }
1512 }
1513
1514 if( !skipItem )
1515 knockoutGraphicClearance( item );
1516 }
1517 }
1518
1519 for( BOARD_ITEM* item : m_board->Drawings() )
1520 {
1521 if( checkForCancel( m_progressReporter ) )
1522 return;
1523
1524 knockoutGraphicClearance( item );
1525 }
1526
1527 // Add non-connected zone clearances
1528 //
1529 auto knockoutZoneClearance =
1530 [&]( ZONE* aKnockout )
1531 {
1532 // If the zones share no common layers
1533 if( !aKnockout->GetLayerSet().test( aLayer ) )
1534 return;
1535
1536 if( aKnockout->GetBoundingBox().Intersects( zone_boundingbox ) )
1537 {
1538 if( aKnockout->GetIsRuleArea() )
1539 {
1540 // Keepouts use outline with no clearance
1541 aKnockout->TransformSmoothedOutlineToPolygon( aHoles, 0, m_maxError,
1542 ERROR_OUTSIDE, nullptr );
1543 }
1544 else
1545 {
1546 int gap = std::max( 0, evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
1547 aZone, aKnockout, aLayer ) );
1548
1549 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
1550 aZone, aKnockout, aLayer ) );
1551
1552 SHAPE_POLY_SET poly;
1553 aKnockout->TransformShapeToPolygon( poly, aLayer, gap + extra_margin,
1555 aHoles.Append( poly );
1556 }
1557 }
1558 };
1559
1560 for( ZONE* otherZone : m_board->Zones() )
1561 {
1562 if( checkForCancel( m_progressReporter ) )
1563 return;
1564
1565 // Negative clearance permits zones to short
1566 if( evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, otherZone, aLayer ) < 0 )
1567 continue;
1568
1569 if( otherZone->GetIsRuleArea() )
1570 {
1571 if( otherZone->GetDoNotAllowZoneFills() && !aZone->IsTeardropArea() )
1572 knockoutZoneClearance( otherZone );
1573 }
1574 else if( otherZone->HigherPriority( aZone ) )
1575 {
1576 if( !otherZone->SameNet( aZone ) )
1577 knockoutZoneClearance( otherZone );
1578 }
1579 }
1580
1581 for( FOOTPRINT* footprint : m_board->Footprints() )
1582 {
1583 for( ZONE* otherZone : footprint->Zones() )
1584 {
1585 if( checkForCancel( m_progressReporter ) )
1586 return;
1587
1588 if( otherZone->GetIsRuleArea() )
1589 {
1590 if( otherZone->GetDoNotAllowZoneFills() && !aZone->IsTeardropArea() )
1591 knockoutZoneClearance( otherZone );
1592 }
1593 else if( otherZone->HigherPriority( aZone ) )
1594 {
1595 if( !otherZone->SameNet( aZone ) )
1596 knockoutZoneClearance( otherZone );
1597 }
1598 }
1599 }
1600
1601 aHoles.Simplify();
1602}
1603
1604
1610 SHAPE_POLY_SET& aRawFill )
1611{
1612 BOX2I zoneBBox = aZone->GetBoundingBox();
1613
1614 auto knockoutZoneOutline =
1615 [&]( ZONE* aKnockout )
1616 {
1617 // If the zones share no common layers
1618 if( !aKnockout->GetLayerSet().test( aLayer ) )
1619 return;
1620
1621 if( aKnockout->GetBoundingBox().Intersects( zoneBBox ) )
1622 {
1623 // Processing of arc shapes in zones is not yet supported because Clipper
1624 // can't do boolean operations on them. The poly outline must be converted to
1625 // segments first.
1626 SHAPE_POLY_SET outline = aKnockout->Outline()->CloneDropTriangulation();
1627 outline.ClearArcs();
1628
1629 aRawFill.BooleanSubtract( outline );
1630 }
1631 };
1632
1633 for( ZONE* otherZone : m_board->Zones() )
1634 {
1635 // Don't use the `HigherPriority()` check here because we _only_ want to knock out zones
1636 // with explicitly higher priorities, not those with equal priorities
1637 if( otherZone->SameNet( aZone )
1638 && otherZone->GetAssignedPriority() > aZone->GetAssignedPriority() )
1639 {
1640 // Do not remove teardrop area: it is not useful and not good
1641 if( !otherZone->IsTeardropArea() )
1642 knockoutZoneOutline( otherZone );
1643 }
1644 }
1645
1646 for( FOOTPRINT* footprint : m_board->Footprints() )
1647 {
1648 for( ZONE* otherZone : footprint->Zones() )
1649 {
1650 if( otherZone->SameNet( aZone ) && otherZone->HigherPriority( aZone ) )
1651 {
1652 // Do not remove teardrop area: it is not useful and not good
1653 if( !otherZone->IsTeardropArea() )
1654 knockoutZoneOutline( otherZone );
1655 }
1656 }
1657 }
1658}
1659
1660
1661void ZONE_FILLER::connect_nearby_polys( SHAPE_POLY_SET& aPolys, double aDistance )
1662{
1663 if( aPolys.OutlineCount() < 1 )
1664 return;
1665
1666 VERTEX_CONNECTOR vs( aPolys.BBoxFromCaches(), aPolys, aDistance );
1667
1668 vs.FindResults();
1669
1670 // This cannot be a reference because we need to do the comparison below while
1671 // changing the values
1672 std::map<int, std::vector<std::pair<int, VECTOR2I>>> insertion_points;
1673
1674 for( const RESULTS& result : vs.GetResults() )
1675 {
1676 SHAPE_LINE_CHAIN& line1 = aPolys.Outline( result.m_outline1 );
1677 SHAPE_LINE_CHAIN& line2 = aPolys.Outline( result.m_outline2 );
1678
1679 VECTOR2I pt1 = line1.CPoint( result.m_vertex1 );
1680 VECTOR2I pt2 = line2.CPoint( result.m_vertex2 );
1681
1682 // We want to insert the existing point first so that we can place the new point
1683 // between the two points at the same location.
1684 insertion_points[result.m_outline1].push_back( { result.m_vertex1, pt1 } );
1685 insertion_points[result.m_outline1].push_back( { result.m_vertex1, pt2 } );
1686 }
1687
1688 for( auto& [outline, vertices] : insertion_points )
1689 {
1690 SHAPE_LINE_CHAIN& line = aPolys.Outline( outline );
1691
1692 // Stable sort here because we want to make sure that we are inserting pt1 first and
1693 // pt2 second but still sorting the rest of the indices from highest to lowest.
1694 // This allows us to insert into the existing polygon without modifying the future
1695 // insertion points.
1696 std::stable_sort( vertices.begin(), vertices.end(),
1697 []( const std::pair<int, VECTOR2I>& a, const std::pair<int, VECTOR2I>& b )
1698 {
1699 return a.first > b.first;
1700 } );
1701
1702 for( const auto& [vertex, pt] : vertices )
1703 line.Insert( vertex + 1, pt ); // +1 here because we want to insert after the existing point
1704 }
1705}
1706
1707
1708#define DUMP_POLYS_TO_COPPER_LAYER( a, b, c ) \
1709 { if( m_debugZoneFiller && aDebugLayer == b ) \
1710 { \
1711 m_board->SetLayerName( b, c ); \
1712 SHAPE_POLY_SET d = a; \
1713 d.Fracture(); \
1714 aFillPolys = d; \
1715 return false; \
1716 } \
1717 }
1718
1719
1720/*
1721 * Note that aSmoothedOutline is larger than the zone where it intersects with other, same-net
1722 * zones. This is to prevent the re-inflation post min-width trimming from createing divots
1723 * between adjacent zones. The final aMaxExtents trimming will remove these areas from the final
1724 * fill.
1725 */
1726bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer,
1727 const SHAPE_POLY_SET& aSmoothedOutline,
1728 const SHAPE_POLY_SET& aMaxExtents, SHAPE_POLY_SET& aFillPolys )
1729{
1731
1732 // Features which are min_width should survive pruning; features that are *less* than
1733 // min_width should not. Therefore we subtract epsilon from the min_width when
1734 // deflating/inflating.
1735 int half_min_width = aZone->GetMinThickness() / 2;
1736 int epsilon = pcbIUScale.mmToIU( 0.001 );
1737
1738 // Solid polygons are deflated and inflated during calculations. Deflating doesn't cause
1739 // issues, but inflate is tricky as it can create excessively long and narrow spikes for
1740 // acute angles.
1741 // ALLOW_ACUTE_CORNERS cannot be used due to the spike problem.
1742 // CHAMFER_ACUTE_CORNERS is tempting, but can still produce spikes in some unusual
1743 // circumstances (https://gitlab.com/kicad/code/kicad/-/issues/5581).
1744 // It's unclear if ROUND_ACUTE_CORNERS would have the same issues, but is currently avoided
1745 // as a "less-safe" option.
1746 // ROUND_ALL_CORNERS produces the uniformly nicest shapes, but also a lot of segments.
1747 // CHAMFER_ALL_CORNERS improves the segment count.
1748 CORNER_STRATEGY fastCornerStrategy = CORNER_STRATEGY::CHAMFER_ALL_CORNERS;
1749 CORNER_STRATEGY cornerStrategy = CORNER_STRATEGY::ROUND_ALL_CORNERS;
1750
1751 std::vector<BOARD_ITEM*> thermalConnectionPads;
1752 std::vector<PAD*> noConnectionPads;
1753 std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
1754 SHAPE_POLY_SET clearanceHoles;
1755
1756 aFillPolys = aSmoothedOutline;
1757 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In1_Cu, wxT( "smoothed-outline" ) );
1758
1760 return false;
1761
1762 /* -------------------------------------------------------------------------------------
1763 * Knockout thermal reliefs.
1764 */
1765
1766 knockoutThermalReliefs( aZone, aLayer, aFillPolys, thermalConnectionPads, noConnectionPads );
1767 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In2_Cu, wxT( "minus-thermal-reliefs" ) );
1768
1770 return false;
1771
1772 /* -------------------------------------------------------------------------------------
1773 * Knockout electrical clearances.
1774 */
1775
1776 buildCopperItemClearances( aZone, aLayer, noConnectionPads, clearanceHoles );
1777 DUMP_POLYS_TO_COPPER_LAYER( clearanceHoles, In3_Cu, wxT( "clearance-holes" ) );
1778
1780 return false;
1781
1782 /* -------------------------------------------------------------------------------------
1783 * Add thermal relief spokes.
1784 */
1785
1786 buildThermalSpokes( aZone, aLayer, thermalConnectionPads, thermalSpokes );
1787
1789 return false;
1790
1791 // Create a temporary zone that we can hit-test spoke-ends against. It's only temporary
1792 // because the "real" subtract-clearance-holes has to be done after the spokes are added.
1793 static const bool USE_BBOX_CACHES = true;
1794 SHAPE_POLY_SET testAreas = aFillPolys.CloneDropTriangulation();
1795 testAreas.BooleanSubtract( clearanceHoles );
1796 DUMP_POLYS_TO_COPPER_LAYER( testAreas, In4_Cu, wxT( "minus-clearance-holes" ) );
1797
1798 // Prune features that don't meet minimum-width criteria
1799 if( half_min_width - epsilon > epsilon )
1800 {
1801 testAreas.Deflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
1802 DUMP_POLYS_TO_COPPER_LAYER( testAreas, In5_Cu, wxT( "spoke-test-deflated" ) );
1803
1804 testAreas.Inflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
1805 DUMP_POLYS_TO_COPPER_LAYER( testAreas, In6_Cu, wxT( "spoke-test-reinflated" ) );
1806 }
1807
1809 return false;
1810
1811 // Spoke-end-testing is hugely expensive so we generate cached bounding-boxes to speed
1812 // things up a bit.
1813 testAreas.BuildBBoxCaches();
1814 int interval = 0;
1815
1816 SHAPE_POLY_SET debugSpokes;
1817
1818 for( const SHAPE_LINE_CHAIN& spoke : thermalSpokes )
1819 {
1820 const VECTOR2I& testPt = spoke.CPoint( 3 );
1821
1822 // Hit-test against zone body
1823 if( testAreas.Contains( testPt, -1, 1, USE_BBOX_CACHES ) )
1824 {
1825 if( m_debugZoneFiller )
1826 debugSpokes.AddOutline( spoke );
1827
1828 aFillPolys.AddOutline( spoke );
1829 continue;
1830 }
1831
1832 if( interval++ > 400 )
1833 {
1835 return false;
1836
1837 interval = 0;
1838 }
1839
1840 // Hit-test against other spokes
1841 for( const SHAPE_LINE_CHAIN& other : thermalSpokes )
1842 {
1843 // Hit test in both directions to avoid interactions with round-off errors.
1844 // (See https://gitlab.com/kicad/code/kicad/-/issues/13316.)
1845 if( &other != &spoke
1846 && other.PointInside( testPt, 1, USE_BBOX_CACHES )
1847 && spoke.PointInside( other.CPoint( 3 ), 1, USE_BBOX_CACHES ) )
1848 {
1849 if( m_debugZoneFiller )
1850 debugSpokes.AddOutline( spoke );
1851
1852 aFillPolys.AddOutline( spoke );
1853 break;
1854 }
1855 }
1856 }
1857
1858 DUMP_POLYS_TO_COPPER_LAYER( debugSpokes, In7_Cu, wxT( "spokes" ) );
1859
1861 return false;
1862
1863 aFillPolys.BooleanSubtract( clearanceHoles );
1864 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In8_Cu, wxT( "after-spoke-trimming" ) );
1865
1866 /* -------------------------------------------------------------------------------------
1867 * Prune features that don't meet minimum-width criteria
1868 */
1869
1870 if( half_min_width - epsilon > epsilon )
1871 aFillPolys.Deflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
1872
1873 // Min-thickness is the web thickness. On the other hand, a blob min-thickness by
1874 // min-thickness is not useful. Since there's no obvious definition of web vs. blob, we
1875 // arbitrarily choose "at least 2X min-thickness on one axis". (Since we're doing this
1876 // during the deflated state, that means we test for "at least min-thickness".)
1877 for( int ii = aFillPolys.OutlineCount() - 1; ii >= 0; ii-- )
1878 {
1879 std::vector<SHAPE_LINE_CHAIN>& island = aFillPolys.Polygon( ii );
1880 BOX2I islandExtents;
1881
1882 for( const VECTOR2I& pt : island.front().CPoints() )
1883 {
1884 islandExtents.Merge( pt );
1885
1886 if( islandExtents.GetSizeMax() > aZone->GetMinThickness() )
1887 break;
1888 }
1889
1890 if( islandExtents.GetSizeMax() < aZone->GetMinThickness() )
1891 aFillPolys.DeletePolygon( ii );
1892 }
1893
1894 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In9_Cu, wxT( "deflated" ) );
1895
1897 return false;
1898
1899 /* -------------------------------------------------------------------------------------
1900 * Process the hatch pattern (note that we do this while deflated)
1901 */
1902
1903 if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN
1904 && ( !m_board->GetProject()
1906 {
1907 if( !addHatchFillTypeOnZone( aZone, aLayer, aDebugLayer, aFillPolys ) )
1908 return false;
1909 }
1910 else
1911 {
1912 /* ---------------------------------------------------------------------------------
1913 * Connect nearby polygons with zero-width lines in order to ensure correct
1914 * re-inflation.
1915 */
1916 aFillPolys.Fracture();
1917 connect_nearby_polys( aFillPolys, aZone->GetMinThickness() );
1918
1919 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In10_Cu, wxT( "connected-nearby-polys" ) );
1920 }
1921
1923 return false;
1924
1925 /* -------------------------------------------------------------------------------------
1926 * Finish minimum-width pruning by re-inflating
1927 */
1928
1929 if( half_min_width - epsilon > epsilon )
1930 aFillPolys.Inflate( half_min_width - epsilon, cornerStrategy, m_maxError, true );
1931
1932 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In15_Cu, wxT( "after-reinflating" ) );
1933
1934 /* -------------------------------------------------------------------------------------
1935 * Ensure additive changes (thermal stubs and inflating acute corners) do not add copper
1936 * outside the zone boundary, inside the clearance holes, or between otherwise isolated
1937 * islands
1938 */
1939
1940 for( BOARD_ITEM* item : thermalConnectionPads )
1941 {
1942 if( item->Type() == PCB_PAD_T )
1943 addHoleKnockout( static_cast<PAD*>( item ), 0, clearanceHoles );
1944 }
1945
1946 aFillPolys.BooleanIntersection( aMaxExtents );
1947 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In16_Cu, wxT( "after-trim-to-outline" ) );
1948 aFillPolys.BooleanSubtract( clearanceHoles );
1949 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In17_Cu, wxT( "after-trim-to-clearance-holes" ) );
1950
1951 /* -------------------------------------------------------------------------------------
1952 * Lastly give any same-net but higher-priority zones control over their own area.
1953 */
1954
1955 subtractHigherPriorityZones( aZone, aLayer, aFillPolys );
1956 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In18_Cu, wxT( "minus-higher-priority-zones" ) );
1957
1958 aFillPolys.Fracture();
1959 return true;
1960}
1961
1962
1964 const SHAPE_POLY_SET& aSmoothedOutline,
1965 SHAPE_POLY_SET& aFillPolys )
1966{
1967 BOX2I zone_boundingbox = aZone->GetBoundingBox();
1968 SHAPE_POLY_SET clearanceHoles;
1969 long ticker = 0;
1970
1971 auto checkForCancel =
1972 [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool
1973 {
1974 return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled();
1975 };
1976
1977 auto knockoutGraphicItem =
1978 [&]( BOARD_ITEM* aItem )
1979 {
1980 if( aItem->IsKnockout() && aItem->IsOnLayer( aLayer )
1981 && aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
1982 {
1983 addKnockout( aItem, aLayer, 0, true, clearanceHoles );
1984 }
1985 };
1986
1987 for( FOOTPRINT* footprint : m_board->Footprints() )
1988 {
1989 if( checkForCancel( m_progressReporter ) )
1990 return false;
1991
1992 knockoutGraphicItem( &footprint->Reference() );
1993 knockoutGraphicItem( &footprint->Value() );
1994
1995 for( BOARD_ITEM* item : footprint->GraphicalItems() )
1996 knockoutGraphicItem( item );
1997 }
1998
1999 for( BOARD_ITEM* item : m_board->Drawings() )
2000 {
2001 if( checkForCancel( m_progressReporter ) )
2002 return false;
2003
2004 knockoutGraphicItem( item );
2005 }
2006
2007 aFillPolys = aSmoothedOutline;
2008 aFillPolys.BooleanSubtract( clearanceHoles );
2009
2010 auto subtractKeepout =
2011 [&]( ZONE* candidate )
2012 {
2013 if( !candidate->GetIsRuleArea() )
2014 return;
2015
2016 if( !candidate->HasKeepoutParametersSet() )
2017 return;
2018
2019 if( candidate->GetDoNotAllowZoneFills() && candidate->IsOnLayer( aLayer ) )
2020 {
2021 if( candidate->GetBoundingBox().Intersects( zone_boundingbox ) )
2022 {
2023 if( candidate->Outline()->ArcCount() == 0 )
2024 {
2025 aFillPolys.BooleanSubtract( *candidate->Outline() );
2026 }
2027 else
2028 {
2029 SHAPE_POLY_SET keepoutOutline( *candidate->Outline() );
2030 keepoutOutline.ClearArcs();
2031 aFillPolys.BooleanSubtract( keepoutOutline );
2032 }
2033 }
2034 }
2035 };
2036
2037 for( ZONE* keepout : m_board->Zones() )
2038 {
2039 if( checkForCancel( m_progressReporter ) )
2040 return false;
2041
2042 subtractKeepout( keepout );
2043 }
2044
2045 for( FOOTPRINT* footprint : m_board->Footprints() )
2046 {
2047 if( checkForCancel( m_progressReporter ) )
2048 return false;
2049
2050 for( ZONE* keepout : footprint->Zones() )
2051 subtractKeepout( keepout );
2052 }
2053
2054 // Features which are min_width should survive pruning; features that are *less* than
2055 // min_width should not. Therefore we subtract epsilon from the min_width when
2056 // deflating/inflating.
2057 int half_min_width = aZone->GetMinThickness() / 2;
2058 int epsilon = pcbIUScale.mmToIU( 0.001 );
2059
2060 aFillPolys.Deflate( half_min_width - epsilon, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, m_maxError );
2061
2062 // Remove the non filled areas due to the hatch pattern
2063 if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
2064 {
2065 if( !addHatchFillTypeOnZone( aZone, aLayer, aLayer, aFillPolys ) )
2066 return false;
2067 }
2068
2069 // Re-inflate after pruning of areas that don't meet minimum-width criteria
2070 if( half_min_width - epsilon > epsilon )
2071 aFillPolys.Inflate( half_min_width - epsilon, CORNER_STRATEGY::ROUND_ALL_CORNERS, m_maxError );
2072
2073 aFillPolys.Fracture();
2074 return true;
2075}
2076
2077
2078/*
2079 * Build the filled solid areas data from real outlines (stored in m_Poly)
2080 * The solid areas can be more than one on copper layers, and do not have holes
2081 * ( holes are linked by overlapping segments to the main outline)
2082 */
2084{
2085 SHAPE_POLY_SET* boardOutline = m_brdOutlinesValid ? &m_boardOutline : nullptr;
2086 SHAPE_POLY_SET maxExtents;
2087 SHAPE_POLY_SET smoothedPoly;
2088 PCB_LAYER_ID debugLayer = UNDEFINED_LAYER;
2089
2090 if( m_debugZoneFiller && LSET::InternalCuMask().Contains( aLayer ) )
2091 {
2092 debugLayer = aLayer;
2093 aLayer = F_Cu;
2094 }
2095
2096 if( !aZone->BuildSmoothedPoly( maxExtents, aLayer, boardOutline, &smoothedPoly ) )
2097 return false;
2098
2100 return false;
2101
2102 if( aZone->IsOnCopperLayer() )
2103 {
2104 if( fillCopperZone( aZone, aLayer, debugLayer, smoothedPoly, maxExtents, aFillPolys ) )
2105 aZone->SetNeedRefill( false );
2106 }
2107 else
2108 {
2109 if( fillNonCopperZone( aZone, aLayer, smoothedPoly, aFillPolys ) )
2110 aZone->SetNeedRefill( false );
2111 }
2112
2113 return true;
2114}
2115
2116
2121 const std::vector<BOARD_ITEM*>& aSpokedPadsList,
2122 std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
2123{
2125 BOX2I zoneBB = aZone->GetBoundingBox();
2126 DRC_CONSTRAINT constraint;
2127 int zone_half_width = aZone->GetMinThickness() / 2;
2128
2129 if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
2130 zone_half_width = aZone->GetHatchThickness() / 2;
2131
2132 zoneBB.Inflate( std::max( bds.GetBiggestClearanceValue(), aZone->GetLocalClearance().value() ) );
2133
2134 // Is a point on the boundary of the polygon inside or outside?
2135 // The boundary may be off by MaxError
2136 int epsilon = bds.m_MaxError;
2137
2138 for( BOARD_ITEM* item : aSpokedPadsList )
2139 {
2140 // We currently only connect to pads, not pad holes
2141 if( !item->IsOnLayer( aLayer ) )
2142 continue;
2143
2144 int thermalReliefGap = 0;
2145 int spoke_w = 0;
2146 PAD* pad = nullptr;
2147 PCB_VIA* via = nullptr;
2148 bool circular = false;
2149
2150 if( item->Type() == PCB_PAD_T )
2151 {
2152 pad = static_cast<PAD*>( item );
2153 VECTOR2I padSize = pad->GetSize( aLayer );
2154
2155 if( pad->GetShape( aLayer) == PAD_SHAPE::CIRCLE
2156 || ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL && padSize.x == padSize.y ) )
2157 {
2158 circular = true;
2159 }
2160 }
2161 else if( item->Type() == PCB_VIA_T )
2162 {
2163 via = static_cast<PCB_VIA*>( item );
2164 circular = true;
2165 }
2166
2167 // Thermal connections in a hatched zone are based on the hatch. Their primary function is to
2168 // guarantee that pads/vias connect to the webbing. (The thermal gap is the hatch gap width minus
2169 // the pad/via size, making it impossible for the pad/via to be isolated within the center of a
2170 // hole.)
2171 if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
2172 {
2173 spoke_w = aZone->GetHatchThickness();
2174 thermalReliefGap = getHatchFillThermalClearance( aZone, item, aLayer );
2175
2176 if( thermalReliefGap < 0 )
2177 continue;
2178 }
2179 else if( pad )
2180 {
2181 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
2182 thermalReliefGap = constraint.GetValue().Min();
2183
2184 constraint = bds.m_DRCEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, aZone, aLayer );
2185 spoke_w = constraint.GetValue().Opt();
2186
2187 // Spoke width should ideally be smaller than the pad minor axis.
2188 // Otherwise the thermal shape is not really a thermal relief,
2189 // and the algo to count the actual number of spokes can fail
2190 int spoke_max_allowed_w = std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y );
2191
2192 spoke_w = std::clamp( spoke_w, constraint.Value().Min(), constraint.Value().Max() );
2193
2194 // ensure the spoke width is smaller than the pad minor size
2195 spoke_w = std::min( spoke_w, spoke_max_allowed_w );
2196
2197 // Cannot create stubs having a width < zone min thickness
2198 if( spoke_w < aZone->GetMinThickness() )
2199 continue;
2200 }
2201 else
2202 {
2203 // We don't currently support via thermal connections *except* in a hatched zone.
2204 continue;
2205 }
2206
2207 int spoke_half_w = spoke_w / 2;
2208
2209 // Quick test here to possibly save us some work
2210 BOX2I itemBB = item->GetBoundingBox();
2211 itemBB.Inflate( thermalReliefGap + epsilon );
2212
2213 if( !( itemBB.Intersects( zoneBB ) ) )
2214 continue;
2215
2216 bool customSpokes = false;
2217
2218 if( pad && pad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
2219 {
2220 for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) )
2221 {
2222 if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
2223 {
2224 customSpokes = true;
2225 break;
2226 }
2227 }
2228 }
2229
2230 // Thermal spokes consist of square-ended segments from the pad center to points just
2231 // outside the thermal relief. The outside end has an extra center point (which must be
2232 // at idx 3) which is used for testing whether or not the spoke connects to copper in the
2233 // parent zone.
2234
2235 auto buildSpokesFromOrigin =
2236 [&]( const BOX2I& box, EDA_ANGLE angle )
2237 {
2238 VECTOR2I center = box.GetCenter();
2239 VECTOR2I half_size( box.GetWidth() / 2, box.GetHeight() / 2 );
2240
2241 // Function to find intersection of line with box edge
2242 auto intersectLineBox =
2243 [&](const VECTOR2D& direction) -> VECTOR2I
2244 {
2245 double dx = direction.x;
2246 double dy = direction.y;
2247
2248 // Short-circuit the axis cases because they will be degenerate in the
2249 // intersection test
2250 if( direction.x == 0 )
2251 return VECTOR2I( 0, dy * half_size.y );
2252 else if( direction.y == 0 )
2253 return VECTOR2I( dx * half_size.x, 0 );
2254
2255 // We are going to intersect with one side or the other. Whichever
2256 // we hit first is the fraction of the spoke length we keep
2257 double tx = std::min( half_size.x / std::abs( dx ),
2258 half_size.y / std::abs( dy ) );
2259 return VECTOR2I( dx * tx, dy * tx );
2260 };
2261
2262 // Precalculate angles for four cardinal directions
2263 const EDA_ANGLE angles[4] = {
2264 EDA_ANGLE( 0.0, DEGREES_T ) + angle, // Right
2265 EDA_ANGLE( 90.0, DEGREES_T ) + angle, // Up
2266 EDA_ANGLE( 180.0, DEGREES_T ) + angle, // Left
2267 EDA_ANGLE( 270.0, DEGREES_T ) + angle // Down
2268 };
2269
2270 // Generate four spokes in cardinal directions
2271 for( const EDA_ANGLE& spokeAngle : angles )
2272 {
2273 VECTOR2D direction( spokeAngle.Cos(), spokeAngle.Sin() );
2274 VECTOR2D perpendicular = direction.Perpendicular();
2275
2276 VECTOR2I intersection = intersectLineBox( direction );
2277 VECTOR2I spoke_side = perpendicular.Resize( spoke_half_w );
2278
2279 SHAPE_LINE_CHAIN spoke;
2280 spoke.Append( center + spoke_side );
2281 spoke.Append( center - spoke_side );
2282 spoke.Append( center + intersection - spoke_side );
2283 spoke.Append( center + intersection ); // test pt
2284 spoke.Append( center + intersection + spoke_side );
2285 spoke.SetClosed( true );
2286 aSpokesList.push_back( std::move( spoke ) );
2287 }
2288 };
2289
2290 if( customSpokes )
2291 {
2292 SHAPE_POLY_SET thermalPoly;
2293 SHAPE_LINE_CHAIN thermalOutline;
2294
2295 pad->TransformShapeToPolygon( thermalPoly, aLayer, thermalReliefGap + epsilon,
2297
2298 if( thermalPoly.OutlineCount() )
2299 thermalOutline = thermalPoly.Outline( 0 );
2300
2301 SHAPE_LINE_CHAIN padOutline = pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Outline( 0 );
2302
2303 auto trimToOutline = [&]( SEG& aSegment )
2304 {
2305 SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
2306
2307 if( padOutline.Intersect( aSegment, intersections ) )
2308 {
2309 intersections.clear();
2310
2311 // Trim the segment to the thermal outline
2312 if( thermalOutline.Intersect( aSegment, intersections ) )
2313 {
2314 aSegment.B = intersections.front().p;
2315 return true;
2316 }
2317 }
2318 return false;
2319 };
2320
2321 for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) )
2322 {
2323 if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
2324 {
2325 SEG seg( primitive->GetStart(), primitive->GetEnd() );
2326 SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
2327
2328 RotatePoint( seg.A, pad->GetOrientation() );
2329 RotatePoint( seg.B, pad->GetOrientation() );
2330 seg.A += pad->ShapePos( aLayer );
2331 seg.B += pad->ShapePos( aLayer );
2332
2333 // Make sure seg.A is the origin
2334 if( !pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Contains( seg.A ) )
2335 {
2336 // Do not create this spoke if neither point is in the pad.
2337 if( !pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Contains( seg.B ) )
2338 continue;
2339
2340 seg.Reverse();
2341 }
2342
2343 // Trim segment to pad and thermal outline polygon.
2344 // If there is no intersection with the pad, don't create the spoke.
2345 if( trimToOutline( seg ) )
2346 {
2347 VECTOR2I direction = ( seg.B - seg.A ).Resize( spoke_half_w );
2348 VECTOR2I offset = direction.Perpendicular().Resize( spoke_half_w );
2349 // Extend the spoke edges by half the spoke width to capture convex pad shapes
2350 // with a maximum of 45 degrees.
2351 SEG segL( seg.A - direction - offset, seg.B + direction - offset );
2352 SEG segR( seg.A - direction + offset, seg.B + direction + offset );
2353
2354 // Only create this spoke if both edges intersect the pad and thermal outline
2355 if( trimToOutline( segL ) && trimToOutline( segR ) )
2356 {
2357 // Extend the spoke by the minimum thickness for the zone to ensure full
2358 // connection width
2359 direction = direction.Resize( aZone->GetMinThickness() );
2360
2361 SHAPE_LINE_CHAIN spoke;
2362
2363 spoke.Append( seg.A + offset );
2364 spoke.Append( seg.A - offset );
2365
2366 spoke.Append( segL.B + direction );
2367 spoke.Append( seg.B + direction ); // test pt at index 3.
2368 spoke.Append( segR.B + direction );
2369
2370 spoke.SetClosed( true );
2371 aSpokesList.push_back( std::move( spoke ) );
2372 }
2373 }
2374 }
2375 }
2376 }
2377 else
2378 {
2379 EDA_ANGLE thermalSpokeAngle;
2380
2381 if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
2382 thermalSpokeAngle = aZone->GetHatchOrientation();
2383 else if( pad )
2384 thermalSpokeAngle = pad->GetThermalSpokeAngle();
2385
2386 BOX2I spokesBox;
2387 VECTOR2I position;
2388 EDA_ANGLE orientation;
2389
2390 // Since the bounding-box needs to be correclty rotated we use a dummy pad to keep
2391 // from dirtying the real pad's cached shapes.
2392 if( pad )
2393 {
2394 PAD dummy_pad( *pad );
2395 dummy_pad.SetOrientation( ANGLE_0 );
2396
2397 // Spokes are from center of pad shape, not from hole. So the dummy pad has no shape
2398 // offset and is at position 0,0
2399 dummy_pad.SetPosition( VECTOR2I( 0, 0 ) );
2400 dummy_pad.SetOffset( aLayer, VECTOR2I( 0, 0 ) );
2401
2402 spokesBox = dummy_pad.GetBoundingBox( aLayer );
2403 position = pad->ShapePos( aLayer );
2404 orientation = pad->GetOrientation();
2405 }
2406 else if( via )
2407 {
2408 PCB_VIA dummy_via( *via );
2409 dummy_via.SetPosition( VECTOR2I( 0, 0 ) );
2410
2411 spokesBox = dummy_via.GetBoundingBox( aLayer );
2412 position = via->GetPosition();
2413 }
2414
2415 // Add the half width of the zone mininum width to the inflate amount to account for
2416 // the fact that the deflation procedure will shrink the results by half the half the
2417 // zone min width
2418 spokesBox.Inflate( thermalReliefGap + epsilon + zone_half_width );
2419
2420 // This is a touchy case because the bounding box for circles overshoots the mark
2421 // when rotated at 45 degrees. So we just build spokes at 0 degrees and rotate
2422 // them later.
2423 if( circular )
2424 {
2425 buildSpokesFromOrigin( spokesBox, ANGLE_0 );
2426
2427 if( thermalSpokeAngle != ANGLE_0 )
2428 {
2429 //Rotate the last four elements of aspokeslist
2430 for( auto it = aSpokesList.rbegin(); it != aSpokesList.rbegin() + 4; ++it )
2431 it->Rotate( thermalSpokeAngle );
2432 }
2433 }
2434 else
2435 {
2436 buildSpokesFromOrigin( spokesBox, thermalSpokeAngle );
2437 }
2438
2439 auto spokeIter = aSpokesList.rbegin();
2440
2441 for( int ii = 0; ii < 4; ++ii, ++spokeIter )
2442 {
2443 spokeIter->Rotate( orientation );
2444 spokeIter->Move( position );
2445 }
2446 }
2447 }
2448
2449 for( size_t ii = 0; ii < aSpokesList.size(); ++ii )
2450 aSpokesList[ii].GenerateBBoxCache();
2451}
2452
2453
2455 PCB_LAYER_ID aDebugLayer, SHAPE_POLY_SET& aFillPolys )
2456{
2457 // Build grid:
2458
2459 // obviously line thickness must be > zone min thickness.
2460 // It can happens if a board file was edited by hand by a python script
2461 // Use 1 micron margin to be *sure* there is no issue in Gerber files
2462 // (Gbr file unit = 1 or 10 nm) due to some truncation in coordinates or calculations
2463 // This margin also avoid problems due to rounding coordinates in next calculations
2464 // that can create incorrect polygons
2465 int thickness = std::max( aZone->GetHatchThickness(),
2466 aZone->GetMinThickness() + pcbIUScale.mmToIU( 0.001 ) );
2467
2468 int gridsize = thickness + aZone->GetHatchGap();
2469 int maxError = m_board->GetDesignSettings().m_MaxError;
2470
2471 SHAPE_POLY_SET filledPolys = aFillPolys.CloneDropTriangulation();
2472 // Use a area that contains the rotated bbox by orientation, and after rotate the result
2473 // by -orientation.
2474 if( !aZone->GetHatchOrientation().IsZero() )
2475 filledPolys.Rotate( - aZone->GetHatchOrientation() );
2476
2477 BOX2I bbox = filledPolys.BBox( 0 );
2478
2479 // Build hole shape
2480 // the hole size is aZone->GetHatchGap(), but because the outline thickness
2481 // is aZone->GetMinThickness(), the hole shape size must be larger
2482 SHAPE_LINE_CHAIN hole_base;
2483 int hole_size = aZone->GetHatchGap() + aZone->GetMinThickness();
2484 VECTOR2I corner( 0, 0 );;
2485 hole_base.Append( corner );
2486 corner.x += hole_size;
2487 hole_base.Append( corner );
2488 corner.y += hole_size;
2489 hole_base.Append( corner );
2490 corner.x = 0;
2491 hole_base.Append( corner );
2492 hole_base.SetClosed( true );
2493
2494 // Calculate minimal area of a grid hole.
2495 // All holes smaller than a threshold will be removed
2496 double minimal_hole_area = hole_base.Area() * aZone->GetHatchHoleMinArea();
2497
2498 // Now convert this hole to a smoothed shape:
2499 if( aZone->GetHatchSmoothingLevel() > 0 )
2500 {
2501 // the actual size of chamfer, or rounded corner radius is the half size
2502 // of the HatchFillTypeGap scaled by aZone->GetHatchSmoothingValue()
2503 // aZone->GetHatchSmoothingValue() = 1.0 is the max value for the chamfer or the
2504 // radius of corner (radius = half size of the hole)
2505 int smooth_value = KiROUND( aZone->GetHatchGap()
2506 * aZone->GetHatchSmoothingValue() / 2 );
2507
2508 // Minimal optimization:
2509 // make smoothing only for reasonable smooth values, to avoid a lot of useless segments
2510 // and if the smooth value is small, use chamfer even if fillet is requested
2511 #define SMOOTH_MIN_VAL_MM 0.02
2512 #define SMOOTH_SMALL_VAL_MM 0.04
2513
2514 if( smooth_value > pcbIUScale.mmToIU( SMOOTH_MIN_VAL_MM ) )
2515 {
2516 SHAPE_POLY_SET smooth_hole;
2517 smooth_hole.AddOutline( hole_base );
2518 int smooth_level = aZone->GetHatchSmoothingLevel();
2519
2520 if( smooth_value < pcbIUScale.mmToIU( SMOOTH_SMALL_VAL_MM ) && smooth_level > 1 )
2521 smooth_level = 1;
2522
2523 // Use a larger smooth_value to compensate the outline tickness
2524 // (chamfer is not visible is smooth value < outline thickess)
2525 smooth_value += aZone->GetMinThickness() / 2;
2526
2527 // smooth_value cannot be bigger than the half size oh the hole:
2528 smooth_value = std::min( smooth_value, aZone->GetHatchGap() / 2 );
2529
2530 // the error to approximate a circle by segments when smoothing corners by a arc
2531 maxError = std::max( maxError * 2, smooth_value / 20 );
2532
2533 switch( smooth_level )
2534 {
2535 case 1:
2536 // Chamfer() uses the distance from a corner to create a end point
2537 // for the chamfer.
2538 hole_base = smooth_hole.Chamfer( smooth_value ).Outline( 0 );
2539 break;
2540
2541 default:
2542 if( aZone->GetHatchSmoothingLevel() > 2 )
2543 maxError /= 2; // Force better smoothing
2544
2545 hole_base = smooth_hole.Fillet( smooth_value, maxError ).Outline( 0 );
2546 break;
2547
2548 case 0:
2549 break;
2550 };
2551 }
2552 }
2553
2554 // Build holes
2555 SHAPE_POLY_SET holes;
2556
2557 VECTOR2I offset_opt = VECTOR2I();
2558 bool zone_has_offset = false;
2559
2560 if( aZone->LayerProperties().contains( aLayer ) )
2561 {
2562 zone_has_offset = aZone->HatchingOffset( aLayer ).has_value();
2563
2564 offset_opt = aZone->HatchingOffset( aLayer ).value_or( VECTOR2I( 0, 0 ) );
2565 }
2566
2567 if( !zone_has_offset )
2568 {
2570 aLayer ) )
2571 {
2572 const ZONE_LAYER_PROPERTIES& properties =
2574 aLayer );
2575
2576 offset_opt = properties.hatching_offset.value_or( VECTOR2I( 0, 0 ) );
2577 }
2578 }
2579
2580
2581 int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize;
2582 int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize;
2583
2584
2585 for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize )
2586 {
2587 for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize )
2588 {
2589 // Generate hole
2590 SHAPE_LINE_CHAIN hole( hole_base );
2591 hole.Move( VECTOR2I( xx, yy ) );
2592
2593 if( !aZone->GetHatchOrientation().IsZero() )
2594 {
2595 hole.Rotate( aZone->GetHatchOrientation() );
2596 }
2597
2598 hole.Move( VECTOR2I( offset_opt.x % gridsize, offset_opt.y % gridsize ) );
2599
2600 holes.AddOutline( hole );
2601 }
2602 }
2603
2604
2605 DUMP_POLYS_TO_COPPER_LAYER( holes, In10_Cu, wxT( "hatch-holes" ) );
2606
2607 int deflated_thickness = aZone->GetHatchThickness() - aZone->GetMinThickness();
2608
2609 // Don't let thickness drop below maxError * 2 or it might not get reinflated.
2610 deflated_thickness = std::max( deflated_thickness, maxError * 2 );
2611
2612 // The fill has already been deflated to ensure GetMinThickness() so we just have to
2613 // account for anything beyond that.
2614 SHAPE_POLY_SET deflatedFilledPolys = aFillPolys.CloneDropTriangulation();
2615 deflatedFilledPolys.Deflate( deflated_thickness, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
2616 holes.BooleanIntersection( deflatedFilledPolys );
2617 DUMP_POLYS_TO_COPPER_LAYER( holes, In11_Cu, wxT( "fill-clipped-hatch-holes" ) );
2618
2619 SHAPE_POLY_SET deflatedOutline = aZone->Outline()->CloneDropTriangulation();
2620 deflatedOutline.Deflate( aZone->GetMinThickness(), CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
2621 holes.BooleanIntersection( deflatedOutline );
2622 DUMP_POLYS_TO_COPPER_LAYER( holes, In12_Cu, wxT( "outline-clipped-hatch-holes" ) );
2623
2624 // Now filter truncated holes to avoid small holes in pattern
2625 // It happens for holes near the zone outline
2626 for( int ii = 0; ii < holes.OutlineCount(); )
2627 {
2628 double area = holes.Outline( ii ).Area();
2629
2630 if( area < minimal_hole_area ) // The current hole is too small: remove it
2631 holes.DeletePolygon( ii );
2632 else
2633 ++ii;
2634 }
2635
2636 // create grid. Useto
2637 // generate strictly simple polygons needed by Gerber files and Fracture()
2638 aFillPolys.BooleanSubtract( aFillPolys, holes );
2639 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In14_Cu, wxT( "after-hatching" ) );
2640
2641 return true;
2642}
@ ERROR_OUTSIDE
Definition: approximation.h:33
@ ERROR_INSIDE
Definition: approximation.h:34
constexpr int ARC_HIGH_DEF
Definition: base_units.h:129
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:112
@ ZLO_FORCE_NO_ZONE_CONNECTION
Definition: board_item.h:70
@ ZLO_FORCE_FLASHED
Definition: board_item.h:69
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
ZONE_SETTINGS & GetDefaultZoneSettings()
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:79
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.
Definition: board_item.cpp:291
virtual void SetIsKnockout(bool aKnockout)
Definition: board_item.h:323
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
Definition: board_item.cpp:79
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:317
bool GetBoardPolygonOutlines(SHAPE_POLY_SET &aOutlines, OUTLINE_ERROR_HANDLER *aErrorHandler=nullptr, bool aAllowUseArcsInPolygons=false, bool aIncludeNPTHAsOutlines=false)
Extract the board outlines and build a closed polygon from lines, arcs and circle items on edge cut l...
Definition: board.cpp:2524
const ZONES & Zones() const
Definition: board.h:362
int GetMaxClearanceValue() const
Returns the maximum clearance value for any object on the board.
Definition: board.cpp:958
int GetCopperLayerCount() const
Definition: board.cpp:781
const FOOTPRINTS & Footprints() const
Definition: board.h:358
const TRACKS & Tracks() const
Definition: board.h:356
PROJECT * GetProject() const
Definition: board.h:536
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:946
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition: board.cpp:829
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition: board.h:520
const DRAWINGS & Drawings() const
Definition: board.h:360
constexpr int GetSizeMax() const
Definition: box2.h:235
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition: box2.h:558
constexpr coord_type GetY() const
Definition: box2.h:208
constexpr size_type GetWidth() const
Definition: box2.h:214
constexpr coord_type GetX() const
Definition: box2.h:207
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition: box2.h:658
constexpr const Vec GetCenter() const
Definition: box2.h:230
constexpr size_type GetHeight() const
Definition: box2.h:215
constexpr coord_type GetRight() const
Definition: box2.h:217
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition: box2.h:311
constexpr coord_type GetBottom() const
Definition: box2.h:222
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition: commit.h:73
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition: commit.h:107
MINOPTMAX< int > & Value()
Definition: drc_rule.h:161
const MINOPTMAX< int > & GetValue() const
Definition: drc_rule.h:160
ZONE_CONNECTION m_ZoneConnection
Definition: drc_rule.h:204
bool IsNull() const
Definition: drc_rule.h:155
bool IsZero() const
Definition: eda_angle.h:136
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:109
Helper class to create more flexible dialogs, including 'do not show again' checkbox handling.
Definition: kidialog.h:50
void DoNotShowCheckbox(wxString file, int line)
Shows the 'do not show again' checkbox.
Definition: kidialog.cpp:51
bool SetOKCancelLabels(const ButtonLabel &ok, const ButtonLabel &cancel) override
Definition: kidialog.h:60
int ShowModal() override
Definition: kidialog.cpp:95
LSET is a set of PCB_LAYER_IDs.
Definition: lset.h:37
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:583
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition: lset.cpp:297
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:561
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition: lset.h:63
T Min() const
Definition: minoptmax.h:33
T Max() const
Definition: minoptmax.h:34
T Opt() const
Definition: minoptmax.h:35
Definition: pad.h:54
const BOX2I GetBoundingBox() const override
The bounding box is cached, so this will be efficient most of the time.
Definition: pad.cpp:850
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition: pad.h:195
void SetOffset(PCB_LAYER_ID aLayer, const VECTOR2I &aOffset)
Definition: pad.h:311
void SetPosition(const VECTOR2I &aPos) override
Definition: pad.h:202
void SetOrientation(const EDA_ANGLE &aAngle)
Set the rotation angle of the pad.
Definition: pad.cpp:941
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:1970
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:549
void SetPosition(const VECTOR2I &aPoint) override
Definition: pcb_track.h:552
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition: pcb_track.cpp:724
A progress reporter interface for use in multi-threaded environments.
virtual bool IsCancelled() const =0
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void Report(const wxString &aMessage)=0
Display aMessage in the progress bar dialog.
virtual void AdvancePhase()=0
Use the next available virtual zone of the dialog progress bar.
virtual void AdvanceProgress()=0
Increment the progress bar length (inside the current virtual zone).
virtual void SetMaxProgress(int aMaxProgress)=0
Fix the value that gives the 100 percent progress bar length (inside the current virtual zone).
bool m_PrototypeZoneFill
Whether Zone fill should always be solid for performance with large boards.
virtual PROJECT_LOCAL_SETTINGS & GetLocalSettings() const
Definition: project.h:210
int m_vertex2
Definition: zone_filler.cpp:80
RESULTS(int aOutline1, int aOutline2, int aVertex1, int aVertex2)
Definition: zone_filler.cpp:60
int m_outline2
Definition: zone_filler.cpp:78
int m_outline1
Definition: zone_filler.cpp:77
int m_vertex1
Definition: zone_filler.cpp:79
bool operator<(const RESULTS &aOther) const
Definition: zone_filler.cpp:66
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:358
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.
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)
std::vector< INTERSECTION > INTERSECTIONS
Represent a set of closed polygons.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
SHAPE_POLY_SET Chamfer(int aDistance)
Return a chamfered version of the polygon set.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
void DeletePolygon(int aIdx)
Delete aIdx-th polygon from the set.
double Area()
Return the area of this poly set.
void Fracture()
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
void Deflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError)
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
void BuildBBoxCaches() const
Construct BBoxCaches for Contains(), below.
int OutlineCount() const
Return the number of outlines in the set.
SHAPE_POLY_SET Fillet(int aRadius, int aErrorMax)
Return a filleted version of the polygon set.
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
SHAPE_POLY_SET CloneDropTriangulation() const
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const BOX2I BBoxFromCaches() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition: vector2d.h:307
constexpr VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition: vector2d.h:314
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition: vector2d.h:385
VERTEX * getPoint(VERTEX *aPt) const
Definition: zone_filler.cpp:99
std::set< RESULTS > GetResults() const
VERTEX_CONNECTOR(const BOX2I &aBBox, const SHAPE_POLY_SET &aPolys, int aDist)
Definition: zone_filler.cpp:86
std::set< RESULTS > m_results
std::deque< VERTEX > m_vertices
Definition: vertex_set.h:343
VERTEX * createList(const SHAPE_LINE_CHAIN &points, VERTEX *aTail=nullptr, void *aUserData=nullptr)
Create a list of vertices from a line chain.
Definition: vertex_set.cpp:27
void SetBoundingBox(const BOX2I &aBBox)
Definition: vertex_set.cpp:21
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 ...
Definition: vertex_set.cpp:81
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.
Definition: vertex_set.cpp:264
const double y
Definition: vertex_set.h:236
COMMIT * m_commit
Definition: zone_filler.h:141
int m_worstClearance
Definition: zone_filler.h:145
void buildCopperItemClearances(const ZONE *aZone, PCB_LAYER_ID aLayer, const std::vector< PAD * > &aNoConnectionPads, SHAPE_POLY_SET &aHoles)
Removes clearance from the shape for copper items which share the zone's layer but are not connected ...
bool m_debugZoneFiller
Definition: zone_filler.h:147
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.
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
Definition: zone_filler.h:139
bool m_brdOutlinesValid
Definition: zone_filler.h:140
bool addHatchFillTypeOnZone(const ZONE *aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer, SHAPE_POLY_SET &aFillPolys)
for zones having the ZONE_FILL_MODE::ZONE_FILL_MODE::HATCH_PATTERN, create a grid pattern in filled a...
void SetProgressReporter(PROGRESS_REPORTER *aReporter)
BOARD * m_board
Definition: zone_filler.h:138
PROGRESS_REPORTER * m_progressReporter
Definition: zone_filler.h:142
bool fillCopperZone(const ZONE *aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer, const SHAPE_POLY_SET &aSmoothedOutline, const SHAPE_POLY_SET &aMaxExtents, SHAPE_POLY_SET &aFillPolys)
Function fillCopperZone Add non copper areas polygons (pads and tracks with clearance) to a filled co...
void addHoleKnockout(PAD *aPad, int aGap, SHAPE_POLY_SET &aHoles)
Add a knockout for a pad's hole.
bool fillNonCopperZone(const ZONE *candidate, PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aSmoothedOutline, SHAPE_POLY_SET &aFillPolys)
bool fillSingleZone(ZONE *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aFillPolys)
Build the filled solid areas polygons from zone outlines (stored in m_Poly) The solid areas can be mo...
bool Fill(const std::vector< ZONE * > &aZones, bool aCheck=false, wxWindow *aParent=nullptr)
Fills the given list of zones.
std::map< PCB_LAYER_ID, ZONE_LAYER_PROPERTIES > m_layerProperties
Handle a list of polygons defining a copper zone.
Definition: zone.h:74
void SetNeedRefill(bool aNeedRefill)
Definition: zone.h:296
std::optional< int > GetLocalClearance() const override
Definition: zone.cpp:769
ZONE_LAYER_PROPERTIES & LayerProperties(PCB_LAYER_ID aLayer)
Definition: zone.h:145
const std::optional< VECTOR2I > & HatchingOffset(PCB_LAYER_ID aLayer) const
Definition: zone.cpp:563
void CacheTriangulation(PCB_LAYER_ID aLayer=UNDEFINED_LAYER)
Create a list of triangles that "fill" the solid areas used for instance to draw these solid areas on...
Definition: zone.cpp:1256
const BOX2I GetBoundingBox() const override
Definition: zone.cpp:622
SHAPE_POLY_SET * Outline()
Definition: zone.h:335
void SetFillFlag(PCB_LAYER_ID aLayer, bool aFlag)
Definition: zone.h:290
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition: zone.h:616
int GetMinThickness() const
Definition: zone.h:301
bool HigherPriority(const ZONE *aOther) const
Definition: zone.cpp:423
int GetHatchThickness() const
Definition: zone.h:310
double GetHatchHoleMinArea() const
Definition: zone.h:325
bool IsTeardropArea() const
Definition: zone.h:674
EDA_ANGLE GetHatchOrientation() const
Definition: zone.h:316
bool BuildSmoothedPoly(SHAPE_POLY_SET &aSmoothedPoly, PCB_LAYER_ID aLayer, SHAPE_POLY_SET *aBoardOutline, SHAPE_POLY_SET *aSmoothedPolyWithApron=nullptr) const
Definition: zone.cpp:1320
ZONE_FILL_MODE GetFillMode() const
Definition: zone.h:224
int GetHatchGap() const
Definition: zone.h:313
double GetHatchSmoothingValue() const
Definition: zone.h:322
int GetHatchSmoothingLevel() const
Definition: zone.h:319
bool IsOnCopperLayer() const override
Definition: zone.cpp:500
std::mutex & GetLock()
Definition: zone.h:280
unsigned GetAssignedPriority() const
Definition: zone.h:126
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.
Definition: convex_hull.cpp:87
CORNER_STRATEGY
define how inflate transform build inflated polygon
DRC_CONSTRAINT_T
Definition: drc_rule.h:47
@ EDGE_CLEARANCE_CONSTRAINT
Definition: drc_rule.h:53
@ PHYSICAL_HOLE_CLEARANCE_CONSTRAINT
Definition: drc_rule.h:78
@ CLEARANCE_CONSTRAINT
Definition: drc_rule.h:49
@ THERMAL_SPOKE_WIDTH_CONSTRAINT
Definition: drc_rule.h:64
@ THERMAL_RELIEF_GAP_CONSTRAINT
Definition: drc_rule.h:63
@ HOLE_CLEARANCE_CONSTRAINT
Definition: drc_rule.h:51
@ PHYSICAL_CLEARANCE_CONSTRAINT
Definition: drc_rule.h:77
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition: eda_angle.h:404
@ DEGREES_T
Definition: eda_angle.h:31
a few functions useful in geometry calculations.
double m_ExtraClearance
When filling zones, we add an extra amount of clearance to each zone to ensure that rounding errors d...
bool m_DebugZoneFiller
A mode that dumps the various stages of a F_Cu fill into In1_Cu through In9_Cu.
This file is part of the common library.
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:60
@ In11_Cu
Definition: layer_ids.h:76
@ In17_Cu
Definition: layer_ids.h:82
@ Edge_Cuts
Definition: layer_ids.h:112
@ In9_Cu
Definition: layer_ids.h:74
@ In7_Cu
Definition: layer_ids.h:72
@ In15_Cu
Definition: layer_ids.h:80
@ In2_Cu
Definition: layer_ids.h:67
@ In10_Cu
Definition: layer_ids.h:75
@ Margin
Definition: layer_ids.h:113
@ In4_Cu
Definition: layer_ids.h:69
@ UNDEFINED_LAYER
Definition: layer_ids.h:61
@ In16_Cu
Definition: layer_ids.h:81
@ In1_Cu
Definition: layer_ids.h:66
@ In8_Cu
Definition: layer_ids.h:73
@ In14_Cu
Definition: layer_ids.h:79
@ In12_Cu
Definition: layer_ids.h:77
@ In6_Cu
Definition: layer_ids.h:71
@ In5_Cu
Definition: layer_ids.h:70
@ In3_Cu
Definition: layer_ids.h:68
@ F_Cu
Definition: layer_ids.h:64
@ In18_Cu
Definition: layer_ids.h:83
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:393
const double epsilon
constexpr int mmToIU(double mm) const
Definition: base_units.h:92
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
std::optional< VECTOR2I > hatching_offset
Definition: zone_settings.h:51
VECTOR2I center
int radius
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
Definition: thread_pool.cpp:30
static thread_pool * tp
Definition: thread_pool.cpp:28
BS::thread_pool thread_pool
Definition: thread_pool.h:31
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition: trigo.cpp:229
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition: typeinfo.h:88
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition: typeinfo.h:105
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition: typeinfo.h:102
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:97
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition: typeinfo.h:103
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition: typeinfo.h:93
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition: typeinfo.h:92
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition: typeinfo.h:90
@ PCB_TARGET_T
class PCB_TARGET, a target (graphic item)
Definition: typeinfo.h:106
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition: typeinfo.h:101
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition: typeinfo.h:87
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition: typeinfo.h:94
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition: typeinfo.h:104
VECTOR2< int32_t > VECTOR2I
Definition: vector2d.h:695
#define SMOOTH_MIN_VAL_MM
int getHatchFillThermalClearance(const ZONE *aZone, BOARD_ITEM *aItem, PCB_LAYER_ID aLayer)
#define DUMP_POLYS_TO_COPPER_LAYER(a, b, c)
#define SMOOTH_SMALL_VAL_MM
ISLAND_REMOVAL_MODE
Whether or not to remove isolated islands from a zone.
Definition: zone_settings.h:68
ZONE_CONNECTION
How pads are covered by copper in zone.
Definition: zones.h:47