You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1697 lines
64 KiB

Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
4 years ago
3 years ago
3 years ago
3 years ago
  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-2022 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. #include <future>
  26. #include <core/kicad_algo.h>
  27. #include <advanced_config.h>
  28. #include <board.h>
  29. #include <board_design_settings.h>
  30. #include <zone.h>
  31. #include <footprint.h>
  32. #include <pad.h>
  33. #include <pcb_shape.h>
  34. #include <pcb_target.h>
  35. #include <pcb_track.h>
  36. #include <pcb_text.h>
  37. #include <pcb_textbox.h>
  38. #include <fp_text.h>
  39. #include <fp_textbox.h>
  40. #include <connectivity/connectivity_data.h>
  41. #include <convert_basic_shapes_to_polygon.h>
  42. #include <board_commit.h>
  43. #include <progress_reporter.h>
  44. #include <geometry/shape_poly_set.h>
  45. #include <geometry/convex_hull.h>
  46. #include <geometry/geometry_utils.h>
  47. #include <confirm.h>
  48. #include <convert_to_biu.h>
  49. #include <thread_pool.h>
  50. #include <math/util.h> // for KiROUND
  51. #include "zone_filler.h"
  52. ZONE_FILLER::ZONE_FILLER( BOARD* aBoard, COMMIT* aCommit ) :
  53. m_board( aBoard ),
  54. m_brdOutlinesValid( false ),
  55. m_commit( aCommit ),
  56. m_progressReporter( nullptr ),
  57. m_maxError( ARC_HIGH_DEF ),
  58. m_worstClearance( 0 )
  59. {
  60. // To enable add "DebugZoneFiller=1" to kicad_advanced settings file.
  61. m_debugZoneFiller = ADVANCED_CFG::GetCfg().m_DebugZoneFiller;
  62. }
  63. ZONE_FILLER::~ZONE_FILLER()
  64. {
  65. }
  66. void ZONE_FILLER::SetProgressReporter( PROGRESS_REPORTER* aReporter )
  67. {
  68. m_progressReporter = aReporter;
  69. wxASSERT_MSG( m_commit, wxT( "ZONE_FILLER must have a valid commit to call "
  70. "SetProgressReporter" ) );
  71. }
  72. bool ZONE_FILLER::Fill( std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aParent )
  73. {
  74. std::lock_guard<KISPINLOCK> lock( m_board->GetConnectivity()->GetLock() );
  75. std::vector<std::pair<ZONE*, PCB_LAYER_ID>> toFill;
  76. std::map<std::pair<ZONE*, PCB_LAYER_ID>, MD5_HASH> oldFillHashes;
  77. std::vector<CN_ZONE_ISOLATED_ISLAND_LIST> islandsList;
  78. std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_board->GetConnectivity();
  79. // Rebuild just in case. This really needs to be reliable.
  80. connectivity->Clear();
  81. connectivity->Build( m_board, m_progressReporter );
  82. BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
  83. m_worstClearance = bds.GetBiggestClearanceValue();
  84. if( m_progressReporter )
  85. {
  86. m_progressReporter->Report( aCheck ? _( "Checking zone fills..." )
  87. : _( "Building zone fills..." ) );
  88. m_progressReporter->SetMaxProgress( aZones.size() );
  89. m_progressReporter->KeepRefreshing();
  90. }
  91. // The board outlines is used to clip solid areas inside the board (when outlines are valid)
  92. m_boardOutline.RemoveAllContours();
  93. m_brdOutlinesValid = m_board->GetBoardPolygonOutlines( m_boardOutline );
  94. // Update and cache zone bounding boxes and pad effective shapes so that we don't have to
  95. // make them thread-safe.
  96. //
  97. for( ZONE* zone : m_board->Zones() )
  98. {
  99. zone->CacheBoundingBox();
  100. m_worstClearance = std::max( m_worstClearance, zone->GetLocalClearance() );
  101. }
  102. for( FOOTPRINT* footprint : m_board->Footprints() )
  103. {
  104. for( PAD* pad : footprint->Pads() )
  105. {
  106. if( pad->IsDirty() )
  107. {
  108. pad->BuildEffectiveShapes( UNDEFINED_LAYER );
  109. pad->BuildEffectivePolygon();
  110. }
  111. m_worstClearance = std::max( m_worstClearance, pad->GetLocalClearance() );
  112. }
  113. for( ZONE* zone : footprint->Zones() )
  114. {
  115. zone->CacheBoundingBox();
  116. m_worstClearance = std::max( m_worstClearance, zone->GetLocalClearance() );
  117. }
  118. // Rules may depend on insideCourtyard() or other expressions
  119. footprint->BuildPolyCourtyards();
  120. }
  121. // Sort by priority to reduce deferrals waiting on higher priority zones.
  122. //
  123. std::sort( aZones.begin(), aZones.end(),
  124. []( const ZONE* lhs, const ZONE* rhs )
  125. {
  126. return lhs->HigherPriority( rhs );
  127. } );
  128. for( ZONE* zone : aZones )
  129. {
  130. // Rule areas are not filled
  131. if( zone->GetIsRuleArea() )
  132. continue;
  133. if( m_commit )
  134. m_commit->Modify( zone );
  135. // calculate the hash value for filled areas. it will be used later to know if the
  136. // current filled areas are up to date
  137. for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
  138. {
  139. zone->BuildHashValue( layer );
  140. oldFillHashes[ { zone, layer } ] = zone->GetHashValue( layer );
  141. // Add the zone to the list of zones to test or refill
  142. toFill.emplace_back( std::make_pair( zone, layer ) );
  143. }
  144. islandsList.emplace_back( CN_ZONE_ISOLATED_ISLAND_LIST( zone ) );
  145. // Remove existing fill first to prevent drawing invalid polygons on some platforms
  146. zone->UnFill();
  147. }
  148. auto check_fill_dependency =
  149. [&]( ZONE* aZone, PCB_LAYER_ID aLayer, ZONE* aOtherZone ) -> bool
  150. {
  151. // Check to see if we have to knock-out the filled areas of a higher-priority
  152. // zone. If so we have to wait until said zone is filled before we can fill.
  153. // If the other zone is already filled then we're good-to-go
  154. if( aOtherZone->GetFillFlag( aLayer ) )
  155. return false;
  156. // Even if keepouts exclude copper pours the exclusion is by outline, not by
  157. // filled area, so we're good-to-go here too.
  158. if( aOtherZone->GetIsRuleArea() )
  159. return false;
  160. // If the zones share no common layers
  161. if( !aOtherZone->GetLayerSet().test( aLayer ) )
  162. return false;
  163. if( aZone->HigherPriority( aOtherZone ) )
  164. return false;
  165. // Same-net zones always use outline to produce predictable results
  166. if( aOtherZone->SameNet( aZone ) )
  167. return false;
  168. // A higher priority zone is found: if we intersect and it's not filled yet
  169. // then we have to wait.
  170. EDA_RECT inflatedBBox = aZone->GetCachedBoundingBox();
  171. inflatedBBox.Inflate( m_worstClearance );
  172. return inflatedBBox.Intersects( aOtherZone->GetCachedBoundingBox() );
  173. };
  174. auto fill_lambda =
  175. [&]( std::pair<ZONE*, PCB_LAYER_ID> aFillItem ) -> int
  176. {
  177. PCB_LAYER_ID layer = aFillItem.second;
  178. ZONE* zone = aFillItem.first;
  179. bool canFill = true;
  180. // Check for any fill dependencies. If our zone needs to be clipped by
  181. // another zone then we can't fill until that zone is filled.
  182. for( ZONE* otherZone : aZones )
  183. {
  184. if( otherZone == zone )
  185. continue;
  186. if( check_fill_dependency( zone, layer, otherZone ) )
  187. {
  188. canFill = false;
  189. break;
  190. }
  191. }
  192. if( m_progressReporter && m_progressReporter->IsCancelled() )
  193. return 0;
  194. if( !canFill )
  195. return 0;
  196. // Now we're ready to fill.
  197. std::unique_lock<std::mutex> zoneLock( zone->GetLock(), std::try_to_lock );
  198. if( zoneLock.owns_lock() )
  199. {
  200. SHAPE_POLY_SET fillPolys;
  201. if( !fillSingleZone( zone, layer, fillPolys ) )
  202. return 0;
  203. zone->SetFilledPolysList( layer, fillPolys );
  204. zone->SetFillFlag( layer, true );
  205. if( m_progressReporter )
  206. m_progressReporter->AdvanceProgress();
  207. }
  208. return 0;
  209. };
  210. // Calculate the copper fills (NB: this is multi-threaded)
  211. //
  212. while( !toFill.empty() )
  213. {
  214. std::vector<std::future<int>> returns;
  215. returns.reserve( toFill.size() );
  216. thread_pool& tp = GetKiCadThreadPool();
  217. for( auto& fillItem : toFill )
  218. returns.emplace_back( tp.submit( fill_lambda, fillItem ) );
  219. for( auto& ret : returns )
  220. {
  221. std::future_status status;
  222. do
  223. {
  224. if( m_progressReporter )
  225. m_progressReporter->KeepRefreshing();
  226. status = ret.wait_for( std::chrono::milliseconds( 100 ) );
  227. } while( status != std::future_status::ready );
  228. }
  229. alg::delete_if( toFill, [&]( const std::pair<ZONE*, PCB_LAYER_ID> pair ) -> bool
  230. {
  231. return pair.first->GetFillFlag( pair.second );
  232. } );
  233. if( m_progressReporter && m_progressReporter->IsCancelled() )
  234. break;
  235. }
  236. // Triangulate the copper fills (NB: this is multi-threaded)
  237. //
  238. m_board->CacheTriangulation( m_progressReporter, aZones );
  239. // Now update the connectivity to check for isolated copper islands
  240. // (NB: FindIsolatedCopperIslands() is multi-threaded)
  241. //
  242. if( m_progressReporter )
  243. {
  244. if( m_progressReporter->IsCancelled() )
  245. return false;
  246. m_progressReporter->AdvancePhase();
  247. m_progressReporter->Report( _( "Removing isolated copper islands..." ) );
  248. m_progressReporter->KeepRefreshing();
  249. }
  250. connectivity->SetProgressReporter( m_progressReporter );
  251. connectivity->FindIsolatedCopperIslands( islandsList );
  252. connectivity->SetProgressReporter( nullptr );
  253. if( m_progressReporter && m_progressReporter->IsCancelled() )
  254. return false;
  255. for( ZONE* zone : aZones )
  256. {
  257. // Keepout zones are not filled
  258. if( zone->GetIsRuleArea() )
  259. continue;
  260. zone->SetIsFilled( true );
  261. }
  262. // Now remove isolated copper islands according to the isolated islands strategy assigned
  263. // by the user (always, never, below-certain-size).
  264. //
  265. for( CN_ZONE_ISOLATED_ISLAND_LIST& zone : islandsList )
  266. {
  267. for( PCB_LAYER_ID layer : zone.m_zone->GetLayerSet().Seq() )
  268. {
  269. if( m_debugZoneFiller && LSET::InternalCuMask().Contains( layer ) )
  270. continue;
  271. if( !zone.m_islands.count( layer ) )
  272. continue;
  273. std::vector<int>& islands = zone.m_islands.at( layer );
  274. // The list of polygons to delete must be explored from last to first in list,
  275. // to allow deleting a polygon from list without breaking the remaining of the list
  276. std::sort( islands.begin(), islands.end(), std::greater<int>() );
  277. std::shared_ptr<SHAPE_POLY_SET> poly = zone.m_zone->GetFilledPolysList( layer );
  278. long long int minArea = zone.m_zone->GetMinIslandArea();
  279. ISLAND_REMOVAL_MODE mode = zone.m_zone->GetIslandRemovalMode();
  280. for( int idx : islands )
  281. {
  282. SHAPE_LINE_CHAIN& outline = poly->Outline( idx );
  283. if( mode == ISLAND_REMOVAL_MODE::ALWAYS )
  284. poly->DeletePolygonAndTriangulationData( idx, false );
  285. else if ( mode == ISLAND_REMOVAL_MODE::AREA && outline.Area() < minArea )
  286. poly->DeletePolygonAndTriangulationData( idx, false );
  287. else
  288. zone.m_zone->SetIsIsland( layer, idx );
  289. }
  290. poly->UpdateTriangulationDataHash();
  291. zone.m_zone->CalculateFilledArea();
  292. if( m_progressReporter && m_progressReporter->IsCancelled() )
  293. return false;
  294. }
  295. }
  296. // Now remove islands which are either outside the board edge or fail to meet the minimum
  297. // area requirements
  298. //
  299. for( ZONE* zone : aZones )
  300. {
  301. LSET zoneCopperLayers = zone->GetLayerSet() & LSET::AllCuMask( MAX_CU_LAYERS );
  302. // Min-thickness is the web thickness. On the other hand, a blob min-thickness by
  303. // min-thickness is not useful. Since there's no obvious definition of web vs. blob, we
  304. // arbitrarily choose "at least 2X the area".
  305. double minArea = (double) zone->GetMinThickness() * zone->GetMinThickness() * 2;
  306. for( PCB_LAYER_ID layer : zoneCopperLayers.Seq() )
  307. {
  308. if( m_debugZoneFiller && LSET::InternalCuMask().Contains( layer ) )
  309. continue;
  310. std::shared_ptr<SHAPE_POLY_SET> poly = zone->GetFilledPolysList( layer );
  311. for( int ii = poly->OutlineCount() - 1; ii >= 0; ii-- )
  312. {
  313. std::vector<SHAPE_LINE_CHAIN>& island = poly->Polygon( ii );
  314. if( island.empty()
  315. || !m_boardOutline.Contains( island.front().CPoint( 0 ) )
  316. || island.front().Area() < minArea )
  317. {
  318. poly->DeletePolygonAndTriangulationData( ii, false );
  319. }
  320. }
  321. poly->UpdateTriangulationDataHash();
  322. zone->CalculateFilledArea();
  323. if( m_progressReporter && m_progressReporter->IsCancelled() )
  324. return false;
  325. }
  326. }
  327. if( aCheck )
  328. {
  329. bool outOfDate = false;
  330. for( ZONE* zone : aZones )
  331. {
  332. // Keepout zones are not filled
  333. if( zone->GetIsRuleArea() )
  334. continue;
  335. for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
  336. {
  337. zone->BuildHashValue( layer );
  338. if( oldFillHashes[ { zone, layer } ] != zone->GetHashValue( layer ) )
  339. outOfDate = true;
  340. }
  341. }
  342. if( outOfDate )
  343. {
  344. KIDIALOG dlg( aParent, _( "Zone fills are out-of-date. Refill?" ),
  345. _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
  346. dlg.SetOKCancelLabels( _( "Refill" ), _( "Continue without Refill" ) );
  347. dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
  348. if( dlg.ShowModal() == wxID_CANCEL )
  349. return false;
  350. }
  351. else
  352. {
  353. // No need to commit something that hasn't changed (and committing will set
  354. // the modified flag).
  355. return false;
  356. }
  357. }
  358. if( m_progressReporter )
  359. {
  360. if( m_progressReporter->IsCancelled() )
  361. return false;
  362. m_progressReporter->AdvancePhase();
  363. m_progressReporter->KeepRefreshing();
  364. }
  365. return true;
  366. }
  367. /**
  368. * Add a knockout for a pad. The knockout is 'aGap' larger than the pad (which might be
  369. * either the thermal clearance or the electrical clearance).
  370. */
  371. void ZONE_FILLER::addKnockout( PAD* aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles )
  372. {
  373. if( aPad->GetShape() == PAD_SHAPE::CUSTOM )
  374. {
  375. SHAPE_POLY_SET poly;
  376. aPad->TransformShapeWithClearanceToPolygon( poly, aLayer, aGap, m_maxError,
  377. ERROR_OUTSIDE );
  378. // the pad shape in zone can be its convex hull or the shape itself
  379. if( aPad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL )
  380. {
  381. std::vector<VECTOR2I> convex_hull;
  382. BuildConvexHull( convex_hull, poly );
  383. aHoles.NewOutline();
  384. for( const VECTOR2I& pt : convex_hull )
  385. aHoles.Append( pt );
  386. }
  387. else
  388. aHoles.Append( poly );
  389. }
  390. else
  391. {
  392. aPad->TransformShapeWithClearanceToPolygon( aHoles, aLayer, aGap, m_maxError,
  393. ERROR_OUTSIDE );
  394. }
  395. }
  396. /**
  397. * Add a knockout for a pad's hole.
  398. */
  399. void ZONE_FILLER::addHoleKnockout( PAD* aPad, int aGap, SHAPE_POLY_SET& aHoles )
  400. {
  401. // Note: drill size represents finish size, which means the actual hole size is the plating
  402. // thickness larger.
  403. if( aPad->GetAttribute() == PAD_ATTRIB::PTH )
  404. aGap += aPad->GetBoard()->GetDesignSettings().GetHolePlatingThickness();
  405. aPad->TransformHoleWithClearanceToPolygon( aHoles, aGap, m_maxError, ERROR_OUTSIDE );
  406. }
  407. /**
  408. * Add a knockout for a graphic item. The knockout is 'aGap' larger than the item (which
  409. * might be either the electrical clearance or the board edge clearance).
  410. */
  411. void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap,
  412. bool aIgnoreLineWidth, SHAPE_POLY_SET& aHoles )
  413. {
  414. EDA_TEXT* text = nullptr;
  415. switch( aItem->Type() )
  416. {
  417. case PCB_TEXT_T: text = static_cast<PCB_TEXT*>( aItem ); break;
  418. case PCB_TEXTBOX_T: text = static_cast<PCB_TEXTBOX*>( aItem ); break;
  419. case PCB_FP_TEXT_T: text = static_cast<FP_TEXT*>( aItem ); break;
  420. case PCB_FP_TEXTBOX_T: text = static_cast<FP_TEXTBOX*>( aItem ); break;
  421. default: break;
  422. }
  423. if( text )
  424. aGap += GetKnockoutTextMargin( text->GetTextSize(), text->GetTextThickness() );
  425. switch( aItem->Type() )
  426. {
  427. case PCB_SHAPE_T:
  428. case PCB_TEXT_T:
  429. case PCB_TEXTBOX_T:
  430. case PCB_FP_TEXTBOX_T:
  431. case PCB_FP_SHAPE_T:
  432. case PCB_TARGET_T:
  433. aItem->TransformShapeWithClearanceToPolygon( aHoles, aLayer, aGap, m_maxError,
  434. ERROR_OUTSIDE, aIgnoreLineWidth );
  435. break;
  436. case PCB_FP_TEXT_T:
  437. if( text->IsVisible() )
  438. {
  439. aItem->TransformShapeWithClearanceToPolygon( aHoles, aLayer, aGap, m_maxError,
  440. ERROR_OUTSIDE, aIgnoreLineWidth );
  441. }
  442. break;
  443. default:
  444. break;
  445. }
  446. }
  447. /**
  448. * Removes thermal reliefs from the shape for any pads connected to the zone. Does NOT add
  449. * in spokes, which must be done later.
  450. */
  451. void ZONE_FILLER::knockoutThermalReliefs( const ZONE* aZone, PCB_LAYER_ID aLayer,
  452. SHAPE_POLY_SET& aFill,
  453. std::vector<PAD*>& aThermalConnectionPads,
  454. std::vector<PAD*>& aNoConnectionPads )
  455. {
  456. BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
  457. ZONE_CONNECTION connection;
  458. DRC_CONSTRAINT constraint;
  459. int padClearance;
  460. int holeClearance;
  461. SHAPE_POLY_SET holes;
  462. for( FOOTPRINT* footprint : m_board->Footprints() )
  463. {
  464. for( PAD* pad : footprint->Pads() )
  465. {
  466. EDA_RECT padBBox = pad->GetBoundingBox();
  467. padBBox.Inflate( m_worstClearance );
  468. if( !padBBox.Intersects( aZone->GetCachedBoundingBox() ) )
  469. continue;
  470. if( pad->GetNetCode() != aZone->GetNetCode() || pad->GetNetCode() <= 0 )
  471. {
  472. // collect these for knockout in buildCopperItemClearances()
  473. aNoConnectionPads.push_back( pad );
  474. continue;
  475. }
  476. if( aZone->IsTeardropArea() )
  477. {
  478. connection = ZONE_CONNECTION::FULL;
  479. }
  480. else
  481. {
  482. constraint = bds.m_DRCEngine->EvalZoneConnection( pad, aZone, aLayer );
  483. connection = constraint.m_ZoneConnection;
  484. }
  485. switch( connection )
  486. {
  487. case ZONE_CONNECTION::THERMAL:
  488. constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone,
  489. aLayer );
  490. padClearance = constraint.GetValue().Min();
  491. holeClearance = padClearance;
  492. if( pad->FlashLayer( aLayer ) )
  493. aThermalConnectionPads.push_back( pad );
  494. break;
  495. case ZONE_CONNECTION::NONE:
  496. constraint = bds.m_DRCEngine->EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, pad,
  497. aZone, aLayer );
  498. if( constraint.GetValue().Min() > aZone->GetLocalClearance() )
  499. padClearance = constraint.GetValue().Min();
  500. else
  501. padClearance = aZone->GetLocalClearance();
  502. constraint = bds.m_DRCEngine->EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, pad,
  503. aZone, aLayer );
  504. if( constraint.GetValue().Min() > padClearance )
  505. holeClearance = constraint.GetValue().Min();
  506. else
  507. holeClearance = padClearance;
  508. break;
  509. default:
  510. // No knockout
  511. continue;
  512. }
  513. if( pad->FlashLayer( aLayer ) )
  514. {
  515. addKnockout( pad, aLayer, padClearance, holes );
  516. }
  517. else if( pad->GetDrillSize().x > 0 )
  518. {
  519. // Note: drill size represents finish size, which means the actual holes size
  520. // is the plating thickness larger.
  521. holeClearance += pad->GetBoard()->GetDesignSettings().GetHolePlatingThickness();
  522. pad->TransformHoleWithClearanceToPolygon( holes, holeClearance, m_maxError,
  523. ERROR_OUTSIDE );
  524. }
  525. }
  526. }
  527. aFill.BooleanSubtract( holes, SHAPE_POLY_SET::PM_FAST );
  528. }
  529. /**
  530. * Removes clearance from the shape for copper items which share the zone's layer but are
  531. * not connected to it.
  532. */
  533. void ZONE_FILLER::buildCopperItemClearances( const ZONE* aZone, PCB_LAYER_ID aLayer,
  534. const std::vector<PAD*> aNoConnectionPads,
  535. SHAPE_POLY_SET& aHoles )
  536. {
  537. BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
  538. long ticker = 0;
  539. auto checkForCancel =
  540. [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool
  541. {
  542. return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled();
  543. };
  544. // A small extra clearance to be sure actual track clearances are not smaller than
  545. // requested clearance due to many approximations in calculations, like arc to segment
  546. // approx, rounding issues, etc.
  547. EDA_RECT zone_boundingbox = aZone->GetCachedBoundingBox();
  548. int extra_margin = Millimeter2iu( ADVANCED_CFG::GetCfg().m_ExtraClearance );
  549. // Items outside the zone bounding box are skipped, so it needs to be inflated by the
  550. // largest clearance value found in the netclasses and rules
  551. zone_boundingbox.Inflate( m_worstClearance + extra_margin );
  552. auto evalRulesForItems =
  553. [&bds]( DRC_CONSTRAINT_T aConstraint, const BOARD_ITEM* a, const BOARD_ITEM* b,
  554. PCB_LAYER_ID aEvalLayer ) -> int
  555. {
  556. DRC_CONSTRAINT c = bds.m_DRCEngine->EvalRules( aConstraint, a, b, aEvalLayer );
  557. return c.GetValue().Min();
  558. };
  559. // Add non-connected pad clearances
  560. //
  561. auto knockoutPadClearance =
  562. [&]( PAD* aPad )
  563. {
  564. int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone, aPad, aLayer );
  565. bool hasHole = aPad->GetDrillSize().x > 0;
  566. bool flashLayer = aPad->FlashLayer( aLayer );
  567. bool platedHole = hasHole && aPad->GetAttribute() == PAD_ATTRIB::PTH;
  568. if( flashLayer || platedHole )
  569. {
  570. gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
  571. aZone, aPad, aLayer ) );
  572. }
  573. if( flashLayer && gap > 0 )
  574. addKnockout( aPad, aLayer, gap + extra_margin, aHoles );
  575. if( hasHole )
  576. {
  577. gap = std::max( gap, evalRulesForItems( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT,
  578. aZone, aPad, aLayer ) );
  579. gap = std::max( gap, evalRulesForItems( HOLE_CLEARANCE_CONSTRAINT,
  580. aZone, aPad, aLayer ) );
  581. if( gap > 0 )
  582. addHoleKnockout( aPad, gap + extra_margin, aHoles );
  583. }
  584. };
  585. for( PAD* pad : aNoConnectionPads )
  586. {
  587. if( checkForCancel( m_progressReporter ) )
  588. return;
  589. knockoutPadClearance( pad );
  590. }
  591. // Add non-connected track clearances
  592. //
  593. auto knockoutTrackClearance =
  594. [&]( PCB_TRACK* aTrack )
  595. {
  596. if( aTrack->GetBoundingBox().Intersects( zone_boundingbox ) )
  597. {
  598. bool sameNet = aTrack->GetNetCode() == aZone->GetNetCode()
  599. && aZone->GetNetCode() != 0;
  600. int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
  601. aZone, aTrack, aLayer );
  602. if( !sameNet )
  603. {
  604. gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
  605. aZone, aTrack, aLayer ) );
  606. }
  607. if( aTrack->Type() == PCB_VIA_T )
  608. {
  609. PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
  610. if( via->FlashLayer( aLayer ) && gap > 0 )
  611. {
  612. via->TransformShapeWithClearanceToPolygon( aHoles, aLayer,
  613. gap + extra_margin,
  614. m_maxError, ERROR_OUTSIDE );
  615. }
  616. gap = std::max( gap, evalRulesForItems( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT,
  617. aZone, via, aLayer ) );
  618. if( !sameNet )
  619. {
  620. gap = std::max( gap, evalRulesForItems( HOLE_CLEARANCE_CONSTRAINT,
  621. aZone, via, aLayer ) );
  622. }
  623. if( gap > 0 )
  624. {
  625. int radius = via->GetDrillValue() / 2 + bds.GetHolePlatingThickness();
  626. TransformCircleToPolygon( aHoles, via->GetPosition(),
  627. radius + gap + extra_margin,
  628. m_maxError, ERROR_OUTSIDE );
  629. }
  630. }
  631. else
  632. {
  633. if( gap > 0 )
  634. {
  635. aTrack->TransformShapeWithClearanceToPolygon( aHoles, aLayer,
  636. gap + extra_margin,
  637. m_maxError,
  638. ERROR_OUTSIDE );
  639. }
  640. }
  641. }
  642. };
  643. for( PCB_TRACK* track : m_board->Tracks() )
  644. {
  645. if( !track->IsOnLayer( aLayer ) )
  646. continue;
  647. if( checkForCancel( m_progressReporter ) )
  648. return;
  649. knockoutTrackClearance( track );
  650. }
  651. // Add graphic item clearances. They are by definition unconnected, and have no clearance
  652. // definitions of their own.
  653. //
  654. auto knockoutGraphicClearance =
  655. [&]( BOARD_ITEM* aItem )
  656. {
  657. // A item on the Edge_Cuts or Margin is always seen as on any layer:
  658. if( aItem->IsOnLayer( aLayer )
  659. || aItem->IsOnLayer( Edge_Cuts )
  660. || aItem->IsOnLayer( Margin ) )
  661. {
  662. if( aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
  663. {
  664. bool ignoreLineWidths = false;
  665. int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
  666. aZone, aItem, aLayer );
  667. if( aItem->IsOnLayer( aLayer ) )
  668. {
  669. gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
  670. aZone, aItem, aLayer ) );
  671. }
  672. else if( aItem->IsOnLayer( Edge_Cuts ) )
  673. {
  674. gap = std::max( gap, evalRulesForItems( EDGE_CLEARANCE_CONSTRAINT,
  675. aZone, aItem, Edge_Cuts ) );
  676. ignoreLineWidths = true;
  677. }
  678. else if( aItem->IsOnLayer( Margin ) )
  679. {
  680. gap = std::max( gap, evalRulesForItems( EDGE_CLEARANCE_CONSTRAINT,
  681. aZone, aItem, Margin ) );
  682. }
  683. addKnockout( aItem, aLayer, gap + extra_margin, ignoreLineWidths, aHoles );
  684. }
  685. }
  686. };
  687. for( FOOTPRINT* footprint : m_board->Footprints() )
  688. {
  689. bool skipFootprint = false;
  690. knockoutGraphicClearance( &footprint->Reference() );
  691. knockoutGraphicClearance( &footprint->Value() );
  692. // Don't knock out holes in zones that share a net with a nettie footprint
  693. if( footprint->IsNetTie() )
  694. {
  695. for( PAD* pad : footprint->Pads() )
  696. {
  697. if( aZone->GetNetCode() == pad->GetNetCode() )
  698. {
  699. skipFootprint = true;
  700. break;
  701. }
  702. }
  703. }
  704. if( skipFootprint )
  705. continue;
  706. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  707. {
  708. if( checkForCancel( m_progressReporter ) )
  709. return;
  710. knockoutGraphicClearance( item );
  711. }
  712. }
  713. for( BOARD_ITEM* item : m_board->Drawings() )
  714. {
  715. if( checkForCancel( m_progressReporter ) )
  716. return;
  717. knockoutGraphicClearance( item );
  718. }
  719. // Add non-connected zone clearances
  720. //
  721. auto knockoutZoneClearance =
  722. [&]( ZONE* aKnockout )
  723. {
  724. // If the zones share no common layers
  725. if( !aKnockout->GetLayerSet().test( aLayer ) )
  726. return;
  727. if( aKnockout->GetCachedBoundingBox().Intersects( zone_boundingbox ) )
  728. {
  729. if( aKnockout->GetIsRuleArea() )
  730. {
  731. // Keepouts use outline with no clearance
  732. aKnockout->TransformSmoothedOutlineToPolygon( aHoles, 0, m_maxError,
  733. ERROR_OUTSIDE, nullptr );
  734. }
  735. else
  736. {
  737. int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone,
  738. aKnockout, aLayer );
  739. gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT, aZone,
  740. aKnockout, aLayer ) );
  741. SHAPE_POLY_SET poly;
  742. aKnockout->TransformShapeWithClearanceToPolygon( poly, aLayer,
  743. gap + extra_margin,
  744. m_maxError,
  745. ERROR_OUTSIDE );
  746. aHoles.Append( poly );
  747. }
  748. }
  749. };
  750. for( ZONE* otherZone : m_board->Zones() )
  751. {
  752. if( checkForCancel( m_progressReporter ) )
  753. return;
  754. if( otherZone->GetIsRuleArea() )
  755. {
  756. if( otherZone->GetDoNotAllowCopperPour() && !aZone->IsTeardropArea() )
  757. knockoutZoneClearance( otherZone );
  758. }
  759. else if( otherZone->HigherPriority( aZone ) )
  760. {
  761. if( !otherZone->SameNet( aZone ) )
  762. knockoutZoneClearance( otherZone );
  763. }
  764. }
  765. for( FOOTPRINT* footprint : m_board->Footprints() )
  766. {
  767. for( ZONE* otherZone : footprint->Zones() )
  768. {
  769. if( checkForCancel( m_progressReporter ) )
  770. return;
  771. if( otherZone->GetIsRuleArea() )
  772. {
  773. if( otherZone->GetDoNotAllowCopperPour() && !aZone->IsTeardropArea() )
  774. knockoutZoneClearance( otherZone );
  775. }
  776. else if( otherZone->HigherPriority( aZone ) )
  777. {
  778. if( !otherZone->SameNet( aZone ) )
  779. knockoutZoneClearance( otherZone );
  780. }
  781. }
  782. }
  783. aHoles.Simplify( SHAPE_POLY_SET::PM_FAST );
  784. }
  785. /**
  786. * Removes the outlines of higher-proirity zones with the same net. These zones should be
  787. * in charge of the fill parameters within their own outlines.
  788. */
  789. void ZONE_FILLER::subtractHigherPriorityZones( const ZONE* aZone, PCB_LAYER_ID aLayer,
  790. SHAPE_POLY_SET& aRawFill )
  791. {
  792. auto knockoutZoneOutline =
  793. [&]( ZONE* aKnockout )
  794. {
  795. // If the zones share no common layers
  796. if( !aKnockout->GetLayerSet().test( aLayer ) )
  797. return;
  798. if( aKnockout->GetCachedBoundingBox().Intersects( aZone->GetCachedBoundingBox() ) )
  799. {
  800. // Processing of arc shapes in zones is not yet supported because Clipper
  801. // can't do boolean operations on them. The poly outline must be converted to
  802. // segments first.
  803. SHAPE_POLY_SET outline = aKnockout->Outline()->CloneDropTriangulation();
  804. outline.ClearArcs();
  805. aRawFill.BooleanSubtract( outline, SHAPE_POLY_SET::PM_FAST );
  806. }
  807. };
  808. for( ZONE* otherZone : m_board->Zones() )
  809. {
  810. if( otherZone->SameNet( aZone ) && otherZone->HigherPriority( aZone ) )
  811. {
  812. // Do not remove teardrop area: it is not useful and not good
  813. if( !otherZone->IsTeardropArea() )
  814. knockoutZoneOutline( otherZone );
  815. }
  816. }
  817. for( FOOTPRINT* footprint : m_board->Footprints() )
  818. {
  819. for( ZONE* otherZone : footprint->Zones() )
  820. {
  821. if( otherZone->SameNet( aZone ) && otherZone->HigherPriority( aZone ) )
  822. {
  823. // Do not remove teardrop area: it is not useful and not good
  824. if( !otherZone->IsTeardropArea() )
  825. knockoutZoneOutline( otherZone );
  826. }
  827. }
  828. }
  829. }
  830. #define DUMP_POLYS_TO_COPPER_LAYER( a, b, c ) \
  831. { if( m_debugZoneFiller && aDebugLayer == b ) \
  832. { \
  833. m_board->SetLayerName( b, c ); \
  834. SHAPE_POLY_SET d = a; \
  835. d.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); \
  836. d.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); \
  837. aFillPolys = d; \
  838. return false; \
  839. } \
  840. }
  841. /**
  842. * 1 - Creates the main zone outline using a correction to shrink the resulting area by
  843. * m_ZoneMinThickness / 2. The result is areas with a margin of m_ZoneMinThickness / 2
  844. * so that when drawing outline with segments having a thickness of m_ZoneMinThickness the
  845. * outlines will match exactly the initial outlines
  846. * 2 - Knocks out thermal reliefs around thermally-connected pads
  847. * 3 - Builds a set of thermal spoke for the whole zone
  848. * 4 - Knocks out unconnected copper items, deleting any affected spokes
  849. * 5 - Removes unconnected copper islands, deleting any affected spokes
  850. * 6 - Adds in the remaining spokes
  851. */
  852. bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer,
  853. const SHAPE_POLY_SET& aSmoothedOutline,
  854. const SHAPE_POLY_SET& aMaxExtents, SHAPE_POLY_SET& aFillPolys )
  855. {
  856. m_maxError = m_board->GetDesignSettings().m_MaxError;
  857. // Features which are min_width should survive pruning; features that are *less* than
  858. // min_width should not. Therefore we subtract epsilon from the min_width when
  859. // deflating/inflating.
  860. int half_min_width = aZone->GetMinThickness() / 2;
  861. int epsilon = Millimeter2iu( 0.001 );
  862. int numSegs = GetArcToSegmentCount( half_min_width, m_maxError, FULL_CIRCLE );
  863. // Solid polygons are deflated and inflated during calculations. Deflating doesn't cause
  864. // issues, but inflate is tricky as it can create excessively long and narrow spikes for
  865. // acute angles.
  866. // ALLOW_ACUTE_CORNERS cannot be used due to the spike problem.
  867. // CHAMFER_ACUTE_CORNERS is tempting, but can still produce spikes in some unusual
  868. // circumstances (https://gitlab.com/kicad/code/kicad/-/issues/5581).
  869. // It's unclear if ROUND_ACUTE_CORNERS would have the same issues, but is currently avoided
  870. // as a "less-safe" option.
  871. // ROUND_ALL_CORNERS produces the uniformly nicest shapes, but also a lot of segments.
  872. // CHAMFER_ALL_CORNERS improves the segment count.
  873. SHAPE_POLY_SET::CORNER_STRATEGY fastCornerStrategy = SHAPE_POLY_SET::CHAMFER_ALL_CORNERS;
  874. SHAPE_POLY_SET::CORNER_STRATEGY cornerStrategy = SHAPE_POLY_SET::ROUND_ALL_CORNERS;
  875. std::vector<PAD*> thermalConnectionPads;
  876. std::vector<PAD*> noConnectionPads;
  877. std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
  878. SHAPE_POLY_SET clearanceHoles;
  879. aFillPolys = aSmoothedOutline;
  880. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In1_Cu, wxT( "smoothed-outline" ) );
  881. if( m_progressReporter && m_progressReporter->IsCancelled() )
  882. return false;
  883. /* -------------------------------------------------------------------------------------
  884. * Knockout thermal reliefs.
  885. */
  886. knockoutThermalReliefs( aZone, aLayer, aFillPolys, thermalConnectionPads, noConnectionPads );
  887. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In2_Cu, wxT( "minus-thermal-reliefs" ) );
  888. if( m_progressReporter && m_progressReporter->IsCancelled() )
  889. return false;
  890. /* -------------------------------------------------------------------------------------
  891. * Knockout electrical clearances.
  892. */
  893. buildCopperItemClearances( aZone, aLayer, noConnectionPads, clearanceHoles );
  894. DUMP_POLYS_TO_COPPER_LAYER( clearanceHoles, In3_Cu, wxT( "clearance-holes" ) );
  895. if( m_progressReporter && m_progressReporter->IsCancelled() )
  896. return false;
  897. /* -------------------------------------------------------------------------------------
  898. * Add thermal relief spokes.
  899. */
  900. buildThermalSpokes( aZone, aLayer, thermalConnectionPads, thermalSpokes );
  901. if( m_progressReporter && m_progressReporter->IsCancelled() )
  902. return false;
  903. // Create a temporary zone that we can hit-test spoke-ends against. It's only temporary
  904. // because the "real" subtract-clearance-holes has to be done after the spokes are added.
  905. static const bool USE_BBOX_CACHES = true;
  906. SHAPE_POLY_SET testAreas = aFillPolys.CloneDropTriangulation();
  907. testAreas.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
  908. DUMP_POLYS_TO_COPPER_LAYER( testAreas, In4_Cu, wxT( "minus-clearance-holes" ) );
  909. // Prune features that don't meet minimum-width criteria
  910. if( half_min_width - epsilon > epsilon )
  911. {
  912. testAreas.Deflate( half_min_width - epsilon, numSegs, fastCornerStrategy );
  913. DUMP_POLYS_TO_COPPER_LAYER( testAreas, In5_Cu, wxT( "spoke-test-deflated" ) );
  914. testAreas.Inflate( half_min_width - epsilon, numSegs, fastCornerStrategy );
  915. DUMP_POLYS_TO_COPPER_LAYER( testAreas, In6_Cu, wxT( "spoke-test-reinflated" ) );
  916. }
  917. if( m_progressReporter && m_progressReporter->IsCancelled() )
  918. return false;
  919. // Spoke-end-testing is hugely expensive so we generate cached bounding-boxes to speed
  920. // things up a bit.
  921. testAreas.BuildBBoxCaches();
  922. int interval = 0;
  923. SHAPE_POLY_SET debugSpokes;
  924. for( const SHAPE_LINE_CHAIN& spoke : thermalSpokes )
  925. {
  926. const VECTOR2I& testPt = spoke.CPoint( 3 );
  927. // Hit-test against zone body
  928. if( testAreas.Contains( testPt, -1, 1, USE_BBOX_CACHES ) )
  929. {
  930. if( m_debugZoneFiller )
  931. debugSpokes.AddOutline( spoke );
  932. aFillPolys.AddOutline( spoke );
  933. continue;
  934. }
  935. if( interval++ > 400 )
  936. {
  937. if( m_progressReporter && m_progressReporter->IsCancelled() )
  938. return false;
  939. interval = 0;
  940. }
  941. // Hit-test against other spokes
  942. for( const SHAPE_LINE_CHAIN& other : thermalSpokes )
  943. {
  944. if( &other != &spoke && other.PointInside( testPt, 1, USE_BBOX_CACHES ) )
  945. {
  946. if( m_debugZoneFiller )
  947. debugSpokes.AddOutline( spoke );
  948. aFillPolys.AddOutline( spoke );
  949. break;
  950. }
  951. }
  952. }
  953. DUMP_POLYS_TO_COPPER_LAYER( debugSpokes, In7_Cu, wxT( "spokes" ) );
  954. if( m_progressReporter && m_progressReporter->IsCancelled() )
  955. return false;
  956. aFillPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
  957. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In8_Cu, wxT( "after-spoke-trimming" ) );
  958. /* -------------------------------------------------------------------------------------
  959. * Prune features that don't meet minimum-width criteria
  960. */
  961. if( half_min_width - epsilon > epsilon )
  962. aFillPolys.Deflate( half_min_width - epsilon, numSegs, cornerStrategy );
  963. // Min-thickness is the web thickness. On the other hand, a blob min-thickness by
  964. // min-thickness is not useful. Since there's no obvious definition of web vs. blob, we
  965. // arbitrarily choose "at least 1/2 min-thickness on one axis".
  966. for( int ii = aFillPolys.OutlineCount() - 1; ii >= 0; ii-- )
  967. {
  968. std::vector<SHAPE_LINE_CHAIN>& island = aFillPolys.Polygon( ii );
  969. EDA_RECT islandExtents = island.front().BBox();
  970. if( islandExtents.GetSizeMax() < half_min_width )
  971. aFillPolys.DeletePolygon( ii );
  972. }
  973. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In9_Cu, wxT( "deflated" ) );
  974. if( m_progressReporter && m_progressReporter->IsCancelled() )
  975. return false;
  976. /* -------------------------------------------------------------------------------------
  977. * Process the hatch pattern (note that we do this while deflated)
  978. */
  979. if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
  980. {
  981. if( !addHatchFillTypeOnZone( aZone, aLayer, aDebugLayer, aFillPolys ) )
  982. return false;
  983. }
  984. if( m_progressReporter && m_progressReporter->IsCancelled() )
  985. return false;
  986. /* -------------------------------------------------------------------------------------
  987. * Finish minimum-width pruning by re-inflating
  988. */
  989. if( half_min_width - epsilon > epsilon )
  990. aFillPolys.Inflate( half_min_width - epsilon, numSegs, cornerStrategy );
  991. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In15_Cu, wxT( "after-reinflating" ) );
  992. /* -------------------------------------------------------------------------------------
  993. * Ensure additive changes (thermal stubs and particularly inflating acute corners) do not
  994. * add copper outside the zone boundary or inside the clearance holes
  995. */
  996. aFillPolys.BooleanIntersection( aMaxExtents, SHAPE_POLY_SET::PM_FAST );
  997. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In16_Cu, wxT( "after-trim-to-outline" ) );
  998. aFillPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
  999. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In17_Cu, wxT( "after-trim-to-clearance-holes" ) );
  1000. /* -------------------------------------------------------------------------------------
  1001. * Lastly give any same-net but higher-priority zones control over their own area.
  1002. */
  1003. subtractHigherPriorityZones( aZone, aLayer, aFillPolys );
  1004. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In18_Cu, wxT( "minus-higher-priority-zones" ) );
  1005. aFillPolys.Fracture( SHAPE_POLY_SET::PM_FAST );
  1006. return true;
  1007. }
  1008. bool ZONE_FILLER::fillNonCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer,
  1009. const SHAPE_POLY_SET& aSmoothedOutline,
  1010. SHAPE_POLY_SET& aFillPolys )
  1011. {
  1012. BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
  1013. EDA_RECT zone_boundingbox = aZone->GetCachedBoundingBox();
  1014. SHAPE_POLY_SET clearanceHoles;
  1015. long ticker = 0;
  1016. auto checkForCancel =
  1017. [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool
  1018. {
  1019. return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled();
  1020. };
  1021. auto knockoutGraphicClearance =
  1022. [&]( BOARD_ITEM* aItem )
  1023. {
  1024. if( aItem->IsKnockout() && aItem->IsOnLayer( aLayer )
  1025. && aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
  1026. {
  1027. DRC_CONSTRAINT cc = bds.m_DRCEngine->EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT,
  1028. aZone, aItem, aLayer );
  1029. addKnockout( aItem, aLayer, cc.GetValue().Min(), false, clearanceHoles );
  1030. }
  1031. };
  1032. for( FOOTPRINT* footprint : m_board->Footprints() )
  1033. {
  1034. if( checkForCancel( m_progressReporter ) )
  1035. return false;
  1036. knockoutGraphicClearance( &footprint->Reference() );
  1037. knockoutGraphicClearance( &footprint->Value() );
  1038. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  1039. knockoutGraphicClearance( item );
  1040. }
  1041. for( BOARD_ITEM* item : m_board->Drawings() )
  1042. {
  1043. if( checkForCancel( m_progressReporter ) )
  1044. return false;
  1045. knockoutGraphicClearance( item );
  1046. }
  1047. aFillPolys = aSmoothedOutline;
  1048. aFillPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
  1049. // Features which are min_width should survive pruning; features that are *less* than
  1050. // min_width should not. Therefore we subtract epsilon from the min_width when
  1051. // deflating/inflating.
  1052. int half_min_width = aZone->GetMinThickness() / 2;
  1053. int epsilon = Millimeter2iu( 0.001 );
  1054. int numSegs = GetArcToSegmentCount( half_min_width, m_maxError, FULL_CIRCLE );
  1055. aFillPolys.Deflate( half_min_width - epsilon, numSegs );
  1056. // Remove the non filled areas due to the hatch pattern
  1057. if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
  1058. {
  1059. if( !addHatchFillTypeOnZone( aZone, aLayer, aLayer, aFillPolys ) )
  1060. return false;
  1061. }
  1062. // Re-inflate after pruning of areas that don't meet minimum-width criteria
  1063. if( half_min_width - epsilon > epsilon )
  1064. aFillPolys.Inflate( half_min_width - epsilon, numSegs );
  1065. aFillPolys.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  1066. return true;
  1067. }
  1068. /*
  1069. * Build the filled solid areas data from real outlines (stored in m_Poly)
  1070. * The solid areas can be more than one on copper layers, and do not have holes
  1071. * ( holes are linked by overlapping segments to the main outline)
  1072. */
  1073. bool ZONE_FILLER::fillSingleZone( ZONE* aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aFillPolys )
  1074. {
  1075. SHAPE_POLY_SET* boardOutline = m_brdOutlinesValid ? &m_boardOutline : nullptr;
  1076. SHAPE_POLY_SET maxExtents;
  1077. SHAPE_POLY_SET smoothedPoly;
  1078. PCB_LAYER_ID debugLayer = UNDEFINED_LAYER;
  1079. if( m_debugZoneFiller && LSET::InternalCuMask().Contains( aLayer ) )
  1080. {
  1081. debugLayer = aLayer;
  1082. aLayer = F_Cu;
  1083. }
  1084. if ( !aZone->BuildSmoothedPoly( maxExtents, aLayer, boardOutline, &smoothedPoly ) )
  1085. return false;
  1086. if( m_progressReporter && m_progressReporter->IsCancelled() )
  1087. return false;
  1088. if( aZone->IsOnCopperLayer() )
  1089. {
  1090. if( fillCopperZone( aZone, aLayer, debugLayer, smoothedPoly, maxExtents, aFillPolys ) )
  1091. aZone->SetNeedRefill( false );
  1092. }
  1093. else
  1094. {
  1095. if( fillNonCopperZone( aZone, aLayer, smoothedPoly, aFillPolys ) )
  1096. aZone->SetNeedRefill( false );
  1097. }
  1098. return true;
  1099. }
  1100. /**
  1101. * Function buildThermalSpokes
  1102. */
  1103. void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer,
  1104. const std::vector<PAD*>& aSpokedPadsList,
  1105. std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
  1106. {
  1107. BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
  1108. EDA_RECT zoneBB = aZone->GetCachedBoundingBox();
  1109. DRC_CONSTRAINT constraint;
  1110. zoneBB.Inflate( std::max( bds.GetBiggestClearanceValue(), aZone->GetLocalClearance() ) );
  1111. // Is a point on the boundary of the polygon inside or outside? This small epsilon lets
  1112. // us avoid the question.
  1113. int epsilon = KiROUND( IU_PER_MM * 0.04 ); // about 1.5 mil
  1114. for( PAD* pad : aSpokedPadsList )
  1115. {
  1116. // We currently only connect to pads, not pad holes
  1117. if( !pad->IsOnLayer( aLayer ) )
  1118. continue;
  1119. constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
  1120. int thermalReliefGap = constraint.GetValue().Min();
  1121. constraint = bds.m_DRCEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, aZone, aLayer );
  1122. int spoke_w = constraint.GetValue().Opt();
  1123. // Spoke width should ideally be smaller than the pad minor axis.
  1124. // Otherwise the thermal shape is not really a thermal relief,
  1125. // and the algo to count the actual number of spokes can fail
  1126. int spoke_max_allowed_w = std::min( pad->GetSize().x, pad->GetSize().y );
  1127. spoke_w = std::max( spoke_w, constraint.Value().Min() );
  1128. spoke_w = std::min( spoke_w, constraint.Value().Max() );
  1129. // ensure the spoke width is smaller than the pad minor size
  1130. spoke_w = std::min( spoke_w, spoke_max_allowed_w );
  1131. // Cannot create stubs having a width < zone min thickness
  1132. if( spoke_w < aZone->GetMinThickness() )
  1133. continue;
  1134. int spoke_half_w = spoke_w / 2;
  1135. // Quick test here to possibly save us some work
  1136. BOX2I itemBB = pad->GetBoundingBox();
  1137. itemBB.Inflate( thermalReliefGap + epsilon );
  1138. if( !( itemBB.Intersects( zoneBB ) ) )
  1139. continue;
  1140. // Thermal spokes consist of segments from the pad center to points just outside
  1141. // the thermal relief.
  1142. VECTOR2I shapePos = pad->ShapePos();
  1143. EDA_ANGLE spokesAngle = pad->GetThermalSpokeAngle();
  1144. // We use the bounding-box to lay out the spokes, but for this to work the bounding
  1145. // box has to be built at the same rotation as the spokes. We have to use a dummy pad
  1146. // to avoid dirtying the cached shapes.
  1147. PAD dummy_pad( *pad );
  1148. dummy_pad.SetOrientation( spokesAngle );
  1149. // Spokes are from center of pad, not from hole
  1150. dummy_pad.SetPosition( -1 * pad->GetOffset() );
  1151. BOX2I reliefBB = dummy_pad.GetBoundingBox();
  1152. reliefBB.Inflate( thermalReliefGap + epsilon );
  1153. for( int i = 0; i < 4; i++ )
  1154. {
  1155. SHAPE_LINE_CHAIN spoke;
  1156. switch( i )
  1157. {
  1158. case 0: // lower stub
  1159. spoke.Append( +spoke_half_w, -spoke_half_w );
  1160. spoke.Append( -spoke_half_w, -spoke_half_w );
  1161. spoke.Append( -spoke_half_w, reliefBB.GetBottom() );
  1162. spoke.Append( 0, reliefBB.GetBottom() ); // test pt
  1163. spoke.Append( +spoke_half_w, reliefBB.GetBottom() );
  1164. break;
  1165. case 1: // upper stub
  1166. spoke.Append( +spoke_half_w, spoke_half_w );
  1167. spoke.Append( -spoke_half_w, spoke_half_w );
  1168. spoke.Append( -spoke_half_w, reliefBB.GetTop() );
  1169. spoke.Append( 0, reliefBB.GetTop() ); // test pt
  1170. spoke.Append( +spoke_half_w, reliefBB.GetTop() );
  1171. break;
  1172. case 2: // right stub
  1173. spoke.Append( -spoke_half_w, spoke_half_w );
  1174. spoke.Append( -spoke_half_w, -spoke_half_w );
  1175. spoke.Append( reliefBB.GetRight(), -spoke_half_w );
  1176. spoke.Append( reliefBB.GetRight(), 0 ); // test pt
  1177. spoke.Append( reliefBB.GetRight(), spoke_half_w );
  1178. break;
  1179. case 3: // left stub
  1180. spoke.Append( spoke_half_w, spoke_half_w );
  1181. spoke.Append( spoke_half_w, -spoke_half_w );
  1182. spoke.Append( reliefBB.GetLeft(), -spoke_half_w );
  1183. spoke.Append( reliefBB.GetLeft(), 0 ); // test pt
  1184. spoke.Append( reliefBB.GetLeft(), spoke_half_w );
  1185. break;
  1186. }
  1187. // Rotate and move the spokes to the right position
  1188. spoke.Rotate( pad->GetOrientation() + spokesAngle );
  1189. spoke.Move( shapePos );
  1190. spoke.SetClosed( true );
  1191. spoke.GenerateBBoxCache();
  1192. aSpokesList.push_back( std::move( spoke ) );
  1193. }
  1194. }
  1195. }
  1196. bool ZONE_FILLER::addHatchFillTypeOnZone( const ZONE* aZone, PCB_LAYER_ID aLayer,
  1197. PCB_LAYER_ID aDebugLayer, SHAPE_POLY_SET& aFillPolys )
  1198. {
  1199. // Build grid:
  1200. // obviously line thickness must be > zone min thickness.
  1201. // It can happens if a board file was edited by hand by a python script
  1202. // Use 1 micron margin to be *sure* there is no issue in Gerber files
  1203. // (Gbr file unit = 1 or 10 nm) due to some truncation in coordinates or calculations
  1204. // This margin also avoid problems due to rounding coordinates in next calculations
  1205. // that can create incorrect polygons
  1206. int thickness = std::max( aZone->GetHatchThickness(),
  1207. aZone->GetMinThickness() + Millimeter2iu( 0.001 ) );
  1208. int linethickness = thickness - aZone->GetMinThickness();
  1209. int gridsize = thickness + aZone->GetHatchGap();
  1210. SHAPE_POLY_SET filledPolys = aFillPolys.CloneDropTriangulation();
  1211. // Use a area that contains the rotated bbox by orientation, and after rotate the result
  1212. // by -orientation.
  1213. if( !aZone->GetHatchOrientation().IsZero() )
  1214. filledPolys.Rotate( - aZone->GetHatchOrientation() );
  1215. BOX2I bbox = filledPolys.BBox( 0 );
  1216. // Build hole shape
  1217. // the hole size is aZone->GetHatchGap(), but because the outline thickness
  1218. // is aZone->GetMinThickness(), the hole shape size must be larger
  1219. SHAPE_LINE_CHAIN hole_base;
  1220. int hole_size = aZone->GetHatchGap() + aZone->GetMinThickness();
  1221. VECTOR2I corner( 0, 0 );;
  1222. hole_base.Append( corner );
  1223. corner.x += hole_size;
  1224. hole_base.Append( corner );
  1225. corner.y += hole_size;
  1226. hole_base.Append( corner );
  1227. corner.x = 0;
  1228. hole_base.Append( corner );
  1229. hole_base.SetClosed( true );
  1230. // Calculate minimal area of a grid hole.
  1231. // All holes smaller than a threshold will be removed
  1232. double minimal_hole_area = hole_base.Area() * aZone->GetHatchHoleMinArea();
  1233. // Now convert this hole to a smoothed shape:
  1234. if( aZone->GetHatchSmoothingLevel() > 0 )
  1235. {
  1236. // the actual size of chamfer, or rounded corner radius is the half size
  1237. // of the HatchFillTypeGap scaled by aZone->GetHatchSmoothingValue()
  1238. // aZone->GetHatchSmoothingValue() = 1.0 is the max value for the chamfer or the
  1239. // radius of corner (radius = half size of the hole)
  1240. int smooth_value = KiROUND( aZone->GetHatchGap()
  1241. * aZone->GetHatchSmoothingValue() / 2 );
  1242. // Minimal optimization:
  1243. // make smoothing only for reasonable smooth values, to avoid a lot of useless segments
  1244. // and if the smooth value is small, use chamfer even if fillet is requested
  1245. #define SMOOTH_MIN_VAL_MM 0.02
  1246. #define SMOOTH_SMALL_VAL_MM 0.04
  1247. if( smooth_value > Millimeter2iu( SMOOTH_MIN_VAL_MM ) )
  1248. {
  1249. SHAPE_POLY_SET smooth_hole;
  1250. smooth_hole.AddOutline( hole_base );
  1251. int smooth_level = aZone->GetHatchSmoothingLevel();
  1252. if( smooth_value < Millimeter2iu( SMOOTH_SMALL_VAL_MM ) && smooth_level > 1 )
  1253. smooth_level = 1;
  1254. // Use a larger smooth_value to compensate the outline tickness
  1255. // (chamfer is not visible is smooth value < outline thickess)
  1256. smooth_value += aZone->GetMinThickness() / 2;
  1257. // smooth_value cannot be bigger than the half size oh the hole:
  1258. smooth_value = std::min( smooth_value, aZone->GetHatchGap() / 2 );
  1259. // the error to approximate a circle by segments when smoothing corners by a arc
  1260. int error_max = std::max( Millimeter2iu( 0.01 ), smooth_value / 20 );
  1261. switch( smooth_level )
  1262. {
  1263. case 1:
  1264. // Chamfer() uses the distance from a corner to create a end point
  1265. // for the chamfer.
  1266. hole_base = smooth_hole.Chamfer( smooth_value ).Outline( 0 );
  1267. break;
  1268. default:
  1269. if( aZone->GetHatchSmoothingLevel() > 2 )
  1270. error_max /= 2; // Force better smoothing
  1271. hole_base = smooth_hole.Fillet( smooth_value, error_max ).Outline( 0 );
  1272. break;
  1273. case 0:
  1274. break;
  1275. };
  1276. }
  1277. }
  1278. // Build holes
  1279. SHAPE_POLY_SET holes;
  1280. for( int xx = 0; ; xx++ )
  1281. {
  1282. int xpos = xx * gridsize;
  1283. if( xpos > bbox.GetWidth() )
  1284. break;
  1285. for( int yy = 0; ; yy++ )
  1286. {
  1287. int ypos = yy * gridsize;
  1288. if( ypos > bbox.GetHeight() )
  1289. break;
  1290. // Generate hole
  1291. SHAPE_LINE_CHAIN hole( hole_base );
  1292. hole.Move( VECTOR2I( xpos, ypos ) );
  1293. holes.AddOutline( hole );
  1294. }
  1295. }
  1296. holes.Move( bbox.GetPosition() );
  1297. if( !aZone->GetHatchOrientation().IsZero() )
  1298. holes.Rotate( aZone->GetHatchOrientation() );
  1299. DUMP_POLYS_TO_COPPER_LAYER( holes, In10_Cu, wxT( "hatch-holes" ) );
  1300. int outline_margin = aZone->GetMinThickness() * 1.1;
  1301. // Using GetHatchThickness() can look more consistent than GetMinThickness().
  1302. if( aZone->GetHatchBorderAlgorithm() && aZone->GetHatchThickness() > outline_margin )
  1303. outline_margin = aZone->GetHatchThickness();
  1304. // The fill has already been deflated to ensure GetMinThickness() so we just have to
  1305. // account for anything beyond that.
  1306. SHAPE_POLY_SET deflatedFilledPolys = aFillPolys.CloneDropTriangulation();
  1307. deflatedFilledPolys.Deflate( outline_margin - aZone->GetMinThickness(), 16 );
  1308. holes.BooleanIntersection( deflatedFilledPolys, SHAPE_POLY_SET::PM_FAST );
  1309. DUMP_POLYS_TO_COPPER_LAYER( holes, In11_Cu, wxT( "fill-clipped-hatch-holes" ) );
  1310. SHAPE_POLY_SET deflatedOutline = aZone->Outline()->CloneDropTriangulation();
  1311. deflatedOutline.Deflate( outline_margin, 16 );
  1312. holes.BooleanIntersection( deflatedOutline, SHAPE_POLY_SET::PM_FAST );
  1313. DUMP_POLYS_TO_COPPER_LAYER( holes, In12_Cu, wxT( "outline-clipped-hatch-holes" ) );
  1314. if( aZone->GetNetCode() != 0 )
  1315. {
  1316. // Vias and pads connected to the zone must not be allowed to become isolated inside
  1317. // one of the holes. Effectively this means their copper outline needs to be expanded
  1318. // to be at least as wide as the gap so that it is guaranteed to touch at least one
  1319. // edge.
  1320. EDA_RECT zone_boundingbox = aZone->GetCachedBoundingBox();
  1321. SHAPE_POLY_SET aprons;
  1322. int min_apron_radius = ( aZone->GetHatchGap() * 10 ) / 19;
  1323. for( PCB_TRACK* track : m_board->Tracks() )
  1324. {
  1325. if( track->Type() == PCB_VIA_T )
  1326. {
  1327. PCB_VIA* via = static_cast<PCB_VIA*>( track );
  1328. if( via->GetNetCode() == aZone->GetNetCode()
  1329. && via->IsOnLayer( aLayer )
  1330. && via->GetBoundingBox().Intersects( zone_boundingbox ) )
  1331. {
  1332. int r = std::max( min_apron_radius,
  1333. via->GetDrillValue() / 2 + outline_margin );
  1334. TransformCircleToPolygon( aprons, via->GetPosition(), r, ARC_HIGH_DEF,
  1335. ERROR_OUTSIDE );
  1336. }
  1337. }
  1338. }
  1339. for( FOOTPRINT* footprint : m_board->Footprints() )
  1340. {
  1341. for( PAD* pad : footprint->Pads() )
  1342. {
  1343. if( pad->GetNetCode() == aZone->GetNetCode()
  1344. && pad->IsOnLayer( aLayer )
  1345. && pad->GetBoundingBox().Intersects( zone_boundingbox ) )
  1346. {
  1347. // What we want is to bulk up the pad shape so that the narrowest bit of
  1348. // copper between the hole and the apron edge is at least outline_margin
  1349. // wide (and that the apron itself meets min_apron_radius. But that would
  1350. // take a lot of code and math, and the following approximation is close
  1351. // enough.
  1352. int pad_width = std::min( pad->GetSize().x, pad->GetSize().y );
  1353. int slot_width = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
  1354. int min_annular_ring_width = ( pad_width - slot_width ) / 2;
  1355. int clearance = std::max( min_apron_radius - pad_width / 2,
  1356. outline_margin - min_annular_ring_width );
  1357. clearance = std::max( 0, clearance - linethickness / 2 );
  1358. pad->TransformShapeWithClearanceToPolygon( aprons, aLayer, clearance,
  1359. ARC_HIGH_DEF, ERROR_OUTSIDE );
  1360. }
  1361. }
  1362. }
  1363. holes.BooleanSubtract( aprons, SHAPE_POLY_SET::PM_FAST );
  1364. }
  1365. DUMP_POLYS_TO_COPPER_LAYER( holes, In13_Cu, wxT( "pad-via-clipped-hatch-holes" ) );
  1366. // Now filter truncated holes to avoid small holes in pattern
  1367. // It happens for holes near the zone outline
  1368. for( int ii = 0; ii < holes.OutlineCount(); )
  1369. {
  1370. double area = holes.Outline( ii ).Area();
  1371. if( area < minimal_hole_area ) // The current hole is too small: remove it
  1372. holes.DeletePolygon( ii );
  1373. else
  1374. ++ii;
  1375. }
  1376. // create grid. Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to
  1377. // generate strictly simple polygons needed by Gerber files and Fracture()
  1378. aFillPolys.BooleanSubtract( aFillPolys, holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  1379. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In14_Cu, wxT( "after-hatching" ) );
  1380. return true;
  1381. }