KiCad PCB EDA Suite
zone_filler.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2014-2017 CERN
5  * Copyright (C) 2014-2020 KiCad Developers, see AUTHORS.txt for contributors.
6  * @author Tomasz W┼éostowski <tomasz.wlostowski@cern.ch>
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 <thread>
27 #include <algorithm>
28 #include <future>
29 
30 #include <advanced_config.h>
31 #include <board.h>
32 #include <zone.h>
33 #include <footprint.h>
34 #include <pcb_shape.h>
35 #include <pcb_target.h>
36 #include <track.h>
39 #include <board_commit.h>
42 #include <geometry/convex_hull.h>
44 #include <confirm.h>
45 #include <convert_to_biu.h>
46 #include <math/util.h> // for KiROUND
47 #include "zone_filler.h"
48 
49 static const double s_RoundPadThermalSpokeAngle = 450; // in deci-degrees
50 
51 
52 ZONE_FILLER::ZONE_FILLER( BOARD* aBoard, COMMIT* aCommit ) :
53  m_board( aBoard ),
54  m_brdOutlinesValid( false ),
55  m_commit( aCommit ),
56  m_progressReporter( nullptr ),
57  m_maxError( ARC_HIGH_DEF ),
58  m_worstClearance( 0 )
59 {
60  // To enable add "DebugZoneFiller=1" to kicad_advanced settings file.
62 }
63 
64 
66 {
67 }
68 
69 
70 void ZONE_FILLER::InstallNewProgressReporter( wxWindow* aParent, const wxString& aTitle,
71  int aNumPhases )
72 {
73  m_uniqueReporter = std::make_unique<WX_PROGRESS_REPORTER>( aParent, aTitle, aNumPhases );
75 }
76 
77 
79 {
80  m_progressReporter = aReporter;
81  wxASSERT_MSG( m_commit, "ZONE_FILLER must have a valid commit to call SetProgressReporter" );
82 }
83 
84 
85 bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aParent )
86 {
87  std::vector<std::pair<ZONE*, PCB_LAYER_ID>> toFill;
88  std::vector<CN_ZONE_ISOLATED_ISLAND_LIST> islandsList;
89 
90  std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_board->GetConnectivity();
91 
92  // Rebuild just in case. This really needs to be reliable.
93  connectivity->Clear();
94  connectivity->Build( m_board, m_progressReporter );
95 
97 
99 
100  if( m_progressReporter )
101  {
102  m_progressReporter->Report( aCheck ? _( "Checking zone fills..." )
103  : _( "Building zone fills..." ) );
104  m_progressReporter->SetMaxProgress( aZones.size() );
106  }
107 
108  // The board outlines is used to clip solid areas inside the board (when outlines are valid)
111 
112  // Update and cache zone bounding boxes and pad effective shapes so that we don't have to
113  // make them thread-safe.
114  for( ZONE* zone : m_board->Zones() )
115  {
116  zone->CacheBoundingBox();
117  m_worstClearance = std::max( m_worstClearance, zone->GetLocalClearance() );
118  }
119 
120  for( FOOTPRINT* footprint : m_board->Footprints() )
121  {
122  for( PAD* pad : footprint->Pads() )
123  {
124  if( pad->IsDirty() )
125  {
126  pad->BuildEffectiveShapes( UNDEFINED_LAYER );
127  pad->BuildEffectivePolygon();
128  }
129 
130  m_worstClearance = std::max( m_worstClearance, pad->GetLocalClearance() );
131  }
132 
133  for( ZONE* zone : footprint->Zones() )
134  {
135  zone->CacheBoundingBox();
136  m_worstClearance = std::max( m_worstClearance, zone->GetLocalClearance() );
137  }
138 
139  // Rules may depend on insideCourtyard() or other expressions
140  footprint->BuildPolyCourtyards();
141  }
142 
143  // Sort by priority to reduce deferrals waiting on higher priority zones.
144  std::sort( aZones.begin(), aZones.end(),
145  []( const ZONE* lhs, const ZONE* rhs )
146  {
147  return lhs->GetPriority() > rhs->GetPriority();
148  } );
149 
150  for( ZONE* zone : aZones )
151  {
152  // Rule areas are not filled
153  if( zone->GetIsRuleArea() )
154  continue;
155 
156  if( m_commit )
157  m_commit->Modify( zone );
158 
159  // calculate the hash value for filled areas. it will be used later
160  // to know if the current filled areas are up to date
161  for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
162  {
163  zone->BuildHashValue( layer );
164 
165  // Add the zone to the list of zones to test or refill
166  toFill.emplace_back( std::make_pair( zone, layer ) );
167  }
168 
169  islandsList.emplace_back( CN_ZONE_ISOLATED_ISLAND_LIST( zone ) );
170 
171  // Remove existing fill first to prevent drawing invalid polygons
172  // on some platforms
173  zone->UnFill();
174 
175  zone->SetFillVersion( bds.m_ZoneFillVersion );
176  }
177 
178  size_t cores = std::thread::hardware_concurrency();
179  std::atomic<size_t> nextItem;
180 
181  auto check_fill_dependency =
182  [&]( ZONE* aZone, PCB_LAYER_ID aLayer, ZONE* aOtherZone ) -> bool
183  {
184  // Check to see if we have to knock-out the filled areas of a higher-priority
185  // zone. If so we have to wait until said zone is filled before we can fill.
186 
187  // If the other zone is already filled then we're good-to-go
188  if( aOtherZone->GetFillFlag( aLayer ) )
189  return false;
190 
191  // Even if keepouts exclude copper pours the exclusion is by outline, not by
192  // filled area, so we're good-to-go here too.
193  if( aOtherZone->GetIsRuleArea() )
194  return false;
195 
196  // If the zones share no common layers
197  if( !aOtherZone->GetLayerSet().test( aLayer ) )
198  return false;
199 
200  if( aOtherZone->GetPriority() <= aZone->GetPriority() )
201  return false;
202 
203  // Same-net zones always use outline to produce predictable results
204  if( aOtherZone->GetNetCode() == aZone->GetNetCode() )
205  return false;
206 
207  // A higher priority zone is found: if we intersect and it's not filled yet
208  // then we have to wait.
209  EDA_RECT inflatedBBox = aZone->GetCachedBoundingBox();
210  inflatedBBox.Inflate( m_worstClearance );
211 
212  return inflatedBBox.Intersects( aOtherZone->GetCachedBoundingBox() );
213  };
214 
215  auto fill_lambda =
216  [&]( PROGRESS_REPORTER* aReporter )
217  {
218  size_t num = 0;
219 
220  for( size_t i = nextItem++; i < toFill.size(); i = nextItem++ )
221  {
222  PCB_LAYER_ID layer = toFill[i].second;
223  ZONE* zone = toFill[i].first;
224  bool canFill = true;
225 
226  // Check for any fill dependencies. If our zone needs to be clipped by
227  // another zone then we can't fill until that zone is filled.
228  for( ZONE* otherZone : aZones )
229  {
230  if( otherZone == zone )
231  continue;
232 
233  if( check_fill_dependency( zone, layer, otherZone ) )
234  {
235  canFill = false;
236  break;
237  }
238  }
239 
241  break;
242 
243  if( !canFill )
244  continue;
245 
246  // Now we're ready to fill.
247  SHAPE_POLY_SET rawPolys, finalPolys;
248  fillSingleZone( zone, layer, rawPolys, finalPolys );
249 
250  std::unique_lock<std::mutex> zoneLock( zone->GetLock() );
251 
252  zone->SetRawPolysList( layer, rawPolys );
253  zone->SetFilledPolysList( layer, finalPolys );
254  zone->SetFillFlag( layer, true );
255 
256  if( m_progressReporter )
258 
259  num++;
260  }
261 
262  return num;
263  };
264 
265  while( !toFill.empty() )
266  {
267  size_t parallelThreadCount = std::min( cores, toFill.size() );
268  std::vector<std::future<size_t>> returns( parallelThreadCount );
269 
270  nextItem = 0;
271 
272  if( parallelThreadCount <= 1 )
273  fill_lambda( m_progressReporter );
274  else
275  {
276  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
277  returns[ii] = std::async( std::launch::async, fill_lambda, m_progressReporter );
278 
279  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
280  {
281  // Here we balance returns with a 100ms timeout to allow UI updating
282  std::future_status status;
283  do
284  {
285  if( m_progressReporter )
287 
288  status = returns[ii].wait_for( std::chrono::milliseconds( 100 ) );
289  } while( status != std::future_status::ready );
290  }
291  }
292 
293  toFill.erase( std::remove_if( toFill.begin(), toFill.end(),
294  [&] ( const std::pair<ZONE*, PCB_LAYER_ID> pair ) -> bool
295  {
296  return pair.first->GetFillFlag( pair.second );
297  } ),
298  toFill.end() );
299 
301  break;
302  }
303 
304  // Now update the connectivity to check for copper islands
305  if( m_progressReporter )
306  {
308  return false;
309 
311  m_progressReporter->Report( _( "Removing isolated copper islands..." ) );
313  }
314 
315  connectivity->SetProgressReporter( m_progressReporter );
316  connectivity->FindIsolatedCopperIslands( islandsList );
317  connectivity->SetProgressReporter( nullptr );
318 
320  return false;
321 
322  for( ZONE* zone : aZones )
323  {
324  // Keepout zones are not filled
325  if( zone->GetIsRuleArea() )
326  continue;
327 
328  zone->SetIsFilled( true );
329  }
330 
331  // Now remove insulated copper islands
332  for( CN_ZONE_ISOLATED_ISLAND_LIST& zone : islandsList )
333  {
334  for( PCB_LAYER_ID layer : zone.m_zone->GetLayerSet().Seq() )
335  {
337  continue;
338 
339  if( !zone.m_islands.count( layer ) )
340  continue;
341 
342  std::vector<int>& islands = zone.m_islands.at( layer );
343 
344  // The list of polygons to delete must be explored from last to first in list,
345  // to allow deleting a polygon from list without breaking the remaining of the list
346  std::sort( islands.begin(), islands.end(), std::greater<int>() );
347 
348  SHAPE_POLY_SET poly = zone.m_zone->GetFilledPolysList( layer );
349  long long int minArea = zone.m_zone->GetMinIslandArea();
350  ISLAND_REMOVAL_MODE mode = zone.m_zone->GetIslandRemovalMode();
351 
352  for( int idx : islands )
353  {
354  SHAPE_LINE_CHAIN& outline = poly.Outline( idx );
355 
356  if( mode == ISLAND_REMOVAL_MODE::ALWAYS )
357  poly.DeletePolygon( idx );
358  else if ( mode == ISLAND_REMOVAL_MODE::AREA && outline.Area() < minArea )
359  poly.DeletePolygon( idx );
360  else
361  zone.m_zone->SetIsIsland( layer, idx );
362  }
363 
364  zone.m_zone->SetFilledPolysList( layer, poly );
365  zone.m_zone->CalculateFilledArea();
366 
368  return false;
369  }
370  }
371 
372  // Now remove islands outside the board edge
373  for( ZONE* zone : aZones )
374  {
375  LSET zoneCopperLayers = zone->GetLayerSet() & LSET::AllCuMask( MAX_CU_LAYERS );
376 
377  for( PCB_LAYER_ID layer : zoneCopperLayers.Seq() )
378  {
380  continue;
381 
382  SHAPE_POLY_SET poly = zone->GetFilledPolysList( layer );
383 
384  for( int ii = poly.OutlineCount() - 1; ii >= 0; ii-- )
385  {
386  std::vector<SHAPE_LINE_CHAIN>& island = poly.Polygon( ii );
387 
388  if( island.empty() || !m_boardOutline.Contains( island.front().CPoint( 0 ) ) )
389  poly.DeletePolygon( ii );
390  }
391 
392  zone->SetFilledPolysList( layer, poly );
393  zone->CalculateFilledArea();
394 
396  return false;
397  }
398  }
399 
400  if( aCheck )
401  {
402  bool outOfDate = false;
403 
404  for( ZONE* zone : aZones )
405  {
406  // Keepout zones are not filled
407  if( zone->GetIsRuleArea() )
408  continue;
409 
410  for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
411  {
412  MD5_HASH was = zone->GetHashValue( layer );
413  zone->CacheTriangulation( layer );
414  zone->BuildHashValue( layer );
415  MD5_HASH is = zone->GetHashValue( layer );
416 
417  if( is != was )
418  outOfDate = true;
419  }
420  }
421 
422  if( outOfDate )
423  {
424  KIDIALOG dlg( aParent, _( "Zone fills are out-of-date. Refill?" ),
425  _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
426  dlg.SetOKCancelLabels( _( "Refill" ), _( "Continue without Refill" ) );
427  dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
428 
429  if( dlg.ShowModal() == wxID_CANCEL )
430  return false;
431  }
432  }
433 
434  if( m_progressReporter )
435  {
437  m_progressReporter->Report( _( "Performing polygon fills..." ) );
438  m_progressReporter->SetMaxProgress( islandsList.size() );
439  }
440 
441  nextItem = 0;
442 
443  auto tri_lambda =
444  [&]( PROGRESS_REPORTER* aReporter ) -> size_t
445  {
446  size_t num = 0;
447 
448  for( size_t i = nextItem++; i < islandsList.size(); i = nextItem++ )
449  {
450  islandsList[i].m_zone->CacheTriangulation();
451  num++;
452 
453  if( m_progressReporter )
454  {
456 
458  break;
459  }
460  }
461 
462  return num;
463  };
464 
465  size_t parallelThreadCount = std::min( cores, islandsList.size() );
466  std::vector<std::future<size_t>> returns( parallelThreadCount );
467 
468  if( parallelThreadCount <= 1 )
469  tri_lambda( m_progressReporter );
470  else
471  {
472  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
473  returns[ii] = std::async( std::launch::async, tri_lambda, m_progressReporter );
474 
475  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
476  {
477  // Here we balance returns with a 100ms timeout to allow UI updating
478  std::future_status status;
479  do
480  {
481  if( m_progressReporter )
482  {
484 
486  break;
487  }
488 
489  status = returns[ii].wait_for( std::chrono::milliseconds( 100 ) );
490  } while( status != std::future_status::ready );
491  }
492  }
493 
494  if( m_progressReporter )
495  {
497  return false;
498 
501  }
502 
503  return true;
504 }
505 
506 
510 bool hasThermalConnection( PAD* pad, const ZONE* aZone )
511 {
512  // Rejects non-standard pads with tht-only thermal reliefs
514  && pad->GetAttribute() != PAD_ATTRIB::PTH )
515  {
516  return false;
517  }
518 
521  {
522  return false;
523  }
524 
525  if( pad->GetNetCode() != aZone->GetNetCode() || pad->GetNetCode() <= 0 )
526  return false;
527 
528  EDA_RECT item_boundingbox = pad->GetBoundingBox();
529  int thermalGap = aZone->GetThermalReliefGap( pad );
530  item_boundingbox.Inflate( thermalGap, thermalGap );
531 
532  return item_boundingbox.Intersects( aZone->GetCachedBoundingBox() );
533 }
534 
535 
540 void ZONE_FILLER::addKnockout( PAD* aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles )
541 {
542  if( aPad->GetShape() == PAD_SHAPE::CUSTOM )
543  {
544  SHAPE_POLY_SET poly;
545  aPad->TransformShapeWithClearanceToPolygon( poly, aLayer, aGap, m_maxError,
546  ERROR_OUTSIDE );
547 
548  // the pad shape in zone can be its convex hull or the shape itself
550  {
551  std::vector<wxPoint> convex_hull;
552  BuildConvexHull( convex_hull, poly );
553 
554  aHoles.NewOutline();
555 
556  for( const wxPoint& pt : convex_hull )
557  aHoles.Append( pt );
558  }
559  else
560  aHoles.Append( poly );
561  }
562  else
563  {
564  aPad->TransformShapeWithClearanceToPolygon( aHoles, aLayer, aGap, m_maxError,
565  ERROR_OUTSIDE );
566  }
567 }
568 
569 
574 void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap,
575  bool aIgnoreLineWidth, SHAPE_POLY_SET& aHoles )
576 {
577  switch( aItem->Type() )
578  {
579  case PCB_SHAPE_T:
580  case PCB_TEXT_T:
581  case PCB_FP_SHAPE_T:
582  aItem->TransformShapeWithClearanceToPolygon( aHoles, aLayer, aGap, m_maxError,
583  ERROR_OUTSIDE, aIgnoreLineWidth );
584  break;
585 
586  case PCB_FP_TEXT_T:
587  {
588  FP_TEXT* text = static_cast<FP_TEXT*>( aItem );
589 
590  if( text->IsVisible() )
591  {
592  text->TransformShapeWithClearanceToPolygon( aHoles, aLayer, aGap, m_maxError,
593  ERROR_OUTSIDE, aIgnoreLineWidth );
594  }
595  }
596  break;
597 
598  default:
599  break;
600  }
601 }
602 
603 
609  SHAPE_POLY_SET& aFill )
610 {
611  SHAPE_POLY_SET holes;
612 
613  for( FOOTPRINT* footprint : m_board->Footprints() )
614  {
615  for( PAD* pad : footprint->Pads() )
616  {
617  if( !hasThermalConnection( pad, aZone ) )
618  continue;
619 
620  int gap = aZone->GetThermalReliefGap( pad );
621 
622  // If the pad is flashed to the current layer, or is on the same layer and shares a netcode, then
623  // we need to knock out the thermal relief.
624  if( pad->FlashLayer( aLayer ) || ( pad->IsOnLayer( aLayer ) && pad->GetNetCode() == aZone->GetNetCode() ) )
625  {
626  addKnockout( pad, aLayer, gap, holes );
627  }
628  else
629  {
630  // If the pad isn't on the current layer but has a hole, knock out a thermal relief
631  // for the hole.
632  if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 )
633  continue;
634 
635  // Note: drill size represents finish size, which means the actual holes size is
636  // the plating thickness larger.
637  if( pad->GetAttribute() == PAD_ATTRIB::PTH )
638  gap += pad->GetBoard()->GetDesignSettings().GetHolePlatingThickness();
639 
640  pad->TransformHoleWithClearanceToPolygon( holes, gap, m_maxError, ERROR_OUTSIDE );
641  }
642  }
643  }
644 
646 }
647 
648 
654  SHAPE_POLY_SET& aHoles )
655 {
656  long ticker = 0;
657 
658  auto checkForCancel =
659  [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool
660  {
661  return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled();
662  };
663 
664  // A small extra clearance to be sure actual track clearances are not smaller than
665  // requested clearance due to many approximations in calculations, like arc to segment
666  // approx, rounding issues, etc.
667  int extra_margin = Millimeter2iu( ADVANCED_CFG::GetCfg().m_ExtraClearance );
668 
670  int zone_clearance = aZone->GetLocalClearance();
671  EDA_RECT zone_boundingbox = aZone->GetCachedBoundingBox();
672 
673  // Items outside the zone bounding box are skipped, so it needs to be inflated by the
674  // largest clearance value found in the netclasses and rules
675  zone_boundingbox.Inflate( m_worstClearance + extra_margin );
676 
677  auto evalRulesForItems =
678  [&bds]( DRC_CONSTRAINT_T aConstraint, const BOARD_ITEM* a, const BOARD_ITEM* b,
679  PCB_LAYER_ID aEvalLayer ) -> int
680  {
681  auto c = bds.m_DRCEngine->EvalRules( aConstraint, a, b, aEvalLayer );
682  return c.Value().Min();
683  };
684 
685  // Add non-connected pad clearances
686  //
687  auto knockoutPadClearance =
688  [&]( PAD* aPad )
689  {
690  if( aPad->GetBoundingBox().Intersects( zone_boundingbox ) )
691  {
692  int gap;
693 
694  // For pads having the same netcode as the zone, the net clearance has no
695  // meaning so use the greater of the zone clearance and the thermal relief.
696  if( aPad->GetNetCode() > 0 && aPad->GetNetCode() == aZone->GetNetCode() )
697  gap = std::max( zone_clearance, aZone->GetThermalReliefGap( aPad ) );
698  else
699  gap = evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, aPad, aLayer );
700 
701  gap += extra_margin;
702 
703  // If the pad isn't on the current layer but has a hole, knock out the hole.
704  if( !aPad->FlashLayer( aLayer ) )
705  {
706  if( aPad->GetDrillSize().x == 0 && aPad->GetDrillSize().y == 0 )
707  return;
708 
709  // Note: drill size represents finish size, which means the actual hole
710  // size is the plating thickness larger.
711  if( aPad->GetAttribute() == PAD_ATTRIB::PTH )
712  gap += aPad->GetBoard()->GetDesignSettings().GetHolePlatingThickness();
713 
714  aPad->TransformHoleWithClearanceToPolygon( aHoles, gap, m_maxError,
715  ERROR_OUTSIDE );
716  }
717  else
718  {
719  addKnockout( aPad, aLayer, gap, aHoles );
720  }
721  }
722  };
723 
724  for( FOOTPRINT* footprint : m_board->Footprints() )
725  {
726  for( PAD* pad : footprint->Pads() )
727  {
728  if( checkForCancel( m_progressReporter ) )
729  return;
730 
731  if( pad->GetNetCode() != aZone->GetNetCode()
732  || pad->GetNetCode() <= 0
733  || aZone->GetPadConnection( pad ) == ZONE_CONNECTION::NONE )
734  {
735  knockoutPadClearance( pad );
736  }
737  }
738  }
739 
740  // Add non-connected track clearances
741  //
742  auto knockoutTrackClearance =
743  [&]( TRACK* aTrack )
744  {
745  if( aTrack->GetBoundingBox().Intersects( zone_boundingbox ) )
746  {
747  int gap = evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, aTrack, aLayer );
748 
749  gap += extra_margin;
750 
751  if( aTrack->Type() == PCB_VIA_T )
752  {
753  VIA* via = static_cast<VIA*>( aTrack );
754 
755  if( !via->FlashLayer( aLayer ) && via->GetNetCode() != aZone->GetNetCode() )
756  {
757  int radius = via->GetDrillValue() / 2 + bds.GetHolePlatingThickness();
758  TransformCircleToPolygon( aHoles, via->GetPosition(), radius + gap,
760  }
761  else
762  {
763  via->TransformShapeWithClearanceToPolygon( aHoles, aLayer, gap,
765  }
766  }
767  else
768  {
769  aTrack->TransformShapeWithClearanceToPolygon( aHoles, aLayer, gap,
771  }
772  }
773  };
774 
775  for( TRACK* track : m_board->Tracks() )
776  {
777  if( !track->IsOnLayer( aLayer ) )
778  continue;
779 
780  if( track->GetNetCode() == aZone->GetNetCode() && ( aZone->GetNetCode() != 0) )
781  continue;
782 
783  if( checkForCancel( m_progressReporter ) )
784  return;
785 
786  knockoutTrackClearance( track );
787  }
788 
789  // Add graphic item clearances. They are by definition unconnected, and have no clearance
790  // definitions of their own.
791  //
792  auto knockoutGraphicClearance =
793  [&]( BOARD_ITEM* aItem )
794  {
795  // A item on the Edge_Cuts or Margin is always seen as on any layer:
796  if( aItem->IsOnLayer( aLayer )
797  || aItem->IsOnLayer( Edge_Cuts )
798  || aItem->IsOnLayer( Margin ) )
799  {
800  if( aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
801  {
802  int gap = evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, aItem, aLayer );
803 
804  if( aItem->IsOnLayer( Edge_Cuts ) )
805  {
806  gap = std::max( gap, evalRulesForItems( EDGE_CLEARANCE_CONSTRAINT,
807  aZone, aItem, Edge_Cuts ) );
808  }
809 
810  if( aItem->IsOnLayer( Margin ) )
811  {
812  gap = std::max( gap, evalRulesForItems( EDGE_CLEARANCE_CONSTRAINT,
813  aZone, aItem, Margin ) );
814  }
815 
816  addKnockout( aItem, aLayer, gap, aItem->IsOnLayer( Edge_Cuts ), aHoles );
817  }
818  }
819  };
820 
821  for( FOOTPRINT* footprint : m_board->Footprints() )
822  {
823  bool skipFootprint = false;
824 
825  knockoutGraphicClearance( &footprint->Reference() );
826  knockoutGraphicClearance( &footprint->Value() );
827 
828  // Don't knock out holes in zones that share a net
829  // with a nettie footprint
830  if( footprint->IsNetTie() )
831  {
832  for( PAD* pad : footprint->Pads() )
833  {
834  if( aZone->GetNetCode() == pad->GetNetCode() )
835  {
836  skipFootprint = true;
837  break;
838  }
839  }
840  }
841 
842  if( skipFootprint )
843  continue;
844 
845  for( BOARD_ITEM* item : footprint->GraphicalItems() )
846  {
847  if( checkForCancel( m_progressReporter ) )
848  return;
849 
850  knockoutGraphicClearance( item );
851  }
852  }
853 
854  for( BOARD_ITEM* item : m_board->Drawings() )
855  {
856  if( checkForCancel( m_progressReporter ) )
857  return;
858 
859  knockoutGraphicClearance( item );
860  }
861 
862  // Add non-connected zone clearances
863  //
864  auto knockoutZoneClearance =
865  [&]( ZONE* aKnockout )
866  {
867  // If the zones share no common layers
868  if( !aKnockout->GetLayerSet().test( aLayer ) )
869  return;
870 
871  if( aKnockout->GetCachedBoundingBox().Intersects( zone_boundingbox ) )
872  {
873  if( aKnockout->GetIsRuleArea() )
874  {
875  // Keepouts use outline with no clearance
876  aKnockout->TransformSmoothedOutlineToPolygon( aHoles, 0, nullptr );
877  }
878  else if( bds.m_ZoneFillVersion == 5 )
879  {
880  // 5.x used outline with clearance
881  int gap = evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, aKnockout,
882  aLayer );
883 
884  aKnockout->TransformSmoothedOutlineToPolygon( aHoles, gap, nullptr );
885  }
886  else
887  {
888  // 6.0 uses filled areas with clearance
889  int gap = evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, aKnockout,
890  aLayer );
891 
892  SHAPE_POLY_SET poly;
893  aKnockout->TransformShapeWithClearanceToPolygon( poly, aLayer, gap,
894  m_maxError,
895  ERROR_OUTSIDE );
896  aHoles.Append( poly );
897  }
898  }
899  };
900 
901  for( ZONE* otherZone : m_board->Zones() )
902  {
903  if( checkForCancel( m_progressReporter ) )
904  return;
905 
906  if( otherZone->GetNetCode() != aZone->GetNetCode()
907  && otherZone->GetPriority() > aZone->GetPriority() )
908  {
909  knockoutZoneClearance( otherZone );
910  }
911  else if( otherZone->GetIsRuleArea() && otherZone->GetDoNotAllowCopperPour() )
912  {
913  knockoutZoneClearance( otherZone );
914  }
915  }
916 
917  for( FOOTPRINT* footprint : m_board->Footprints() )
918  {
919  for( ZONE* otherZone : footprint->Zones() )
920  {
921  if( checkForCancel( m_progressReporter ) )
922  return;
923 
924  if( otherZone->GetNetCode() != aZone->GetNetCode()
925  && otherZone->GetPriority() > aZone->GetPriority() )
926  {
927  knockoutZoneClearance( otherZone );
928  }
929  else if( otherZone->GetIsRuleArea() && otherZone->GetDoNotAllowCopperPour() )
930  {
931  knockoutZoneClearance( otherZone );
932  }
933  }
934  }
935 
937 }
938 
939 
945  SHAPE_POLY_SET& aRawFill )
946 {
947  auto knockoutZoneOutline =
948  [&]( ZONE* aKnockout )
949  {
950  // If the zones share no common layers
951  if( !aKnockout->GetLayerSet().test( aLayer ) )
952  return;
953 
954  if( aKnockout->GetCachedBoundingBox().Intersects( aZone->GetCachedBoundingBox() ) )
955  {
956  aRawFill.BooleanSubtract( *aKnockout->Outline(), SHAPE_POLY_SET::PM_FAST );
957  }
958  };
959 
960  for( ZONE* otherZone : m_board->Zones() )
961  {
962  if( otherZone->GetNetCode() == aZone->GetNetCode()
963  && otherZone->GetPriority() > aZone->GetPriority() )
964  {
965  knockoutZoneOutline( otherZone );
966  }
967  }
968 
969  for( FOOTPRINT* footprint : m_board->Footprints() )
970  {
971  for( ZONE* otherZone : footprint->Zones() )
972  {
973  if( otherZone->GetNetCode() == aZone->GetNetCode()
974  && otherZone->GetPriority() > aZone->GetPriority() )
975  {
976  knockoutZoneOutline( otherZone );
977  }
978  }
979  }
980 }
981 
982 
983 #define DUMP_POLYS_TO_COPPER_LAYER( a, b, c ) \
984  { if( m_debugZoneFiller && aDebugLayer == b ) \
985  { \
986  m_board->SetLayerName( b, c ); \
987  SHAPE_POLY_SET d = a; \
988  d.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); \
989  d.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); \
990  aRawPolys = d; \
991  return false; \
992  } \
993  }
994 
995 
1008  PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer,
1009  const SHAPE_POLY_SET& aSmoothedOutline,
1010  const SHAPE_POLY_SET& aMaxExtents,
1011  SHAPE_POLY_SET& aRawPolys )
1012 {
1014 
1015  // Features which are min_width should survive pruning; features that are *less* than
1016  // min_width should not. Therefore we subtract epsilon from the min_width when
1017  // deflating/inflating.
1018  int half_min_width = aZone->GetMinThickness() / 2;
1019  int epsilon = Millimeter2iu( 0.001 );
1020  int numSegs = GetArcToSegmentCount( half_min_width, m_maxError, 360.0 );
1021 
1022  // Solid polygons are deflated and inflated during calculations. Deflating doesn't cause
1023  // issues, but inflate is tricky as it can create excessively long and narrow spikes for
1024  // acute angles.
1025  // ALLOW_ACUTE_CORNERS cannot be used due to the spike problem.
1026  // CHAMFER_ACUTE_CORNERS is tempting, but can still produce spikes in some unusual
1027  // circumstances (https://gitlab.com/kicad/code/kicad/-/issues/5581).
1028  // It's unclear if ROUND_ACUTE_CORNERS would have the same issues, but is currently avoided
1029  // as a "less-safe" option.
1030  // ROUND_ALL_CORNERS produces the uniformly nicest shapes, but also a lot of segments.
1031  // CHAMFER_ALL_CORNERS improves the segement count.
1034 
1035  std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
1036  SHAPE_POLY_SET clearanceHoles;
1037 
1038  aRawPolys = aSmoothedOutline;
1039  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In1_Cu, "smoothed-outline" );
1040 
1042  return false;
1043 
1044  knockoutThermalReliefs( aZone, aLayer, aRawPolys );
1045  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In2_Cu, "minus-thermal-reliefs" );
1046 
1048  return false;
1049 
1050  buildCopperItemClearances( aZone, aLayer, clearanceHoles );
1051  DUMP_POLYS_TO_COPPER_LAYER( clearanceHoles, In3_Cu, "clearance-holes" );
1052 
1054  return false;
1055 
1056  buildThermalSpokes( aZone, aLayer, thermalSpokes );
1057 
1059  return false;
1060 
1061  // Create a temporary zone that we can hit-test spoke-ends against. It's only temporary
1062  // because the "real" subtract-clearance-holes has to be done after the spokes are added.
1063  static const bool USE_BBOX_CACHES = true;
1064  SHAPE_POLY_SET testAreas = aRawPolys;
1065  testAreas.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
1066  DUMP_POLYS_TO_COPPER_LAYER( testAreas, In4_Cu, "minus-clearance-holes" );
1067 
1068  // Prune features that don't meet minimum-width criteria
1069  if( half_min_width - epsilon > epsilon )
1070  {
1071  testAreas.Deflate( half_min_width - epsilon, numSegs, fastCornerStrategy );
1072  DUMP_POLYS_TO_COPPER_LAYER( testAreas, In5_Cu, "spoke-test-deflated" );
1073 
1074  testAreas.Inflate( half_min_width - epsilon, numSegs, fastCornerStrategy );
1075  DUMP_POLYS_TO_COPPER_LAYER( testAreas, In6_Cu, "spoke-test-reinflated" );
1076  }
1077 
1079  return false;
1080 
1081  // Spoke-end-testing is hugely expensive so we generate cached bounding-boxes to speed
1082  // things up a bit.
1083  testAreas.BuildBBoxCaches();
1084  int interval = 0;
1085 
1086  SHAPE_POLY_SET debugSpokes;
1087 
1088  for( const SHAPE_LINE_CHAIN& spoke : thermalSpokes )
1089  {
1090  const VECTOR2I& testPt = spoke.CPoint( 3 );
1091 
1092  // Hit-test against zone body
1093  if( testAreas.Contains( testPt, -1, 1, USE_BBOX_CACHES ) )
1094  {
1095  if( m_debugZoneFiller )
1096  debugSpokes.AddOutline( spoke );
1097 
1098  aRawPolys.AddOutline( spoke );
1099  continue;
1100  }
1101 
1102  if( interval++ > 400 )
1103  {
1105  return false;
1106 
1107  interval = 0;
1108  }
1109 
1110  // Hit-test against other spokes
1111  for( const SHAPE_LINE_CHAIN& other : thermalSpokes )
1112  {
1113  if( &other != &spoke && other.PointInside( testPt, 1, USE_BBOX_CACHES ) )
1114  {
1115  if( m_debugZoneFiller )
1116  debugSpokes.AddOutline( spoke );
1117 
1118  aRawPolys.AddOutline( spoke );
1119  break;
1120  }
1121  }
1122  }
1123 
1124  DUMP_POLYS_TO_COPPER_LAYER( debugSpokes, In7_Cu, "spokes" );
1125 
1127  return false;
1128 
1129  aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
1130  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In8_Cu, "after-spoke-trimming" );
1131 
1132  // Prune features that don't meet minimum-width criteria
1133  if( half_min_width - epsilon > epsilon )
1134  aRawPolys.Deflate( half_min_width - epsilon, numSegs, cornerStrategy );
1135 
1136  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In9_Cu, "deflated" );
1137 
1139  return false;
1140 
1141  // Now remove the non filled areas due to the hatch pattern
1142  if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
1143  {
1144  if( !addHatchFillTypeOnZone( aZone, aLayer, aDebugLayer, aRawPolys ) )
1145  return false;
1146  }
1147 
1149  return false;
1150 
1151  // Re-inflate after pruning of areas that don't meet minimum-width criteria
1152  if( aZone->GetFilledPolysUseThickness() )
1153  {
1154  // If we're stroking the zone with a min_width stroke then this will naturally inflate
1155  // the zone by half_min_width
1156  }
1157  else if( half_min_width - epsilon > epsilon )
1158  {
1159  aRawPolys.Inflate( half_min_width - epsilon, numSegs, cornerStrategy );
1160  }
1161 
1162  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In15_Cu, "after-reinflating" );
1163 
1164  // Ensure additive changes (thermal stubs and particularly inflating acute corners) do not
1165  // add copper outside the zone boundary or inside the clearance holes
1166  aRawPolys.BooleanIntersection( aMaxExtents, SHAPE_POLY_SET::PM_FAST );
1167  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In16_Cu, "after-trim-to-outline" );
1168  aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
1169  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In17_Cu, "after-trim-to-clearance-holes" );
1170 
1171  // Lastly give any same-net but higher-priority zones control over their own area.
1172  subtractHigherPriorityZones( aZone, aLayer, aRawPolys );
1173  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In18_Cu, "minus-higher-priority-zones" );
1174 
1175  aRawPolys.Fracture( SHAPE_POLY_SET::PM_FAST );
1176  return true;
1177 }
1178 
1179 
1180 /*
1181  * Build the filled solid areas data from real outlines (stored in m_Poly)
1182  * The solid areas can be more than one on copper layers, and do not have holes
1183  * ( holes are linked by overlapping segments to the main outline)
1184  */
1186  SHAPE_POLY_SET& aFinalPolys )
1187 {
1188  SHAPE_POLY_SET* boardOutline = m_brdOutlinesValid ? &m_boardOutline : nullptr;
1189  SHAPE_POLY_SET maxExtents;
1190  SHAPE_POLY_SET smoothedPoly;
1191  PCB_LAYER_ID debugLayer = UNDEFINED_LAYER;
1192 
1193  if( m_debugZoneFiller && LSET::InternalCuMask().Contains( aLayer ) )
1194  {
1195  debugLayer = aLayer;
1196  aLayer = F_Cu;
1197  }
1198 
1199  if ( !aZone->BuildSmoothedPoly( maxExtents, aLayer, boardOutline, &smoothedPoly ) )
1200  return false;
1201 
1203  return false;
1204 
1205  if( aZone->IsOnCopperLayer() )
1206  {
1207  if( computeRawFilledArea( aZone, aLayer, debugLayer, smoothedPoly, maxExtents, aRawPolys ) )
1208  aZone->SetNeedRefill( false );
1209 
1210  aFinalPolys = aRawPolys;
1211  }
1212  else
1213  {
1214  // Features which are min_width should survive pruning; features that are *less* than
1215  // min_width should not. Therefore we subtract epsilon from the min_width when
1216  // deflating/inflating.
1217  int half_min_width = aZone->GetMinThickness() / 2;
1218  int epsilon = Millimeter2iu( 0.001 );
1219  int numSegs = GetArcToSegmentCount( half_min_width, m_maxError, 360.0 );
1220 
1221  smoothedPoly.Deflate( half_min_width - epsilon, numSegs );
1222 
1223  // Remove the non filled areas due to the hatch pattern
1224  if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
1225  addHatchFillTypeOnZone( aZone, aLayer, debugLayer, smoothedPoly );
1226 
1227  // Re-inflate after pruning of areas that don't meet minimum-width criteria
1228  if( aZone->GetFilledPolysUseThickness() )
1229  {
1230  // If we're stroking the zone with a min_width stroke then this will naturally
1231  // inflate the zone by half_min_width
1232  }
1233  else if( half_min_width - epsilon > epsilon )
1234  {
1235  smoothedPoly.Inflate( half_min_width - epsilon, numSegs );
1236  }
1237 
1238  aRawPolys = smoothedPoly;
1239  aFinalPolys = smoothedPoly;
1240 
1242  aZone->SetNeedRefill( false );
1243  }
1244 
1245  return true;
1246 }
1247 
1248 
1253  std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
1254 {
1255  auto zoneBB = aZone->GetCachedBoundingBox();
1256  int zone_clearance = aZone->GetLocalClearance();
1257  int biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue();
1258  biggest_clearance = std::max( biggest_clearance, zone_clearance );
1259  zoneBB.Inflate( biggest_clearance );
1260 
1261  // Is a point on the boundary of the polygon inside or outside? This small epsilon lets
1262  // us avoid the question.
1263  int epsilon = KiROUND( IU_PER_MM * 0.04 ); // about 1.5 mil
1264 
1265  for( FOOTPRINT* footprint : m_board->Footprints() )
1266  {
1267  for( PAD* pad : footprint->Pads() )
1268  {
1269  if( !hasThermalConnection( pad, aZone ) )
1270  continue;
1271 
1272  // We currently only connect to pads, not pad holes
1273  if( !pad->IsOnLayer( aLayer ) )
1274  continue;
1275 
1276  int thermalReliefGap = aZone->GetThermalReliefGap( pad );
1277 
1278  // Calculate thermal bridge half width
1279  int spoke_w = aZone->GetThermalReliefSpokeWidth( pad );
1280  // Avoid spoke_w bigger than the smaller pad size, because
1281  // it is not possible to create stubs bigger than the pad.
1282  // Possible refinement: have a separate size for vertical and horizontal stubs
1283  spoke_w = std::min( spoke_w, pad->GetSize().x );
1284  spoke_w = std::min( spoke_w, pad->GetSize().y );
1285 
1286  // Cannot create stubs having a width < zone min thickness
1287  if( spoke_w < aZone->GetMinThickness() )
1288  continue;
1289 
1290  int spoke_half_w = spoke_w / 2;
1291 
1292  // Quick test here to possibly save us some work
1293  BOX2I itemBB = pad->GetBoundingBox();
1294  itemBB.Inflate( thermalReliefGap + epsilon );
1295 
1296  if( !( itemBB.Intersects( zoneBB ) ) )
1297  continue;
1298 
1299  // Thermal spokes consist of segments from the pad center to points just outside
1300  // the thermal relief.
1301  //
1302  // We use the bounding-box to lay out the spokes, but for this to work the
1303  // bounding box has to be built at the same rotation as the spokes.
1304  // We have to use a dummy pad to avoid dirtying the cached shapes
1305  wxPoint shapePos = pad->ShapePos();
1306  double padAngle = pad->GetOrientation();
1307  PAD dummy_pad( *pad );
1308  dummy_pad.SetOrientation( 0.0 );
1309 
1310  // Spokes are from center of pad, not from hole
1311  dummy_pad.SetPosition( -pad->GetOffset() );
1312 
1313  BOX2I reliefBB = dummy_pad.GetBoundingBox();
1314  reliefBB.Inflate( thermalReliefGap + epsilon );
1315 
1316  // For circle pads, the thermal spoke orientation is 45 deg
1317  if( pad->GetShape() == PAD_SHAPE::CIRCLE )
1318  padAngle = s_RoundPadThermalSpokeAngle;
1319 
1320  for( int i = 0; i < 4; i++ )
1321  {
1322  SHAPE_LINE_CHAIN spoke;
1323  switch( i )
1324  {
1325  case 0: // lower stub
1326  spoke.Append( +spoke_half_w, -spoke_half_w );
1327  spoke.Append( -spoke_half_w, -spoke_half_w );
1328  spoke.Append( -spoke_half_w, reliefBB.GetBottom() );
1329  spoke.Append( 0, reliefBB.GetBottom() ); // test pt
1330  spoke.Append( +spoke_half_w, reliefBB.GetBottom() );
1331  break;
1332 
1333  case 1: // upper stub
1334  spoke.Append( +spoke_half_w, spoke_half_w );
1335  spoke.Append( -spoke_half_w, spoke_half_w );
1336  spoke.Append( -spoke_half_w, reliefBB.GetTop() );
1337  spoke.Append( 0, reliefBB.GetTop() ); // test pt
1338  spoke.Append( +spoke_half_w, reliefBB.GetTop() );
1339  break;
1340 
1341  case 2: // right stub
1342  spoke.Append( -spoke_half_w, spoke_half_w );
1343  spoke.Append( -spoke_half_w, -spoke_half_w );
1344  spoke.Append( reliefBB.GetRight(), -spoke_half_w );
1345  spoke.Append( reliefBB.GetRight(), 0 ); // test pt
1346  spoke.Append( reliefBB.GetRight(), spoke_half_w );
1347  break;
1348 
1349  case 3: // left stub
1350  spoke.Append( spoke_half_w, spoke_half_w );
1351  spoke.Append( spoke_half_w, -spoke_half_w );
1352  spoke.Append( reliefBB.GetLeft(), -spoke_half_w );
1353  spoke.Append( reliefBB.GetLeft(), 0 ); // test pt
1354  spoke.Append( reliefBB.GetLeft(), spoke_half_w );
1355  break;
1356  }
1357 
1358  spoke.Rotate( -DECIDEG2RAD( padAngle ) );
1359  spoke.Move( shapePos );
1360 
1361  spoke.SetClosed( true );
1362  spoke.GenerateBBoxCache();
1363  aSpokesList.push_back( std::move( spoke ) );
1364  }
1365  }
1366  }
1367 }
1368 
1369 
1371  PCB_LAYER_ID aDebugLayer, SHAPE_POLY_SET& aRawPolys )
1372 {
1373  // Build grid:
1374 
1375  // obviously line thickness must be > zone min thickness.
1376  // It can happens if a board file was edited by hand by a python script
1377  // Use 1 micron margin to be *sure* there is no issue in Gerber files
1378  // (Gbr file unit = 1 or 10 nm) due to some truncation in coordinates or calculations
1379  // This margin also avoid problems due to rounding coordinates in next calculations
1380  // that can create incorrect polygons
1381  int thickness = std::max( aZone->GetHatchThickness(),
1382  aZone->GetMinThickness() + Millimeter2iu( 0.001 ) );
1383 
1384  int linethickness = thickness - aZone->GetMinThickness();
1385  int gridsize = thickness + aZone->GetHatchGap();
1386  double orientation = aZone->GetHatchOrientation();
1387 
1388  SHAPE_POLY_SET filledPolys = aRawPolys;
1389  // Use a area that contains the rotated bbox by orientation,
1390  // and after rotate the result by -orientation.
1391  if( orientation != 0.0 )
1392  filledPolys.Rotate( M_PI / 180.0 * orientation, VECTOR2I( 0, 0 ) );
1393 
1394  BOX2I bbox = filledPolys.BBox( 0 );
1395 
1396  // Build hole shape
1397  // the hole size is aZone->GetHatchGap(), but because the outline thickness
1398  // is aZone->GetMinThickness(), the hole shape size must be larger
1399  SHAPE_LINE_CHAIN hole_base;
1400  int hole_size = aZone->GetHatchGap() + aZone->GetMinThickness();
1401  VECTOR2I corner( 0, 0 );;
1402  hole_base.Append( corner );
1403  corner.x += hole_size;
1404  hole_base.Append( corner );
1405  corner.y += hole_size;
1406  hole_base.Append( corner );
1407  corner.x = 0;
1408  hole_base.Append( corner );
1409  hole_base.SetClosed( true );
1410 
1411  // Calculate minimal area of a grid hole.
1412  // All holes smaller than a threshold will be removed
1413  double minimal_hole_area = hole_base.Area() * aZone->GetHatchHoleMinArea();
1414 
1415  // Now convert this hole to a smoothed shape:
1416  if( aZone->GetHatchSmoothingLevel() > 0 )
1417  {
1418  // the actual size of chamfer, or rounded corner radius is the half size
1419  // of the HatchFillTypeGap scaled by aZone->GetHatchSmoothingValue()
1420  // aZone->GetHatchSmoothingValue() = 1.0 is the max value for the chamfer or the
1421  // radius of corner (radius = half size of the hole)
1422  int smooth_value = KiROUND( aZone->GetHatchGap()
1423  * aZone->GetHatchSmoothingValue() / 2 );
1424 
1425  // Minimal optimization:
1426  // make smoothing only for reasonnable smooth values, to avoid a lot of useless segments
1427  // and if the smooth value is small, use chamfer even if fillet is requested
1428  #define SMOOTH_MIN_VAL_MM 0.02
1429  #define SMOOTH_SMALL_VAL_MM 0.04
1430 
1431  if( smooth_value > Millimeter2iu( SMOOTH_MIN_VAL_MM ) )
1432  {
1433  SHAPE_POLY_SET smooth_hole;
1434  smooth_hole.AddOutline( hole_base );
1435  int smooth_level = aZone->GetHatchSmoothingLevel();
1436 
1437  if( smooth_value < Millimeter2iu( SMOOTH_SMALL_VAL_MM ) && smooth_level > 1 )
1438  smooth_level = 1;
1439 
1440  // Use a larger smooth_value to compensate the outline tickness
1441  // (chamfer is not visible is smooth value < outline thickess)
1442  smooth_value += aZone->GetMinThickness() / 2;
1443 
1444  // smooth_value cannot be bigger than the half size oh the hole:
1445  smooth_value = std::min( smooth_value, aZone->GetHatchGap() / 2 );
1446 
1447  // the error to approximate a circle by segments when smoothing corners by a arc
1448  int error_max = std::max( Millimeter2iu( 0.01 ), smooth_value / 20 );
1449 
1450  switch( smooth_level )
1451  {
1452  case 1:
1453  // Chamfer() uses the distance from a corner to create a end point
1454  // for the chamfer.
1455  hole_base = smooth_hole.Chamfer( smooth_value ).Outline( 0 );
1456  break;
1457 
1458  default:
1459  if( aZone->GetHatchSmoothingLevel() > 2 )
1460  error_max /= 2; // Force better smoothing
1461 
1462  hole_base = smooth_hole.Fillet( smooth_value, error_max ).Outline( 0 );
1463  break;
1464 
1465  case 0:
1466  break;
1467  };
1468  }
1469  }
1470 
1471  // Build holes
1472  SHAPE_POLY_SET holes;
1473 
1474  for( int xx = 0; ; xx++ )
1475  {
1476  int xpos = xx * gridsize;
1477 
1478  if( xpos > bbox.GetWidth() )
1479  break;
1480 
1481  for( int yy = 0; ; yy++ )
1482  {
1483  int ypos = yy * gridsize;
1484 
1485  if( ypos > bbox.GetHeight() )
1486  break;
1487 
1488  // Generate hole
1489  SHAPE_LINE_CHAIN hole( hole_base );
1490  hole.Move( VECTOR2I( xpos, ypos ) );
1491  holes.AddOutline( hole );
1492  }
1493  }
1494 
1495  holes.Move( bbox.GetPosition() );
1496 
1497  if( orientation != 0.0 )
1498  holes.Rotate( -M_PI/180.0 * orientation, VECTOR2I( 0,0 ) );
1499 
1500  DUMP_POLYS_TO_COPPER_LAYER( holes, In10_Cu, "hatch-holes" );
1501 
1502  int outline_margin = aZone->GetMinThickness() * 1.1;
1503 
1504  // Using GetHatchThickness() can look more consistent than GetMinThickness().
1505  if( aZone->GetHatchBorderAlgorithm() && aZone->GetHatchThickness() > outline_margin )
1506  outline_margin = aZone->GetHatchThickness();
1507 
1508  // The fill has already been deflated to ensure GetMinThickness() so we just have to
1509  // account for anything beyond that.
1510  SHAPE_POLY_SET deflatedFilledPolys = aRawPolys;
1511  deflatedFilledPolys.Deflate( outline_margin - aZone->GetMinThickness(), 16 );
1512  holes.BooleanIntersection( deflatedFilledPolys, SHAPE_POLY_SET::PM_FAST );
1513  DUMP_POLYS_TO_COPPER_LAYER( holes, In11_Cu, "fill-clipped-hatch-holes" );
1514 
1515  SHAPE_POLY_SET deflatedOutline = *aZone->Outline();
1516  deflatedOutline.Deflate( outline_margin, 16 );
1517  holes.BooleanIntersection( deflatedOutline, SHAPE_POLY_SET::PM_FAST );
1518  DUMP_POLYS_TO_COPPER_LAYER( holes, In12_Cu, "outline-clipped-hatch-holes" );
1519 
1520  if( aZone->GetNetCode() != 0 )
1521  {
1522  // Vias and pads connected to the zone must not be allowed to become isolated inside
1523  // one of the holes. Effectively this means their copper outline needs to be expanded
1524  // to be at least as wide as the gap so that it is guaranteed to touch at least one
1525  // edge.
1526  EDA_RECT zone_boundingbox = aZone->GetCachedBoundingBox();
1527  SHAPE_POLY_SET aprons;
1528  int min_apron_radius = ( aZone->GetHatchGap() * 10 ) / 19;
1529 
1530  for( TRACK* track : m_board->Tracks() )
1531  {
1532  if( track->Type() == PCB_VIA_T )
1533  {
1534  VIA* via = static_cast<VIA*>( track );
1535 
1536  if( via->GetNetCode() == aZone->GetNetCode()
1537  && via->IsOnLayer( aLayer )
1538  && via->GetBoundingBox().Intersects( zone_boundingbox ) )
1539  {
1540  int r = std::max( min_apron_radius,
1541  via->GetDrillValue() / 2 + outline_margin );
1542 
1543  TransformCircleToPolygon( aprons, via->GetPosition(), r, ARC_HIGH_DEF,
1544  ERROR_OUTSIDE );
1545  }
1546  }
1547  }
1548 
1549  for( FOOTPRINT* footprint : m_board->Footprints() )
1550  {
1551  for( PAD* pad : footprint->Pads() )
1552  {
1553  if( pad->GetNetCode() == aZone->GetNetCode()
1554  && pad->IsOnLayer( aLayer )
1555  && pad->GetBoundingBox().Intersects( zone_boundingbox ) )
1556  {
1557  // What we want is to bulk up the pad shape so that the narrowest bit of
1558  // copper between the hole and the apron edge is at least outline_margin
1559  // wide (and that the apron itself meets min_apron_radius. But that would
1560  // take a lot of code and math, and the following approximation is close
1561  // enough.
1562  int pad_width = std::min( pad->GetSize().x, pad->GetSize().y );
1563  int slot_width = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
1564  int min_annulus = ( pad_width - slot_width ) / 2;
1565  int clearance = std::max( min_apron_radius - pad_width / 2,
1566  outline_margin - min_annulus );
1567 
1568  clearance = std::max( 0, clearance - linethickness / 2 );
1569  pad->TransformShapeWithClearanceToPolygon( aprons, aLayer, clearance,
1570  ARC_HIGH_DEF, ERROR_OUTSIDE );
1571  }
1572  }
1573  }
1574 
1575  holes.BooleanSubtract( aprons, SHAPE_POLY_SET::PM_FAST );
1576  }
1577  DUMP_POLYS_TO_COPPER_LAYER( holes, In13_Cu, "pad-via-clipped-hatch-holes" );
1578 
1579  // Now filter truncated holes to avoid small holes in pattern
1580  // It happens for holes near the zone outline
1581  for( int ii = 0; ii < holes.OutlineCount(); )
1582  {
1583  double area = holes.Outline( ii ).Area();
1584 
1585  if( area < minimal_hole_area ) // The current hole is too small: remove it
1586  holes.DeletePolygon( ii );
1587  else
1588  ++ii;
1589  }
1590 
1591  // create grid. Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to
1592  // generate strictly simple polygons needed by Gerber files and Fracture()
1593  aRawPolys.BooleanSubtract( aRawPolys, holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
1594  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In14_Cu, "after-hatching" );
1595 
1596  return true;
1597 }
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:750
virtual void AdvancePhase()
Uses the next vailable virtual zone of the dialog progress bar.
int m_ZoneFillVersion
Option to select different fill algorithms.
#define DUMP_POLYS_TO_COPPER_LAYER(a, b, c)
int GetHatchGap() const
Definition: zone.h:253
Definition: track.h:343
COMMIT & Modify(EDA_ITEM *aItem)
Create an undo entry for an item that has been already modified.
Definition: commit.h:103
void DoNotShowCheckbox(wxString file, int line)
Checks the 'do not show again' setting for the dialog.
Definition: confirm.cpp:55
int OutlineCount() const
Return the number of vertices in a given outline/hole.
All angles are chamfered.
class FP_TEXT, text in a footprint
Definition: typeinfo.h:92
PROGRESS_REPORTER * m_progressReporter
Definition: zone_filler.h:126
bool GetBoardPolygonOutlines(SHAPE_POLY_SET &aOutlines, OUTLINE_ERROR_HANDLER *aErrorHandler=nullptr)
Extract the board outlines and build a closed polygon from lines, arcs and circle items on edge cut l...
Definition: board.cpp:1925
ZONES & Zones()
Definition: board.h:311
int GetHatchThickness() const
Definition: zone.h:250
Helper class to create more flexible dialogs, including 'do not show again' checkbox handling.
Definition: confirm.h:45
unsigned GetPriority() const
Definition: zone.h:122
This file is part of the common library.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:82
BOARD * m_board
Definition: zone_filler.h:122
coord_type GetTop() const
Definition: box2.h:204
static constexpr double IU_PER_MM
Mock up a conversion function.
A progress reporter for use in multi-threaded environments.
int GetHolePlatingThickness() const
Pad & via drills are finish size.
SHAPE_POLY_SET * Outline()
Definition: zone.h:317
bool Contains(PCB_LAYER_ID aLayer)
See if the layer set contains a PCB layer.
void Move(const VECTOR2I &aVector) override
static const double s_RoundPadThermalSpokeAngle
Definition: zone_filler.cpp:49
#define SMOOTH_MIN_VAL_MM
double GetHatchSmoothingValue() const
Definition: zone.h:262
CORNER_STRATEGY
< define how inflate transform build inflated polygon
virtual void Report(const wxString &aMessage)
Display aMessage in the progress bar dialog.
class PCB_TEXT, text on a layer
Definition: typeinfo.h:91
coord_type GetRight() const
Definition: box2.h:199
bool BuildSmoothedPoly(SHAPE_POLY_SET &aSmoothedPoly, PCB_LAYER_ID aLayer, SHAPE_POLY_SET *aBoardOutline, SHAPE_POLY_SET *aSmoothedPolyWithApron=nullptr) const
Definition: zone.cpp:1167
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition: commit.h:71
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.h:593
class FP_SHAPE, a footprint edge
Definition: typeinfo.h:93
ZONE_FILLER(BOARD *aBoard, COMMIT *aCommit)
Definition: zone_filler.cpp:52
std::unique_ptr< WX_PROGRESS_REPORTER > m_uniqueReporter
Definition: zone_filler.h:128
void Rotate(double aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
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.
coord_type GetBottom() const
Definition: box2.h:200
void Inflate(int aAmount, int aCircleSegmentsCount, CORNER_STRATEGY aCornerStrategy=ROUND_ALL_CORNERS)
Perform outline inflation/deflation.
VECTOR2< int > VECTOR2I
Definition: vector2d.h:623
#define SMOOTH_SMALL_VAL_MM
LSEQ Seq(const PCB_LAYER_ID *aWishListSequence, unsigned aCount) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition: lset.cpp:411
ZONE_FILL_MODE GetFillMode() const
Definition: zone.h:181
void BuildBBoxCaches() const
Construct BBoxCaches for Contains(), below.
void Append(int aX, int aY, bool aAllowDuplication=false)
Function Append()
void InstallNewProgressReporter(wxWindow *aParent, const wxString &aTitle, int aNumPhases)
Definition: zone_filler.cpp:70
Plated through hole pad.
void DeletePolygon(int aIdx)
void buildCopperItemClearances(const ZONE *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aHoles)
Removes clearance from the shape for copper items which share the zone's layer but are not connected ...
int GetThermalReliefGap() const
Definition: zone.h:190
DRC_CONSTRAINT_T
Definition: drc_rule.h:41
bool Intersects(const BOX2< Vec > &aRect) const
Function Intersects.
Definition: box2.h:236
double GetHatchHoleMinArea() const
Definition: zone.h:265
bool Fill(std::vector< ZONE * > &aZones, bool aCheck=false, wxWindow *aParent=nullptr)
Fills the given list of zones.
Definition: zone_filler.cpp:85
void SetClosed(bool aClosed)
Function SetClosed()
PCB_LAYER_ID
A quick note on layer IDs:
LSET is a set of PCB_LAYER_IDs.
bool IsCancelled() const
int GetMinThickness() const
Definition: zone.h:241
void SetProgressReporter(PROGRESS_REPORTER *aReporter)
Definition: zone_filler.cpp:78
void Move(const VECTOR2I &aVector) override
COMMIT * m_commit
Definition: zone_filler.h:125
int GetHatchSmoothingLevel() const
Definition: zone.h:259
const EDA_RECT GetCachedBoundingBox() const
ONLY TO BE USED BY CLIENTS WHICH SET UP THE CACHE!
Definition: zone.h:145
Represent a set of closed polygons.
SHAPE_LINE_CHAIN & Outline(int aIndex)
int GetLocalClearance(wxString *aSource) const override
Return any local clearances set in the "classic" (ie: pre-rule) system.
Definition: zone.cpp:492
coord_type GetWidth() const
Definition: box2.h:197
FOOTPRINTS & Footprints()
Definition: board.h:305
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition: board.h:416
void Deflate(int aAmount, int aCircleSegmentsCount, CORNER_STRATEGY aCornerStrategy=ROUND_ALL_CORNERS)
void addKnockout(PAD *aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET &aHoles)
Add a knockout for a pad.
static LSET InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Definition: lset.cpp:710
const EDA_RECT GetBoundingBox() const override
The bounding box is cached, so this will be efficient most of the time.
Definition: pad.cpp:517
bool addHatchFillTypeOnZone(const ZONE *aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer, SHAPE_POLY_SET &aRawPolys)
for zones having the ZONE_FILL_MODE::ZONE_FILL_MODE::HATCH_PATTERN, create a grid pattern in filled a...
PAD_SHAPE GetShape() const
Definition: pad.h:169
a few functions useful in geometry calculations.
void Simplify(POLYGON_MODE aFastMode)
Handle a list of polygons defining a copper zone.
Definition: zone.h:57
void BooleanIntersection(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset union between a and b, store the result in it self For aFastMode meaning,...
int NewOutline()
Creates a new hole in a given outline.
void Fracture(POLYGON_MODE aFastMode)
Convert a single outline slitted ("fractured") polygon into a set ouf outlines with holes.
void SetFilledPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition: zone.h:647
Thermal relief only for THT pads.
const Vec & GetPosition() const
Definition: box2.h:194
void knockoutThermalReliefs(const ZONE *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aFill)
Removes thermal reliefs from the shape for any pads connected to the zone.
void Rotate(double aAngle, const VECTOR2I &aCenter=VECTOR2I(0, 0)) override
Function Rotate rotates all vertices by a given angle.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new hole to the given outline (default: last) and returns its index.
double GetHatchOrientation() const
Definition: zone.h:256
Use thermal relief for pads.
bool computeRawFilledArea(const ZONE *aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer, const SHAPE_POLY_SET &aSmoothedOutline, const SHAPE_POLY_SET &aMaxExtents, SHAPE_POLY_SET &aRawPolys)
Function computeRawFilledArea Add non copper areas polygons (pads and tracks with clearance) to a fil...
int GetHatchBorderAlgorithm() const
Definition: zone.h:268
void TransformShapeWithClearanceToPolygon(SHAPE_POLY_SET &aCornerBuffer, PCB_LAYER_ID aLayer, int aClearanceValue, int aMaxError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const override
Convert the pad shape to a closed polygon.
BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Function Inflate inflates the rectangle horizontally by dx and vertically by dy.
Definition: box2.h:302
CUST_PAD_SHAPE_IN_ZONE GetCustomShapeInZoneOpt() const
Definition: pad.h:187
bool SetOKCancelLabels(const ButtonLabel &ok, const ButtonLabel &cancel) override
Shows the 'do not show again' checkbox.
Definition: confirm.h:56
SHAPE_POLY_SET Chamfer(int aDistance)
Return a chamfered version of the polygon set.
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:190
#define _(s)
Definition: 3d_actions.cpp:33
SHAPE_LINE_CHAIN.
ISLAND_REMOVAL_MODE
Whether or not to remove isolated islands from a zone.
Definition: zone_settings.h:54
bool m_debugZoneFiller
Definition: zone_filler.h:133
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 SetPosition(const wxPoint &aPos) override
Definition: pad.h:171
bool KeepRefreshing(bool aWait=false)
Update the UI dialog.
Handle the component boundary box.
Definition: eda_rect.h:42
double DECIDEG2RAD(double deg)
Definition: trigo.h:235
bool IsOnCopperLayer() const override
Definition: zone.cpp:222
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:68
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aCornerBuffer, wxPoint aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc)
Function TransformCircleToPolygon convert a circle to a polygon, using multiple straight lines.
coord_type GetHeight() const
Definition: box2.h:198
Pads are not covered.
void SetFillFlag(PCB_LAYER_ID aLayer, bool aFlag)
Definition: zone.h:229
bool Intersects(const EDA_RECT &aRect) const
Test for a common area between rectangles.
Definition: eda_rect.cpp:150
std::mutex & GetLock()
Definition: zone.h:220
int ShowModal() override
Definition: confirm.cpp:99
SHAPE_POLY_SET m_boardOutline
Definition: zone_filler.h:123
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
class VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:96
void GenerateBBoxCache() const
virtual void TransformShapeWithClearanceToPolygon(SHAPE_POLY_SET &aCornerBuffer, PCB_LAYER_ID aLayer, int aClearanceValue, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the item shape to a closed polygon.
Definition: board_item.cpp:129
bool fillSingleZone(ZONE *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aRawPolys, SHAPE_POLY_SET &aFinalPolys)
Build the filled solid areas polygons from zone outlines (stored in m_Poly) The solid areas can be mo...
bool m_brdOutlinesValid
Definition: zone_filler.h:124
void SetNeedRefill(bool aNeedRefill)
Definition: zone.h:235
int GetThermalReliefSpokeWidth() const
Definition: zone.h:200
void BooleanSubtract(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset intersection For aFastMode meaning, see function booleanOp.
coord_type GetLeft() const
Definition: box2.h:203
POLYGON & Polygon(int aIndex)
void SetRawPolysList(PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aPolysList)
Set the list of filled polygons.
Definition: zone.h:655
ZONE_CONNECTION GetPadConnection(PAD *aPad, wxString *aSource=nullptr) const
Definition: zone.cpp:774
Definition: pad.h:60
bool hasThermalConnection(PAD *pad, const ZONE *aZone)
Return true if the given pad has a thermal connection with the given zone.
void SetMaxProgress(int aMaxProgress)
Fix the value thar gives the 100 precent progress bar length (inside the current virtual zone)
void AdvanceProgress()
Increment the progress bar length (inside the current virtual zone)
class PCB_SHAPE, a segment not on copper layers
Definition: typeinfo.h:90
static constexpr int Millimeter2iu(double mm)
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
bool m_DebugZoneFiller
A mode that dumps the various stages of a F_Cu fill into In1_Cu through In9_Cu.
DRAWINGS & Drawings()
Definition: board.h:308
int GetArcToSegmentCount(int aRadius, int aErrorMax, double aArcAngleDegree)
void buildThermalSpokes(const ZONE *aZone, PCB_LAYER_ID aLayer, std::deque< SHAPE_LINE_CHAIN > &aSpokes)
Function buildThermalSpokes Constructs a list of all thermal spokes for the given zone.
TRACKS & Tracks()
Definition: board.h:302
std::shared_ptr< DRC_ENGINE > m_DRCEngine
A structure used for calculating isolated islands on a given zone across all its layers.
Definition: track.h:83
int m_worstClearance
Definition: zone_filler.h:131
void SetOrientation(double aAngle)
Set the rotation angle of the pad.
Definition: pad.cpp:578
EDA_RECT & Inflate(wxCoord dx, wxCoord dy)
Inflate the rectangle horizontally by dx and vertically by dy.
Definition: eda_rect.cpp:363
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:163
bool GetFilledPolysUseThickness() const
Definition: zone.h:686
Container for design settings for a BOARD object.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Add a new vertex to the contour indexed by aOutline and aHole (defaults to the outline of the last po...
void BuildConvexHull(std::vector< wxPoint > &aResult, const std::vector< wxPoint > &aPoly)
Calculate the convex hull of a list of points in counter-clockwise order.
Definition: convex_hull.cpp:87