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