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