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