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