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 (C) 2014-2023 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>
40#include <board_commit.h>
41#include <progress_reporter.h>
45#include <confirm.h>
46#include <core/thread_pool.h>
47#include <math/util.h> // for KiROUND
48#include "zone_filler.h"
49
50
52 m_board( aBoard ),
53 m_brdOutlinesValid( false ),
54 m_commit( aCommit ),
55 m_progressReporter( nullptr ),
56 m_maxError( ARC_HIGH_DEF ),
57 m_worstClearance( 0 )
58{
59 // To enable add "DebugZoneFiller=1" to kicad_advanced settings file.
61}
62
63
65{
66}
67
68
70{
71 m_progressReporter = aReporter;
72 wxASSERT_MSG( m_commit, wxT( "ZONE_FILLER must have a valid commit to call "
73 "SetProgressReporter" ) );
74}
75
76
87bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aParent )
88{
89 std::lock_guard<KISPINLOCK> lock( m_board->GetConnectivity()->GetLock() );
90
91 std::vector<std::pair<ZONE*, PCB_LAYER_ID>> toFill;
92 std::map<std::pair<ZONE*, PCB_LAYER_ID>, MD5_HASH> oldFillHashes;
93 std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>> isolatedIslandsMap;
94
95 std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_board->GetConnectivity();
96
97 // Rebuild (from scratch, ignoring dirty flags) just in case. This really needs to be reliable.
98 connectivity->ClearRatsnest();
99 connectivity->Build( m_board, m_progressReporter );
100
102
104 {
105 m_progressReporter->Report( aCheck ? _( "Checking zone fills..." )
106 : _( "Building zone fills..." ) );
107 m_progressReporter->SetMaxProgress( aZones.size() );
109 }
110
111 // The board outlines is used to clip solid areas inside the board (when outlines are valid)
114
115 // Update and cache zone bounding boxes and pad effective shapes so that we don't have to
116 // make them thread-safe.
117 //
118 for( ZONE* zone : m_board->Zones() )
119 zone->CacheBoundingBox();
120
121 for( FOOTPRINT* footprint : m_board->Footprints() )
122 {
123 for( PAD* pad : footprint->Pads() )
124 {
125 if( pad->IsDirty() )
126 {
127 pad->BuildEffectiveShapes( UNDEFINED_LAYER );
128 pad->BuildEffectivePolygon();
129 }
130 }
131
132 for( ZONE* zone : footprint->Zones() )
133 zone->CacheBoundingBox();
134
135 // Rules may depend on insideCourtyard() or other expressions
136 footprint->BuildCourtyardCaches();
137 }
138
139 LSET boardCuMask = m_board->GetEnabledLayers() & LSET::AllCuMask();
140
141 auto findHighestPriorityZone = [&]( const BOX2I& aBBox, const PCB_LAYER_ID aItemLayer,
142 const int aNetcode,
143 const std::function<bool( const ZONE* )> aTestFn ) -> ZONE*
144 {
145 unsigned highestPriority = 0;
146 ZONE* highestPriorityZone = nullptr;
147
148 for( ZONE* zone : m_board->Zones() )
149 {
150 // Rule areas are not filled
151 if( zone->GetIsRuleArea() )
152 continue;
153
154 if( zone->GetAssignedPriority() < highestPriority )
155 continue;
156
157 if( !zone->IsOnLayer( aItemLayer ) )
158 continue;
159
160 // Degenerate zones will cause trouble; skip them
161 if( zone->GetNumCorners() <= 2 )
162 continue;
163
164 if( !zone->GetBoundingBox().Intersects( aBBox ) )
165 continue;
166
167 if( !aTestFn( zone ) )
168 continue;
169
170 // Prefer highest priority and matching netcode
171 if( zone->GetAssignedPriority() > highestPriority || zone->GetNetCode() == aNetcode )
172 {
173 highestPriority = zone->GetAssignedPriority();
174 highestPriorityZone = zone;
175 }
176 }
177
178 return highestPriorityZone;
179 };
180
181 auto isInPourKeepoutArea = [&]( const BOX2I& aBBox, const PCB_LAYER_ID aItemLayer,
182 const VECTOR2I aTestPoint ) -> bool
183 {
184 for( ZONE* zone : m_board->Zones() )
185 {
186 if( !zone->GetIsRuleArea() )
187 continue;
188
189 if( !zone->GetDoNotAllowCopperPour() )
190 continue;
191
192 if( !zone->IsOnLayer( aItemLayer ) )
193 continue;
194
195 // Degenerate zones will cause trouble; skip them
196 if( zone->GetNumCorners() <= 2 )
197 continue;
198
199 if( !zone->GetBoundingBox().Intersects( aBBox ) )
200 continue;
201
202 if( zone->Outline()->Contains( aTestPoint ) )
203 return true;
204 }
205
206 return false;
207 };
208
209 // Determine state of conditional via flashing
210 for( PCB_TRACK* track : m_board->Tracks() )
211 {
212 if( track->Type() == PCB_VIA_T )
213 {
214 PCB_VIA* via = static_cast<PCB_VIA*>( track );
215
216 via->ClearZoneLayerOverrides();
217
218 if( !via->GetRemoveUnconnected() )
219 continue;
220
221 BOX2I bbox = via->GetBoundingBox();
222 VECTOR2I center = via->GetPosition();
223 int testRadius = via->GetDrillValue() / 2 + 1;
224 unsigned netcode = via->GetNetCode();
225 LSET layers = via->GetLayerSet() & boardCuMask;
226
227 // Checking if the via hole touches the zone outline
228 auto viaTestFn = [&]( const ZONE* aZone ) -> bool
229 {
230 return aZone->Outline()->Contains( center, -1, testRadius );
231 };
232
233 for( PCB_LAYER_ID layer : layers.Seq() )
234 {
235 if( !via->ConditionallyFlashed( layer ) )
236 continue;
237
238 if( isInPourKeepoutArea( bbox, layer, center ) )
239 {
240 via->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
241 }
242 else
243 {
244 ZONE* zone = findHighestPriorityZone( bbox, layer, netcode, viaTestFn );
245
246 if( zone && zone->GetNetCode() == via->GetNetCode() )
247 via->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
248 else
249 via->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
250 }
251 }
252 }
253 }
254
255 // Determine state of conditional pad flashing
256 for( FOOTPRINT* footprint : m_board->Footprints() )
257 {
258 for( PAD* pad : footprint->Pads() )
259 {
260 pad->ClearZoneLayerOverrides();
261
262 if( !pad->GetRemoveUnconnected() )
263 continue;
264
265 BOX2I bbox = pad->GetBoundingBox();
266 VECTOR2I center = pad->GetPosition();
267 unsigned netcode = pad->GetNetCode();
268 LSET layers = pad->GetLayerSet() & boardCuMask;
269
270 auto padTestFn = [&]( const ZONE* aZone ) -> bool
271 {
272 return aZone->Outline()->Contains( center );
273 };
274
275 for( PCB_LAYER_ID layer : layers.Seq() )
276 {
277 if( !pad->ConditionallyFlashed( layer ) )
278 continue;
279
280 if( isInPourKeepoutArea( bbox, layer, center ) )
281 {
282 pad->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
283 }
284 else
285 {
286 ZONE* zone = findHighestPriorityZone( bbox, layer, netcode, padTestFn );
287
288 if( zone && zone->GetNetCode() == pad->GetNetCode() )
289 pad->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
290 else
291 pad->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
292 }
293 }
294 }
295 }
296
297 for( ZONE* zone : aZones )
298 {
299 // Rule areas are not filled
300 if( zone->GetIsRuleArea() )
301 continue;
302
303 // Degenerate zones will cause trouble; skip them
304 if( zone->GetNumCorners() <= 2 )
305 continue;
306
307 if( m_commit )
308 m_commit->Modify( zone );
309
310 // calculate the hash value for filled areas. it will be used later to know if the
311 // current filled areas are up to date
312 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
313 {
314 zone->BuildHashValue( layer );
315 oldFillHashes[ { zone, layer } ] = zone->GetHashValue( layer );
316
317 // Add the zone to the list of zones to test or refill
318 toFill.emplace_back( std::make_pair( zone, layer ) );
319
320 isolatedIslandsMap[ zone ][ layer ] = ISOLATED_ISLANDS();
321 }
322
323 // Remove existing fill first to prevent drawing invalid polygons on some platforms
324 zone->UnFill();
325 }
326
327 auto check_fill_dependency =
328 [&]( ZONE* aZone, PCB_LAYER_ID aLayer, ZONE* aOtherZone ) -> bool
329 {
330 // Check to see if we have to knock-out the filled areas of a higher-priority
331 // zone. If so we have to wait until said zone is filled before we can fill.
332
333 // If the other zone is already filled on the requested layer then we're
334 // good-to-go
335 if( aOtherZone->GetFillFlag( aLayer ) )
336 return false;
337
338 // Even if keepouts exclude copper pours, the exclusion is by outline rather than
339 // filled area, so we're good-to-go here too
340 if( aOtherZone->GetIsRuleArea() )
341 return false;
342
343 // If the other zone is never going to be filled then don't wait for it
344 if( aOtherZone->GetNumCorners() <= 2 )
345 return false;
346
347 // If the zones share no common layers
348 if( !aOtherZone->GetLayerSet().test( aLayer ) )
349 return false;
350
351 if( aZone->HigherPriority( aOtherZone ) )
352 return false;
353
354 // Same-net zones always use outlines to produce determinate results
355 if( aOtherZone->SameNet( aZone ) )
356 return false;
357
358 // A higher priority zone is found: if we intersect and it's not filled yet
359 // then we have to wait.
360 BOX2I inflatedBBox = aZone->GetBoundingBox();
361 inflatedBBox.Inflate( m_worstClearance );
362
363 if( !inflatedBBox.Intersects( aOtherZone->GetBoundingBox() ) )
364 return false;
365
366 return aZone->Outline()->Collide( aOtherZone->Outline(), m_worstClearance );
367 };
368
369 auto fill_lambda =
370 [&]( std::pair<ZONE*, PCB_LAYER_ID> aFillItem ) -> int
371 {
372 PCB_LAYER_ID layer = aFillItem.second;
373 ZONE* zone = aFillItem.first;
374 bool canFill = true;
375
376 // Check for any fill dependencies. If our zone needs to be clipped by
377 // another zone then we can't fill until that zone is filled.
378 for( ZONE* otherZone : aZones )
379 {
380 if( otherZone == zone )
381 continue;
382
383 if( check_fill_dependency( zone, layer, otherZone ) )
384 {
385 canFill = false;
386 break;
387 }
388 }
389
391 return 0;
392
393 if( !canFill )
394 return 0;
395
396 // Now we're ready to fill.
397 {
398 std::unique_lock<std::mutex> zoneLock( zone->GetLock(), std::try_to_lock );
399
400 if( !zoneLock.owns_lock() )
401 return 0;
402
403 SHAPE_POLY_SET fillPolys;
404
405 if( !fillSingleZone( zone, layer, fillPolys ) )
406 return 0;
407
408 zone->SetFilledPolysList( layer, fillPolys );
409 }
410
413
414 return 1;
415 };
416
417 auto tesselate_lambda =
418 [&]( std::pair<ZONE*, PCB_LAYER_ID> aFillItem ) -> int
419 {
421 return 0;
422
423 PCB_LAYER_ID layer = aFillItem.second;
424 ZONE* zone = aFillItem.first;
425
426 {
427 std::unique_lock<std::mutex> zoneLock( zone->GetLock(), std::try_to_lock );
428
429 if( !zoneLock.owns_lock() )
430 return 0;
431
432 zone->CacheTriangulation( layer );
433 zone->SetFillFlag( layer, true );
434 }
435
436 return 1;
437 };
438
439 // Calculate the copper fills (NB: this is multi-threaded)
440 //
441 std::vector<std::pair<std::future<int>, int>> returns;
442 returns.reserve( toFill.size() );
443 size_t finished = 0;
444 bool cancelled = false;
445
447
448 for( const std::pair<ZONE*, PCB_LAYER_ID>& fillItem : toFill )
449 returns.emplace_back( std::make_pair( tp.submit( fill_lambda, fillItem ), 0 ) );
450
451 while( !cancelled && finished != 2 * toFill.size() )
452 {
453 for( size_t ii = 0; ii < returns.size(); ++ii )
454 {
455 auto& ret = returns[ii];
456
457 if( ret.second > 1 )
458 continue;
459
460 std::future_status status = ret.first.wait_for( std::chrono::seconds( 0 ) );
461
462 if( status == std::future_status::ready )
463 {
464 if( ret.first.get() ) // lambda completed
465 {
466 ++finished;
467 ret.second++; // go to next step
468 }
469
470 if( !cancelled )
471 {
472 // Queue the next step (will re-queue the existing step if it didn't complete)
473 if( ret.second == 0 )
474 returns[ii].first = tp.submit( fill_lambda, toFill[ii] );
475 else if( ret.second == 1 )
476 returns[ii].first = tp.submit( tesselate_lambda, toFill[ii] );
477 }
478 }
479 }
480
481 std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
482
483
485 {
487
489 cancelled = true;
490 }
491 }
492
493 // Make sure that all futures have finished.
494 // This can happen when the user cancels the above operation
495 for( auto& ret : returns )
496 {
497 if( ret.first.valid() )
498 {
499 std::future_status status = ret.first.wait_for( std::chrono::seconds( 0 ) );
500
501 while( status != std::future_status::ready )
502 {
505
506 status = ret.first.wait_for( std::chrono::milliseconds( 100 ) );
507 }
508 }
509 }
510
511 // Now update the connectivity to check for isolated copper islands
512 // (NB: FindIsolatedCopperIslands() is multi-threaded)
513 //
515 {
517 return false;
518
520 m_progressReporter->Report( _( "Removing isolated copper islands..." ) );
522 }
523
524 connectivity->SetProgressReporter( m_progressReporter );
525 connectivity->FillIsolatedIslandsMap( isolatedIslandsMap );
526 connectivity->SetProgressReporter( nullptr );
527
529 return false;
530
531 for( ZONE* zone : aZones )
532 {
533 // Keepout zones are not filled
534 if( zone->GetIsRuleArea() )
535 continue;
536
537 zone->SetIsFilled( true );
538 }
539
540 // Now remove isolated copper islands according to the isolated islands strategy assigned
541 // by the user (always, never, below-certain-size).
542 //
543 for( const auto& [ zone, zoneIslands ] : isolatedIslandsMap )
544 {
545 // If *all* the polygons are islands, do not remove any of them
546 bool allIslands = true;
547
548 for( const auto& [ layer, layerIslands ] : zoneIslands )
549 {
550 if( layerIslands.m_IsolatedOutlines.size()
551 != static_cast<size_t>( zone->GetFilledPolysList( layer )->OutlineCount() ) )
552 {
553 allIslands = false;
554 break;
555 }
556 }
557
558 if( allIslands )
559 continue;
560
561 for( const auto& [ layer, layerIslands ] : zoneIslands )
562 {
563 if( m_debugZoneFiller && LSET::InternalCuMask().Contains( layer ) )
564 continue;
565
566 if( layerIslands.m_IsolatedOutlines.empty() )
567 continue;
568
569 std::vector<int> islands = layerIslands.m_IsolatedOutlines;
570
571 // The list of polygons to delete must be explored from last to first in list,
572 // to allow deleting a polygon from list without breaking the remaining of the list
573 std::sort( islands.begin(), islands.end(), std::greater<int>() );
574
575 std::shared_ptr<SHAPE_POLY_SET> poly = zone->GetFilledPolysList( layer );
576 long long int minArea = zone->GetMinIslandArea();
577 ISLAND_REMOVAL_MODE mode = zone->GetIslandRemovalMode();
578
579 for( int idx : islands )
580 {
581 SHAPE_LINE_CHAIN& outline = poly->Outline( idx );
582
583 if( mode == ISLAND_REMOVAL_MODE::ALWAYS )
584 poly->DeletePolygonAndTriangulationData( idx, false );
585 else if ( mode == ISLAND_REMOVAL_MODE::AREA && outline.Area( true ) < minArea )
586 poly->DeletePolygonAndTriangulationData( idx, false );
587 else
588 zone->SetIsIsland( layer, idx );
589 }
590
591 poly->UpdateTriangulationDataHash();
592 zone->CalculateFilledArea();
593
595 return false;
596 }
597 }
598
599 // Now remove islands which are either outside the board edge or fail to meet the minimum
600 // area requirements
601 using island_check_return = std::vector<std::pair<std::shared_ptr<SHAPE_POLY_SET>, int>>;
602
603 std::vector<std::pair<std::shared_ptr<SHAPE_POLY_SET>, double>> polys_to_check;
604
605 // rough estimate to save re-allocation time
606 polys_to_check.reserve( m_board->GetCopperLayerCount() * aZones.size() );
607
608 for( ZONE* zone : aZones )
609 {
610 LSET zoneCopperLayers = zone->GetLayerSet() & LSET::AllCuMask( MAX_CU_LAYERS );
611
612 // Min-thickness is the web thickness. On the other hand, a blob min-thickness by
613 // min-thickness is not useful. Since there's no obvious definition of web vs. blob, we
614 // arbitrarily choose "at least 3X the area".
615 double minArea = (double) zone->GetMinThickness() * zone->GetMinThickness() * 3;
616
617 for( PCB_LAYER_ID layer : zoneCopperLayers.Seq() )
618 {
620 continue;
621
622 polys_to_check.emplace_back( zone->GetFilledPolysList( layer ), minArea );
623 }
624 }
625
626 auto island_lambda =
627 [&]( int aStart, int aEnd ) -> island_check_return
628 {
629 island_check_return retval;
630
631 for( int ii = aStart; ii < aEnd && !cancelled; ++ii )
632 {
633 auto [poly, minArea] = polys_to_check[ii];
634
635 for( int jj = poly->OutlineCount() - 1; jj >= 0; jj-- )
636 {
637 SHAPE_POLY_SET island;
638 SHAPE_POLY_SET intersection;
639 const SHAPE_LINE_CHAIN& test_poly = poly->Polygon( jj ).front();
640 double island_area = test_poly.Area();
641
642 if( island_area < minArea )
643 continue;
644
645
646 island.AddOutline( test_poly );
647 intersection.BooleanIntersection( m_boardOutline, island,
649
650 // Nominally, all of these areas should be either inside or outside the
651 // board outline. So this test should be able to just compare areas (if
652 // they are equal, you are inside). But in practice, we sometimes have
653 // slight overlap at the edges, so testing against half-size area acts as
654 // a fail-safe.
655 if( intersection.Area() < island_area / 2.0 )
656 retval.emplace_back( poly, jj );
657 }
658 }
659
660 return retval;
661 };
662
663 auto island_returns = tp.parallelize_loop( 0, polys_to_check.size(), island_lambda );
664 cancelled = false;
665
666 // Allow island removal threads to finish
667 for( size_t ii = 0; ii < island_returns.size(); ++ii )
668 {
669 std::future<island_check_return>& ret = island_returns[ii];
670
671 if( ret.valid() )
672 {
673 std::future_status status = ret.wait_for( std::chrono::seconds( 0 ) );
674
675 while( status != std::future_status::ready )
676 {
678 {
680
682 cancelled = true;
683 }
684
685 status = ret.wait_for( std::chrono::milliseconds( 100 ) );
686 }
687 }
688 }
689
690 if( cancelled )
691 return false;
692
693 for( size_t ii = 0; ii < island_returns.size(); ++ii )
694 {
695 std::future<island_check_return>& ret = island_returns[ii];
696
697 if( ret.valid() )
698 {
699 for( auto& action_item : ret.get() )
700 action_item.first->DeletePolygonAndTriangulationData( action_item.second, true );
701 }
702 }
703
704 for( ZONE* zone : aZones )
705 zone->CalculateFilledArea();
706
707
708 if( aCheck )
709 {
710 bool outOfDate = false;
711
712 for( ZONE* zone : aZones )
713 {
714 // Keepout zones are not filled
715 if( zone->GetIsRuleArea() )
716 continue;
717
718 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
719 {
720 zone->BuildHashValue( layer );
721
722 if( oldFillHashes[ { zone, layer } ] != zone->GetHashValue( layer ) )
723 outOfDate = true;
724 }
725 }
726
727 if( outOfDate )
728 {
729 KIDIALOG dlg( aParent, _( "Zone fills are out-of-date. Refill?" ),
730 _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
731 dlg.SetOKCancelLabels( _( "Refill" ), _( "Continue without Refill" ) );
732 dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
733
734 if( dlg.ShowModal() == wxID_CANCEL )
735 return false;
736 }
737 else
738 {
739 // No need to commit something that hasn't changed (and committing will set
740 // the modified flag).
741 return false;
742 }
743 }
744
746 {
748 return false;
749
752 }
753
754 return true;
755}
756
757
762void ZONE_FILLER::addKnockout( PAD* aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles )
763{
764 if( aPad->GetShape() == PAD_SHAPE::CUSTOM )
765 {
766 SHAPE_POLY_SET poly;
767 aPad->TransformShapeToPolygon( poly, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
768
769 // the pad shape in zone can be its convex hull or the shape itself
771 {
772 std::vector<VECTOR2I> convex_hull;
773 BuildConvexHull( convex_hull, poly );
774
775 aHoles.NewOutline();
776
777 for( const VECTOR2I& pt : convex_hull )
778 aHoles.Append( pt );
779 }
780 else
781 aHoles.Append( poly );
782 }
783 else
784 {
785 aPad->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
786 }
787}
788
789
793void ZONE_FILLER::addHoleKnockout( PAD* aPad, int aGap, SHAPE_POLY_SET& aHoles )
794{
795 aPad->TransformHoleToPolygon( aHoles, aGap, m_maxError, ERROR_OUTSIDE );
796}
797
798
803void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap,
804 bool aIgnoreLineWidth, SHAPE_POLY_SET& aHoles )
805{
806 switch( aItem->Type() )
807 {
808 case PCB_FIELD_T:
809 case PCB_TEXT_T:
810 {
811 PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
812
813 if( text->IsVisible() )
814 {
815 if( text->IsKnockout() )
816 {
817 int antiGap = -m_maxError * 2; // Don't leave gaps around knockout text
818 text->TransformShapeToPolygon( aHoles, aLayer, antiGap, m_maxError, ERROR_OUTSIDE );
819 }
820 else
821 {
822 text->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
823 }
824 }
825
826 break;
827 }
828
829 case PCB_TEXTBOX_T:
830 {
831 PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( aItem );
832
833 if( textbox->IsVisible() )
834 textbox->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
835
836 break;
837 }
838
839 case PCB_SHAPE_T:
840 case PCB_TARGET_T:
841 aItem->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE,
842 aIgnoreLineWidth );
843 break;
844
845 default:
846 break;
847 }
848}
849
850
856 SHAPE_POLY_SET& aFill,
857 std::vector<PAD*>& aThermalConnectionPads,
858 std::vector<PAD*>& aNoConnectionPads )
859{
861 ZONE_CONNECTION connection;
862 DRC_CONSTRAINT constraint;
863 int padClearance;
864 int holeClearance;
865 SHAPE_POLY_SET holes;
866
867 for( FOOTPRINT* footprint : m_board->Footprints() )
868 {
869 for( PAD* pad : footprint->Pads() )
870 {
871 BOX2I padBBox = pad->GetBoundingBox();
872 padBBox.Inflate( m_worstClearance );
873
874 if( !padBBox.Intersects( aZone->GetBoundingBox() ) )
875 continue;
876
877 if( pad->GetNetCode() != aZone->GetNetCode()
878 || pad->GetNetCode() <= 0
879 || pad->GetZoneLayerOverride( aLayer ) == ZLO_FORCE_NO_ZONE_CONNECTION )
880 {
881 // collect these for knockout in buildCopperItemClearances()
882 aNoConnectionPads.push_back( pad );
883 continue;
884 }
885
886 if( aZone->IsTeardropArea() )
887 {
888 connection = ZONE_CONNECTION::FULL;
889 }
890 else
891 {
892 constraint = bds.m_DRCEngine->EvalZoneConnection( pad, aZone, aLayer );
893 connection = constraint.m_ZoneConnection;
894 }
895
896 switch( connection )
897 {
898 case ZONE_CONNECTION::THERMAL:
899 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone,
900 aLayer );
901 padClearance = constraint.GetValue().Min();
902
903 if( pad->CanFlashLayer( aLayer ) )
904 {
905 aThermalConnectionPads.push_back( pad );
906 addKnockout( pad, aLayer, padClearance, holes );
907 }
908 else if( pad->GetDrillSize().x > 0 )
909 {
910 pad->TransformHoleToPolygon( holes, padClearance, m_maxError, ERROR_OUTSIDE );
911 }
912
913 break;
914
915 case ZONE_CONNECTION::NONE:
916 constraint = bds.m_DRCEngine->EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, pad,
917 aZone, aLayer );
918
919 if( constraint.GetValue().Min() > aZone->GetLocalClearance() )
920 padClearance = constraint.GetValue().Min();
921 else
922 padClearance = aZone->GetLocalClearance();
923
924 if( pad->FlashLayer( aLayer ) )
925 {
926 addKnockout( pad, aLayer, padClearance, holes );
927 }
928 else if( pad->GetDrillSize().x > 0 )
929 {
930 constraint = bds.m_DRCEngine->EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT,
931 pad, aZone, aLayer );
932
933 if( constraint.GetValue().Min() > padClearance )
934 holeClearance = constraint.GetValue().Min();
935 else
936 holeClearance = padClearance;
937
938 pad->TransformHoleToPolygon( holes, holeClearance, m_maxError, ERROR_OUTSIDE );
939 }
940
941 break;
942
943 default:
944 // No knockout
945 continue;
946 }
947 }
948 }
949
951}
952
953
959 const std::vector<PAD*> aNoConnectionPads,
960 SHAPE_POLY_SET& aHoles )
961{
963 long ticker = 0;
964
965 auto checkForCancel =
966 [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool
967 {
968 return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled();
969 };
970
971 // A small extra clearance to be sure actual track clearances are not smaller than
972 // requested clearance due to many approximations in calculations, like arc to segment
973 // approx, rounding issues, etc.
974 BOX2I zone_boundingbox = aZone->GetBoundingBox();
976
977 // Items outside the zone bounding box are skipped, so it needs to be inflated by the
978 // largest clearance value found in the netclasses and rules
979 zone_boundingbox.Inflate( m_worstClearance + extra_margin );
980
981 auto evalRulesForItems =
982 [&bds]( DRC_CONSTRAINT_T aConstraint, const BOARD_ITEM* a, const BOARD_ITEM* b,
983 PCB_LAYER_ID aEvalLayer ) -> int
984 {
985 DRC_CONSTRAINT c = bds.m_DRCEngine->EvalRules( aConstraint, a, b, aEvalLayer );
986 return c.GetValue().Min();
987 };
988
989 // Add non-connected pad clearances
990 //
991 auto knockoutPadClearance =
992 [&]( PAD* aPad )
993 {
994 int init_gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone, aPad, aLayer );
995 int gap = init_gap;
996 bool hasHole = aPad->GetDrillSize().x > 0;
997 bool flashLayer = aPad->FlashLayer( aLayer );
998 bool platedHole = hasHole && aPad->GetAttribute() == PAD_ATTRIB::PTH;
999
1000 if( flashLayer || platedHole )
1001 {
1002 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
1003 aZone, aPad, aLayer ) );
1004 }
1005
1006 if( flashLayer && gap > 0 )
1007 addKnockout( aPad, aLayer, gap + extra_margin, aHoles );
1008
1009 if( hasHole )
1010 {
1011 // NPTH do not need copper clearance gaps to their holes
1012 if( aPad->GetAttribute() == PAD_ATTRIB::NPTH )
1013 gap = init_gap;
1014
1015 gap = std::max( gap, evalRulesForItems( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT,
1016 aZone, aPad, aLayer ) );
1017
1018 gap = std::max( gap, evalRulesForItems( HOLE_CLEARANCE_CONSTRAINT,
1019 aZone, aPad, aLayer ) );
1020
1021 if( gap > 0 )
1022 addHoleKnockout( aPad, gap + extra_margin, aHoles );
1023 }
1024 };
1025
1026 for( PAD* pad : aNoConnectionPads )
1027 {
1028 if( checkForCancel( m_progressReporter ) )
1029 return;
1030
1031 knockoutPadClearance( pad );
1032 }
1033
1034 // Add non-connected track clearances
1035 //
1036 auto knockoutTrackClearance =
1037 [&]( PCB_TRACK* aTrack )
1038 {
1039 if( aTrack->GetBoundingBox().Intersects( zone_boundingbox ) )
1040 {
1041 bool sameNet = aTrack->GetNetCode() == aZone->GetNetCode()
1042 && aZone->GetNetCode() != 0;
1043
1044 int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
1045 aZone, aTrack, aLayer );
1046
1047 if( aTrack->Type() == PCB_VIA_T )
1048 {
1049 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
1050
1051 if( via->GetZoneLayerOverride( aLayer ) == ZLO_FORCE_NO_ZONE_CONNECTION )
1052 sameNet = false;
1053 }
1054
1055 if( !sameNet )
1056 {
1057 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
1058 aZone, aTrack, aLayer ) );
1059 }
1060
1061 if( aTrack->Type() == PCB_VIA_T )
1062 {
1063 PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
1064
1065 if( via->FlashLayer( aLayer ) && gap > 0 )
1066 {
1067 via->TransformShapeToPolygon( aHoles, aLayer, gap + extra_margin,
1069 }
1070
1071 gap = std::max( gap, evalRulesForItems( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT,
1072 aZone, via, aLayer ) );
1073
1074 if( !sameNet )
1075 {
1076 gap = std::max( gap, evalRulesForItems( HOLE_CLEARANCE_CONSTRAINT,
1077 aZone, via, aLayer ) );
1078 }
1079
1080 if( gap > 0 )
1081 {
1082 int radius = via->GetDrillValue() / 2;
1083
1084 TransformCircleToPolygon( aHoles, via->GetPosition(),
1085 radius + gap + extra_margin,
1087 }
1088 }
1089 else
1090 {
1091 if( gap > 0 )
1092 {
1093 aTrack->TransformShapeToPolygon( aHoles, aLayer, gap + extra_margin,
1095 }
1096 }
1097 }
1098 };
1099
1100 for( PCB_TRACK* track : m_board->Tracks() )
1101 {
1102 if( !track->IsOnLayer( aLayer ) )
1103 continue;
1104
1105 if( checkForCancel( m_progressReporter ) )
1106 return;
1107
1108 knockoutTrackClearance( track );
1109 }
1110
1111 // Add graphic item clearances.
1112 //
1113 auto knockoutGraphicClearance =
1114 [&]( BOARD_ITEM* aItem )
1115 {
1116 int shapeNet = ( aItem->Type() == PCB_SHAPE_T ) ? static_cast<PCB_SHAPE*>( aItem )->GetNetCode() : -1;
1117 bool sameNet = shapeNet == aZone->GetNetCode() && aZone->GetNetCode() != 0;
1118
1119 // A item on the Edge_Cuts or Margin is always seen as on any layer:
1120 if( aItem->IsOnLayer( aLayer )
1121 || aItem->IsOnLayer( Edge_Cuts )
1122 || aItem->IsOnLayer( Margin ) )
1123 {
1124 if( aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
1125 {
1126 bool ignoreLineWidths = false;
1127 int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
1128 aZone, aItem, aLayer );
1129
1130 if( aItem->IsOnLayer( aLayer ) && !sameNet )
1131 {
1132 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
1133 aZone, aItem, aLayer ) );
1134 }
1135 else if( aItem->IsOnLayer( Edge_Cuts ) )
1136 {
1137 gap = std::max( gap, evalRulesForItems( EDGE_CLEARANCE_CONSTRAINT,
1138 aZone, aItem, Edge_Cuts ) );
1139 ignoreLineWidths = true;
1140 }
1141 else if( aItem->IsOnLayer( Margin ) )
1142 {
1143 gap = std::max( gap, evalRulesForItems( EDGE_CLEARANCE_CONSTRAINT,
1144 aZone, aItem, Margin ) );
1145 }
1146
1147 if( gap > 0 )
1148 addKnockout( aItem, aLayer, gap + extra_margin, ignoreLineWidths, aHoles );
1149 }
1150 }
1151 };
1152
1153 for( FOOTPRINT* footprint : m_board->Footprints() )
1154 {
1155 knockoutGraphicClearance( &footprint->Reference() );
1156 knockoutGraphicClearance( &footprint->Value() );
1157
1158 std::set<PAD*> allowedNetTiePads;
1159
1160 // Don't knock out holes for graphic items which implement a net-tie to the zone's net
1161 // on the layer being filled.
1162 if( footprint->IsNetTie() )
1163 {
1164 for( PAD* pad : footprint->Pads() )
1165 {
1166 if( pad->GetNetCode() == aZone->GetNetCode() )
1167 {
1168 if( pad->IsOnLayer( aLayer ) )
1169 allowedNetTiePads.insert( pad );
1170
1171 for( PAD* other : footprint->GetNetTiePads( pad ) )
1172 {
1173 if( other->IsOnLayer( aLayer ) )
1174 allowedNetTiePads.insert( other );
1175 }
1176 }
1177 }
1178 }
1179
1180 for( BOARD_ITEM* item : footprint->GraphicalItems() )
1181 {
1182 if( checkForCancel( m_progressReporter ) )
1183 return;
1184
1185 BOX2I itemBBox = item->GetBoundingBox();
1186
1187 if( !zone_boundingbox.Intersects( itemBBox ) )
1188 continue;
1189
1190 bool skipItem = false;
1191
1192 if( item->IsOnLayer( aLayer ) )
1193 {
1194 std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape();
1195
1196 for( PAD* pad : allowedNetTiePads )
1197 {
1198 if( pad->GetBoundingBox().Intersects( itemBBox )
1199 && pad->GetEffectiveShape()->Collide( itemShape.get() ) )
1200 {
1201 skipItem = true;
1202 break;
1203 }
1204 }
1205 }
1206
1207 if( !skipItem )
1208 knockoutGraphicClearance( item );
1209 }
1210 }
1211
1212 for( BOARD_ITEM* item : m_board->Drawings() )
1213 {
1214 if( checkForCancel( m_progressReporter ) )
1215 return;
1216
1217 knockoutGraphicClearance( item );
1218 }
1219
1220 // Add non-connected zone clearances
1221 //
1222 auto knockoutZoneClearance =
1223 [&]( ZONE* aKnockout )
1224 {
1225 // If the zones share no common layers
1226 if( !aKnockout->GetLayerSet().test( aLayer ) )
1227 return;
1228
1229 if( aKnockout->GetBoundingBox().Intersects( zone_boundingbox ) )
1230 {
1231 if( aKnockout->GetIsRuleArea() )
1232 {
1233 // Keepouts use outline with no clearance
1234 aKnockout->TransformSmoothedOutlineToPolygon( aHoles, 0, m_maxError,
1235 ERROR_OUTSIDE, nullptr );
1236 }
1237 else
1238 {
1239 int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone,
1240 aKnockout, aLayer );
1241
1242 gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT, aZone,
1243 aKnockout, aLayer ) );
1244
1245 SHAPE_POLY_SET poly;
1246 aKnockout->TransformShapeToPolygon( poly, aLayer, gap + extra_margin,
1248 aHoles.Append( poly );
1249 }
1250 }
1251 };
1252
1253 for( ZONE* otherZone : m_board->Zones() )
1254 {
1255 if( checkForCancel( m_progressReporter ) )
1256 return;
1257
1258 // Negative clearance permits zones to short
1259 if( evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, otherZone, aLayer ) < 0 )
1260 continue;
1261
1262 if( otherZone->GetIsRuleArea() )
1263 {
1264 if( otherZone->GetDoNotAllowCopperPour() && !aZone->IsTeardropArea() )
1265 knockoutZoneClearance( otherZone );
1266 }
1267 else if( otherZone->HigherPriority( aZone ) )
1268 {
1269 if( !otherZone->SameNet( aZone ) )
1270 knockoutZoneClearance( otherZone );
1271 }
1272 }
1273
1274 for( FOOTPRINT* footprint : m_board->Footprints() )
1275 {
1276 for( ZONE* otherZone : footprint->Zones() )
1277 {
1278 if( checkForCancel( m_progressReporter ) )
1279 return;
1280
1281 if( otherZone->GetIsRuleArea() )
1282 {
1283 if( otherZone->GetDoNotAllowCopperPour() && !aZone->IsTeardropArea() )
1284 knockoutZoneClearance( otherZone );
1285 }
1286 else if( otherZone->HigherPriority( aZone ) )
1287 {
1288 if( !otherZone->SameNet( aZone ) )
1289 knockoutZoneClearance( otherZone );
1290 }
1291 }
1292 }
1293
1295}
1296
1297
1303 SHAPE_POLY_SET& aRawFill )
1304{
1305 BOX2I zoneBBox = aZone->GetBoundingBox();
1306
1307 auto knockoutZoneOutline =
1308 [&]( ZONE* aKnockout )
1309 {
1310 // If the zones share no common layers
1311 if( !aKnockout->GetLayerSet().test( aLayer ) )
1312 return;
1313
1314 if( aKnockout->GetBoundingBox().Intersects( zoneBBox ) )
1315 {
1316 // Processing of arc shapes in zones is not yet supported because Clipper
1317 // can't do boolean operations on them. The poly outline must be converted to
1318 // segments first.
1319 SHAPE_POLY_SET outline = aKnockout->Outline()->CloneDropTriangulation();
1320 outline.ClearArcs();
1321
1322 aRawFill.BooleanSubtract( outline, SHAPE_POLY_SET::PM_FAST );
1323 }
1324 };
1325
1326 for( ZONE* otherZone : m_board->Zones() )
1327 {
1328 // Don't use the `HigherPriority()` check here because we _only_ want to knock out zones
1329 // with explicitly higher priorities, not those with equal priorities
1330 if( otherZone->SameNet( aZone )
1331 && otherZone->GetAssignedPriority() > aZone->GetAssignedPriority() )
1332 {
1333 // Do not remove teardrop area: it is not useful and not good
1334 if( !otherZone->IsTeardropArea() )
1335 knockoutZoneOutline( otherZone );
1336 }
1337 }
1338
1339 for( FOOTPRINT* footprint : m_board->Footprints() )
1340 {
1341 for( ZONE* otherZone : footprint->Zones() )
1342 {
1343 if( otherZone->SameNet( aZone ) && otherZone->HigherPriority( aZone ) )
1344 {
1345 // Do not remove teardrop area: it is not useful and not good
1346 if( !otherZone->IsTeardropArea() )
1347 knockoutZoneOutline( otherZone );
1348 }
1349 }
1350 }
1351}
1352
1353
1354#define DUMP_POLYS_TO_COPPER_LAYER( a, b, c ) \
1355 { if( m_debugZoneFiller && aDebugLayer == b ) \
1356 { \
1357 m_board->SetLayerName( b, c ); \
1358 SHAPE_POLY_SET d = a; \
1359 d.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); \
1360 aFillPolys = d; \
1361 return false; \
1362 } \
1363 }
1364
1365
1377bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer,
1378 const SHAPE_POLY_SET& aSmoothedOutline,
1379 const SHAPE_POLY_SET& aMaxExtents, SHAPE_POLY_SET& aFillPolys )
1380{
1382
1383 // Features which are min_width should survive pruning; features that are *less* than
1384 // min_width should not. Therefore we subtract epsilon from the min_width when
1385 // deflating/inflating.
1386 int half_min_width = aZone->GetMinThickness() / 2;
1387 int epsilon = pcbIUScale.mmToIU( 0.001 );
1388
1389 // Solid polygons are deflated and inflated during calculations. Deflating doesn't cause
1390 // issues, but inflate is tricky as it can create excessively long and narrow spikes for
1391 // acute angles.
1392 // ALLOW_ACUTE_CORNERS cannot be used due to the spike problem.
1393 // CHAMFER_ACUTE_CORNERS is tempting, but can still produce spikes in some unusual
1394 // circumstances (https://gitlab.com/kicad/code/kicad/-/issues/5581).
1395 // It's unclear if ROUND_ACUTE_CORNERS would have the same issues, but is currently avoided
1396 // as a "less-safe" option.
1397 // ROUND_ALL_CORNERS produces the uniformly nicest shapes, but also a lot of segments.
1398 // CHAMFER_ALL_CORNERS improves the segment count.
1401
1402 std::vector<PAD*> thermalConnectionPads;
1403 std::vector<PAD*> noConnectionPads;
1404 std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
1405 SHAPE_POLY_SET clearanceHoles;
1406
1407 aFillPolys = aSmoothedOutline;
1408 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In1_Cu, wxT( "smoothed-outline" ) );
1409
1411 return false;
1412
1413 /* -------------------------------------------------------------------------------------
1414 * Knockout thermal reliefs.
1415 */
1416
1417 knockoutThermalReliefs( aZone, aLayer, aFillPolys, thermalConnectionPads, noConnectionPads );
1418 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In2_Cu, wxT( "minus-thermal-reliefs" ) );
1419
1421 return false;
1422
1423 /* -------------------------------------------------------------------------------------
1424 * Knockout electrical clearances.
1425 */
1426
1427 buildCopperItemClearances( aZone, aLayer, noConnectionPads, clearanceHoles );
1428 DUMP_POLYS_TO_COPPER_LAYER( clearanceHoles, In3_Cu, wxT( "clearance-holes" ) );
1429
1431 return false;
1432
1433 /* -------------------------------------------------------------------------------------
1434 * Add thermal relief spokes.
1435 */
1436
1437 buildThermalSpokes( aZone, aLayer, thermalConnectionPads, thermalSpokes );
1438
1440 return false;
1441
1442 // Create a temporary zone that we can hit-test spoke-ends against. It's only temporary
1443 // because the "real" subtract-clearance-holes has to be done after the spokes are added.
1444 static const bool USE_BBOX_CACHES = true;
1445 SHAPE_POLY_SET testAreas = aFillPolys.CloneDropTriangulation();
1446 testAreas.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
1447 DUMP_POLYS_TO_COPPER_LAYER( testAreas, In4_Cu, wxT( "minus-clearance-holes" ) );
1448
1449 // Prune features that don't meet minimum-width criteria
1450 if( half_min_width - epsilon > epsilon )
1451 {
1452 testAreas.Deflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
1453 DUMP_POLYS_TO_COPPER_LAYER( testAreas, In5_Cu, wxT( "spoke-test-deflated" ) );
1454
1455 testAreas.Inflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
1456 DUMP_POLYS_TO_COPPER_LAYER( testAreas, In6_Cu, wxT( "spoke-test-reinflated" ) );
1457 }
1458
1460 return false;
1461
1462 // Spoke-end-testing is hugely expensive so we generate cached bounding-boxes to speed
1463 // things up a bit.
1464 testAreas.BuildBBoxCaches();
1465 int interval = 0;
1466
1467 SHAPE_POLY_SET debugSpokes;
1468
1469 for( const SHAPE_LINE_CHAIN& spoke : thermalSpokes )
1470 {
1471 const VECTOR2I& testPt = spoke.CPoint( 3 );
1472
1473 // Hit-test against zone body
1474 if( testAreas.Contains( testPt, -1, 1, USE_BBOX_CACHES ) )
1475 {
1476 if( m_debugZoneFiller )
1477 debugSpokes.AddOutline( spoke );
1478
1479 aFillPolys.AddOutline( spoke );
1480 continue;
1481 }
1482
1483 if( interval++ > 400 )
1484 {
1486 return false;
1487
1488 interval = 0;
1489 }
1490
1491 // Hit-test against other spokes
1492 for( const SHAPE_LINE_CHAIN& other : thermalSpokes )
1493 {
1494 // Hit test in both directions to avoid interactions with round-off errors.
1495 // (See https://gitlab.com/kicad/code/kicad/-/issues/13316.)
1496 if( &other != &spoke
1497 && other.PointInside( testPt, 1, USE_BBOX_CACHES )
1498 && spoke.PointInside( other.CPoint( 3 ), 1, USE_BBOX_CACHES ) )
1499 {
1500 if( m_debugZoneFiller )
1501 debugSpokes.AddOutline( spoke );
1502
1503 aFillPolys.AddOutline( spoke );
1504 break;
1505 }
1506 }
1507 }
1508
1509 DUMP_POLYS_TO_COPPER_LAYER( debugSpokes, In7_Cu, wxT( "spokes" ) );
1510
1512 return false;
1513
1514 aFillPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
1515 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In8_Cu, wxT( "after-spoke-trimming" ) );
1516
1517 /* -------------------------------------------------------------------------------------
1518 * Prune features that don't meet minimum-width criteria
1519 */
1520
1521 if( half_min_width - epsilon > epsilon )
1522 aFillPolys.Deflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
1523
1524 // Min-thickness is the web thickness. On the other hand, a blob min-thickness by
1525 // min-thickness is not useful. Since there's no obvious definition of web vs. blob, we
1526 // arbitrarily choose "at least 2X min-thickness on one axis". (Since we're doing this
1527 // during the deflated state, that means we test for "at least min-thickness".)
1528 for( int ii = aFillPolys.OutlineCount() - 1; ii >= 0; ii-- )
1529 {
1530 std::vector<SHAPE_LINE_CHAIN>& island = aFillPolys.Polygon( ii );
1531 BOX2I islandExtents;
1532
1533 for( const VECTOR2I& pt : island.front().CPoints() )
1534 {
1535 islandExtents.Merge( pt );
1536
1537 if( islandExtents.GetSizeMax() > aZone->GetMinThickness() )
1538 break;
1539 }
1540
1541 if( islandExtents.GetSizeMax() < aZone->GetMinThickness() )
1542 aFillPolys.DeletePolygon( ii );
1543 }
1544
1545 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In9_Cu, wxT( "deflated" ) );
1546
1548 return false;
1549
1550 /* -------------------------------------------------------------------------------------
1551 * Process the hatch pattern (note that we do this while deflated)
1552 */
1553
1554 if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
1555 {
1556 if( !addHatchFillTypeOnZone( aZone, aLayer, aDebugLayer, aFillPolys ) )
1557 return false;
1558 }
1559
1561 return false;
1562
1563 /* -------------------------------------------------------------------------------------
1564 * Finish minimum-width pruning by re-inflating
1565 */
1566
1567 if( half_min_width - epsilon > epsilon )
1568 aFillPolys.Inflate( half_min_width - epsilon, cornerStrategy, m_maxError, true );
1569
1570 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In15_Cu, wxT( "after-reinflating" ) );
1571
1572 /* -------------------------------------------------------------------------------------
1573 * Ensure additive changes (thermal stubs and inflating acute corners) do not add copper
1574 * outside the zone boundary, inside the clearance holes, or between otherwise isolated
1575 * islands
1576 */
1577
1578 for( PAD* pad : thermalConnectionPads )
1579 addHoleKnockout( pad, 0, clearanceHoles );
1580
1581 aFillPolys.BooleanIntersection( aMaxExtents, SHAPE_POLY_SET::PM_FAST );
1582 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In16_Cu, wxT( "after-trim-to-outline" ) );
1583 aFillPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
1584 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In17_Cu, wxT( "after-trim-to-clearance-holes" ) );
1585
1586 /* -------------------------------------------------------------------------------------
1587 * Lastly give any same-net but higher-priority zones control over their own area.
1588 */
1589
1590 subtractHigherPriorityZones( aZone, aLayer, aFillPolys );
1591 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In18_Cu, wxT( "minus-higher-priority-zones" ) );
1592
1593 aFillPolys.Fracture( SHAPE_POLY_SET::PM_FAST );
1594 return true;
1595}
1596
1597
1599 const SHAPE_POLY_SET& aSmoothedOutline,
1600 SHAPE_POLY_SET& aFillPolys )
1601{
1603 BOX2I zone_boundingbox = aZone->GetBoundingBox();
1604 SHAPE_POLY_SET clearanceHoles;
1605 long ticker = 0;
1606
1607 auto checkForCancel =
1608 [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool
1609 {
1610 return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled();
1611 };
1612
1613 auto knockoutGraphicClearance =
1614 [&]( BOARD_ITEM* aItem )
1615 {
1616 if( aItem->IsKnockout() && aItem->IsOnLayer( aLayer )
1617 && aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
1618 {
1620 aZone, aItem, aLayer );
1621
1622 addKnockout( aItem, aLayer, cc.GetValue().Min(), false, clearanceHoles );
1623 }
1624 };
1625
1626 for( FOOTPRINT* footprint : m_board->Footprints() )
1627 {
1628 if( checkForCancel( m_progressReporter ) )
1629 return false;
1630
1631 knockoutGraphicClearance( &footprint->Reference() );
1632 knockoutGraphicClearance( &footprint->Value() );
1633
1634 for( BOARD_ITEM* item : footprint->GraphicalItems() )
1635 knockoutGraphicClearance( item );
1636 }
1637
1638 for( BOARD_ITEM* item : m_board->Drawings() )
1639 {
1640 if( checkForCancel( m_progressReporter ) )
1641 return false;
1642
1643 knockoutGraphicClearance( item );
1644 }
1645
1646 aFillPolys = aSmoothedOutline;
1647 aFillPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
1648
1649 for( ZONE* keepout : m_board->Zones() )
1650 {
1651 if( !keepout->GetIsRuleArea() )
1652 continue;
1653
1654 if( keepout->GetDoNotAllowCopperPour() && keepout->IsOnLayer( aLayer ) )
1655 {
1656 if( keepout->GetBoundingBox().Intersects( zone_boundingbox ) )
1657 aFillPolys.BooleanSubtract( *keepout->Outline(), SHAPE_POLY_SET::PM_FAST );
1658 }
1659 }
1660
1661 // Features which are min_width should survive pruning; features that are *less* than
1662 // min_width should not. Therefore we subtract epsilon from the min_width when
1663 // deflating/inflating.
1664 int half_min_width = aZone->GetMinThickness() / 2;
1665 int epsilon = pcbIUScale.mmToIU( 0.001 );
1666
1667 aFillPolys.Deflate( half_min_width - epsilon, SHAPE_POLY_SET::CHAMFER_ALL_CORNERS, m_maxError );
1668
1669 // Remove the non filled areas due to the hatch pattern
1670 if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
1671 {
1672 if( !addHatchFillTypeOnZone( aZone, aLayer, aLayer, aFillPolys ) )
1673 return false;
1674 }
1675
1676 // Re-inflate after pruning of areas that don't meet minimum-width criteria
1677 if( half_min_width - epsilon > epsilon )
1678 aFillPolys.Inflate( half_min_width - epsilon, SHAPE_POLY_SET::ROUND_ALL_CORNERS, m_maxError );
1679
1681 return true;
1682}
1683
1684
1685/*
1686 * Build the filled solid areas data from real outlines (stored in m_Poly)
1687 * The solid areas can be more than one on copper layers, and do not have holes
1688 * ( holes are linked by overlapping segments to the main outline)
1689 */
1691{
1692 SHAPE_POLY_SET* boardOutline = m_brdOutlinesValid ? &m_boardOutline : nullptr;
1693 SHAPE_POLY_SET maxExtents;
1694 SHAPE_POLY_SET smoothedPoly;
1695 PCB_LAYER_ID debugLayer = UNDEFINED_LAYER;
1696
1697 if( m_debugZoneFiller && LSET::InternalCuMask().Contains( aLayer ) )
1698 {
1699 debugLayer = aLayer;
1700 aLayer = F_Cu;
1701 }
1702
1703 if( !aZone->BuildSmoothedPoly( maxExtents, aLayer, boardOutline, &smoothedPoly ) )
1704 return false;
1705
1707 return false;
1708
1709 if( aZone->IsOnCopperLayer() )
1710 {
1711 if( fillCopperZone( aZone, aLayer, debugLayer, smoothedPoly, maxExtents, aFillPolys ) )
1712 aZone->SetNeedRefill( false );
1713 }
1714 else
1715 {
1716 if( fillNonCopperZone( aZone, aLayer, smoothedPoly, aFillPolys ) )
1717 aZone->SetNeedRefill( false );
1718 }
1719
1720 return true;
1721}
1722
1723
1728 const std::vector<PAD*>& aSpokedPadsList,
1729 std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
1730{
1732 BOX2I zoneBB = aZone->GetBoundingBox();
1733 DRC_CONSTRAINT constraint;
1734
1735 zoneBB.Inflate( std::max( bds.GetBiggestClearanceValue(), aZone->GetLocalClearance() ) );
1736
1737 // Is a point on the boundary of the polygon inside or outside? This small epsilon lets
1738 // us avoid the question.
1739 int epsilon = KiROUND( pcbIUScale.IU_PER_MM * 0.04 ); // about 1.5 mil
1740
1741 for( PAD* pad : aSpokedPadsList )
1742 {
1743 // We currently only connect to pads, not pad holes
1744 if( !pad->IsOnLayer( aLayer ) )
1745 continue;
1746
1747 constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
1748 int thermalReliefGap = constraint.GetValue().Min();
1749
1750 constraint = bds.m_DRCEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, aZone, aLayer );
1751 int spoke_w = constraint.GetValue().Opt();
1752
1753 // Spoke width should ideally be smaller than the pad minor axis.
1754 // Otherwise the thermal shape is not really a thermal relief,
1755 // and the algo to count the actual number of spokes can fail
1756 int spoke_max_allowed_w = std::min( pad->GetSize().x, pad->GetSize().y );
1757
1758 spoke_w = std::max( spoke_w, constraint.Value().Min() );
1759 spoke_w = std::min( spoke_w, constraint.Value().Max() );
1760
1761 // ensure the spoke width is smaller than the pad minor size
1762 spoke_w = std::min( spoke_w, spoke_max_allowed_w );
1763
1764 // Cannot create stubs having a width < zone min thickness
1765 if( spoke_w < aZone->GetMinThickness() )
1766 continue;
1767
1768 int spoke_half_w = spoke_w / 2;
1769
1770 // Quick test here to possibly save us some work
1771 BOX2I itemBB = pad->GetBoundingBox();
1772 itemBB.Inflate( thermalReliefGap + epsilon );
1773
1774 if( !( itemBB.Intersects( zoneBB ) ) )
1775 continue;
1776
1777 bool customSpokes = false;
1778
1779 if( pad->GetShape() == PAD_SHAPE::CUSTOM )
1780 {
1781 for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives() )
1782 {
1783 if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
1784 {
1785 customSpokes = true;
1786 break;
1787 }
1788 }
1789 }
1790
1791 // Thermal spokes consist of square-ended segments from the pad center to points just
1792 // outside the thermal relief. The outside end has an extra center point (which must be
1793 // at idx 3) which is used for testing whether or not the spoke connects to copper in the
1794 // parent zone.
1795
1796 auto buildSpokesFromOrigin =
1797 [&]( const BOX2I& box )
1798 {
1799 for( int i = 0; i < 4; i++ )
1800 {
1801 SHAPE_LINE_CHAIN spoke;
1802
1803 switch( i )
1804 {
1805 case 0: // lower stub
1806 spoke.Append( +spoke_half_w, -spoke_half_w );
1807 spoke.Append( -spoke_half_w, -spoke_half_w );
1808 spoke.Append( -spoke_half_w, box.GetBottom() );
1809 spoke.Append( 0, box.GetBottom() ); // test pt
1810 spoke.Append( +spoke_half_w, box.GetBottom() );
1811 break;
1812
1813 case 1: // upper stub
1814 spoke.Append( +spoke_half_w, +spoke_half_w );
1815 spoke.Append( -spoke_half_w, +spoke_half_w );
1816 spoke.Append( -spoke_half_w, box.GetTop() );
1817 spoke.Append( 0, box.GetTop() ); // test pt
1818 spoke.Append( +spoke_half_w, box.GetTop() );
1819 break;
1820
1821 case 2: // right stub
1822 spoke.Append( -spoke_half_w, +spoke_half_w );
1823 spoke.Append( -spoke_half_w, -spoke_half_w );
1824 spoke.Append( box.GetRight(), -spoke_half_w );
1825 spoke.Append( box.GetRight(), 0 ); // test pt
1826 spoke.Append( box.GetRight(), +spoke_half_w );
1827 break;
1828
1829 case 3: // left stub
1830 spoke.Append( +spoke_half_w, +spoke_half_w );
1831 spoke.Append( +spoke_half_w, -spoke_half_w );
1832 spoke.Append( box.GetLeft(), -spoke_half_w );
1833 spoke.Append( box.GetLeft(), 0 ); // test pt
1834 spoke.Append( box.GetLeft(), +spoke_half_w );
1835 break;
1836 }
1837
1838 spoke.SetClosed( true );
1839 aSpokesList.push_back( std::move( spoke ) );
1840 }
1841 };
1842
1843 if( customSpokes )
1844 {
1845 SHAPE_POLY_SET thermalPoly;
1846 SHAPE_LINE_CHAIN thermalOutline;
1847
1848 pad->TransformShapeToPolygon( thermalPoly, aLayer, thermalReliefGap + epsilon,
1850
1851 if( thermalPoly.OutlineCount() )
1852 thermalOutline = thermalPoly.Outline( 0 );
1853
1854 for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives() )
1855 {
1856 if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
1857 {
1858 SEG seg( primitive->GetStart(), primitive->GetEnd() );
1859 SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
1860
1861 RotatePoint( seg.A, pad->GetOrientation() );
1862 RotatePoint( seg.B, pad->GetOrientation() );
1863 seg.A += pad->ShapePos();
1864 seg.B += pad->ShapePos();
1865
1866 // Make sure seg.A is the origin
1867 if( !pad->GetEffectivePolygon()->Contains( seg.A ) )
1868 seg.Reverse();
1869
1870 // Trim seg.B to the thermal outline
1871 if( thermalOutline.Intersect( seg, intersections ) )
1872 {
1873 seg.B = intersections.front().p;
1874
1875 VECTOR2I offset = ( seg.B - seg.A ).Perpendicular().Resize( spoke_half_w );
1876 SHAPE_LINE_CHAIN spoke;
1877
1878 spoke.Append( seg.A + offset );
1879 spoke.Append( seg.A - offset );
1880 spoke.Append( seg.B - offset );
1881 spoke.Append( seg.B ); // test pt
1882 spoke.Append( seg.B + offset );
1883
1884 spoke.SetClosed( true );
1885 aSpokesList.push_back( std::move( spoke ) );
1886 }
1887 }
1888 }
1889 }
1890 // If the spokes are at a cardinal angle then we can generate them from a bounding box
1891 // without trig.
1892 else if( ( pad->GetOrientation() + pad->GetThermalSpokeAngle() ).IsCardinal() )
1893 {
1894 BOX2I spokesBox = pad->GetBoundingBox();
1895 spokesBox.Inflate( thermalReliefGap + epsilon );
1896
1897 // Spokes are from center of pad shape, not from hole.
1898 spokesBox.Offset( - pad->ShapePos() );
1899
1900 buildSpokesFromOrigin( spokesBox );
1901
1902 auto spokeIter = aSpokesList.rbegin();
1903
1904 for( int ii = 0; ii < 4; ++ii, ++spokeIter )
1905 spokeIter->Move( pad->ShapePos() );
1906 }
1907 // Even if the spokes are rotated, we can fudge it for round and square pads by rotating
1908 // the bounding box to match the spokes.
1909 else if( pad->GetSizeX() == pad->GetSizeY() && pad->GetShape() != PAD_SHAPE::CUSTOM )
1910 {
1911 // Since the bounding-box needs to be correclty rotated we use a dummy pad to keep
1912 // from dirtying the real pad's cached shapes.
1913 PAD dummy_pad( *pad );
1914 dummy_pad.SetOrientation( pad->GetThermalSpokeAngle() );
1915
1916 // Spokes are from center of pad shape, not from hole. So the dummy pad has no shape
1917 // offset and is at position 0,0
1918 dummy_pad.SetPosition( VECTOR2I( 0, 0 ) );
1919 dummy_pad.SetOffset( VECTOR2I( 0, 0 ) );
1920
1921 BOX2I spokesBox = dummy_pad.GetBoundingBox();
1922 spokesBox.Inflate( thermalReliefGap + epsilon );
1923
1924 buildSpokesFromOrigin( spokesBox );
1925
1926 auto spokeIter = aSpokesList.rbegin();
1927
1928 for( int ii = 0; ii < 4; ++ii, ++spokeIter )
1929 {
1930 spokeIter->Rotate( pad->GetOrientation() + pad->GetThermalSpokeAngle() );
1931 spokeIter->Move( pad->ShapePos() );
1932 }
1933 }
1934 // And lastly, even when we have to resort to trig, we can use it only in a post-process
1935 // after the rotated-bounding-box trick from above.
1936 else
1937 {
1938 // Since the bounding-box needs to be correclty rotated we use a dummy pad to keep
1939 // from dirtying the real pad's cached shapes.
1940 PAD dummy_pad( *pad );
1941 dummy_pad.SetOrientation( pad->GetThermalSpokeAngle() );
1942
1943 // Spokes are from center of pad shape, not from hole. So the dummy pad has no shape
1944 // offset and is at position 0,0
1945 dummy_pad.SetPosition( VECTOR2I( 0, 0 ) );
1946 dummy_pad.SetOffset( VECTOR2I( 0, 0 ) );
1947
1948 BOX2I spokesBox = dummy_pad.GetBoundingBox();
1949
1950 // In this case make the box -big-; we're going to clip to the "real" bbox later.
1951 spokesBox.Inflate( thermalReliefGap + spokesBox.GetWidth() + spokesBox.GetHeight() );
1952
1953 buildSpokesFromOrigin( spokesBox );
1954
1955 BOX2I realBBox = pad->GetBoundingBox();
1956 realBBox.Inflate( thermalReliefGap + epsilon );
1957
1958 auto spokeIter = aSpokesList.rbegin();
1959
1960 for( int ii = 0; ii < 4; ++ii, ++spokeIter )
1961 {
1962 spokeIter->Rotate( pad->GetOrientation() + pad->GetThermalSpokeAngle() );
1963 spokeIter->Move( pad->ShapePos() );
1964
1965 VECTOR2I origin_p = spokeIter->GetPoint( 0 );
1966 VECTOR2I origin_m = spokeIter->GetPoint( 1 );
1967 VECTOR2I origin = ( origin_p + origin_m ) / 2;
1968 VECTOR2I end_m = spokeIter->GetPoint( 2 );
1969 VECTOR2I end = spokeIter->GetPoint( 3 );
1970 VECTOR2I end_p = spokeIter->GetPoint( 4 );
1971
1972 ClipLine( &realBBox, origin_p.x, origin_p.y, end_p.x, end_p.y );
1973 ClipLine( &realBBox, origin_m.x, origin_m.y, end_m.x, end_m.y );
1974 ClipLine( &realBBox, origin.x, origin.y, end.x, end.y );
1975
1976 spokeIter->SetPoint( 2, end_m );
1977 spokeIter->SetPoint( 3, end );
1978 spokeIter->SetPoint( 4, end_p );
1979 }
1980 }
1981 }
1982
1983 for( size_t ii = 0; ii < aSpokesList.size(); ++ii )
1984 aSpokesList[ii].GenerateBBoxCache();
1985}
1986
1987
1989 PCB_LAYER_ID aDebugLayer, SHAPE_POLY_SET& aFillPolys )
1990{
1991 // Build grid:
1992
1993 // obviously line thickness must be > zone min thickness.
1994 // It can happens if a board file was edited by hand by a python script
1995 // Use 1 micron margin to be *sure* there is no issue in Gerber files
1996 // (Gbr file unit = 1 or 10 nm) due to some truncation in coordinates or calculations
1997 // This margin also avoid problems due to rounding coordinates in next calculations
1998 // that can create incorrect polygons
1999 int thickness = std::max( aZone->GetHatchThickness(),
2000 aZone->GetMinThickness() + pcbIUScale.mmToIU( 0.001 ) );
2001
2002 int linethickness = thickness - aZone->GetMinThickness();
2003 int gridsize = thickness + aZone->GetHatchGap();
2004 int maxError = m_board->GetDesignSettings().m_MaxError;
2005
2006 SHAPE_POLY_SET filledPolys = aFillPolys.CloneDropTriangulation();
2007 // Use a area that contains the rotated bbox by orientation, and after rotate the result
2008 // by -orientation.
2009 if( !aZone->GetHatchOrientation().IsZero() )
2010 filledPolys.Rotate( - aZone->GetHatchOrientation() );
2011
2012 BOX2I bbox = filledPolys.BBox( 0 );
2013
2014 // Build hole shape
2015 // the hole size is aZone->GetHatchGap(), but because the outline thickness
2016 // is aZone->GetMinThickness(), the hole shape size must be larger
2017 SHAPE_LINE_CHAIN hole_base;
2018 int hole_size = aZone->GetHatchGap() + aZone->GetMinThickness();
2019 VECTOR2I corner( 0, 0 );;
2020 hole_base.Append( corner );
2021 corner.x += hole_size;
2022 hole_base.Append( corner );
2023 corner.y += hole_size;
2024 hole_base.Append( corner );
2025 corner.x = 0;
2026 hole_base.Append( corner );
2027 hole_base.SetClosed( true );
2028
2029 // Calculate minimal area of a grid hole.
2030 // All holes smaller than a threshold will be removed
2031 double minimal_hole_area = hole_base.Area() * aZone->GetHatchHoleMinArea();
2032
2033 // Now convert this hole to a smoothed shape:
2034 if( aZone->GetHatchSmoothingLevel() > 0 )
2035 {
2036 // the actual size of chamfer, or rounded corner radius is the half size
2037 // of the HatchFillTypeGap scaled by aZone->GetHatchSmoothingValue()
2038 // aZone->GetHatchSmoothingValue() = 1.0 is the max value for the chamfer or the
2039 // radius of corner (radius = half size of the hole)
2040 int smooth_value = KiROUND( aZone->GetHatchGap()
2041 * aZone->GetHatchSmoothingValue() / 2 );
2042
2043 // Minimal optimization:
2044 // make smoothing only for reasonable smooth values, to avoid a lot of useless segments
2045 // and if the smooth value is small, use chamfer even if fillet is requested
2046 #define SMOOTH_MIN_VAL_MM 0.02
2047 #define SMOOTH_SMALL_VAL_MM 0.04
2048
2049 if( smooth_value > pcbIUScale.mmToIU( SMOOTH_MIN_VAL_MM ) )
2050 {
2051 SHAPE_POLY_SET smooth_hole;
2052 smooth_hole.AddOutline( hole_base );
2053 int smooth_level = aZone->GetHatchSmoothingLevel();
2054
2055 if( smooth_value < pcbIUScale.mmToIU( SMOOTH_SMALL_VAL_MM ) && smooth_level > 1 )
2056 smooth_level = 1;
2057
2058 // Use a larger smooth_value to compensate the outline tickness
2059 // (chamfer is not visible is smooth value < outline thickess)
2060 smooth_value += aZone->GetMinThickness() / 2;
2061
2062 // smooth_value cannot be bigger than the half size oh the hole:
2063 smooth_value = std::min( smooth_value, aZone->GetHatchGap() / 2 );
2064
2065 // the error to approximate a circle by segments when smoothing corners by a arc
2066 maxError = std::max( maxError * 2, smooth_value / 20 );
2067
2068 switch( smooth_level )
2069 {
2070 case 1:
2071 // Chamfer() uses the distance from a corner to create a end point
2072 // for the chamfer.
2073 hole_base = smooth_hole.Chamfer( smooth_value ).Outline( 0 );
2074 break;
2075
2076 default:
2077 if( aZone->GetHatchSmoothingLevel() > 2 )
2078 maxError /= 2; // Force better smoothing
2079
2080 hole_base = smooth_hole.Fillet( smooth_value, maxError ).Outline( 0 );
2081 break;
2082
2083 case 0:
2084 break;
2085 };
2086 }
2087 }
2088
2089 // Build holes
2090 SHAPE_POLY_SET holes;
2091
2092 for( int xx = 0; ; xx++ )
2093 {
2094 int xpos = xx * gridsize;
2095
2096 if( xpos > bbox.GetWidth() )
2097 break;
2098
2099 for( int yy = 0; ; yy++ )
2100 {
2101 int ypos = yy * gridsize;
2102
2103 if( ypos > bbox.GetHeight() )
2104 break;
2105
2106 // Generate hole
2107 SHAPE_LINE_CHAIN hole( hole_base );
2108 hole.Move( VECTOR2I( xpos, ypos ) );
2109 holes.AddOutline( hole );
2110 }
2111 }
2112
2113 holes.Move( bbox.GetPosition() );
2114
2115 if( !aZone->GetHatchOrientation().IsZero() )
2116 holes.Rotate( aZone->GetHatchOrientation() );
2117
2118 DUMP_POLYS_TO_COPPER_LAYER( holes, In10_Cu, wxT( "hatch-holes" ) );
2119
2120 int outline_margin = aZone->GetMinThickness() * 1.1;
2121
2122 // Using GetHatchThickness() can look more consistent than GetMinThickness().
2123 if( aZone->GetHatchBorderAlgorithm() && aZone->GetHatchThickness() > outline_margin )
2124 outline_margin = aZone->GetHatchThickness();
2125
2126 // The fill has already been deflated to ensure GetMinThickness() so we just have to
2127 // account for anything beyond that.
2128 SHAPE_POLY_SET deflatedFilledPolys = aFillPolys.CloneDropTriangulation();
2129 deflatedFilledPolys.Deflate( outline_margin - aZone->GetMinThickness(),
2131 holes.BooleanIntersection( deflatedFilledPolys, SHAPE_POLY_SET::PM_FAST );
2132 DUMP_POLYS_TO_COPPER_LAYER( holes, In11_Cu, wxT( "fill-clipped-hatch-holes" ) );
2133
2134 SHAPE_POLY_SET deflatedOutline = aZone->Outline()->CloneDropTriangulation();
2135 deflatedOutline.Deflate( outline_margin, SHAPE_POLY_SET::CHAMFER_ALL_CORNERS, maxError );
2136 holes.BooleanIntersection( deflatedOutline, SHAPE_POLY_SET::PM_FAST );
2137 DUMP_POLYS_TO_COPPER_LAYER( holes, In12_Cu, wxT( "outline-clipped-hatch-holes" ) );
2138
2139 if( aZone->GetNetCode() != 0 )
2140 {
2141 // Vias and pads connected to the zone must not be allowed to become isolated inside
2142 // one of the holes. Effectively this means their copper outline needs to be expanded
2143 // to be at least as wide as the gap so that it is guaranteed to touch at least one
2144 // edge.
2145 BOX2I zone_boundingbox = aZone->GetBoundingBox();
2146 SHAPE_POLY_SET aprons;
2147 int min_apron_radius = ( aZone->GetHatchGap() * 10 ) / 19;
2148
2149 for( PCB_TRACK* track : m_board->Tracks() )
2150 {
2151 if( track->Type() == PCB_VIA_T )
2152 {
2153 PCB_VIA* via = static_cast<PCB_VIA*>( track );
2154
2155 if( via->GetNetCode() == aZone->GetNetCode()
2156 && via->IsOnLayer( aLayer )
2157 && via->GetBoundingBox().Intersects( zone_boundingbox ) )
2158 {
2159 int r = std::max( min_apron_radius,
2160 via->GetDrillValue() / 2 + outline_margin );
2161
2162 TransformCircleToPolygon( aprons, via->GetPosition(), r, maxError,
2163 ERROR_OUTSIDE );
2164 }
2165 }
2166 }
2167
2168 for( FOOTPRINT* footprint : m_board->Footprints() )
2169 {
2170 for( PAD* pad : footprint->Pads() )
2171 {
2172 if( pad->GetNetCode() == aZone->GetNetCode()
2173 && pad->IsOnLayer( aLayer )
2174 && pad->GetBoundingBox().Intersects( zone_boundingbox ) )
2175 {
2176 // What we want is to bulk up the pad shape so that the narrowest bit of
2177 // copper between the hole and the apron edge is at least outline_margin
2178 // wide (and that the apron itself meets min_apron_radius. But that would
2179 // take a lot of code and math, and the following approximation is close
2180 // enough.
2181 int pad_width = std::min( pad->GetSize().x, pad->GetSize().y );
2182 int slot_width = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
2183 int min_annular_ring_width = ( pad_width - slot_width ) / 2;
2184 int clearance = std::max( min_apron_radius - pad_width / 2,
2185 outline_margin - min_annular_ring_width );
2186
2187 clearance = std::max( 0, clearance - linethickness / 2 );
2188 pad->TransformShapeToPolygon( aprons, aLayer, clearance, maxError,
2189 ERROR_OUTSIDE );
2190 }
2191 }
2192 }
2193
2195 }
2196 DUMP_POLYS_TO_COPPER_LAYER( holes, In13_Cu, wxT( "pad-via-clipped-hatch-holes" ) );
2197
2198 // Now filter truncated holes to avoid small holes in pattern
2199 // It happens for holes near the zone outline
2200 for( int ii = 0; ii < holes.OutlineCount(); )
2201 {
2202 double area = holes.Outline( ii ).Area();
2203
2204 if( area < minimal_hole_area ) // The current hole is too small: remove it
2205 holes.DeletePolygon( ii );
2206 else
2207 ++ii;
2208 }
2209
2210 // create grid. Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to
2211 // generate strictly simple polygons needed by Gerber files and Fracture()
2212 aFillPolys.BooleanSubtract( aFillPolys, holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
2213 DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In14_Cu, wxT( "after-hatching" ) );
2214
2215 return true;
2216}
constexpr int ARC_HIGH_DEF
Definition: base_units.h:121
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:109
@ ZLO_FORCE_NO_ZONE_CONNECTION
Definition: board_item.h:67
@ ZLO_FORCE_FLASHED
Definition: board_item.h:66
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
Container for design settings for a BOARD object.
std::shared_ptr< DRC_ENGINE > m_DRCEngine
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:77
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:204
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:271
LSET GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition: board.cpp:614
bool GetBoardPolygonOutlines(SHAPE_POLY_SET &aOutlines, OUTLINE_ERROR_HANDLER *aErrorHandler=nullptr, bool aAllowUseArcsInPolygons=false)
Extract the board outlines and build a closed polygon from lines, arcs and circle items on edge cut l...
Definition: board.cpp:2056
ZONES & Zones()
Definition: board.h:319
int GetMaxClearanceValue() const
Returns the maximum clearance value for any object on the board.
Definition: board.cpp:748
FOOTPRINTS & Footprints()
Definition: board.h:313
int GetCopperLayerCount() const
Definition: board.cpp:590
TRACKS & Tracks()
Definition: board.h:310
DRAWINGS & Drawings()
Definition: board.h:316
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:731
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition: board.h:433
const Vec & GetPosition() const
Definition: box2.h:185
int GetSizeMax() const
Definition: box2.h:201
void Offset(coord_type dx, coord_type dy)
Definition: box2.h:225
bool Intersects(const BOX2< Vec > &aRect) const
Definition: box2.h:270
coord_type GetHeight() const
Definition: box2.h:189
coord_type GetWidth() const
Definition: box2.h:188
void Move(const Vec &aMoveVector)
Move the rectangle by the aMoveVector.
Definition: box2.h:112
BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition: box2.h:507
BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition: box2.h:589
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition: commit.h:72
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Create an undo entry for an item that has been already modified.
Definition: commit.h:103
MINOPTMAX< int > & Value()
Definition: drc_rule.h:142
const MINOPTMAX< int > & GetValue() const
Definition: drc_rule.h:141
ZONE_CONNECTION m_ZoneConnection
Definition: drc_rule.h:174
bool IsZero() const
Definition: eda_angle.h:169
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:97
Helper class to create more flexible dialogs, including 'do not show again' checkbox handling.
Definition: confirm.h:47
void DoNotShowCheckbox(wxString file, int line)
Checks the 'do not show again' setting for the dialog.
Definition: confirm.cpp:56
bool SetOKCancelLabels(const ButtonLabel &ok, const ButtonLabel &cancel) override
Shows the 'do not show again' checkbox.
Definition: confirm.h:57
int ShowModal() override
Definition: confirm.cpp:100
LSET is a set of PCB_LAYER_IDs.
Definition: layer_ids.h:552
LSEQ Seq(const PCB_LAYER_ID *aWishListSequence, unsigned aCount) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition: lset.cpp:411
bool Contains(PCB_LAYER_ID aLayer)
See if the layer set contains a PCB layer.
Definition: layer_ids.h:622
static LSET InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Definition: lset.cpp:733
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:773
T Min() const
Definition: minoptmax.h:33
T Max() const
Definition: minoptmax.h:34
T Opt() const
Definition: minoptmax.h:35
Definition: pad.h:58
const BOX2I GetBoundingBox() const override
The bounding box is cached, so this will be efficient most of the time.
Definition: pad.cpp:636
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const override
Convert the pad shape to a closed polygon.
Definition: pad.cpp:1578
bool TransformHoleToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc) const
Build the corner list of the polygonal drill shape in the board coordinate system.
Definition: pad.cpp:1561
void SetOffset(const VECTOR2I &aOffset)
Definition: pad.h:259
CUST_PAD_SHAPE_IN_ZONE GetCustomShapeInZoneOpt() const
Definition: pad.h:207
void SetPosition(const VECTOR2I &aPos) override
Definition: pad.h:191
PAD_SHAPE GetShape() const
Definition: pad.h:189
void SetOrientation(const EDA_ANGLE &aAngle)
Set the rotation angle of the pad.
Definition: pad.cpp:664
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc, bool aIgnoreLineWidth=false) const override
Convert the shape to a closed polygon.
bool IsVisible() const override
Definition: pcb_textbox.h:85
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).
Definition: seg.h:42
VECTOR2I A
Definition: seg.h:49
VECTOR2I B
Definition: seg.h:50
void Reverse()
Definition: seg.h:351
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.
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 BooleanSubtract(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset difference For aFastMode meaning, see function booleanOp.
void Fracture(POLYGON_MODE aFastMode)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
CORNER_STRATEGY
define how inflate transform build inflated polygon
@ CHAMFER_ALL_CORNERS
All angles are chamfered.
@ ROUND_ALL_CORNERS
All angles are rounded.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
void DeletePolygon(int aIdx)
Delete aIdx-th polygon from the set.
double Area()
Return the area of this poly set.
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
void BooleanIntersection(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset intersection For aFastMode meaning, see function booleanOp.
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(POLYGON_MODE aFastMode)
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections) For aFastMo...
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 BuildBBoxCaches() const
Construct BBoxCaches for Contains(), below.
int OutlineCount() const
Return the number of outlines in the set.
SHAPE_POLY_SET Fillet(int aRadius, int aErrorMax)
Return a filleted version of the polygon set.
void Move(const VECTOR2I &aVector) override
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
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition: vector2d.h:350
COMMIT * m_commit
Definition: zone_filler.h:134
int m_worstClearance
Definition: zone_filler.h:138
void addKnockout(PAD *aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET &aHoles)
Add a knockout for a pad.
bool m_debugZoneFiller
Definition: zone_filler.h:140
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 ...
ZONE_FILLER(BOARD *aBoard, COMMIT *aCommit)
Definition: zone_filler.cpp:51
void buildThermalSpokes(const ZONE *box, PCB_LAYER_ID aLayer, const std::vector< PAD * > &aSpokedPadsList, std::deque< SHAPE_LINE_CHAIN > &aSpokes)
Function buildThermalSpokes Constructs a list of all thermal spokes for the given zone.
void subtractHigherPriorityZones(const ZONE *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aRawFill)
Removes the outlines of higher-proirity zones with the same net.
SHAPE_POLY_SET m_boardOutline
Definition: zone_filler.h:132
bool m_brdOutlinesValid
Definition: zone_filler.h:133
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)
Definition: zone_filler.cpp:69
bool Fill(std::vector< ZONE * > &aZones, bool aCheck=false, wxWindow *aParent=nullptr)
Fills the given list of zones.
Definition: zone_filler.cpp:87
BOARD * m_board
Definition: zone_filler.h:131
void knockoutThermalReliefs(const ZONE *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aFill, std::vector< PAD * > &aThermalConnectionPads, std::vector< PAD * > &aNoConnectionPads)
Removes thermal reliefs from the shape for any pads connected to the zone.
PROGRESS_REPORTER * m_progressReporter
Definition: zone_filler.h:135
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 *aZone, 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...
Handle a list of polygons defining a copper zone.
Definition: zone.h:72
void SetNeedRefill(bool aNeedRefill)
Definition: zone.h:253
int GetHatchBorderAlgorithm() const
Definition: zone.h:291
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:1075
const BOX2I GetBoundingBox() const override
Definition: zone.cpp:354
SHAPE_POLY_SET * Outline()
Definition: zone.h:325
void SetFillFlag(PCB_LAYER_ID aLayer, bool aFlag)
Definition: zone.h:247
int GetLocalClearance(wxString *aSource) const override
Return any local clearances set in the "classic" (ie: pre-rule) system.
Definition: zone.cpp:501
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition: zone.h:636
int GetMinThickness() const
Definition: zone.h:258
bool HigherPriority(const ZONE *aOther) const
Definition: zone.cpp:195
int GetHatchThickness() const
Definition: zone.h:273
double GetHatchHoleMinArea() const
Definition: zone.h:288
bool IsTeardropArea() const
Definition: zone.h:694
EDA_ANGLE GetHatchOrientation() const
Definition: zone.h:279
bool BuildSmoothedPoly(SHAPE_POLY_SET &aSmoothedPoly, PCB_LAYER_ID aLayer, SHAPE_POLY_SET *aBoardOutline, SHAPE_POLY_SET *aSmoothedPolyWithApron=nullptr) const
Definition: zone.cpp:1139
ZONE_FILL_MODE GetFillMode() const
Definition: zone.h:181
int GetHatchGap() const
Definition: zone.h:276
double GetHatchSmoothingValue() const
Definition: zone.h:285
int GetHatchSmoothingLevel() const
Definition: zone.h:282
bool IsOnCopperLayer() const override
Definition: zone.cpp:266
std::mutex & GetLock()
Definition: zone.h:237
unsigned GetAssignedPriority() const
Definition: zone.h:119
This file is part of the common library.
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
DRC_CONSTRAINT_T
Definition: drc_rule.h:45
@ EDGE_CLEARANCE_CONSTRAINT
Definition: drc_rule.h:50
@ PHYSICAL_HOLE_CLEARANCE_CONSTRAINT
Definition: drc_rule.h:71
@ CLEARANCE_CONSTRAINT
Definition: drc_rule.h:47
@ THERMAL_SPOKE_WIDTH_CONSTRAINT
Definition: drc_rule.h:60
@ THERMAL_RELIEF_GAP_CONSTRAINT
Definition: drc_rule.h:59
@ HOLE_CLEARANCE_CONSTRAINT
Definition: drc_rule.h:48
@ PHYSICAL_CLEARANCE_CONSTRAINT
Definition: drc_rule.h:70
#define _(s)
a few functions useful in geometry calculations.
@ ERROR_OUTSIDE
bool ClipLine(const BOX2I *aClipBox, int &x1, int &y1, int &x2, int &y2)
Test if any part of a line falls within the bounds of a rectangle.
double m_ExtraClearance
Extra fill clearance for zone fills.
bool m_DebugZoneFiller
A mode that dumps the various stages of a F_Cu fill into In1_Cu through In9_Cu.
#define MAX_CU_LAYERS
Definition: layer_ids.h:141
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:114
@ 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:115
@ 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
@ In13_Cu
Definition: layer_ids.h:78
@ 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:65
@ In18_Cu
Definition: layer_ids.h:83
@ CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL
Definition: pad.h:45
const double epsilon
const double IU_PER_MM
Definition: base_units.h:77
constexpr int mmToIU(double mm) const
Definition: base_units.h:89
A struct recording the isolated and single-pad islands within a zone.
Definition: zone.h:59
static thread_pool * tp
Definition: thread_pool.cpp:30
BS::thread_pool thread_pool
Definition: thread_pool.h:30
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
Definition: thread_pool.cpp:32
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Definition: trigo.cpp:183
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition: typeinfo.h:88
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:94
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition: typeinfo.h:92
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition: typeinfo.h:91
@ 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:103
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:85
VECTOR2< int > VECTOR2I
Definition: vector2d.h:588
#define SMOOTH_MIN_VAL_MM
#define DUMP_POLYS_TO_COPPER_LAYER(a, b, c)
#define SMOOTH_SMALL_VAL_MM
ISLAND_REMOVAL_MODE
Whether or not to remove isolated islands from a zone.
Definition: zone_settings.h:57
ZONE_CONNECTION
How pads are covered by copper in zone.
Definition: zones.h:49