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 <class_board.h>
32 #include <class_zone.h>
33 #include <class_module.h>
34 #include <class_edge_mod.h>
35 #include <class_drawsegment.h>
36 #include <class_pcb_text.h>
37 #include <class_pcb_target.h>
38 #include <class_track.h>
41 #include <board_commit.h>
44 #include <geometry/convex_hull.h>
46 #include <confirm.h>
47 #include <convert_to_biu.h>
48 #include <math/util.h> // for KiROUND
49 #include "zone_filler.h"
50 
51 static const double s_RoundPadThermalSpokeAngle = 450; // in deci-degrees
52 
53 
54 ZONE_FILLER::ZONE_FILLER( BOARD* aBoard, COMMIT* aCommit ) :
55  m_board( aBoard ),
56  m_brdOutlinesValid( false ),
57  m_commit( aCommit ),
58  m_progressReporter( nullptr ),
59  m_maxError( ARC_HIGH_DEF )
60 {
61 }
62 
63 
65 {
66 }
67 
68 
69 void ZONE_FILLER::InstallNewProgressReporter( wxWindow* aParent, const wxString& aTitle,
70  int aNumPhases )
71 {
72  m_uniqueReporter = std::make_unique<WX_PROGRESS_REPORTER>( aParent, aTitle, aNumPhases );
74 }
75 
76 
78 {
79  m_progressReporter = aReporter;
80 }
81 
82 
83 bool ZONE_FILLER::Fill( std::vector<ZONE_CONTAINER*>& aZones, bool aCheck, wxWindow* aParent )
84 {
85  std::vector<std::pair<ZONE_CONTAINER*, PCB_LAYER_ID>> toFill;
86  std::vector<CN_ZONE_ISOLATED_ISLAND_LIST> islandsList;
87 
88  std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_board->GetConnectivity();
89  std::unique_lock<std::mutex> lock( connectivity->GetLock(), std::try_to_lock );
90 
92  int worstClearance = bds.GetBiggestClearanceValue();
93 
94  if( !lock )
95  return false;
96 
97  if( m_progressReporter )
98  {
99  m_progressReporter->Report( aCheck ? _( "Checking zone fills..." )
100  : _( "Building zone fills..." ) );
101  m_progressReporter->SetMaxProgress( aZones.size() );
103  }
104 
105  // The board outlines is used to clip solid areas inside the board (when outlines are valid)
108 
109  // Update the bounding box and shape caches in the pads to prevent multi-threaded rebuilds.
110  for( MODULE* module : m_board->Modules() )
111  {
112  for( D_PAD* pad : module->Pads() )
113  {
114  if( pad->IsDirty() )
115  pad->BuildEffectiveShapes( UNDEFINED_LAYER );
116  }
117  }
118 
119  // Sort by priority to reduce deferrals waiting on higher priority zones.
120  std::sort( aZones.begin(), aZones.end(),
121  []( const ZONE_CONTAINER* lhs, const ZONE_CONTAINER* rhs )
122  {
123  return lhs->GetPriority() > rhs->GetPriority();
124  } );
125 
126  for( ZONE_CONTAINER* zone : aZones )
127  {
128  zone->CacheBoundingBox();
129 
130  // Rule areas are not filled
131  if( zone->GetIsRuleArea() )
132  continue;
133 
134  m_commit->Modify( zone );
135 
136  // calculate the hash value for filled areas. it will be used later
137  // to know if the current filled areas are up to date
138  for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
139  {
140  zone->BuildHashValue( layer );
141 
142  // Add the zone to the list of zones to test or refill
143  toFill.emplace_back( std::make_pair( zone, layer ) );
144  }
145 
146  islandsList.emplace_back( CN_ZONE_ISOLATED_ISLAND_LIST( zone ) );
147 
148  // Remove existing fill first to prevent drawing invalid polygons
149  // on some platforms
150  zone->UnFill();
151 
152  zone->SetFillVersion( bds.m_ZoneFillVersion );
153  }
154 
155  size_t cores = std::thread::hardware_concurrency();
156  std::atomic<size_t> nextItem;
157 
158  auto check_fill_dependency =
159  [&]( ZONE_CONTAINER* aZone, PCB_LAYER_ID aLayer, ZONE_CONTAINER* aOtherZone ) -> bool
160  {
161  // Check to see if we have to knock-out the filled areas of a higher-priority
162  // zone. If so we have to wait until said zone is filled before we can fill.
163 
164  // If the other zone is already filled then we're good-to-go
165  if( aOtherZone->GetFillFlag( aLayer ) )
166  return false;
167 
168  // Even if keepouts exclude copper pours the exclusion is by outline, not by
169  // filled area, so we're good-to-go here too.
170  if( aOtherZone->GetIsRuleArea() )
171  return false;
172 
173  // If the zones share no common layers
174  if( !aOtherZone->GetLayerSet().test( aLayer ) )
175  return false;
176 
177  if( aOtherZone->GetPriority() <= aZone->GetPriority() )
178  return false;
179 
180  // Same-net zones always use outline to produce predictable results
181  if( aOtherZone->GetNetCode() == aZone->GetNetCode() )
182  return false;
183 
184  // A higher priority zone is found: if we intersect and it's not filled yet
185  // then we have to wait.
186  EDA_RECT inflatedBBox = aZone->GetCachedBoundingBox();
187  inflatedBBox.Inflate( worstClearance );
188 
189  return inflatedBBox.Intersects( aOtherZone->GetCachedBoundingBox() );
190  };
191 
192  auto fill_lambda =
193  [&]( PROGRESS_REPORTER* aReporter )
194  {
195  size_t num = 0;
196 
197  for( size_t i = nextItem++; i < toFill.size(); i = nextItem++ )
198  {
199  PCB_LAYER_ID layer = toFill[i].second;
200  ZONE_CONTAINER* zone = toFill[i].first;
201  bool canFill = true;
202 
203  // Check for any fill dependencies. If our zone needs to be clipped by
204  // another zone then we can't fill until that zone is filled.
205  for( ZONE_CONTAINER* otherZone : m_board->Zones() )
206  {
207  if( otherZone == zone )
208  continue;
209 
210  if( check_fill_dependency( zone, layer, otherZone ) )
211  {
212  canFill = false;
213  break;
214  }
215  }
216 
217  for( MODULE* module : m_board->Modules() )
218  {
219  for( ZONE_CONTAINER* otherZone : module->Zones() )
220  {
221  if( check_fill_dependency( zone, layer, otherZone ) )
222  {
223  canFill = false;
224  break;
225  }
226  }
227  }
228 
230  break;
231 
232  if( !canFill )
233  continue;
234 
235  // Now we're ready to fill.
236  SHAPE_POLY_SET rawPolys, finalPolys;
237  fillSingleZone( zone, layer, rawPolys, finalPolys );
238 
239  std::unique_lock<std::mutex> zoneLock( zone->GetLock() );
240 
241  zone->SetRawPolysList( layer, rawPolys );
242  zone->SetFilledPolysList( layer, finalPolys );
243  zone->SetFillFlag( layer, true );
244 
245  if( m_progressReporter )
247 
248  num++;
249  }
250 
251  return num;
252  };
253 
254  while( !toFill.empty() )
255  {
256  size_t parallelThreadCount = std::min( cores, toFill.size() );
257  std::vector<std::future<size_t>> returns( parallelThreadCount );
258 
259  nextItem = 0;
260 
261  if( parallelThreadCount <= 1 )
262  fill_lambda( m_progressReporter );
263  else
264  {
265  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
266  returns[ii] = std::async( std::launch::async, fill_lambda, m_progressReporter );
267 
268  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
269  {
270  // Here we balance returns with a 100ms timeout to allow UI updating
271  std::future_status status;
272  do
273  {
274  if( m_progressReporter )
276 
277  status = returns[ii].wait_for( std::chrono::milliseconds( 100 ) );
278  } while( status != std::future_status::ready );
279  }
280  }
281 
282  toFill.erase( std::remove_if( toFill.begin(), toFill.end(),
283  [&] ( const std::pair<ZONE_CONTAINER*, PCB_LAYER_ID> pair ) -> bool
284  {
285  return pair.first->GetFillFlag( pair.second );
286  } ),
287  toFill.end() );
288 
290  break;
291  }
292 
293  // Now update the connectivity to check for copper islands
294  if( m_progressReporter )
295  {
297  return false;
298 
300  m_progressReporter->Report( _( "Removing insulated copper islands..." ) );
302  }
303 
304  connectivity->SetProgressReporter( m_progressReporter );
305  connectivity->FindIsolatedCopperIslands( islandsList );
306  connectivity->SetProgressReporter( nullptr );
307 
309  return false;
310 
311  for( ZONE_CONTAINER* zone : aZones )
312  {
313  // Keepout zones are not filled
314  if( zone->GetIsRuleArea() )
315  continue;
316 
317  zone->SetIsFilled( true );
318  }
319 
320  // Now remove insulated copper islands
321  for( CN_ZONE_ISOLATED_ISLAND_LIST& zone : islandsList )
322  {
323  for( PCB_LAYER_ID layer : zone.m_zone->GetLayerSet().Seq() )
324  {
326  continue;
327 
328  if( !zone.m_islands.count( layer ) )
329  continue;
330 
331  std::vector<int>& islands = zone.m_islands.at( layer );
332 
333  // The list of polygons to delete must be explored from last to first in list,
334  // to allow deleting a polygon from list without breaking the remaining of the list
335  std::sort( islands.begin(), islands.end(), std::greater<int>() );
336 
337  SHAPE_POLY_SET poly = zone.m_zone->GetFilledPolysList( layer );
338  long long int minArea = zone.m_zone->GetMinIslandArea();
339  ISLAND_REMOVAL_MODE mode = zone.m_zone->GetIslandRemovalMode();
340 
341  for( int idx : islands )
342  {
343  SHAPE_LINE_CHAIN& outline = poly.Outline( idx );
344 
345  if( mode == ISLAND_REMOVAL_MODE::ALWAYS )
346  poly.DeletePolygon( idx );
347  else if ( mode == ISLAND_REMOVAL_MODE::AREA && outline.Area() < minArea )
348  poly.DeletePolygon( idx );
349  else
350  zone.m_zone->SetIsIsland( layer, idx );
351  }
352 
353  zone.m_zone->SetFilledPolysList( layer, poly );
354  zone.m_zone->CalculateFilledArea();
355 
357  return false;
358  }
359  }
360 
361  // Now remove islands outside the board edge
362  for( ZONE_CONTAINER* zone : aZones )
363  {
364  for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
365  {
367  continue;
368 
369  SHAPE_POLY_SET poly = zone->GetFilledPolysList( layer );
370 
371  for( int ii = 0; ii < poly.OutlineCount(); ii++ )
372  {
373  std::vector<SHAPE_LINE_CHAIN>& island = poly.Polygon( ii );
374 
375  if( island.empty() || !m_boardOutline.Contains( island.front().CPoint( 0 ) ) )
376  poly.DeletePolygon( ii );
377  }
378 
379  zone->SetFilledPolysList( layer, poly );
380  zone->CalculateFilledArea();
381 
383  return false;
384  }
385  }
386 
387  if( aCheck )
388  {
389  bool outOfDate = false;
390 
391  for( ZONE_CONTAINER* zone : aZones )
392  {
393  // Keepout zones are not filled
394  if( zone->GetIsRuleArea() )
395  continue;
396 
397  for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
398  {
399  MD5_HASH was = zone->GetHashValue( layer );
400  zone->CacheTriangulation( layer );
401  zone->BuildHashValue( layer );
402  MD5_HASH is = zone->GetHashValue( layer );
403 
404  if( is != was )
405  outOfDate = true;
406  }
407  }
408 
409  if( outOfDate )
410  {
411  KIDIALOG dlg( aParent, _( "Zone fills are out-of-date. Refill?" ),
412  _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
413  dlg.SetOKCancelLabels( _( "Refill" ), _( "Continue without Refill" ) );
414  dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
415 
416  if( dlg.ShowModal() == wxID_CANCEL )
417  return false;
418  }
419  }
420 
421  if( m_progressReporter )
422  {
424  m_progressReporter->Report( _( "Performing polygon fills..." ) );
425  m_progressReporter->SetMaxProgress( islandsList.size() );
426  }
427 
428  nextItem = 0;
429 
430  auto tri_lambda =
431  [&]( PROGRESS_REPORTER* aReporter ) -> size_t
432  {
433  size_t num = 0;
434 
435  for( size_t i = nextItem++; i < islandsList.size(); i = nextItem++ )
436  {
437  islandsList[i].m_zone->CacheTriangulation();
438  num++;
439 
440  if( m_progressReporter )
441  {
443 
445  break;
446  }
447  }
448 
449  return num;
450  };
451 
452  size_t parallelThreadCount = std::min( cores, islandsList.size() );
453  std::vector<std::future<size_t>> returns( parallelThreadCount );
454 
455  if( parallelThreadCount <= 1 )
456  tri_lambda( m_progressReporter );
457  else
458  {
459  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
460  returns[ii] = std::async( std::launch::async, tri_lambda, m_progressReporter );
461 
462  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
463  {
464  // Here we balance returns with a 100ms timeout to allow UI updating
465  std::future_status status;
466  do
467  {
468  if( m_progressReporter )
469  {
471 
473  break;
474  }
475 
476  status = returns[ii].wait_for( std::chrono::milliseconds( 100 ) );
477  } while( status != std::future_status::ready );
478  }
479  }
480 
481  if( m_progressReporter )
482  {
484  return false;
485 
488  }
489 
490  connectivity->SetProgressReporter( nullptr );
491  return true;
492 }
493 
494 
498 bool hasThermalConnection( D_PAD* pad, const ZONE_CONTAINER* aZone )
499 {
500  // Rejects non-standard pads with tht-only thermal reliefs
502  && pad->GetAttribute() != PAD_ATTRIB_STANDARD )
503  {
504  return false;
505  }
506 
507  if( aZone->GetPadConnection( pad ) != ZONE_CONNECTION::THERMAL
508  && aZone->GetPadConnection( pad ) != ZONE_CONNECTION::THT_THERMAL )
509  {
510  return false;
511  }
512 
513  if( pad->GetNetCode() != aZone->GetNetCode() || pad->GetNetCode() <= 0 )
514  return false;
515 
516  EDA_RECT item_boundingbox = pad->GetBoundingBox();
517  int thermalGap = aZone->GetThermalReliefGap( pad );
518  item_boundingbox.Inflate( thermalGap, thermalGap );
519 
520  return item_boundingbox.Intersects( aZone->GetCachedBoundingBox() );
521 }
522 
523 
528 static void setupDummyPadForHole( const D_PAD* aPad, D_PAD& aDummyPad )
529 {
530  // People may author clearance rules that look at a bunch of different stuff so we need
531  // to copy over pretty much everything except the shape info, which we fill from the drill
532  // info.
533  aDummyPad.SetLayerSet( aPad->GetLayerSet() );
534  aDummyPad.SetAttribute( aPad->GetAttribute() );
535  aDummyPad.SetProperty( aPad->GetProperty() );
536 
537  aDummyPad.SetNetCode( aPad->GetNetCode() );
538  aDummyPad.SetLocalClearance( aPad->GetLocalClearance() );
542 
543  aDummyPad.SetZoneConnection( aPad->GetEffectiveZoneConnection() );
545  aDummyPad.SetThermalGap( aPad->GetEffectiveThermalGap() );
546 
548 
549  // Note: drill size represents finish size, which means the actual holes size is the
550  // plating thickness larger.
551  int platingThickness = 0;
552 
553  if( aPad->GetAttribute() == PAD_ATTRIB_STANDARD )
554  platingThickness = aPad->GetBoard()->GetDesignSettings().GetHolePlatingThickness();
555 
556  aDummyPad.SetOffset( wxPoint( 0, 0 ) );
557  aDummyPad.SetSize( aPad->GetDrillSize() + wxSize( platingThickness, platingThickness ) );
559  : PAD_SHAPE_CIRCLE );
560  aDummyPad.SetOrientation( aPad->GetOrientation() );
561  aDummyPad.SetPosition( aPad->GetPosition() );
562 }
563 
564 
569 void ZONE_FILLER::addKnockout( D_PAD* aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles )
570 {
571  if( aPad->GetShape() == PAD_SHAPE_CUSTOM )
572  {
573  SHAPE_POLY_SET poly;
574  aPad->TransformShapeWithClearanceToPolygon( poly, aLayer, aGap, m_maxError );
575 
576  // the pad shape in zone can be its convex hull or the shape itself
578  {
579  std::vector<wxPoint> convex_hull;
580  BuildConvexHull( convex_hull, poly );
581 
582  aHoles.NewOutline();
583 
584  for( const wxPoint& pt : convex_hull )
585  aHoles.Append( pt );
586  }
587  else
588  aHoles.Append( poly );
589  }
590  else
591  {
592  aPad->TransformShapeWithClearanceToPolygon( aHoles, aLayer, aGap, m_maxError );
593  }
594 }
595 
596 
601 void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap,
602  bool aIgnoreLineWidth, SHAPE_POLY_SET& aHoles )
603 {
604  switch( aItem->Type() )
605  {
606  case PCB_LINE_T:
607  {
608  DRAWSEGMENT* seg = (DRAWSEGMENT*) aItem;
609  seg->TransformShapeWithClearanceToPolygon( aHoles, aLayer, aGap, m_maxError,
610  aIgnoreLineWidth );
611  break;
612  }
613  case PCB_TEXT_T:
614  {
615  TEXTE_PCB* text = (TEXTE_PCB*) aItem;
616  text->TransformBoundingBoxWithClearanceToPolygon( &aHoles, aGap );
617  break;
618  }
619  case PCB_MODULE_EDGE_T:
620  {
621  EDGE_MODULE* edge = (EDGE_MODULE*) aItem;
622  edge->TransformShapeWithClearanceToPolygon( aHoles, aLayer, aGap, m_maxError,
623  aIgnoreLineWidth );
624  break;
625  }
626  case PCB_MODULE_TEXT_T:
627  {
628  TEXTE_MODULE* text = (TEXTE_MODULE*) aItem;
629 
630  if( text->IsVisible() )
631  text->TransformBoundingBoxWithClearanceToPolygon( &aHoles, aGap );
632 
633  break;
634  }
635  default:
636  break;
637  }
638 }
639 
640 
646  SHAPE_POLY_SET& aFill )
647 {
648  SHAPE_POLY_SET holes;
649 
650  // Use a dummy pad to calculate relief when a pad has a hole but is not on the zone's
651  // copper layer. The dummy pad has the size and shape of the original pad's hole. We have
652  // to give it a parent because some functions expect a non-null parent to find clearance
653  // data, etc.
654  MODULE dummymodule( m_board );
655  D_PAD dummypad( &dummymodule );
656 
657  for( auto module : m_board->Modules() )
658  {
659  for( auto pad : module->Pads() )
660  {
661  if( !hasThermalConnection( pad, aZone ) )
662  continue;
663 
664  // If the pad isn't on the current layer but has a hole, knock out a thermal relief
665  // for the hole.
666  if( !pad->IsOnLayer( aLayer ) )
667  {
668  if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 )
669  continue;
670 
671  setupDummyPadForHole( pad, dummypad );
672  pad = &dummypad;
673  }
674 
675  addKnockout( pad, aLayer, aZone->GetThermalReliefGap( pad ), holes );
676  }
677  }
678 
680 }
681 
682 
688  SHAPE_POLY_SET& aHoles )
689 {
690  static DRAWSEGMENT dummyEdge;
691  dummyEdge.SetParent( m_board );
692  dummyEdge.SetLayer( Edge_Cuts );
693 
694  // A small extra clearance to be sure actual track clearances are not smaller than
695  // requested clearance due to many approximations in calculations, like arc to segment
696  // approx, rounding issues, etc.
697  // 1 micron is a good value
698  int extra_margin = Millimeter2iu( ADVANCED_CFG::GetCfg().m_ExtraClearance );
699 
701  int zone_clearance = aZone->GetLocalClearance();
702  EDA_RECT zone_boundingbox = aZone->GetCachedBoundingBox();
703 
704  // Items outside the zone bounding box are skipped, so it needs to be inflated by the
705  // largest clearance value found in the netclasses and rules
706  int biggest_clearance = std::max( zone_clearance, bds.GetBiggestClearanceValue() );
707  zone_boundingbox.Inflate( biggest_clearance + extra_margin );
708 
709  // Use a dummy pad to calculate hole clearance when a pad has a hole but is not on the
710  // zone's copper layer. The dummy pad has the size and shape of the original pad's hole.
711  // We have to give it a parent because some functions expect a non-null parent to find
712  // clearance data, etc.
713  MODULE dummymodule( m_board );
714  D_PAD dummypad( &dummymodule );
715 
716  // Add non-connected pad clearances
717  //
718  for( MODULE* module : m_board->Modules() )
719  {
720  for( D_PAD* pad : module->Pads() )
721  {
722  if( !pad->IsPadOnLayer( aLayer ) )
723  {
724  if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 )
725  continue;
726 
727  setupDummyPadForHole( pad, dummypad );
728  pad = &dummypad;
729  }
730 
731  if( pad->GetNetCode() != aZone->GetNetCode() || pad->GetNetCode() <= 0
732  || aZone->GetPadConnection( pad ) == ZONE_CONNECTION::NONE )
733  {
734  if( pad->GetBoundingBox().Intersects( zone_boundingbox ) )
735  {
736  int gap;
737 
738  // for pads having the same netcode as the zone, the net clearance has no
739  // meaning so use the greater of the zone clearance and the thermal relief
740  if( pad->GetNetCode() > 0 && pad->GetNetCode() == aZone->GetNetCode() )
741  gap = std::max( zone_clearance, aZone->GetThermalReliefGap( pad ) );
742  else
743  gap = aZone->GetClearance( aLayer, pad );
744 
745  addKnockout( pad, aLayer, gap, aHoles );
746  }
747  }
748  }
749  }
750 
751  // Add non-connected track clearances
752  //
753  for( TRACK* track : m_board->Tracks() )
754  {
755  if( !track->IsOnLayer( aLayer ) )
756  continue;
757 
758  if( track->GetNetCode() == aZone->GetNetCode() && ( aZone->GetNetCode() != 0) )
759  continue;
760 
761  if( track->GetBoundingBox().Intersects( zone_boundingbox ) )
762  {
763  int gap = aZone->GetClearance( aLayer, track ) + extra_margin;
764 
765  if( track->Type() == PCB_VIA_T )
766  {
767  VIA* via = static_cast<VIA*>( track );
768 
769  if( !via->IsPadOnLayer( aLayer ) )
770  {
771  int radius = via->GetDrillValue() / 2 + bds.GetHolePlatingThickness() + gap;
772  TransformCircleToPolygon( aHoles, via->GetPosition(), radius, m_maxError );
773  }
774  else
775  {
776  via->TransformShapeWithClearanceToPolygon( aHoles, aLayer, gap, m_maxError );
777  }
778  }
779  else
780  {
781  track->TransformShapeWithClearanceToPolygon( aHoles, aLayer, gap, m_maxError );
782  }
783  }
784  }
785 
786  // Add graphic item clearances. They are by definition unconnected, and have no clearance
787  // definitions of their own.
788  //
789  auto doGraphicItem =
790  [&]( BOARD_ITEM* aItem )
791  {
792  // A item on the Edge_Cuts is always seen as on any layer:
793  if( !aItem->IsOnLayer( aLayer ) && !aItem->IsOnLayer( Edge_Cuts ) )
794  return;
795 
796  if( aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
797  {
798  PCB_LAYER_ID layer = aLayer;
799  bool ignoreLineWidth = false;
800 
801  if( aItem->IsOnLayer( Edge_Cuts ) )
802  {
803  layer = Edge_Cuts;
804  ignoreLineWidth = true;
805  }
806 
807  int gap = aZone->GetClearance( aLayer, aItem ) + extra_margin;
808 
809  addKnockout( aItem, layer, gap, ignoreLineWidth, aHoles );
810  }
811  };
812 
813  for( MODULE* module : m_board->Modules() )
814  {
815  doGraphicItem( &module->Reference() );
816  doGraphicItem( &module->Value() );
817 
818  for( BOARD_ITEM* item : module->GraphicalItems() )
819  doGraphicItem( item );
820  }
821 
822  for( BOARD_ITEM* item : m_board->Drawings() )
823  doGraphicItem( item );
824 
825  // Add keepout zones and higher-priority zones
826  //
827  auto knockoutZone =
828  [&]( ZONE_CONTAINER* aKnockout )
829  {
830  // If the zones share no common layers
831  if( !aKnockout->GetLayerSet().test( aLayer ) )
832  return;
833 
834  if( aKnockout->GetBoundingBox().Intersects( zone_boundingbox ) )
835  {
836  if( aKnockout->GetIsRuleArea()
837  || aZone->GetNetCode() == aKnockout->GetNetCode() )
838  {
839  // Keepouts and same-net zones use outline with no clearance
840  aKnockout->TransformSmoothedOutlineWithClearanceToPolygon( aHoles, 0 );
841  }
842  else
843  {
844  int gap = aZone->GetClearance( aLayer, aKnockout );
845 
846  if( bds.m_ZoneFillVersion == 5 )
847  {
848  // 5.x used outline with clearance
849  aKnockout->TransformSmoothedOutlineWithClearanceToPolygon( aHoles, gap );
850  }
851  else
852  {
853  // 6.0 uses filled areas with clearance
854  SHAPE_POLY_SET poly;
855  aKnockout->TransformShapeWithClearanceToPolygon( poly, aLayer, gap );
856  aHoles.Append( poly );
857  }
858  }
859  }
860  };
861 
862  for( ZONE_CONTAINER* otherZone : m_board->Zones() )
863  {
864  if( otherZone == aZone )
865  continue;
866 
867  if( otherZone->GetIsRuleArea() )
868  {
869  if( otherZone->GetDoNotAllowCopperPour() )
870  knockoutZone( otherZone );
871  }
872  else
873  {
874  if( otherZone->GetPriority() > aZone->GetPriority() )
875  knockoutZone( otherZone );
876  }
877  }
878 
879  for( MODULE* module : m_board->Modules() )
880  {
881  for( ZONE_CONTAINER* otherZone : module->Zones() )
882  {
883  if( otherZone->GetIsRuleArea() )
884  {
885  if( otherZone->GetDoNotAllowCopperPour() )
886  knockoutZone( otherZone );
887  }
888  else
889  {
890  if( otherZone->GetPriority() > aZone->GetPriority() )
891  knockoutZone( otherZone );
892  }
893  }
894  }
895 
897 }
898 
899 
900 #define DUMP_POLYS_TO_COPPER_LAYER( a, b, c ) \
901  { if( s_DumpZonesWhenFilling && dumpLayer == b ) \
902  { \
903  m_board->SetLayerName( b, c ); \
904  a.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); \
905  a.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); \
906  aRawPolys = a; \
907  aFinalPolys = a; \
908  return; \
909  } \
910  }
911 
924  const SHAPE_POLY_SET& aSmoothedOutline,
925  SHAPE_POLY_SET& aRawPolys,
926  SHAPE_POLY_SET& aFinalPolys )
927 {
928  PCB_LAYER_ID dumpLayer = aLayer;
929 
930  if( s_DumpZonesWhenFilling && LSET::InternalCuMask().Contains( aLayer ) )
931  aLayer = F_Cu;
932 
934 
935  // Features which are min_width should survive pruning; features that are *less* than
936  // min_width should not. Therefore we subtract epsilon from the min_width when
937  // deflating/inflating.
938  int half_min_width = aZone->GetMinThickness() / 2;
939  int epsilon = Millimeter2iu( 0.001 );
940  int numSegs = GetArcToSegmentCount( half_min_width, m_maxError, 360.0 );
941 
942  SHAPE_POLY_SET::CORNER_STRATEGY cornerStrategy;
943 
945  cornerStrategy = SHAPE_POLY_SET::ROUND_ACUTE_CORNERS;
946  else
947  cornerStrategy = SHAPE_POLY_SET::CHAMFER_ACUTE_CORNERS;
948 
949  std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
950  SHAPE_POLY_SET clearanceHoles;
951 
952  aRawPolys = aSmoothedOutline;
953  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In1_Cu, "smoothed-outline" );
954 
956  return;
957 
958  knockoutThermalReliefs( aZone, aLayer, aRawPolys );
959  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In2_Cu, "minus-thermal-reliefs" );
960 
962  return;
963 
964  buildCopperItemClearances( aZone, aLayer, clearanceHoles );
965 
967  return;
968 
969  buildThermalSpokes( aZone, aLayer, thermalSpokes );
970 
972  return;
973 
974  // Create a temporary zone that we can hit-test spoke-ends against. It's only temporary
975  // because the "real" subtract-clearance-holes has to be done after the spokes are added.
976  static const bool USE_BBOX_CACHES = true;
977  SHAPE_POLY_SET testAreas = aRawPolys;
978  testAreas.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
979  DUMP_POLYS_TO_COPPER_LAYER( testAreas, In3_Cu, "minus-clearance-holes" );
980 
981  // Prune features that don't meet minimum-width criteria
982  if( half_min_width - epsilon > epsilon )
983  {
984  testAreas.Deflate( half_min_width - epsilon, numSegs, cornerStrategy );
985  DUMP_POLYS_TO_COPPER_LAYER( testAreas, In4_Cu, "spoke-test-deflated" );
986 
987  testAreas.Inflate( half_min_width - epsilon, numSegs, cornerStrategy );
988  DUMP_POLYS_TO_COPPER_LAYER( testAreas, In5_Cu, "spoke-test-reinflated" );
989  }
990 
992  return;
993 
994  // Spoke-end-testing is hugely expensive so we generate cached bounding-boxes to speed
995  // things up a bit.
996  testAreas.BuildBBoxCaches();
997  int interval = 0;
998 
999  for( const SHAPE_LINE_CHAIN& spoke : thermalSpokes )
1000  {
1001  const VECTOR2I& testPt = spoke.CPoint( 3 );
1002 
1003  // Hit-test against zone body
1004  if( testAreas.Contains( testPt, -1, 1, USE_BBOX_CACHES ) )
1005  {
1006  aRawPolys.AddOutline( spoke );
1007  continue;
1008  }
1009 
1010  if( interval++ > 400 )
1011  {
1013  return;
1014 
1015  interval = 0;
1016  }
1017 
1018  // Hit-test against other spokes
1019  for( const SHAPE_LINE_CHAIN& other : thermalSpokes )
1020  {
1021  if( &other != &spoke && other.PointInside( testPt, 1, USE_BBOX_CACHES ) )
1022  {
1023  aRawPolys.AddOutline( spoke );
1024  break;
1025  }
1026  }
1027  }
1028 
1029  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In6_Cu, "plus-spokes" );
1030 
1032  return;
1033 
1034  aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
1035  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In7_Cu, "trimmed-spokes" );
1036 
1037  // Prune features that don't meet minimum-width criteria
1038  if( half_min_width - epsilon > epsilon )
1039  aRawPolys.Deflate( half_min_width - epsilon, numSegs, cornerStrategy );
1040 
1041  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In8_Cu, "deflated" );
1042 
1044  return;
1045 
1046  // Now remove the non filled areas due to the hatch pattern
1047  if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
1048  addHatchFillTypeOnZone( aZone, aLayer, aRawPolys );
1049 
1050  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In9_Cu, "after-hatching" );
1051 
1053  return;
1054 
1055  // Re-inflate after pruning of areas that don't meet minimum-width criteria
1056  if( aZone->GetFilledPolysUseThickness() )
1057  {
1058  // If we're stroking the zone with a min_width stroke then this will naturally inflate
1059  // the zone by half_min_width
1060  }
1061  else if( half_min_width - epsilon > epsilon )
1062  {
1063  aRawPolys.Inflate( half_min_width - epsilon, numSegs, cornerStrategy );
1064  }
1065 
1066  DUMP_POLYS_TO_COPPER_LAYER( aRawPolys, In10_Cu, "after-reinflating" );
1067 
1068  // Ensure additive changes (thermal stubs and particularly inflating acute corners) do not
1069  // add copper outside the zone boundary or inside the clearance holes
1070  aRawPolys.BooleanIntersection( aSmoothedOutline, SHAPE_POLY_SET::PM_FAST );
1071  aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
1072 
1073  aRawPolys.Fracture( SHAPE_POLY_SET::PM_FAST );
1074 
1075  aFinalPolys = aRawPolys;
1076 }
1077 
1078 
1079 /*
1080  * Build the filled solid areas data from real outlines (stored in m_Poly)
1081  * The solid areas can be more than one on copper layers, and do not have holes
1082  * ( holes are linked by overlapping segments to the main outline)
1083  */
1085  SHAPE_POLY_SET& aRawPolys, SHAPE_POLY_SET& aFinalPolys )
1086 {
1087  SHAPE_POLY_SET smoothedPoly;
1088 
1089  /*
1090  * convert outlines + holes to outlines without holes (adding extra segments if necessary)
1091  * m_Poly data is expected normalized, i.e. NormalizeAreaOutlines was used after building
1092  * this zone
1093  */
1094  if ( !aZone->BuildSmoothedPoly( smoothedPoly, aLayer ) )
1095  return false;
1096 
1098  return false;
1099 
1100  if( aZone->IsOnCopperLayer() )
1101  {
1102  computeRawFilledArea( aZone, aLayer, smoothedPoly, aRawPolys, aFinalPolys );
1103  }
1104  else
1105  {
1106  // Features which are min_width should survive pruning; features that are *less* than
1107  // min_width should not. Therefore we subtract epsilon from the min_width when
1108  // deflating/inflating.
1109  int half_min_width = aZone->GetMinThickness() / 2;
1110  int epsilon = Millimeter2iu( 0.001 );
1111  int numSegs = GetArcToSegmentCount( half_min_width, m_maxError, 360.0 );
1112 
1113  if( m_brdOutlinesValid )
1115 
1116  smoothedPoly.Deflate( half_min_width - epsilon, numSegs );
1117 
1118  // Remove the non filled areas due to the hatch pattern
1119  if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
1120  addHatchFillTypeOnZone( aZone, aLayer, smoothedPoly );
1121 
1122  // Re-inflate after pruning of areas that don't meet minimum-width criteria
1123  if( aZone->GetFilledPolysUseThickness() )
1124  {
1125  // If we're stroking the zone with a min_width stroke then this will naturally
1126  // inflate the zone by half_min_width
1127  }
1128  else if( half_min_width - epsilon > epsilon )
1129  smoothedPoly.Deflate( -( half_min_width - epsilon ), numSegs );
1130 
1131  aRawPolys = smoothedPoly;
1132  aFinalPolys = smoothedPoly;
1133 
1135  }
1136 
1137  aZone->SetNeedRefill( false );
1138  return true;
1139 }
1140 
1141 
1146  std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
1147 {
1148  auto zoneBB = aZone->GetCachedBoundingBox();
1149  int zone_clearance = aZone->GetLocalClearance();
1150  int biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue();
1151  biggest_clearance = std::max( biggest_clearance, zone_clearance );
1152  zoneBB.Inflate( biggest_clearance );
1153 
1154  // Is a point on the boundary of the polygon inside or outside? This small epsilon lets
1155  // us avoid the question.
1156  int epsilon = KiROUND( IU_PER_MM * 0.04 ); // about 1.5 mil
1157 
1158  for( auto module : m_board->Modules() )
1159  {
1160  for( auto pad : module->Pads() )
1161  {
1162  if( !hasThermalConnection( pad, aZone ) )
1163  continue;
1164 
1165  // We currently only connect to pads, not pad holes
1166  if( !pad->IsOnLayer( aLayer ) )
1167  continue;
1168 
1169  int thermalReliefGap = aZone->GetThermalReliefGap( pad );
1170 
1171  // Calculate thermal bridge half width
1172  int spoke_w = aZone->GetThermalReliefSpokeWidth( pad );
1173  // Avoid spoke_w bigger than the smaller pad size, because
1174  // it is not possible to create stubs bigger than the pad.
1175  // Possible refinement: have a separate size for vertical and horizontal stubs
1176  spoke_w = std::min( spoke_w, pad->GetSize().x );
1177  spoke_w = std::min( spoke_w, pad->GetSize().y );
1178 
1179  // Cannot create stubs having a width < zone min thickness
1180  if( spoke_w <= aZone->GetMinThickness() )
1181  continue;
1182 
1183  int spoke_half_w = spoke_w / 2;
1184 
1185  // Quick test here to possibly save us some work
1186  BOX2I itemBB = pad->GetBoundingBox();
1187  itemBB.Inflate( thermalReliefGap + epsilon );
1188 
1189  if( !( itemBB.Intersects( zoneBB ) ) )
1190  continue;
1191 
1192  // Thermal spokes consist of segments from the pad center to points just outside
1193  // the thermal relief.
1194  //
1195  // We use the bounding-box to lay out the spokes, but for this to work the
1196  // bounding box has to be built at the same rotation as the spokes.
1197  // We have to use a dummy pad to avoid dirtying the cached shapes
1198  wxPoint shapePos = pad->ShapePos();
1199  double padAngle = pad->GetOrientation();
1200  D_PAD dummy_pad( *pad );
1201  dummy_pad.SetOrientation( 0.0 );
1202  dummy_pad.SetPosition( { 0, 0 } );
1203 
1204  BOX2I reliefBB = dummy_pad.GetBoundingBox();
1205  reliefBB.Inflate( thermalReliefGap + epsilon );
1206 
1207  // For circle pads, the thermal spoke orientation is 45 deg
1208  if( pad->GetShape() == PAD_SHAPE_CIRCLE )
1209  padAngle = s_RoundPadThermalSpokeAngle;
1210 
1211  for( int i = 0; i < 4; i++ )
1212  {
1213  SHAPE_LINE_CHAIN spoke;
1214  switch( i )
1215  {
1216  case 0: // lower stub
1217  spoke.Append( +spoke_half_w, -spoke_half_w );
1218  spoke.Append( -spoke_half_w, -spoke_half_w );
1219  spoke.Append( -spoke_half_w, reliefBB.GetBottom() );
1220  spoke.Append( 0, reliefBB.GetBottom() ); // test pt
1221  spoke.Append( +spoke_half_w, reliefBB.GetBottom() );
1222  break;
1223 
1224  case 1: // upper stub
1225  spoke.Append( +spoke_half_w, spoke_half_w );
1226  spoke.Append( -spoke_half_w, spoke_half_w );
1227  spoke.Append( -spoke_half_w, reliefBB.GetTop() );
1228  spoke.Append( 0, reliefBB.GetTop() ); // test pt
1229  spoke.Append( +spoke_half_w, reliefBB.GetTop() );
1230  break;
1231 
1232  case 2: // right stub
1233  spoke.Append( -spoke_half_w, spoke_half_w );
1234  spoke.Append( -spoke_half_w, -spoke_half_w );
1235  spoke.Append( reliefBB.GetRight(), -spoke_half_w );
1236  spoke.Append( reliefBB.GetRight(), 0 ); // test pt
1237  spoke.Append( reliefBB.GetRight(), spoke_half_w );
1238  break;
1239 
1240  case 3: // left stub
1241  spoke.Append( spoke_half_w, spoke_half_w );
1242  spoke.Append( spoke_half_w, -spoke_half_w );
1243  spoke.Append( reliefBB.GetLeft(), -spoke_half_w );
1244  spoke.Append( reliefBB.GetLeft(), 0 ); // test pt
1245  spoke.Append( reliefBB.GetLeft(), spoke_half_w );
1246  break;
1247  }
1248 
1249  spoke.Rotate( -DECIDEG2RAD( padAngle ) );
1250  spoke.Move( shapePos );
1251 
1252  spoke.SetClosed( true );
1253  spoke.GenerateBBoxCache();
1254  aSpokesList.push_back( std::move( spoke ) );
1255  }
1256  }
1257  }
1258 }
1259 
1260 
1262  SHAPE_POLY_SET& aRawPolys )
1263 {
1264  // Build grid:
1265 
1266  // obviously line thickness must be > zone min thickness.
1267  // It can happens if a board file was edited by hand by a python script
1268  // Use 1 micron margin to be *sure* there is no issue in Gerber files
1269  // (Gbr file unit = 1 or 10 nm) due to some truncation in coordinates or calculations
1270  // This margin also avoid problems due to rounding coordinates in next calculations
1271  // that can create incorrect polygons
1272  int thickness = std::max( aZone->GetHatchThickness(),
1273  aZone->GetMinThickness() + Millimeter2iu( 0.001 ) );
1274 
1275  int linethickness = thickness - aZone->GetMinThickness();
1276  int gridsize = thickness + aZone->GetHatchGap();
1277  double orientation = aZone->GetHatchOrientation();
1278 
1279  SHAPE_POLY_SET filledPolys = aRawPolys;
1280  // Use a area that contains the rotated bbox by orientation,
1281  // and after rotate the result by -orientation.
1282  if( orientation != 0.0 )
1283  filledPolys.Rotate( M_PI/180.0 * orientation, VECTOR2I( 0,0 ) );
1284 
1285  BOX2I bbox = filledPolys.BBox( 0 );
1286 
1287  // Build hole shape
1288  // the hole size is aZone->GetHatchGap(), but because the outline thickness
1289  // is aZone->GetMinThickness(), the hole shape size must be larger
1290  SHAPE_LINE_CHAIN hole_base;
1291  int hole_size = aZone->GetHatchGap() + aZone->GetMinThickness();
1292  VECTOR2I corner( 0, 0 );;
1293  hole_base.Append( corner );
1294  corner.x += hole_size;
1295  hole_base.Append( corner );
1296  corner.y += hole_size;
1297  hole_base.Append( corner );
1298  corner.x = 0;
1299  hole_base.Append( corner );
1300  hole_base.SetClosed( true );
1301 
1302  // Calculate minimal area of a grid hole.
1303  // All holes smaller than a threshold will be removed
1304  double minimal_hole_area = hole_base.Area() * aZone->GetHatchHoleMinArea();
1305 
1306  // Now convert this hole to a smoothed shape:
1307  if( aZone->GetHatchSmoothingLevel() > 0 )
1308  {
1309  // the actual size of chamfer, or rounded corner radius is the half size
1310  // of the HatchFillTypeGap scaled by aZone->GetHatchSmoothingValue()
1311  // aZone->GetHatchSmoothingValue() = 1.0 is the max value for the chamfer or the
1312  // radius of corner (radius = half size of the hole)
1313  int smooth_value = KiROUND( aZone->GetHatchGap()
1314  * aZone->GetHatchSmoothingValue() / 2 );
1315 
1316  // Minimal optimization:
1317  // make smoothing only for reasonnable smooth values, to avoid a lot of useless segments
1318  // and if the smooth value is small, use chamfer even if fillet is requested
1319  #define SMOOTH_MIN_VAL_MM 0.02
1320  #define SMOOTH_SMALL_VAL_MM 0.04
1321 
1322  if( smooth_value > Millimeter2iu( SMOOTH_MIN_VAL_MM ) )
1323  {
1324  SHAPE_POLY_SET smooth_hole;
1325  smooth_hole.AddOutline( hole_base );
1326  int smooth_level = aZone->GetHatchSmoothingLevel();
1327 
1328  if( smooth_value < Millimeter2iu( SMOOTH_SMALL_VAL_MM ) && smooth_level > 1 )
1329  smooth_level = 1;
1330 
1331  // Use a larger smooth_value to compensate the outline tickness
1332  // (chamfer is not visible is smooth value < outline thickess)
1333  smooth_value += aZone->GetMinThickness() / 2;
1334 
1335  // smooth_value cannot be bigger than the half size oh the hole:
1336  smooth_value = std::min( smooth_value, aZone->GetHatchGap() / 2 );
1337 
1338  // the error to approximate a circle by segments when smoothing corners by a arc
1339  int error_max = std::max( Millimeter2iu( 0.01 ), smooth_value / 20 );
1340 
1341  switch( smooth_level )
1342  {
1343  case 1:
1344  // Chamfer() uses the distance from a corner to create a end point
1345  // for the chamfer.
1346  hole_base = smooth_hole.Chamfer( smooth_value ).Outline( 0 );
1347  break;
1348 
1349  default:
1350  if( aZone->GetHatchSmoothingLevel() > 2 )
1351  error_max /= 2; // Force better smoothing
1352 
1353  hole_base = smooth_hole.Fillet( smooth_value, error_max ).Outline( 0 );
1354  break;
1355 
1356  case 0:
1357  break;
1358  };
1359  }
1360  }
1361 
1362  // Build holes
1363  SHAPE_POLY_SET holes;
1364 
1365  for( int xx = 0; ; xx++ )
1366  {
1367  int xpos = xx * gridsize;
1368 
1369  if( xpos > bbox.GetWidth() )
1370  break;
1371 
1372  for( int yy = 0; ; yy++ )
1373  {
1374  int ypos = yy * gridsize;
1375 
1376  if( ypos > bbox.GetHeight() )
1377  break;
1378 
1379  // Generate hole
1380  SHAPE_LINE_CHAIN hole( hole_base );
1381  hole.Move( VECTOR2I( xpos, ypos ) );
1382  holes.AddOutline( hole );
1383  }
1384  }
1385 
1386  holes.Move( bbox.GetPosition() );
1387 
1388  // We must buffer holes by at least aZone->GetMinThickness() to guarantee that thermal
1389  // reliefs can be built (and to give the zone a solid outline). However, it looks more
1390  // visually consistent if the buffer width is the same as the hatch width.
1391  int outline_margin = KiROUND( aZone->GetMinThickness() * 1.1 );
1392 
1393  if( aZone->GetHatchBorderAlgorithm() )
1394  outline_margin = std::max( outline_margin, aZone->GetHatchThickness() );
1395 
1396  if( outline_margin > linethickness / 2 )
1397  filledPolys.Deflate( outline_margin - linethickness / 2, 16 );
1398 
1399  holes.BooleanIntersection( filledPolys, SHAPE_POLY_SET::PM_FAST );
1400 
1401  if( orientation != 0.0 )
1402  holes.Rotate( -M_PI/180.0 * orientation, VECTOR2I( 0,0 ) );
1403 
1404  if( aZone->GetNetCode() != 0 )
1405  {
1406  // Vias and pads connected to the zone must not be allowed to become isolated inside
1407  // one of the holes. Effectively this means their copper outline needs to be expanded
1408  // to be at least as wide as the gap so that it is guaranteed to touch at least one
1409  // edge.
1410  EDA_RECT zone_boundingbox = aZone->GetCachedBoundingBox();
1411  SHAPE_POLY_SET aprons;
1412  int min_apron_radius = ( aZone->GetHatchGap() * 10 ) / 19;
1413 
1414  for( TRACK* track : m_board->Tracks() )
1415  {
1416  if( track->Type() == PCB_VIA_T )
1417  {
1418  VIA* via = static_cast<VIA*>( track );
1419 
1420  if( via->GetNetCode() == aZone->GetNetCode()
1421  && via->IsOnLayer( aLayer )
1422  && via->GetBoundingBox().Intersects( zone_boundingbox ) )
1423  {
1424  int r = std::max( min_apron_radius,
1425  via->GetDrillValue() / 2 + outline_margin );
1426 
1427  TransformCircleToPolygon( aprons, via->GetPosition(), r, ARC_HIGH_DEF );
1428  }
1429  }
1430  }
1431 
1432  for( MODULE* module : m_board->Modules() )
1433  {
1434  for( D_PAD* pad : module->Pads() )
1435  {
1436  if( pad->GetNetCode() == aZone->GetNetCode()
1437  && pad->IsOnLayer( aLayer )
1438  && pad->GetBoundingBox().Intersects( zone_boundingbox ) )
1439  {
1440  // What we want is to bulk up the pad shape so that the narrowest bit of
1441  // copper between the hole and the apron edge is at least outline_margin
1442  // wide (and that the apron itself meets min_apron_radius. But that would
1443  // take a lot of code and math, and the following approximation is close
1444  // enough.
1445  int pad_width = std::min( pad->GetSize().x, pad->GetSize().y );
1446  int slot_width = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
1447  int min_annulus = ( pad_width - slot_width ) / 2;
1448  int clearance = std::max( min_apron_radius - pad_width / 2,
1449  outline_margin - min_annulus );
1450 
1451  clearance = std::max( 0, clearance - linethickness / 2 );
1452  pad->TransformShapeWithClearanceToPolygon( aprons, aLayer, clearance,
1453  ARC_HIGH_DEF );
1454  }
1455  }
1456  }
1457 
1458  holes.BooleanSubtract( aprons, SHAPE_POLY_SET::PM_FAST );
1459  }
1460 
1461  // Now filter truncated holes to avoid small holes in pattern
1462  // It happens for holes near the zone outline
1463  for( int ii = 0; ii < holes.OutlineCount(); )
1464  {
1465  double area = holes.Outline( ii ).Area();
1466 
1467  if( area < minimal_hole_area ) // The current hole is too small: remove it
1468  holes.DeletePolygon( ii );
1469  else
1470  ++ii;
1471  }
1472 
1473  // create grid. Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to
1474  // generate strictly simple polygons needed by Gerber files and Fracture()
1475  aRawPolys.BooleanSubtract( aRawPolys, holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
1476 }
virtual void AdvancePhase()
Uses the next vailable virtual zone of the dialog progress bar.
#define DUMP_POLYS_TO_COPPER_LAYER(a, b, c)
ZONE_CONTAINER handles a list of polygons defining a copper zone.
Definition: class_zone.h:61
int GetLocalSolderMaskMargin() const
Definition: class_pad.h:347
COMMIT & Modify(EDA_ITEM *aItem)
Modifies a given item in the model.
Definition: commit.h:103
void DoNotShowCheckbox(wxString file, int line)
Shows the 'do not show again' checkbox
Definition: confirm.cpp:53
int GetNetCode() const
Function GetNetCode.
int OutlineCount() const
Returns the number of outlines in the set
PROGRESS_REPORTER * m_progressReporter
Definition: zone_filler.h:118
TEXTE_PCB class definition.
int GetHatchBorderAlgorithm() const
Definition: class_zone.h:251
Helper class to create more flexible dialogs, including 'do not show again' checkbox handling.
Definition: confirm.h:44
void SetRawPolysList(PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aPolysList)
Function SetFilledPolysList sets the list of filled polygons.
Definition: class_zone.h:651
virtual void SetLayer(PCB_LAYER_ID aLayer)
Function SetLayer sets the layer this item is on.
This file is part of the common library.
BOARD_ITEM is a base class for any item which can be embedded within the BOARD container class,...
BOARD * m_board
Definition: zone_filler.h:114
coord_type GetTop() const
Definition: box2.h:204
int GetBiggestClearanceValue()
Function GetBiggestClearanceValue.
wxPoint GetPosition() const override
Definition: class_pad.h:165
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.
bool IsVisible() const
Definition: eda_text.h:186
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:51
#define SMOOTH_MIN_VAL_MM
const EDA_RECT GetBoundingBox() const override
Function GetBoundingBox returns the orthogonal, bounding box of this object for display purposes.
CORNER_STRATEGY
< define how inflate transform build inflated polygon
virtual void Report(const wxString &aMessage)
Display aMessage in the progress bar dialog.
bool GetFilledPolysUseThickness() const
Definition: class_zone.h:682
class TEXTE_PCB, text on a layer
Definition: typeinfo.h:92
bool SetNetCode(int aNetCode, bool aNoAssert)
Sets net using a net code.
coord_type GetRight() const
Definition: box2.h:199
int GetLocalClearance(wxString *aSource) const override
Function GetLocalClearance returns any local clearances set in the "classic" (ie: pre-rule) system.
Definition: class_zone.cpp:469
void SetPosition(const wxPoint &aPos) override
Definition: class_pad.h:159
COMMIT.
Definition: commit.h:71
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Function GetDesignSettings.
Definition: class_board.h:537
ZONE_FILLER(BOARD *aBoard, COMMIT *aCommit)
Definition: zone_filler.cpp:54
std::unique_ptr< WX_PROGRESS_REPORTER > m_uniqueReporter
Definition: zone_filler.h:120
void Rotate(double aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Function Rotate rotates all vertices by a given angle.
SHAPE_POLY_SET Fillet(int aRadius, int aErrorMax)
Function Fillet returns a filleted version of the polygon set.
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Returns true if a given subpolygon contains the point aP.
void TransformBoundingBoxWithClearanceToPolygon(SHAPE_POLY_SET *aCornerBuffer, int aClearanceValue) const
Convert the text bounding box to a rectangular polygon depending on the text orientation,...
coord_type GetBottom() const
Definition: box2.h:200
void Inflate(int aAmount, int aCircleSegmentsCount, CORNER_STRATEGY aCornerStrategy=ROUND_ALL_CORNERS)
Performs outline inflation/deflation.
void buildCopperItemClearances(const ZONE_CONTAINER *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 ...
bool BuildSmoothedPoly(SHAPE_POLY_SET &aSmoothedPoly, PCB_LAYER_ID aLayer) const
Function GetSmoothedPoly.
std::mutex & GetLock()
Definition: class_zone.h:203
VECTOR2< int > VECTOR2I
Definition: vector2d.h:594
#define SMOOTH_SMALL_VAL_MM
class EDGE_MODULE, a footprint edge
Definition: typeinfo.h:94
void addKnockout(D_PAD *aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET &aHoles)
Add a knockout for a pad.
PAD_ATTR_T GetAttribute() const
Definition: class_pad.h:335
virtual void SetParent(EDA_ITEM *aParent)
Definition: base_struct.h:196
A single base class (TRACK) represents both tracks and vias, with subclasses for curved tracks (ARC) ...
void Append(int aX, int aY, bool aAllowDuplication=false)
Function Append()
void InstallNewProgressReporter(wxWindow *aParent, const wxString &aTitle, int aNumPhases)
Definition: zone_filler.cpp:69
void DeletePolygon(int aIdx)
Deletes aIdx-th polygon from the set
void SetFillFlag(PCB_LAYER_ID aLayer, bool aFlag)
Definition: class_zone.h:212
int GetHatchGap() const
Definition: class_zone.h:236
bool Intersects(const BOX2< Vec > &aRect) const
Function Intersects.
Definition: box2.h:236
void SetThermalSpokeWidth(int aWidth)
Set the width of the thermal spokes connecting the pad to a zone.
Definition: class_pad.h:455
void SetClosed(bool aClosed)
Function SetClosed()
virtual int GetClearance(PCB_LAYER_ID aLayer, BOARD_ITEM *aItem=nullptr, wxString *aSource=nullptr) const
Function GetClearance returns the clearance in internal units.
PCB_LAYER_ID
A quick note on layer IDs:
bool IsCancelled() const
Acute angles are rounded.
void SetProgressReporter(PROGRESS_REPORTER *aReporter)
Definition: zone_filler.cpp:77
void Move(const VECTOR2I &aVector) override
COMMIT * m_commit
Definition: zone_filler.h:117
int GetDrillValue() const
Function GetDrillValue "calculates" the drill value for vias (m-Drill if > 0, or default drill value ...
MODULES & Modules()
Definition: class_board.h:249
SHAPE_POLY_SET.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Returns the reference to aIndex-th outline in the set
ZONE_FILL_MODE GetFillMode() const
Definition: class_zone.h:163
coord_type GetWidth() const
Definition: box2.h:197
void addHatchFillTypeOnZone(const ZONE_CONTAINER *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aRawPolys)
for zones having the ZONE_FILL_MODE::ZONE_FILL_MODE::HATCH_PATTERN, create a grid pattern in filled a...
PAD_DRILL_SHAPE_T GetDrillShape() const
Definition: class_pad.h:326
LSET GetLayerSet() const override
Function GetLayerSet returns a std::bitset of all layers on which the item physically resides.
Definition: class_pad.h:332
virtual BOARD * GetBoard() const
Function GetBoard returns the BOARD in which this BOARD_ITEM resides, or NULL if none.
bool fillSingleZone(ZONE_CONTAINER *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...
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Function GetConnectivity() returns list of missing connections between components/tracks.
Definition: class_board.h:345
void Deflate(int aAmount, int aCircleSegmentsCount, CORNER_STRATEGY aCornerStrategy=ROUND_ALL_CORNERS)
static LSET InternalCuMask()
Function InternalCuMask() returns a complete set of internal copper layers, which is all Cu layers ex...
Definition: lset.cpp:679
int GetLocalClearance(wxString *aSource) const override
Function GetLocalClearance returns any local clearances set in the "classic" (ie: pre-rule) system.
Definition: class_pad.cpp:626
void SetSize(const wxSize &aSize)
Definition: class_pad.h:223
void computeRawFilledArea(const ZONE_CONTAINER *aZone, PCB_LAYER_ID aLayer, const SHAPE_POLY_SET &aSmoothedOutline, SHAPE_POLY_SET &aRawPolys, SHAPE_POLY_SET &aFinalPolys)
Function computeRawFilledArea Add non copper areas polygons (pads and tracks with clearance) to a fil...
a few functions useful in geometry calculations.
void Simplify(POLYGON_MODE aFastMode)
Simplifies the polyset (merges overlapping polys, eliminates degeneracy/self-intersections) For aFast...
bool GetBoardPolygonOutlines(SHAPE_POLY_SET &aOutlines, wxString *aErrorText=nullptr, wxPoint *aErrorLocation=nullptr)
Function GetBoardPolygonOutlines Extracts the board outlines and build a closed polygon from lines,...
void SetZoneConnection(ZONE_CONNECTION aType)
Definition: class_pad.h:440
PAD_PROP_T GetProperty() const
Definition: class_pad.h:338
void buildThermalSpokes(const ZONE_CONTAINER *aZone, PCB_LAYER_ID aLayer, std::deque< SHAPE_LINE_CHAIN > &aSpokes)
Function buildThermalSpokes Constructs a list of all thermal spokes for the given zone.
ZONE_CONNECTION GetEffectiveZoneConnection(wxString *aSource=nullptr) const
Return the zone connection in effect (either locally overridden or overridden in the parent module).
Definition: class_pad.cpp:734
void BooleanIntersection(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Performs boolean polyset intersection For aFastMode meaning, see function booleanOp
int NewOutline()
Creates a new empty polygon in the set and returns its index
Acute angles are chamfered.
int GetCornerSmoothingType() const
Definition: class_zone.h:676
void SetAttribute(PAD_ATTR_T aAttribute)
Definition: class_pad.cpp:500
void TransformShapeWithClearanceToPolygon(SHAPE_POLY_SET &aCornerBuffer, PCB_LAYER_ID aLayer, int aClearanceValue, int aMaxError=ARC_HIGH_DEF, bool ignoreLineWidth=false) const override
Function TransformShapeWithClearanceToPolygon Convert the pad shape to a closed polygon.
void Fracture(POLYGON_MODE aFastMode)
Converts a set of polygons with holes to a singe outline with "slits"/"fractures" connecting the oute...
double GetHatchOrientation() const
Definition: class_zone.h:239
static const bool s_DumpZonesWhenFilling
Definition: zone_filler.h:50
Thermal relief only for THT pads.
void SetLocalClearance(int aClearance)
Definition: class_pad.h:352
void BuildBBoxCaches()
Constructs BBoxCaches for Contains(), below.
const Vec & GetPosition() const
Definition: box2.h:194
int GetThermalReliefGap() const
Definition: class_zone.h:172
void SetLocalSolderPasteMarginRatio(double aRatio)
Definition: class_pad.h:358
void knockoutThermalReliefs(const ZONE_CONTAINER *aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aFill)
Removes thermal reliefs from the shape for any pads connected to the zone.
CUST_PAD_SHAPE_IN_ZONE GetCustomShapeInZoneOpt() const
Definition: class_pad.h:177
class TEXTE_MODULE, text in a footprint
Definition: typeinfo.h:93
void Rotate(double aAngle, const VECTOR2I &aCenter=VECTOR2I(0, 0)) override
Function Rotate rotates all vertices by a given angle.
void SetFilledPolysList(PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aPolysList)
Function SetFilledPolysList sets the list of filled polygons.
Definition: class_zone.h:642
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index
Use thermal relief for pads.
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
ZONE_CONTAINERS & Zones()
Definition: class_board.h:254
unsigned GetPriority() const
Function GetPriority.
Definition: class_zone.h:106
SHAPE_POLY_SET Chamfer(int aDistance)
Function Chamfer returns a chamfered version of the polygon set.
double GetHatchSmoothingValue() const
Definition: class_zone.h:245
Class to handle a graphic segment.
BOARD holds information pertinent to a Pcbnew printed circuit board.
Definition: class_board.h:178
void SetLocalSolderMaskMargin(int aMargin)
Definition: class_pad.h:348
#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
double GetOrientation() const
Function GetOrientation returns the rotation angle of the pad in a variety of units (the basic call r...
Definition: class_pad.h:321
void RemoveAllContours()
Removes all outlines & holes (clears) the polygon set.
bool KeepRefreshing(bool aWait=false)
Update the UI dialog.
const wxSize & GetDrillSize() const
Definition: class_pad.h:230
void TransformCircleToPolygon(SHAPE_POLY_SET &aCornerBuffer, wxPoint aCenter, int aRadius, int aError)
Function TransformCircleToPolygon convert a circle to a polygon, using multiple straight lines.
EDA_RECT handles the component boundary box.
Definition: eda_rect.h:44
double DECIDEG2RAD(double deg)
Definition: trigo.h:223
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
coord_type GetHeight() const
Definition: box2.h:198
int GetMinThickness() const
Definition: class_zone.h:224
Pads are not covered.
void SetShape(PAD_SHAPE_T aShape)
Set the new shape of this pad.
Definition: class_pad.h:148
bool Intersects(const EDA_RECT &aRect) const
Function Intersects tests for a common area between rectangles.
ZONE_CONNECTION GetPadConnection(D_PAD *aPad, wxString *aSource=nullptr) const
Definition: class_zone.cpp:765
int GetHatchSmoothingLevel() const
Definition: class_zone.h:242
void SetOrientation(double aAngle)
Function SetOrientation sets the rotation angle of the pad.
Definition: class_pad.cpp:519
int ShowModal() override
Definition: confirm.cpp:95
SHAPE_POLY_SET m_boardOutline
Definition: zone_filler.h:115
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers of advanced config.
PCB_TARGET class definition.
void SetLocalSolderPasteMargin(int aMargin)
Definition: class_pad.h:355
void SetCustomShapeInZoneOpt(CUST_PAD_SHAPE_IN_ZONE aOption)
Set the option for the custom pad shape to use as clearance area in copper zones.
Definition: class_pad.h:187
class VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:97
int GetHatchThickness() const
Definition: class_zone.h:233
bool m_brdOutlinesValid
Definition: zone_filler.h:116
static void setupDummyPadForHole(const D_PAD *aPad, D_PAD &aDummyPad)
Setup aDummyPad to have the same size and shape of aPad's hole.
void BooleanSubtract(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Performs boolean polyset difference For aFastMode meaning, see function booleanOp
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Function IsOnLayer tests to see if this object is on the given layer.
PAD_SHAPE_T GetShape() const
Definition: class_pad.h:157
coord_type GetLeft() const
Definition: box2.h:203
POLYGON & Polygon(int aIndex)
Returns the aIndex-th subpolygon in the set
void SetProperty(PAD_PROP_T aProperty)
Definition: class_pad.cpp:511
wxPoint GetPosition() const override
Definition: class_track.h:420
EDGE_MODULE class definition.
class DRAWSEGMENT, a segment not on copper layers
Definition: typeinfo.h:91
void SetMaxProgress(int aMaxProgress)
Fix the value thar gives the 100 precent progress bar length (inside the current virtual zone)
int GetLocalSolderPasteMargin() const
Definition: class_pad.h:354
bool Fill(std::vector< ZONE_CONTAINER * > &aZones, bool aCheck=false, wxWindow *aParent=nullptr)
Definition: zone_filler.cpp:83
void SetOffset(const wxPoint &aOffset)
Definition: class_pad.h:232
int GetEffectiveThermalGap(wxString *aSource=nullptr) const
Return the effective thermal gap having resolved any inheritance.
Definition: class_pad.cpp:774
void AdvanceProgress()
Increment the progress bar length (inside the current virtual zone)
static constexpr int Millimeter2iu(double mm)
const BOX2I BBox(int aClearance=0) const override
Function BBox()
int GetEffectiveThermalSpokeWidth(wxString *aSource=nullptr) const
Return the effective thermal spoke width having resolved any inheritance.
Definition: class_pad.cpp:755
void SetThermalGap(int aGap)
Definition: class_pad.h:463
int GetThermalReliefSpokeWidth() const
Definition: class_zone.h:182
DRAWINGS & Drawings()
Definition: class_board.h:252
int GetArcToSegmentCount(int aRadius, int aErrorMax, double aArcAngleDegree)
void SetLayerSet(LSET aLayers) override
Definition: class_pad.h:331
const EDA_RECT GetBoundingBox() const override
Function GetBoundingBox The bounding box is cached, so this will be efficient most of the time.
Definition: class_pad.cpp:458
TRACKS & Tracks()
Definition: class_board.h:246
bool IsOnCopperLayer() const override
Function IsOnCopperLayer.
Definition: class_zone.cpp:220
void TransformShapeWithClearanceToPolygon(SHAPE_POLY_SET &aCornerBuffer, PCB_LAYER_ID aLayer, int aClearanceValue, int aError=ARC_HIGH_DEF, bool ignoreLineWidth=false) const override
Function TransformShapeWithClearanceToPolygon Convert the draw segment to a closed polygon Used in fi...
A structure used for calculating isolated islands on a given zone across all its layers.
double GetLocalSolderPasteMarginRatio() const
Definition: class_pad.h:357
double GetHatchHoleMinArea() const
Definition: class_zone.h:248
bool IsPadOnLayer(int aLayer) const
Checks to see whether the via should have a pad on the specific layer.
EDA_RECT & Inflate(wxCoord dx, wxCoord dy)
Function Inflate inflates the rectangle horizontally by dx and vertically by dy.
KICAD_T Type() const
Function Type()
Definition: base_struct.h:193
BOARD_DESIGN_SETTINGS contains design settings for a BOARD object.
const EDA_RECT GetCachedBoundingBox() const
ONLY TO BE USED BY CLIENTS WHICH SET UP THE CACHE!
Definition: class_zone.h:125
bool hasThermalConnection(D_PAD *pad, const ZONE_CONTAINER *aZone)
Return true if the given pad has a thermal connection with the given zone.
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 TransformShapeWithClearanceToPolygon(SHAPE_POLY_SET &aCornerBuffer, PCB_LAYER_ID aLayer, int aClearanceValue, int aError=ARC_HIGH_DEF, bool ignoreLineWidth=false) const override
Function TransformShapeWithClearanceToPolygon Convert the track shape to a closed polygon Used in fil...
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:89
void SetNeedRefill(bool aNeedRefill)
Definition: class_zone.h:218