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