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.

2639 lines
95 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
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months 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
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2014-2017 CERN
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. * @author Tomasz Włostowski <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_target.h>
  34. #include <pcb_track.h>
  35. #include <pcb_text.h>
  36. #include <pcb_textbox.h>
  37. #include <pcb_tablecell.h>
  38. #include <pcb_table.h>
  39. #include <pcb_dimension.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 <geometry/vertex_set.h>
  48. #include <kidialog.h>
  49. #include <thread_pool.h>
  50. #include <math/util.h> // for KiROUND
  51. #include "zone_filler.h"
  52. #include "project.h"
  53. #include "project/project_local_settings.h"
  54. // Helper classes for connect_nearby_polys
  55. class RESULTS
  56. {
  57. public:
  58. RESULTS( int aOutline1, int aOutline2, int aVertex1, int aVertex2 ) :
  59. m_outline1( aOutline1 ), m_outline2( aOutline2 ),
  60. m_vertex1( aVertex1 ), m_vertex2( aVertex2 )
  61. {
  62. }
  63. bool operator<( const RESULTS& aOther ) const
  64. {
  65. if( m_outline1 != aOther.m_outline1 )
  66. return m_outline1 < aOther.m_outline1;
  67. if( m_outline2 != aOther.m_outline2 )
  68. return m_outline2 < aOther.m_outline2;
  69. if( m_vertex1 != aOther.m_vertex1 )
  70. return m_vertex1 < aOther.m_vertex1;
  71. return m_vertex2 < aOther.m_vertex2;
  72. }
  73. int m_outline1;
  74. int m_outline2;
  75. int m_vertex1;
  76. int m_vertex2;
  77. };
  78. class VERTEX_CONNECTOR : protected VERTEX_SET
  79. {
  80. public:
  81. VERTEX_CONNECTOR( const BOX2I& aBBox, const SHAPE_POLY_SET& aPolys, int aDist ) : VERTEX_SET( 0 )
  82. {
  83. SetBoundingBox( aBBox );
  84. VERTEX* tail = nullptr;
  85. for( int i = 0; i < aPolys.OutlineCount(); i++ )
  86. tail = createList( aPolys.Outline( i ), tail, (void*)( intptr_t )( i ) );
  87. if( tail )
  88. tail->updateList();
  89. m_dist = aDist;
  90. }
  91. VERTEX* getPoint( VERTEX* aPt ) const
  92. {
  93. // z-order range for the current point ± limit bounding box
  94. const uint32_t maxZ = zOrder( aPt->x + m_dist, aPt->y + m_dist );
  95. const uint32_t minZ = zOrder( aPt->x - m_dist, aPt->y - m_dist );
  96. const SEG::ecoord limit2 = SEG::Square( m_dist );
  97. // first look for points in increasing z-order
  98. SEG::ecoord min_dist = std::numeric_limits<SEG::ecoord>::max();
  99. VERTEX* retval = nullptr;
  100. auto check_pt = [&]( VERTEX* p )
  101. {
  102. VECTOR2D diff( p->x - aPt->x, p->y - aPt->y );
  103. SEG::ecoord dist2 = diff.SquaredEuclideanNorm();
  104. if( dist2 > 0 && dist2 < limit2 && dist2 < min_dist && p->isEar( true ) )
  105. {
  106. min_dist = dist2;
  107. retval = p;
  108. }
  109. };
  110. VERTEX* p = aPt->nextZ;
  111. while( p && p->z <= maxZ )
  112. {
  113. check_pt( p );
  114. p = p->nextZ;
  115. }
  116. p = aPt->prevZ;
  117. while( p && p->z >= minZ )
  118. {
  119. check_pt( p );
  120. p = p->prevZ;
  121. }
  122. return retval;
  123. }
  124. void FindResults()
  125. {
  126. if( m_vertices.empty() )
  127. return;
  128. VERTEX* p = m_vertices.front().next;
  129. std::set<VERTEX*> visited;
  130. while( p != &m_vertices.front() )
  131. {
  132. // Skip points that are concave
  133. if( !p->isEar() )
  134. {
  135. p = p->next;
  136. continue;
  137. }
  138. VERTEX* q = nullptr;
  139. if( ( visited.empty() || !visited.contains( p ) ) && ( q = getPoint( p ) ) )
  140. {
  141. visited.insert( p );
  142. if( !visited.contains( q ) &&
  143. m_results.emplace( (intptr_t) p->GetUserData(), (intptr_t) q->GetUserData(),
  144. p->i, q->i ).second )
  145. {
  146. // We don't want to connect multiple points in the same vicinity, so skip
  147. // 2 points before and after each point and match.
  148. visited.insert( p->prev );
  149. visited.insert( p->prev->prev );
  150. visited.insert( p->next );
  151. visited.insert( p->next->next );
  152. visited.insert( q->prev );
  153. visited.insert( q->prev->prev );
  154. visited.insert( q->next );
  155. visited.insert( q->next->next );
  156. visited.insert( q );
  157. }
  158. }
  159. p = p->next;
  160. }
  161. }
  162. std::set<RESULTS> GetResults() const
  163. {
  164. return m_results;
  165. }
  166. private:
  167. std::set<RESULTS> m_results;
  168. int m_dist;
  169. };
  170. ZONE_FILLER::ZONE_FILLER( BOARD* aBoard, COMMIT* aCommit ) :
  171. m_board( aBoard ),
  172. m_brdOutlinesValid( false ),
  173. m_commit( aCommit ),
  174. m_progressReporter( nullptr ),
  175. m_maxError( ARC_HIGH_DEF ),
  176. m_worstClearance( 0 )
  177. {
  178. // To enable add "DebugZoneFiller=1" to kicad_advanced settings file.
  179. m_debugZoneFiller = ADVANCED_CFG::GetCfg().m_DebugZoneFiller;
  180. }
  181. ZONE_FILLER::~ZONE_FILLER()
  182. {
  183. }
  184. void ZONE_FILLER::SetProgressReporter( PROGRESS_REPORTER* aReporter )
  185. {
  186. m_progressReporter = aReporter;
  187. wxASSERT_MSG( m_commit, wxT( "ZONE_FILLER must have a valid commit to call "
  188. "SetProgressReporter" ) );
  189. }
  190. /**
  191. * Fills the given list of zones.
  192. *
  193. * NB: Invalidates connectivity - it is up to the caller to obtain a lock on the connectivity
  194. * data before calling Fill to prevent access to stale data by other coroutines (for example,
  195. * ratsnest redraw). This will generally be required if a UI-based progress reporter has been
  196. * installed.
  197. *
  198. * Caller is also responsible for re-building connectivity afterwards.
  199. */
  200. bool ZONE_FILLER::Fill( const std::vector<ZONE*>& aZones, bool aCheck, wxWindow* aParent )
  201. {
  202. std::lock_guard<KISPINLOCK> lock( m_board->GetConnectivity()->GetLock() );
  203. std::vector<std::pair<ZONE*, PCB_LAYER_ID>> toFill;
  204. std::map<std::pair<ZONE*, PCB_LAYER_ID>, HASH_128> oldFillHashes;
  205. std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>> isolatedIslandsMap;
  206. std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_board->GetConnectivity();
  207. // Rebuild (from scratch, ignoring dirty flags) just in case. This really needs to be reliable.
  208. connectivity->ClearRatsnest();
  209. connectivity->Build( m_board, m_progressReporter );
  210. m_worstClearance = m_board->GetMaxClearanceValue();
  211. if( m_progressReporter )
  212. {
  213. m_progressReporter->Report( aCheck ? _( "Checking zone fills..." )
  214. : _( "Building zone fills..." ) );
  215. m_progressReporter->SetMaxProgress( aZones.size() );
  216. m_progressReporter->KeepRefreshing();
  217. }
  218. // The board outlines is used to clip solid areas inside the board (when outlines are valid)
  219. m_boardOutline.RemoveAllContours();
  220. m_brdOutlinesValid = m_board->GetBoardPolygonOutlines( m_boardOutline );
  221. // Update and cache zone bounding boxes and pad effective shapes so that we don't have to
  222. // make them thread-safe.
  223. //
  224. for( ZONE* zone : m_board->Zones() )
  225. zone->CacheBoundingBox();
  226. for( FOOTPRINT* footprint : m_board->Footprints() )
  227. {
  228. for( PAD* pad : footprint->Pads() )
  229. {
  230. if( pad->IsDirty() )
  231. {
  232. pad->BuildEffectiveShapes();
  233. pad->BuildEffectivePolygon( ERROR_OUTSIDE );
  234. }
  235. }
  236. for( ZONE* zone : footprint->Zones() )
  237. zone->CacheBoundingBox();
  238. // Rules may depend on insideCourtyard() or other expressions
  239. footprint->BuildCourtyardCaches();
  240. footprint->BuildNetTieCache();
  241. }
  242. LSET boardCuMask = m_board->GetEnabledLayers() & LSET::AllCuMask();
  243. auto findHighestPriorityZone =
  244. [&]( const BOX2I& bbox, PCB_LAYER_ID itemLayer, int netcode,
  245. const std::function<bool( const ZONE* )>& testFn ) -> ZONE*
  246. {
  247. unsigned highestPriority = 0;
  248. ZONE* highestPriorityZone = nullptr;
  249. for( ZONE* zone : m_board->Zones() )
  250. {
  251. // Rule areas are not filled
  252. if( zone->GetIsRuleArea() )
  253. continue;
  254. if( zone->GetAssignedPriority() < highestPriority )
  255. continue;
  256. if( !zone->IsOnLayer( itemLayer ) )
  257. continue;
  258. // Degenerate zones will cause trouble; skip them
  259. if( zone->GetNumCorners() <= 2 )
  260. continue;
  261. if( !zone->GetBoundingBox().Intersects( bbox ) )
  262. continue;
  263. if( !testFn( zone ) )
  264. continue;
  265. // Prefer highest priority and matching netcode
  266. if( zone->GetAssignedPriority() > highestPriority
  267. || zone->GetNetCode() == netcode )
  268. {
  269. highestPriority = zone->GetAssignedPriority();
  270. highestPriorityZone = zone;
  271. }
  272. }
  273. return highestPriorityZone;
  274. };
  275. auto isInPourKeepoutArea =
  276. [&]( const BOX2I& bbox, PCB_LAYER_ID itemLayer, const VECTOR2I& testPoint ) -> bool
  277. {
  278. for( ZONE* zone : m_board->Zones() )
  279. {
  280. if( !zone->GetIsRuleArea() )
  281. continue;
  282. if( !zone->HasKeepoutParametersSet() )
  283. continue;
  284. if( !zone->GetDoNotAllowZoneFills() )
  285. continue;
  286. if( !zone->IsOnLayer( itemLayer ) )
  287. continue;
  288. // Degenerate zones will cause trouble; skip them
  289. if( zone->GetNumCorners() <= 2 )
  290. continue;
  291. if( !zone->GetBoundingBox().Intersects( bbox ) )
  292. continue;
  293. if( zone->Outline()->Contains( testPoint ) )
  294. return true;
  295. }
  296. return false;
  297. };
  298. // Determine state of conditional via flashing
  299. // This is now done completely deterministically prior to filling due to the pathological
  300. // case presented in https://gitlab.com/kicad/code/kicad/-/issues/12964.
  301. for( PCB_TRACK* track : m_board->Tracks() )
  302. {
  303. if( track->Type() == PCB_VIA_T )
  304. {
  305. PCB_VIA* via = static_cast<PCB_VIA*>( track );
  306. via->ClearZoneLayerOverrides();
  307. if( !via->GetRemoveUnconnected() )
  308. continue;
  309. BOX2I bbox = via->GetBoundingBox();
  310. VECTOR2I center = via->GetPosition();
  311. int holeRadius = via->GetDrillValue() / 2 + 1;
  312. int netcode = via->GetNetCode();
  313. LSET layers = via->GetLayerSet() & boardCuMask;
  314. // Checking if the via hole touches the zone outline
  315. auto viaTestFn =
  316. [&]( const ZONE* aZone ) -> bool
  317. {
  318. return aZone->Outline()->Contains( center, -1, holeRadius );
  319. };
  320. for( PCB_LAYER_ID layer : layers.Seq() )
  321. {
  322. if( !via->ConditionallyFlashed( layer ) )
  323. continue;
  324. if( isInPourKeepoutArea( bbox, layer, center ) )
  325. {
  326. via->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
  327. }
  328. else
  329. {
  330. ZONE* zone = findHighestPriorityZone( bbox, layer, netcode, viaTestFn );
  331. if( zone && zone->GetNetCode() == via->GetNetCode() )
  332. via->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
  333. else
  334. via->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
  335. }
  336. }
  337. }
  338. }
  339. // Determine state of conditional pad flashing
  340. for( FOOTPRINT* footprint : m_board->Footprints() )
  341. {
  342. for( PAD* pad : footprint->Pads() )
  343. {
  344. pad->ClearZoneLayerOverrides();
  345. if( !pad->GetRemoveUnconnected() )
  346. continue;
  347. BOX2I bbox = pad->GetBoundingBox();
  348. VECTOR2I center = pad->GetPosition();
  349. int netcode = pad->GetNetCode();
  350. LSET layers = pad->GetLayerSet() & boardCuMask;
  351. auto padTestFn =
  352. [&]( const ZONE* aZone ) -> bool
  353. {
  354. return aZone->Outline()->Contains( center );
  355. };
  356. for( PCB_LAYER_ID layer : layers.Seq() )
  357. {
  358. if( !pad->ConditionallyFlashed( layer ) )
  359. continue;
  360. if( isInPourKeepoutArea( bbox, layer, center ) )
  361. {
  362. pad->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
  363. }
  364. else
  365. {
  366. ZONE* zone = findHighestPriorityZone( bbox, layer, netcode, padTestFn );
  367. if( zone && zone->GetNetCode() == pad->GetNetCode() )
  368. pad->SetZoneLayerOverride( layer, ZLO_FORCE_FLASHED );
  369. else
  370. pad->SetZoneLayerOverride( layer, ZLO_FORCE_NO_ZONE_CONNECTION );
  371. }
  372. }
  373. }
  374. }
  375. for( ZONE* zone : aZones )
  376. {
  377. // Rule areas are not filled
  378. if( zone->GetIsRuleArea() )
  379. continue;
  380. // Degenerate zones will cause trouble; skip them
  381. if( zone->GetNumCorners() <= 2 )
  382. continue;
  383. if( m_commit )
  384. m_commit->Modify( zone );
  385. // calculate the hash value for filled areas. it will be used later to know if the
  386. // current filled areas are up to date
  387. for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
  388. {
  389. zone->BuildHashValue( layer );
  390. oldFillHashes[ { zone, layer } ] = zone->GetHashValue( layer );
  391. // Add the zone to the list of zones to test or refill
  392. toFill.emplace_back( std::make_pair( zone, layer ) );
  393. isolatedIslandsMap[ zone ][ layer ] = ISOLATED_ISLANDS();
  394. }
  395. // Remove existing fill first to prevent drawing invalid polygons on some platforms
  396. zone->UnFill();
  397. }
  398. auto check_fill_dependency =
  399. [&]( ZONE* aZone, PCB_LAYER_ID aLayer, ZONE* aOtherZone ) -> bool
  400. {
  401. // Check to see if we have to knock-out the filled areas of a higher-priority
  402. // zone. If so we have to wait until said zone is filled before we can fill.
  403. // If the other zone is already filled on the requested layer then we're
  404. // good-to-go
  405. if( aOtherZone->GetFillFlag( aLayer ) )
  406. return false;
  407. // Even if keepouts exclude copper pours, the exclusion is by outline rather than
  408. // filled area, so we're good-to-go here too
  409. if( aOtherZone->GetIsRuleArea() )
  410. return false;
  411. // If the other zone is never going to be filled then don't wait for it
  412. if( aOtherZone->GetNumCorners() <= 2 )
  413. return false;
  414. // If the zones share no common layers
  415. if( !aOtherZone->GetLayerSet().test( aLayer ) )
  416. return false;
  417. if( aZone->HigherPriority( aOtherZone ) )
  418. return false;
  419. // Same-net zones always use outlines to produce determinate results
  420. if( aOtherZone->SameNet( aZone ) )
  421. return false;
  422. // A higher priority zone is found: if we intersect and it's not filled yet
  423. // then we have to wait.
  424. BOX2I inflatedBBox = aZone->GetBoundingBox();
  425. inflatedBBox.Inflate( m_worstClearance );
  426. if( !inflatedBBox.Intersects( aOtherZone->GetBoundingBox() ) )
  427. return false;
  428. return aZone->Outline()->Collide( aOtherZone->Outline(), m_worstClearance );
  429. };
  430. auto fill_lambda =
  431. [&]( std::pair<ZONE*, PCB_LAYER_ID> aFillItem ) -> int
  432. {
  433. PCB_LAYER_ID layer = aFillItem.second;
  434. ZONE* zone = aFillItem.first;
  435. bool canFill = true;
  436. // Check for any fill dependencies. If our zone needs to be clipped by
  437. // another zone then we can't fill until that zone is filled.
  438. for( ZONE* otherZone : aZones )
  439. {
  440. if( otherZone == zone )
  441. continue;
  442. if( check_fill_dependency( zone, layer, otherZone ) )
  443. {
  444. canFill = false;
  445. break;
  446. }
  447. }
  448. if( m_progressReporter && m_progressReporter->IsCancelled() )
  449. return 0;
  450. if( !canFill )
  451. return 0;
  452. // Now we're ready to fill.
  453. {
  454. std::unique_lock<std::mutex> zoneLock( zone->GetLock(), std::try_to_lock );
  455. if( !zoneLock.owns_lock() )
  456. return 0;
  457. SHAPE_POLY_SET fillPolys;
  458. if( !fillSingleZone( zone, layer, fillPolys ) )
  459. return 0;
  460. zone->SetFilledPolysList( layer, fillPolys );
  461. }
  462. if( m_progressReporter )
  463. m_progressReporter->AdvanceProgress();
  464. return 1;
  465. };
  466. auto tesselate_lambda =
  467. [&]( std::pair<ZONE*, PCB_LAYER_ID> aFillItem ) -> int
  468. {
  469. if( m_progressReporter && m_progressReporter->IsCancelled() )
  470. return 0;
  471. PCB_LAYER_ID layer = aFillItem.second;
  472. ZONE* zone = aFillItem.first;
  473. {
  474. std::unique_lock<std::mutex> zoneLock( zone->GetLock(), std::try_to_lock );
  475. if( !zoneLock.owns_lock() )
  476. return 0;
  477. zone->CacheTriangulation( layer );
  478. zone->SetFillFlag( layer, true );
  479. }
  480. return 1;
  481. };
  482. // Calculate the copper fills (NB: this is multi-threaded)
  483. //
  484. std::vector<std::pair<std::future<int>, int>> returns;
  485. returns.reserve( toFill.size() );
  486. size_t finished = 0;
  487. bool cancelled = false;
  488. thread_pool& tp = GetKiCadThreadPool();
  489. for( const std::pair<ZONE*, PCB_LAYER_ID>& fillItem : toFill )
  490. returns.emplace_back( std::make_pair( tp.submit( fill_lambda, fillItem ), 0 ) );
  491. while( !cancelled && finished != 2 * toFill.size() )
  492. {
  493. for( size_t ii = 0; ii < returns.size(); ++ii )
  494. {
  495. auto& ret = returns[ii];
  496. if( ret.second > 1 )
  497. continue;
  498. std::future_status status = ret.first.wait_for( std::chrono::seconds( 0 ) );
  499. if( status == std::future_status::ready )
  500. {
  501. if( ret.first.get() ) // lambda completed
  502. {
  503. ++finished;
  504. ret.second++; // go to next step
  505. }
  506. if( !cancelled )
  507. {
  508. // Queue the next step (will re-queue the existing step if it didn't complete)
  509. if( ret.second == 0 )
  510. returns[ii].first = tp.submit( fill_lambda, toFill[ii] );
  511. else if( ret.second == 1 )
  512. returns[ii].first = tp.submit( tesselate_lambda, toFill[ii] );
  513. }
  514. }
  515. }
  516. std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
  517. if( m_progressReporter )
  518. {
  519. m_progressReporter->KeepRefreshing();
  520. if( m_progressReporter->IsCancelled() )
  521. cancelled = true;
  522. }
  523. }
  524. // Make sure that all futures have finished.
  525. // This can happen when the user cancels the above operation
  526. for( auto& ret : returns )
  527. {
  528. if( ret.first.valid() )
  529. {
  530. std::future_status status = ret.first.wait_for( std::chrono::seconds( 0 ) );
  531. while( status != std::future_status::ready )
  532. {
  533. if( m_progressReporter )
  534. m_progressReporter->KeepRefreshing();
  535. status = ret.first.wait_for( std::chrono::milliseconds( 100 ) );
  536. }
  537. }
  538. }
  539. // Now update the connectivity to check for isolated copper islands
  540. // (NB: FindIsolatedCopperIslands() is multi-threaded)
  541. //
  542. if( m_progressReporter )
  543. {
  544. if( m_progressReporter->IsCancelled() )
  545. return false;
  546. m_progressReporter->AdvancePhase();
  547. m_progressReporter->Report( _( "Removing isolated copper islands..." ) );
  548. m_progressReporter->KeepRefreshing();
  549. }
  550. connectivity->SetProgressReporter( m_progressReporter );
  551. connectivity->FillIsolatedIslandsMap( isolatedIslandsMap );
  552. connectivity->SetProgressReporter( nullptr );
  553. if( m_progressReporter && m_progressReporter->IsCancelled() )
  554. return false;
  555. for( ZONE* zone : aZones )
  556. {
  557. // Keepout zones are not filled
  558. if( zone->GetIsRuleArea() )
  559. continue;
  560. zone->SetIsFilled( true );
  561. }
  562. // Now remove isolated copper islands according to the isolated islands strategy assigned
  563. // by the user (always, never, below-certain-size).
  564. //
  565. for( const auto& [ zone, zoneIslands ] : isolatedIslandsMap )
  566. {
  567. // If *all* the polygons are islands, do not remove any of them
  568. bool allIslands = true;
  569. for( const auto& [ layer, layerIslands ] : zoneIslands )
  570. {
  571. if( layerIslands.m_IsolatedOutlines.size()
  572. != static_cast<size_t>( zone->GetFilledPolysList( layer )->OutlineCount() ) )
  573. {
  574. allIslands = false;
  575. break;
  576. }
  577. }
  578. if( allIslands )
  579. continue;
  580. for( const auto& [ layer, layerIslands ] : zoneIslands )
  581. {
  582. if( m_debugZoneFiller && LSET::InternalCuMask().Contains( layer ) )
  583. continue;
  584. if( layerIslands.m_IsolatedOutlines.empty() )
  585. continue;
  586. std::vector<int> islands = layerIslands.m_IsolatedOutlines;
  587. // The list of polygons to delete must be explored from last to first in list,
  588. // to allow deleting a polygon from list without breaking the remaining of the list
  589. std::sort( islands.begin(), islands.end(), std::greater<int>() );
  590. std::shared_ptr<SHAPE_POLY_SET> poly = zone->GetFilledPolysList( layer );
  591. long long int minArea = zone->GetMinIslandArea();
  592. ISLAND_REMOVAL_MODE mode = zone->GetIslandRemovalMode();
  593. for( int idx : islands )
  594. {
  595. SHAPE_LINE_CHAIN& outline = poly->Outline( idx );
  596. if( mode == ISLAND_REMOVAL_MODE::ALWAYS )
  597. poly->DeletePolygonAndTriangulationData( idx, false );
  598. else if ( mode == ISLAND_REMOVAL_MODE::AREA && outline.Area( true ) < minArea )
  599. poly->DeletePolygonAndTriangulationData( idx, false );
  600. else
  601. zone->SetIsIsland( layer, idx );
  602. }
  603. poly->UpdateTriangulationDataHash();
  604. zone->CalculateFilledArea();
  605. if( m_progressReporter && m_progressReporter->IsCancelled() )
  606. return false;
  607. }
  608. }
  609. // Now remove islands which are either outside the board edge or fail to meet the minimum
  610. // area requirements
  611. using island_check_return = std::vector<std::pair<std::shared_ptr<SHAPE_POLY_SET>, int>>;
  612. std::vector<std::pair<std::shared_ptr<SHAPE_POLY_SET>, double>> polys_to_check;
  613. // rough estimate to save re-allocation time
  614. polys_to_check.reserve( m_board->GetCopperLayerCount() * aZones.size() );
  615. for( ZONE* zone : aZones )
  616. {
  617. // Don't check for connections on layers that only exist in the zone but
  618. // were disabled in the board
  619. BOARD* board = zone->GetBoard();
  620. LSET zoneCopperLayers = zone->GetLayerSet() & LSET::AllCuMask() & board->GetEnabledLayers();
  621. // Min-thickness is the web thickness. On the other hand, a blob min-thickness by
  622. // min-thickness is not useful. Since there's no obvious definition of web vs. blob, we
  623. // arbitrarily choose "at least 3X the area".
  624. double minArea = (double) zone->GetMinThickness() * zone->GetMinThickness() * 3;
  625. for( PCB_LAYER_ID layer : zoneCopperLayers.Seq() )
  626. {
  627. if( m_debugZoneFiller && LSET::InternalCuMask().Contains( layer ) )
  628. continue;
  629. polys_to_check.emplace_back( zone->GetFilledPolysList( layer ), minArea );
  630. }
  631. }
  632. auto island_lambda =
  633. [&]( int aStart, int aEnd ) -> island_check_return
  634. {
  635. island_check_return retval;
  636. for( int ii = aStart; ii < aEnd && !cancelled; ++ii )
  637. {
  638. auto [poly, minArea] = polys_to_check[ii];
  639. for( int jj = poly->OutlineCount() - 1; jj >= 0; jj-- )
  640. {
  641. SHAPE_POLY_SET island;
  642. SHAPE_POLY_SET intersection;
  643. const SHAPE_LINE_CHAIN& test_poly = poly->Polygon( jj ).front();
  644. double island_area = test_poly.Area();
  645. if( island_area < minArea )
  646. continue;
  647. island.AddOutline( test_poly );
  648. intersection.BooleanIntersection( m_boardOutline, island );
  649. // Nominally, all of these areas should be either inside or outside the
  650. // board outline. So this test should be able to just compare areas (if
  651. // they are equal, you are inside). But in practice, we sometimes have
  652. // slight overlap at the edges, so testing against half-size area acts as
  653. // a fail-safe.
  654. if( intersection.Area() < island_area / 2.0 )
  655. retval.emplace_back( poly, jj );
  656. }
  657. }
  658. return retval;
  659. };
  660. auto island_returns = tp.parallelize_loop( 0, polys_to_check.size(), island_lambda );
  661. cancelled = false;
  662. // Allow island removal threads to finish
  663. for( size_t ii = 0; ii < island_returns.size(); ++ii )
  664. {
  665. std::future<island_check_return>& ret = island_returns[ii];
  666. if( ret.valid() )
  667. {
  668. std::future_status status = ret.wait_for( std::chrono::seconds( 0 ) );
  669. while( status != std::future_status::ready )
  670. {
  671. if( m_progressReporter )
  672. {
  673. m_progressReporter->KeepRefreshing();
  674. if( m_progressReporter->IsCancelled() )
  675. cancelled = true;
  676. }
  677. status = ret.wait_for( std::chrono::milliseconds( 100 ) );
  678. }
  679. }
  680. }
  681. if( cancelled )
  682. return false;
  683. for( size_t ii = 0; ii < island_returns.size(); ++ii )
  684. {
  685. std::future<island_check_return>& ret = island_returns[ii];
  686. if( ret.valid() )
  687. {
  688. for( auto& action_item : ret.get() )
  689. action_item.first->DeletePolygonAndTriangulationData( action_item.second, true );
  690. }
  691. }
  692. for( ZONE* zone : aZones )
  693. zone->CalculateFilledArea();
  694. if( aCheck )
  695. {
  696. bool outOfDate = false;
  697. for( ZONE* zone : aZones )
  698. {
  699. // Keepout zones are not filled
  700. if( zone->GetIsRuleArea() )
  701. continue;
  702. for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
  703. {
  704. zone->BuildHashValue( layer );
  705. if( oldFillHashes[ { zone, layer } ] != zone->GetHashValue( layer ) )
  706. outOfDate = true;
  707. }
  708. }
  709. if( ( m_board->GetProject()
  710. && m_board->GetProject()->GetLocalSettings().m_PrototypeZoneFill ) )
  711. {
  712. KIDIALOG dlg( aParent, _( "Prototype zone fill enabled. Disable setting and refill?" ),
  713. _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
  714. dlg.SetOKCancelLabels( _( "Disable and refill" ), _( "Continue without Refill" ) );
  715. dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
  716. if( dlg.ShowModal() == wxID_OK )
  717. {
  718. m_board->GetProject()->GetLocalSettings().m_PrototypeZoneFill = false;
  719. }
  720. else if( !outOfDate )
  721. {
  722. return false;
  723. }
  724. }
  725. if( outOfDate )
  726. {
  727. KIDIALOG dlg( aParent, _( "Zone fills are out-of-date. Refill?" ),
  728. _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
  729. dlg.SetOKCancelLabels( _( "Refill" ), _( "Continue without Refill" ) );
  730. dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
  731. if( dlg.ShowModal() == wxID_CANCEL )
  732. return false;
  733. }
  734. else
  735. {
  736. // No need to commit something that hasn't changed (and committing will set
  737. // the modified flag).
  738. return false;
  739. }
  740. }
  741. if( m_progressReporter )
  742. {
  743. if( m_progressReporter->IsCancelled() )
  744. return false;
  745. m_progressReporter->AdvancePhase();
  746. m_progressReporter->KeepRefreshing();
  747. }
  748. return true;
  749. }
  750. /**
  751. * Add a knockout for a pad or via. The knockout is 'aGap' larger than the pad (which might be
  752. * either the thermal clearance or the electrical clearance).
  753. */
  754. void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles )
  755. {
  756. if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
  757. {
  758. PAD* pad = static_cast<PAD*>( aItem );
  759. SHAPE_POLY_SET poly;
  760. pad->TransformShapeToPolygon( poly, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
  761. // the pad shape in zone can be its convex hull or the shape itself
  762. if( pad->GetCustomShapeInZoneOpt() == PADSTACK::CUSTOM_SHAPE_ZONE_MODE::CONVEXHULL )
  763. {
  764. std::vector<VECTOR2I> convex_hull;
  765. BuildConvexHull( convex_hull, poly );
  766. aHoles.NewOutline();
  767. for( const VECTOR2I& pt : convex_hull )
  768. aHoles.Append( pt );
  769. }
  770. else
  771. aHoles.Append( poly );
  772. }
  773. else
  774. {
  775. aItem->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
  776. }
  777. }
  778. /**
  779. * Add a knockout for a pad's hole.
  780. */
  781. void ZONE_FILLER::addHoleKnockout( PAD* aPad, int aGap, SHAPE_POLY_SET& aHoles )
  782. {
  783. aPad->TransformHoleToPolygon( aHoles, aGap, m_maxError, ERROR_OUTSIDE );
  784. }
  785. int getHatchFillThermalClearance( const ZONE* aZone, BOARD_ITEM* aItem, PCB_LAYER_ID aLayer )
  786. {
  787. int minorAxis = 0;
  788. if( aItem->Type() == PCB_PAD_T )
  789. {
  790. PAD* pad = static_cast<PAD*>( aItem );
  791. VECTOR2I padSize = pad->GetSize( aLayer );
  792. minorAxis = std::min( padSize.x, padSize.y );
  793. }
  794. else if( aItem->Type() == PCB_VIA_T )
  795. {
  796. PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
  797. minorAxis = via->GetWidth( aLayer );
  798. }
  799. return ( aZone->GetHatchGap() - aZone->GetHatchThickness() - minorAxis ) / 2;
  800. }
  801. /**
  802. * Add a knockout for a graphic item. The knockout is 'aGap' larger than the item (which
  803. * might be either the electrical clearance or the board edge clearance).
  804. */
  805. void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap,
  806. bool aIgnoreLineWidth, SHAPE_POLY_SET& aHoles )
  807. {
  808. switch( aItem->Type() )
  809. {
  810. case PCB_FIELD_T:
  811. case PCB_TEXT_T:
  812. {
  813. PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
  814. if( text->IsVisible() )
  815. {
  816. if( text->IsKnockout() )
  817. {
  818. // Knockout text should only leave holes where the text is, not where the copper fill
  819. // around it would be.
  820. PCB_TEXT textCopy = *text;
  821. textCopy.SetIsKnockout( false );
  822. textCopy.TransformTextToPolySet( aHoles, 0, m_maxError, ERROR_INSIDE );
  823. }
  824. else
  825. {
  826. text->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
  827. }
  828. }
  829. break;
  830. }
  831. case PCB_TEXTBOX_T:
  832. case PCB_TABLE_T:
  833. case PCB_SHAPE_T:
  834. case PCB_TARGET_T:
  835. aItem->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE,
  836. aIgnoreLineWidth );
  837. break;
  838. case PCB_DIM_ALIGNED_T:
  839. case PCB_DIM_LEADER_T:
  840. case PCB_DIM_CENTER_T:
  841. case PCB_DIM_RADIAL_T:
  842. case PCB_DIM_ORTHOGONAL_T:
  843. {
  844. PCB_DIMENSION_BASE* dim = static_cast<PCB_DIMENSION_BASE*>( aItem );
  845. dim->TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE, false );
  846. dim->PCB_TEXT::TransformShapeToPolygon( aHoles, aLayer, aGap, m_maxError, ERROR_OUTSIDE );
  847. break;
  848. }
  849. default:
  850. break;
  851. }
  852. }
  853. /**
  854. * Removes thermal reliefs from the shape for any pads connected to the zone. Does NOT add
  855. * in spokes, which must be done later.
  856. */
  857. void ZONE_FILLER::knockoutThermalReliefs( const ZONE* aZone, PCB_LAYER_ID aLayer,
  858. SHAPE_POLY_SET& aFill,
  859. std::vector<BOARD_ITEM*>& aThermalConnectionPads,
  860. std::vector<PAD*>& aNoConnectionPads )
  861. {
  862. BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
  863. ZONE_CONNECTION connection;
  864. DRC_CONSTRAINT constraint;
  865. int padClearance;
  866. std::shared_ptr<SHAPE> padShape;
  867. int holeClearance;
  868. SHAPE_POLY_SET holes;
  869. for( FOOTPRINT* footprint : m_board->Footprints() )
  870. {
  871. for( PAD* pad : footprint->Pads() )
  872. {
  873. BOX2I padBBox = pad->GetBoundingBox();
  874. padBBox.Inflate( m_worstClearance );
  875. if( !padBBox.Intersects( aZone->GetBoundingBox() ) )
  876. continue;
  877. bool noConnection = pad->GetNetCode() != aZone->GetNetCode();
  878. if( !aZone->IsTeardropArea() )
  879. {
  880. if( aZone->GetNetCode() == 0
  881. || pad->GetZoneLayerOverride( aLayer ) == ZLO_FORCE_NO_ZONE_CONNECTION )
  882. {
  883. noConnection = true;
  884. }
  885. }
  886. if( noConnection )
  887. {
  888. // collect these for knockout in buildCopperItemClearances()
  889. aNoConnectionPads.push_back( pad );
  890. continue;
  891. }
  892. // We put thermal reliefs on all connected items in a hatch fill zone as a way of
  893. // guaranteeing that they connect to the webbing. (The thermal gap is the hatch
  894. // gap minus the pad/via size, making it impossible for the pad/via to be isolated
  895. // within the center of a hole.)
  896. if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
  897. {
  898. aThermalConnectionPads.push_back( pad );
  899. addKnockout( pad, aLayer, getHatchFillThermalClearance( aZone, pad, aLayer ), holes );
  900. continue;
  901. }
  902. if( aZone->IsTeardropArea() )
  903. {
  904. connection = ZONE_CONNECTION::FULL;
  905. }
  906. else
  907. {
  908. constraint = bds.m_DRCEngine->EvalZoneConnection( pad, aZone, aLayer );
  909. connection = constraint.m_ZoneConnection;
  910. }
  911. if( connection == ZONE_CONNECTION::THERMAL && !pad->CanFlashLayer( aLayer ) )
  912. connection = ZONE_CONNECTION::NONE;
  913. switch( connection )
  914. {
  915. case ZONE_CONNECTION::THERMAL:
  916. padShape = pad->GetEffectiveShape( aLayer, FLASHING::ALWAYS_FLASHED );
  917. if( aFill.Collide( padShape.get(), 0 ) )
  918. {
  919. constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad,
  920. aZone, aLayer );
  921. padClearance = constraint.GetValue().Min();
  922. aThermalConnectionPads.push_back( pad );
  923. addKnockout( pad, aLayer, padClearance, holes );
  924. }
  925. break;
  926. case ZONE_CONNECTION::NONE:
  927. constraint = bds.m_DRCEngine->EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, pad,
  928. aZone, aLayer );
  929. if( constraint.GetValue().Min() > aZone->GetLocalClearance().value() )
  930. padClearance = constraint.GetValue().Min();
  931. else
  932. padClearance = aZone->GetLocalClearance().value();
  933. if( pad->FlashLayer( aLayer ) )
  934. {
  935. addKnockout( pad, aLayer, padClearance, holes );
  936. }
  937. else if( pad->GetDrillSize().x > 0 )
  938. {
  939. constraint = bds.m_DRCEngine->EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT,
  940. pad, aZone, aLayer );
  941. if( constraint.GetValue().Min() > padClearance )
  942. holeClearance = constraint.GetValue().Min();
  943. else
  944. holeClearance = padClearance;
  945. pad->TransformHoleToPolygon( holes, holeClearance, m_maxError, ERROR_OUTSIDE );
  946. }
  947. break;
  948. default:
  949. // No knockout
  950. continue;
  951. }
  952. }
  953. }
  954. // We put thermal reliefs on all connected items in a hatch fill zone as a way of guaranteeing
  955. // that they connect to the webbing. (The thermal gap is the hatch gap minus the pad/via size,
  956. // making it impossible for the pad/via to be isolated within the center of a hole.)
  957. if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
  958. {
  959. for( PCB_TRACK* track : m_board->Tracks() )
  960. {
  961. if( track->Type() == PCB_VIA_T )
  962. {
  963. PCB_VIA* via = static_cast<PCB_VIA*>( track );
  964. BOX2I viaBBox = via->GetBoundingBox();
  965. viaBBox.Inflate( m_worstClearance );
  966. if( !viaBBox.Intersects( aZone->GetBoundingBox() ) )
  967. continue;
  968. bool noConnection = via->GetNetCode() != aZone->GetNetCode();
  969. if( noConnection )
  970. continue;
  971. aThermalConnectionPads.push_back( via );
  972. addKnockout( via, aLayer, getHatchFillThermalClearance( aZone, via, aLayer ), holes );
  973. }
  974. }
  975. }
  976. aFill.BooleanSubtract( holes );
  977. }
  978. /**
  979. * Removes clearance from the shape for copper items which share the zone's layer but are
  980. * not connected to it.
  981. */
  982. void ZONE_FILLER::buildCopperItemClearances( const ZONE* aZone, PCB_LAYER_ID aLayer,
  983. const std::vector<PAD*>& aNoConnectionPads,
  984. SHAPE_POLY_SET& aHoles )
  985. {
  986. BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
  987. long ticker = 0;
  988. auto checkForCancel =
  989. [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool
  990. {
  991. return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled();
  992. };
  993. // A small extra clearance to be sure actual track clearances are not smaller than
  994. // requested clearance due to many approximations in calculations, like arc to segment
  995. // approx, rounding issues, etc.
  996. BOX2I zone_boundingbox = aZone->GetBoundingBox();
  997. int extra_margin = pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_ExtraClearance );
  998. // Items outside the zone bounding box are skipped, so it needs to be inflated by the
  999. // largest clearance value found in the netclasses and rules
  1000. zone_boundingbox.Inflate( m_worstClearance + extra_margin );
  1001. auto evalRulesForItems =
  1002. [&bds]( DRC_CONSTRAINT_T aConstraint, const BOARD_ITEM* a, const BOARD_ITEM* b,
  1003. PCB_LAYER_ID aEvalLayer ) -> int
  1004. {
  1005. DRC_CONSTRAINT c = bds.m_DRCEngine->EvalRules( aConstraint, a, b, aEvalLayer );
  1006. if( c.IsNull() )
  1007. return -1;
  1008. else
  1009. return c.GetValue().Min();
  1010. };
  1011. // Add non-connected pad clearances
  1012. //
  1013. auto knockoutPadClearance =
  1014. [&]( PAD* aPad )
  1015. {
  1016. int init_gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone, aPad, aLayer );
  1017. int gap = init_gap;
  1018. bool hasHole = aPad->GetDrillSize().x > 0;
  1019. bool flashLayer = aPad->FlashLayer( aLayer );
  1020. bool platedHole = hasHole && aPad->GetAttribute() == PAD_ATTRIB::PTH;
  1021. if( flashLayer || platedHole )
  1022. {
  1023. gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
  1024. aZone, aPad, aLayer ) );
  1025. }
  1026. if( flashLayer && gap >= 0 )
  1027. addKnockout( aPad, aLayer, gap + extra_margin, aHoles );
  1028. if( hasHole )
  1029. {
  1030. // NPTH do not need copper clearance gaps to their holes
  1031. if( aPad->GetAttribute() == PAD_ATTRIB::NPTH )
  1032. gap = init_gap;
  1033. gap = std::max( gap, evalRulesForItems( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT,
  1034. aZone, aPad, aLayer ) );
  1035. gap = std::max( gap, evalRulesForItems( HOLE_CLEARANCE_CONSTRAINT,
  1036. aZone, aPad, aLayer ) );
  1037. if( gap >= 0 )
  1038. addHoleKnockout( aPad, gap + extra_margin, aHoles );
  1039. }
  1040. };
  1041. for( PAD* pad : aNoConnectionPads )
  1042. {
  1043. if( checkForCancel( m_progressReporter ) )
  1044. return;
  1045. knockoutPadClearance( pad );
  1046. }
  1047. // Add non-connected track clearances
  1048. //
  1049. auto knockoutTrackClearance =
  1050. [&]( PCB_TRACK* aTrack )
  1051. {
  1052. if( aTrack->GetBoundingBox().Intersects( zone_boundingbox ) )
  1053. {
  1054. bool sameNet = aTrack->GetNetCode() == aZone->GetNetCode();
  1055. if( !aZone->IsTeardropArea() && aZone->GetNetCode() == 0 )
  1056. sameNet = false;
  1057. int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
  1058. aZone, aTrack, aLayer );
  1059. if( aTrack->Type() == PCB_VIA_T )
  1060. {
  1061. PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
  1062. if( via->GetZoneLayerOverride( aLayer ) == ZLO_FORCE_NO_ZONE_CONNECTION )
  1063. sameNet = false;
  1064. }
  1065. if( !sameNet )
  1066. {
  1067. gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
  1068. aZone, aTrack, aLayer ) );
  1069. }
  1070. if( aTrack->Type() == PCB_VIA_T )
  1071. {
  1072. PCB_VIA* via = static_cast<PCB_VIA*>( aTrack );
  1073. if( via->FlashLayer( aLayer ) && gap > 0 )
  1074. {
  1075. via->TransformShapeToPolygon( aHoles, aLayer, gap + extra_margin,
  1076. m_maxError, ERROR_OUTSIDE );
  1077. }
  1078. gap = std::max( gap, evalRulesForItems( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT,
  1079. aZone, via, aLayer ) );
  1080. if( !sameNet )
  1081. {
  1082. gap = std::max( gap, evalRulesForItems( HOLE_CLEARANCE_CONSTRAINT,
  1083. aZone, via, aLayer ) );
  1084. }
  1085. if( gap >= 0 )
  1086. {
  1087. int radius = via->GetDrillValue() / 2;
  1088. TransformCircleToPolygon( aHoles, via->GetPosition(),
  1089. radius + gap + extra_margin,
  1090. m_maxError, ERROR_OUTSIDE );
  1091. }
  1092. }
  1093. else
  1094. {
  1095. if( gap >= 0 )
  1096. {
  1097. aTrack->TransformShapeToPolygon( aHoles, aLayer, gap + extra_margin,
  1098. m_maxError, ERROR_OUTSIDE );
  1099. }
  1100. }
  1101. }
  1102. };
  1103. for( PCB_TRACK* track : m_board->Tracks() )
  1104. {
  1105. if( !track->IsOnLayer( aLayer ) )
  1106. continue;
  1107. if( checkForCancel( m_progressReporter ) )
  1108. return;
  1109. knockoutTrackClearance( track );
  1110. }
  1111. // Add graphic item clearances.
  1112. //
  1113. auto knockoutGraphicClearance =
  1114. [&]( BOARD_ITEM* aItem )
  1115. {
  1116. int shapeNet = -1;
  1117. if( aItem->Type() == PCB_SHAPE_T )
  1118. shapeNet = static_cast<PCB_SHAPE*>( aItem )->GetNetCode();
  1119. bool sameNet = shapeNet == aZone->GetNetCode();
  1120. if( !aZone->IsTeardropArea() && aZone->GetNetCode() == 0 )
  1121. sameNet = false;
  1122. // A item on the Edge_Cuts or Margin is always seen as on any layer:
  1123. if( aItem->IsOnLayer( aLayer )
  1124. || aItem->IsOnLayer( Edge_Cuts )
  1125. || aItem->IsOnLayer( Margin ) )
  1126. {
  1127. if( aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
  1128. {
  1129. bool ignoreLineWidths = false;
  1130. int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
  1131. aZone, aItem, aLayer );
  1132. if( aItem->IsOnLayer( aLayer ) && !sameNet )
  1133. {
  1134. gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
  1135. aZone, aItem, aLayer ) );
  1136. }
  1137. else if( aItem->IsOnLayer( Edge_Cuts ) )
  1138. {
  1139. gap = std::max( gap, evalRulesForItems( EDGE_CLEARANCE_CONSTRAINT,
  1140. aZone, aItem, aLayer ) );
  1141. ignoreLineWidths = true;
  1142. }
  1143. else if( aItem->IsOnLayer( Margin ) )
  1144. {
  1145. gap = std::max( gap, evalRulesForItems( EDGE_CLEARANCE_CONSTRAINT,
  1146. aZone, aItem, aLayer ) );
  1147. }
  1148. if( gap >= 0 )
  1149. {
  1150. gap += extra_margin;
  1151. addKnockout( aItem, aLayer, gap, ignoreLineWidths, aHoles );
  1152. }
  1153. }
  1154. }
  1155. };
  1156. auto knockoutCourtyardClearance =
  1157. [&]( FOOTPRINT* aFootprint )
  1158. {
  1159. if( aFootprint->GetBoundingBox().Intersects( zone_boundingbox ) )
  1160. {
  1161. int gap = evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT, aZone,
  1162. aFootprint, aLayer );
  1163. if( gap == 0 )
  1164. {
  1165. aHoles.Append( aFootprint->GetCourtyard( aLayer ) );
  1166. }
  1167. else if( gap > 0 )
  1168. {
  1169. SHAPE_POLY_SET hole = aFootprint->GetCourtyard( aLayer );
  1170. hole.Inflate( gap, CORNER_STRATEGY::ROUND_ALL_CORNERS, m_maxError );
  1171. aHoles.Append( hole );
  1172. }
  1173. }
  1174. };
  1175. for( FOOTPRINT* footprint : m_board->Footprints() )
  1176. {
  1177. knockoutCourtyardClearance( footprint );
  1178. knockoutGraphicClearance( &footprint->Reference() );
  1179. knockoutGraphicClearance( &footprint->Value() );
  1180. std::set<PAD*> allowedNetTiePads;
  1181. // Don't knock out holes for graphic items which implement a net-tie to the zone's net
  1182. // on the layer being filled.
  1183. if( footprint->IsNetTie() )
  1184. {
  1185. for( PAD* pad : footprint->Pads() )
  1186. {
  1187. bool sameNet = pad->GetNetCode() == aZone->GetNetCode();
  1188. if( !aZone->IsTeardropArea() && aZone->GetNetCode() == 0 )
  1189. sameNet = false;
  1190. if( sameNet )
  1191. {
  1192. if( pad->IsOnLayer( aLayer ) )
  1193. allowedNetTiePads.insert( pad );
  1194. for( PAD* other : footprint->GetNetTiePads( pad ) )
  1195. {
  1196. if( other->IsOnLayer( aLayer ) )
  1197. allowedNetTiePads.insert( other );
  1198. }
  1199. }
  1200. }
  1201. }
  1202. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  1203. {
  1204. if( checkForCancel( m_progressReporter ) )
  1205. return;
  1206. BOX2I itemBBox = item->GetBoundingBox();
  1207. if( !zone_boundingbox.Intersects( itemBBox ) )
  1208. continue;
  1209. bool skipItem = false;
  1210. if( item->IsOnLayer( aLayer ) )
  1211. {
  1212. std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape();
  1213. for( PAD* pad : allowedNetTiePads )
  1214. {
  1215. if( pad->GetBoundingBox().Intersects( itemBBox )
  1216. && pad->GetEffectiveShape( aLayer )->Collide( itemShape.get() ) )
  1217. {
  1218. skipItem = true;
  1219. break;
  1220. }
  1221. }
  1222. }
  1223. if( !skipItem )
  1224. knockoutGraphicClearance( item );
  1225. }
  1226. }
  1227. for( BOARD_ITEM* item : m_board->Drawings() )
  1228. {
  1229. if( checkForCancel( m_progressReporter ) )
  1230. return;
  1231. knockoutGraphicClearance( item );
  1232. }
  1233. // Add non-connected zone clearances
  1234. //
  1235. auto knockoutZoneClearance =
  1236. [&]( ZONE* aKnockout )
  1237. {
  1238. // If the zones share no common layers
  1239. if( !aKnockout->GetLayerSet().test( aLayer ) )
  1240. return;
  1241. if( aKnockout->GetBoundingBox().Intersects( zone_boundingbox ) )
  1242. {
  1243. if( aKnockout->GetIsRuleArea() )
  1244. {
  1245. // Keepouts use outline with no clearance
  1246. aKnockout->TransformSmoothedOutlineToPolygon( aHoles, 0, m_maxError,
  1247. ERROR_OUTSIDE, nullptr );
  1248. }
  1249. else
  1250. {
  1251. int gap = std::max( 0, evalRulesForItems( PHYSICAL_CLEARANCE_CONSTRAINT,
  1252. aZone, aKnockout, aLayer ) );
  1253. gap = std::max( gap, evalRulesForItems( CLEARANCE_CONSTRAINT,
  1254. aZone, aKnockout, aLayer ) );
  1255. SHAPE_POLY_SET poly;
  1256. aKnockout->TransformShapeToPolygon( poly, aLayer, gap + extra_margin,
  1257. m_maxError, ERROR_OUTSIDE );
  1258. aHoles.Append( poly );
  1259. }
  1260. }
  1261. };
  1262. for( ZONE* otherZone : m_board->Zones() )
  1263. {
  1264. if( checkForCancel( m_progressReporter ) )
  1265. return;
  1266. // Negative clearance permits zones to short
  1267. if( evalRulesForItems( CLEARANCE_CONSTRAINT, aZone, otherZone, aLayer ) < 0 )
  1268. continue;
  1269. if( otherZone->GetIsRuleArea() )
  1270. {
  1271. if( otherZone->GetDoNotAllowZoneFills() && !aZone->IsTeardropArea() )
  1272. knockoutZoneClearance( otherZone );
  1273. }
  1274. else if( otherZone->HigherPriority( aZone ) )
  1275. {
  1276. if( !otherZone->SameNet( aZone ) )
  1277. knockoutZoneClearance( otherZone );
  1278. }
  1279. }
  1280. for( FOOTPRINT* footprint : m_board->Footprints() )
  1281. {
  1282. for( ZONE* otherZone : footprint->Zones() )
  1283. {
  1284. if( checkForCancel( m_progressReporter ) )
  1285. return;
  1286. if( otherZone->GetIsRuleArea() )
  1287. {
  1288. if( otherZone->GetDoNotAllowZoneFills() && !aZone->IsTeardropArea() )
  1289. knockoutZoneClearance( otherZone );
  1290. }
  1291. else if( otherZone->HigherPriority( aZone ) )
  1292. {
  1293. if( !otherZone->SameNet( aZone ) )
  1294. knockoutZoneClearance( otherZone );
  1295. }
  1296. }
  1297. }
  1298. aHoles.Simplify();
  1299. }
  1300. /**
  1301. * Removes the outlines of higher-proirity zones with the same net. These zones should be
  1302. * in charge of the fill parameters within their own outlines.
  1303. */
  1304. void ZONE_FILLER::subtractHigherPriorityZones( const ZONE* aZone, PCB_LAYER_ID aLayer,
  1305. SHAPE_POLY_SET& aRawFill )
  1306. {
  1307. BOX2I zoneBBox = aZone->GetBoundingBox();
  1308. auto knockoutZoneOutline =
  1309. [&]( ZONE* aKnockout )
  1310. {
  1311. // If the zones share no common layers
  1312. if( !aKnockout->GetLayerSet().test( aLayer ) )
  1313. return;
  1314. if( aKnockout->GetBoundingBox().Intersects( zoneBBox ) )
  1315. {
  1316. // Processing of arc shapes in zones is not yet supported because Clipper
  1317. // can't do boolean operations on them. The poly outline must be converted to
  1318. // segments first.
  1319. SHAPE_POLY_SET outline = aKnockout->Outline()->CloneDropTriangulation();
  1320. outline.ClearArcs();
  1321. aRawFill.BooleanSubtract( outline );
  1322. }
  1323. };
  1324. for( ZONE* otherZone : m_board->Zones() )
  1325. {
  1326. // Don't use the `HigherPriority()` check here because we _only_ want to knock out zones
  1327. // with explicitly higher priorities, not those with equal priorities
  1328. if( otherZone->SameNet( aZone )
  1329. && otherZone->GetAssignedPriority() > aZone->GetAssignedPriority() )
  1330. {
  1331. // Do not remove teardrop area: it is not useful and not good
  1332. if( !otherZone->IsTeardropArea() )
  1333. knockoutZoneOutline( otherZone );
  1334. }
  1335. }
  1336. for( FOOTPRINT* footprint : m_board->Footprints() )
  1337. {
  1338. for( ZONE* otherZone : footprint->Zones() )
  1339. {
  1340. if( otherZone->SameNet( aZone ) && otherZone->HigherPriority( aZone ) )
  1341. {
  1342. // Do not remove teardrop area: it is not useful and not good
  1343. if( !otherZone->IsTeardropArea() )
  1344. knockoutZoneOutline( otherZone );
  1345. }
  1346. }
  1347. }
  1348. }
  1349. void ZONE_FILLER::connect_nearby_polys( SHAPE_POLY_SET& aPolys, double aDistance )
  1350. {
  1351. if( aPolys.OutlineCount() < 1 )
  1352. return;
  1353. VERTEX_CONNECTOR vs( aPolys.BBoxFromCaches(), aPolys, aDistance );
  1354. vs.FindResults();
  1355. // This cannot be a reference because we need to do the comparison below while
  1356. // changing the values
  1357. std::map<int, std::vector<std::pair<int, VECTOR2I>>> insertion_points;
  1358. for( const RESULTS& result : vs.GetResults() )
  1359. {
  1360. SHAPE_LINE_CHAIN& line1 = aPolys.Outline( result.m_outline1 );
  1361. SHAPE_LINE_CHAIN& line2 = aPolys.Outline( result.m_outline2 );
  1362. VECTOR2I pt1 = line1.CPoint( result.m_vertex1 );
  1363. VECTOR2I pt2 = line2.CPoint( result.m_vertex2 );
  1364. // We want to insert the existing point first so that we can place the new point
  1365. // between the two points at the same location.
  1366. insertion_points[result.m_outline1].push_back( { result.m_vertex1, pt1 } );
  1367. insertion_points[result.m_outline1].push_back( { result.m_vertex1, pt2 } );
  1368. }
  1369. for( auto& [outline, vertices] : insertion_points )
  1370. {
  1371. SHAPE_LINE_CHAIN& line = aPolys.Outline( outline );
  1372. // Stable sort here because we want to make sure that we are inserting pt1 first and
  1373. // pt2 second but still sorting the rest of the indices from highest to lowest.
  1374. // This allows us to insert into the existing polygon without modifying the future
  1375. // insertion points.
  1376. std::stable_sort( vertices.begin(), vertices.end(),
  1377. []( const std::pair<int, VECTOR2I>& a, const std::pair<int, VECTOR2I>& b )
  1378. {
  1379. return a.first > b.first;
  1380. } );
  1381. for( const auto& [vertex, pt] : vertices )
  1382. line.Insert( vertex + 1, pt ); // +1 here because we want to insert after the existing point
  1383. }
  1384. }
  1385. #define DUMP_POLYS_TO_COPPER_LAYER( a, b, c ) \
  1386. { if( m_debugZoneFiller && aDebugLayer == b ) \
  1387. { \
  1388. m_board->SetLayerName( b, c ); \
  1389. SHAPE_POLY_SET d = a; \
  1390. d.Fracture(); \
  1391. aFillPolys = d; \
  1392. return false; \
  1393. } \
  1394. }
  1395. /*
  1396. * Note that aSmoothedOutline is larger than the zone where it intersects with other, same-net
  1397. * zones. This is to prevent the re-inflation post min-width trimming from createing divots
  1398. * between adjacent zones. The final aMaxExtents trimming will remove these areas from the final
  1399. * fill.
  1400. */
  1401. bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LAYER_ID aDebugLayer,
  1402. const SHAPE_POLY_SET& aSmoothedOutline,
  1403. const SHAPE_POLY_SET& aMaxExtents, SHAPE_POLY_SET& aFillPolys )
  1404. {
  1405. m_maxError = m_board->GetDesignSettings().m_MaxError;
  1406. // Features which are min_width should survive pruning; features that are *less* than
  1407. // min_width should not. Therefore we subtract epsilon from the min_width when
  1408. // deflating/inflating.
  1409. int half_min_width = aZone->GetMinThickness() / 2;
  1410. int epsilon = pcbIUScale.mmToIU( 0.001 );
  1411. // Solid polygons are deflated and inflated during calculations. Deflating doesn't cause
  1412. // issues, but inflate is tricky as it can create excessively long and narrow spikes for
  1413. // acute angles.
  1414. // ALLOW_ACUTE_CORNERS cannot be used due to the spike problem.
  1415. // CHAMFER_ACUTE_CORNERS is tempting, but can still produce spikes in some unusual
  1416. // circumstances (https://gitlab.com/kicad/code/kicad/-/issues/5581).
  1417. // It's unclear if ROUND_ACUTE_CORNERS would have the same issues, but is currently avoided
  1418. // as a "less-safe" option.
  1419. // ROUND_ALL_CORNERS produces the uniformly nicest shapes, but also a lot of segments.
  1420. // CHAMFER_ALL_CORNERS improves the segment count.
  1421. CORNER_STRATEGY fastCornerStrategy = CORNER_STRATEGY::CHAMFER_ALL_CORNERS;
  1422. CORNER_STRATEGY cornerStrategy = CORNER_STRATEGY::ROUND_ALL_CORNERS;
  1423. std::vector<BOARD_ITEM*> thermalConnectionPads;
  1424. std::vector<PAD*> noConnectionPads;
  1425. std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
  1426. SHAPE_POLY_SET clearanceHoles;
  1427. aFillPolys = aSmoothedOutline;
  1428. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In1_Cu, wxT( "smoothed-outline" ) );
  1429. if( m_progressReporter && m_progressReporter->IsCancelled() )
  1430. return false;
  1431. /* -------------------------------------------------------------------------------------
  1432. * Knockout thermal reliefs.
  1433. */
  1434. knockoutThermalReliefs( aZone, aLayer, aFillPolys, thermalConnectionPads, noConnectionPads );
  1435. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In2_Cu, wxT( "minus-thermal-reliefs" ) );
  1436. if( m_progressReporter && m_progressReporter->IsCancelled() )
  1437. return false;
  1438. /* -------------------------------------------------------------------------------------
  1439. * Knockout electrical clearances.
  1440. */
  1441. buildCopperItemClearances( aZone, aLayer, noConnectionPads, clearanceHoles );
  1442. DUMP_POLYS_TO_COPPER_LAYER( clearanceHoles, In3_Cu, wxT( "clearance-holes" ) );
  1443. if( m_progressReporter && m_progressReporter->IsCancelled() )
  1444. return false;
  1445. /* -------------------------------------------------------------------------------------
  1446. * Add thermal relief spokes.
  1447. */
  1448. buildThermalSpokes( aZone, aLayer, thermalConnectionPads, thermalSpokes );
  1449. if( m_progressReporter && m_progressReporter->IsCancelled() )
  1450. return false;
  1451. // Create a temporary zone that we can hit-test spoke-ends against. It's only temporary
  1452. // because the "real" subtract-clearance-holes has to be done after the spokes are added.
  1453. static const bool USE_BBOX_CACHES = true;
  1454. SHAPE_POLY_SET testAreas = aFillPolys.CloneDropTriangulation();
  1455. testAreas.BooleanSubtract( clearanceHoles );
  1456. DUMP_POLYS_TO_COPPER_LAYER( testAreas, In4_Cu, wxT( "minus-clearance-holes" ) );
  1457. // Prune features that don't meet minimum-width criteria
  1458. if( half_min_width - epsilon > epsilon )
  1459. {
  1460. testAreas.Deflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
  1461. DUMP_POLYS_TO_COPPER_LAYER( testAreas, In5_Cu, wxT( "spoke-test-deflated" ) );
  1462. testAreas.Inflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
  1463. DUMP_POLYS_TO_COPPER_LAYER( testAreas, In6_Cu, wxT( "spoke-test-reinflated" ) );
  1464. }
  1465. if( m_progressReporter && m_progressReporter->IsCancelled() )
  1466. return false;
  1467. // Spoke-end-testing is hugely expensive so we generate cached bounding-boxes to speed
  1468. // things up a bit.
  1469. testAreas.BuildBBoxCaches();
  1470. int interval = 0;
  1471. SHAPE_POLY_SET debugSpokes;
  1472. for( const SHAPE_LINE_CHAIN& spoke : thermalSpokes )
  1473. {
  1474. const VECTOR2I& testPt = spoke.CPoint( 3 );
  1475. // Hit-test against zone body
  1476. if( testAreas.Contains( testPt, -1, 1, USE_BBOX_CACHES ) )
  1477. {
  1478. if( m_debugZoneFiller )
  1479. debugSpokes.AddOutline( spoke );
  1480. aFillPolys.AddOutline( spoke );
  1481. continue;
  1482. }
  1483. if( interval++ > 400 )
  1484. {
  1485. if( m_progressReporter && m_progressReporter->IsCancelled() )
  1486. return false;
  1487. interval = 0;
  1488. }
  1489. // Hit-test against other spokes
  1490. for( const SHAPE_LINE_CHAIN& other : thermalSpokes )
  1491. {
  1492. // Hit test in both directions to avoid interactions with round-off errors.
  1493. // (See https://gitlab.com/kicad/code/kicad/-/issues/13316.)
  1494. if( &other != &spoke
  1495. && other.PointInside( testPt, 1, USE_BBOX_CACHES )
  1496. && spoke.PointInside( other.CPoint( 3 ), 1, USE_BBOX_CACHES ) )
  1497. {
  1498. if( m_debugZoneFiller )
  1499. debugSpokes.AddOutline( spoke );
  1500. aFillPolys.AddOutline( spoke );
  1501. break;
  1502. }
  1503. }
  1504. }
  1505. DUMP_POLYS_TO_COPPER_LAYER( debugSpokes, In7_Cu, wxT( "spokes" ) );
  1506. if( m_progressReporter && m_progressReporter->IsCancelled() )
  1507. return false;
  1508. aFillPolys.BooleanSubtract( clearanceHoles );
  1509. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In8_Cu, wxT( "after-spoke-trimming" ) );
  1510. /* -------------------------------------------------------------------------------------
  1511. * Prune features that don't meet minimum-width criteria
  1512. */
  1513. if( half_min_width - epsilon > epsilon )
  1514. aFillPolys.Deflate( half_min_width - epsilon, fastCornerStrategy, m_maxError );
  1515. // Min-thickness is the web thickness. On the other hand, a blob min-thickness by
  1516. // min-thickness is not useful. Since there's no obvious definition of web vs. blob, we
  1517. // arbitrarily choose "at least 2X min-thickness on one axis". (Since we're doing this
  1518. // during the deflated state, that means we test for "at least min-thickness".)
  1519. for( int ii = aFillPolys.OutlineCount() - 1; ii >= 0; ii-- )
  1520. {
  1521. std::vector<SHAPE_LINE_CHAIN>& island = aFillPolys.Polygon( ii );
  1522. BOX2I islandExtents;
  1523. for( const VECTOR2I& pt : island.front().CPoints() )
  1524. {
  1525. islandExtents.Merge( pt );
  1526. if( islandExtents.GetSizeMax() > aZone->GetMinThickness() )
  1527. break;
  1528. }
  1529. if( islandExtents.GetSizeMax() < aZone->GetMinThickness() )
  1530. aFillPolys.DeletePolygon( ii );
  1531. }
  1532. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In9_Cu, wxT( "deflated" ) );
  1533. if( m_progressReporter && m_progressReporter->IsCancelled() )
  1534. return false;
  1535. /* -------------------------------------------------------------------------------------
  1536. * Process the hatch pattern (note that we do this while deflated)
  1537. */
  1538. if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN
  1539. && ( !m_board->GetProject()
  1540. || !m_board->GetProject()->GetLocalSettings().m_PrototypeZoneFill ) )
  1541. {
  1542. if( !addHatchFillTypeOnZone( aZone, aLayer, aDebugLayer, aFillPolys ) )
  1543. return false;
  1544. }
  1545. else
  1546. {
  1547. /* ---------------------------------------------------------------------------------
  1548. * Connect nearby polygons with zero-width lines in order to ensure correct
  1549. * re-inflation.
  1550. */
  1551. aFillPolys.Fracture();
  1552. connect_nearby_polys( aFillPolys, aZone->GetMinThickness() );
  1553. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In10_Cu, wxT( "connected-nearby-polys" ) );
  1554. }
  1555. if( m_progressReporter && m_progressReporter->IsCancelled() )
  1556. return false;
  1557. /* -------------------------------------------------------------------------------------
  1558. * Finish minimum-width pruning by re-inflating
  1559. */
  1560. if( half_min_width - epsilon > epsilon )
  1561. aFillPolys.Inflate( half_min_width - epsilon, cornerStrategy, m_maxError, true );
  1562. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In15_Cu, wxT( "after-reinflating" ) );
  1563. /* -------------------------------------------------------------------------------------
  1564. * Ensure additive changes (thermal stubs and inflating acute corners) do not add copper
  1565. * outside the zone boundary, inside the clearance holes, or between otherwise isolated
  1566. * islands
  1567. */
  1568. for( BOARD_ITEM* item : thermalConnectionPads )
  1569. {
  1570. if( item->Type() == PCB_PAD_T )
  1571. addHoleKnockout( static_cast<PAD*>( item ), 0, clearanceHoles );
  1572. }
  1573. aFillPolys.BooleanIntersection( aMaxExtents );
  1574. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In16_Cu, wxT( "after-trim-to-outline" ) );
  1575. aFillPolys.BooleanSubtract( clearanceHoles );
  1576. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In17_Cu, wxT( "after-trim-to-clearance-holes" ) );
  1577. /* -------------------------------------------------------------------------------------
  1578. * Lastly give any same-net but higher-priority zones control over their own area.
  1579. */
  1580. subtractHigherPriorityZones( aZone, aLayer, aFillPolys );
  1581. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In18_Cu, wxT( "minus-higher-priority-zones" ) );
  1582. aFillPolys.Fracture();
  1583. return true;
  1584. }
  1585. bool ZONE_FILLER::fillNonCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer,
  1586. const SHAPE_POLY_SET& aSmoothedOutline,
  1587. SHAPE_POLY_SET& aFillPolys )
  1588. {
  1589. BOX2I zone_boundingbox = aZone->GetBoundingBox();
  1590. SHAPE_POLY_SET clearanceHoles;
  1591. long ticker = 0;
  1592. auto checkForCancel =
  1593. [&ticker]( PROGRESS_REPORTER* aReporter ) -> bool
  1594. {
  1595. return aReporter && ( ticker++ % 50 ) == 0 && aReporter->IsCancelled();
  1596. };
  1597. auto knockoutGraphicItem =
  1598. [&]( BOARD_ITEM* aItem )
  1599. {
  1600. if( aItem->IsKnockout() && aItem->IsOnLayer( aLayer )
  1601. && aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
  1602. {
  1603. addKnockout( aItem, aLayer, 0, true, clearanceHoles );
  1604. }
  1605. };
  1606. for( FOOTPRINT* footprint : m_board->Footprints() )
  1607. {
  1608. if( checkForCancel( m_progressReporter ) )
  1609. return false;
  1610. knockoutGraphicItem( &footprint->Reference() );
  1611. knockoutGraphicItem( &footprint->Value() );
  1612. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  1613. knockoutGraphicItem( item );
  1614. }
  1615. for( BOARD_ITEM* item : m_board->Drawings() )
  1616. {
  1617. if( checkForCancel( m_progressReporter ) )
  1618. return false;
  1619. knockoutGraphicItem( item );
  1620. }
  1621. aFillPolys = aSmoothedOutline;
  1622. aFillPolys.BooleanSubtract( clearanceHoles );
  1623. auto subtractKeepout =
  1624. [&]( ZONE* candidate )
  1625. {
  1626. if( !candidate->GetIsRuleArea() )
  1627. return;
  1628. if( !candidate->HasKeepoutParametersSet() )
  1629. return;
  1630. if( candidate->GetDoNotAllowZoneFills() && candidate->IsOnLayer( aLayer ) )
  1631. {
  1632. if( candidate->GetBoundingBox().Intersects( zone_boundingbox ) )
  1633. {
  1634. if( candidate->Outline()->ArcCount() == 0 )
  1635. {
  1636. aFillPolys.BooleanSubtract( *candidate->Outline() );
  1637. }
  1638. else
  1639. {
  1640. SHAPE_POLY_SET keepoutOutline( *candidate->Outline() );
  1641. keepoutOutline.ClearArcs();
  1642. aFillPolys.BooleanSubtract( keepoutOutline );
  1643. }
  1644. }
  1645. }
  1646. };
  1647. for( ZONE* keepout : m_board->Zones() )
  1648. {
  1649. if( checkForCancel( m_progressReporter ) )
  1650. return false;
  1651. subtractKeepout( keepout );
  1652. }
  1653. for( FOOTPRINT* footprint : m_board->Footprints() )
  1654. {
  1655. if( checkForCancel( m_progressReporter ) )
  1656. return false;
  1657. for( ZONE* keepout : footprint->Zones() )
  1658. subtractKeepout( keepout );
  1659. }
  1660. // Features which are min_width should survive pruning; features that are *less* than
  1661. // min_width should not. Therefore we subtract epsilon from the min_width when
  1662. // deflating/inflating.
  1663. int half_min_width = aZone->GetMinThickness() / 2;
  1664. int epsilon = pcbIUScale.mmToIU( 0.001 );
  1665. aFillPolys.Deflate( half_min_width - epsilon, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, m_maxError );
  1666. // Remove the non filled areas due to the hatch pattern
  1667. if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
  1668. {
  1669. if( !addHatchFillTypeOnZone( aZone, aLayer, aLayer, aFillPolys ) )
  1670. return false;
  1671. }
  1672. // Re-inflate after pruning of areas that don't meet minimum-width criteria
  1673. if( half_min_width - epsilon > epsilon )
  1674. aFillPolys.Inflate( half_min_width - epsilon, CORNER_STRATEGY::ROUND_ALL_CORNERS, m_maxError );
  1675. aFillPolys.Fracture();
  1676. return true;
  1677. }
  1678. /*
  1679. * Build the filled solid areas data from real outlines (stored in m_Poly)
  1680. * The solid areas can be more than one on copper layers, and do not have holes
  1681. * ( holes are linked by overlapping segments to the main outline)
  1682. */
  1683. bool ZONE_FILLER::fillSingleZone( ZONE* aZone, PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aFillPolys )
  1684. {
  1685. SHAPE_POLY_SET* boardOutline = m_brdOutlinesValid ? &m_boardOutline : nullptr;
  1686. SHAPE_POLY_SET maxExtents;
  1687. SHAPE_POLY_SET smoothedPoly;
  1688. PCB_LAYER_ID debugLayer = UNDEFINED_LAYER;
  1689. if( m_debugZoneFiller && LSET::InternalCuMask().Contains( aLayer ) )
  1690. {
  1691. debugLayer = aLayer;
  1692. aLayer = F_Cu;
  1693. }
  1694. if( !aZone->BuildSmoothedPoly( maxExtents, aLayer, boardOutline, &smoothedPoly ) )
  1695. return false;
  1696. if( m_progressReporter && m_progressReporter->IsCancelled() )
  1697. return false;
  1698. if( aZone->IsOnCopperLayer() )
  1699. {
  1700. if( fillCopperZone( aZone, aLayer, debugLayer, smoothedPoly, maxExtents, aFillPolys ) )
  1701. aZone->SetNeedRefill( false );
  1702. }
  1703. else
  1704. {
  1705. if( fillNonCopperZone( aZone, aLayer, smoothedPoly, aFillPolys ) )
  1706. aZone->SetNeedRefill( false );
  1707. }
  1708. return true;
  1709. }
  1710. /**
  1711. * Function buildThermalSpokes
  1712. */
  1713. void ZONE_FILLER::buildThermalSpokes( const ZONE* aZone, PCB_LAYER_ID aLayer,
  1714. const std::vector<BOARD_ITEM*>& aSpokedPadsList,
  1715. std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
  1716. {
  1717. BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
  1718. BOX2I zoneBB = aZone->GetBoundingBox();
  1719. DRC_CONSTRAINT constraint;
  1720. int zone_half_width = aZone->GetMinThickness() / 2;
  1721. if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
  1722. zone_half_width = aZone->GetHatchThickness() / 2;
  1723. zoneBB.Inflate( std::max( bds.GetBiggestClearanceValue(), aZone->GetLocalClearance().value() ) );
  1724. // Is a point on the boundary of the polygon inside or outside?
  1725. // The boundary may be off by MaxError
  1726. int epsilon = bds.m_MaxError;
  1727. for( BOARD_ITEM* item : aSpokedPadsList )
  1728. {
  1729. // We currently only connect to pads, not pad holes
  1730. if( !item->IsOnLayer( aLayer ) )
  1731. continue;
  1732. int thermalReliefGap = 0;
  1733. int spoke_w = 0;
  1734. PAD* pad = nullptr;
  1735. PCB_VIA* via = nullptr;
  1736. bool circular = false;
  1737. if( item->Type() == PCB_PAD_T )
  1738. {
  1739. pad = static_cast<PAD*>( item );
  1740. VECTOR2I padSize = pad->GetSize( aLayer );
  1741. if( pad->GetShape( aLayer) == PAD_SHAPE::CIRCLE
  1742. || ( pad->GetShape( aLayer ) == PAD_SHAPE::OVAL && padSize.x == padSize.y ) )
  1743. {
  1744. circular = true;
  1745. }
  1746. }
  1747. else if( item->Type() == PCB_VIA_T )
  1748. {
  1749. via = static_cast<PCB_VIA*>( item );
  1750. circular = true;
  1751. }
  1752. // Thermal connections in a hatched zone are based on the hatch. Their primary function is to
  1753. // guarantee that pads/vias connect to the webbing. (The thermal gap is the hatch gap width minus
  1754. // the pad/via size, making it impossible for the pad/via to be isolated within the center of a
  1755. // hole.)
  1756. if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
  1757. {
  1758. spoke_w = aZone->GetHatchThickness();
  1759. thermalReliefGap = getHatchFillThermalClearance( aZone, item, aLayer );
  1760. if( thermalReliefGap < 0 )
  1761. continue;
  1762. }
  1763. else if( pad )
  1764. {
  1765. constraint = bds.m_DRCEngine->EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, aZone, aLayer );
  1766. thermalReliefGap = constraint.GetValue().Min();
  1767. constraint = bds.m_DRCEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, aZone, aLayer );
  1768. spoke_w = constraint.GetValue().Opt();
  1769. // Spoke width should ideally be smaller than the pad minor axis.
  1770. // Otherwise the thermal shape is not really a thermal relief,
  1771. // and the algo to count the actual number of spokes can fail
  1772. int spoke_max_allowed_w = std::min( pad->GetSize( aLayer ).x, pad->GetSize( aLayer ).y );
  1773. spoke_w = std::clamp( spoke_w, constraint.Value().Min(), constraint.Value().Max() );
  1774. // ensure the spoke width is smaller than the pad minor size
  1775. spoke_w = std::min( spoke_w, spoke_max_allowed_w );
  1776. // Cannot create stubs having a width < zone min thickness
  1777. if( spoke_w < aZone->GetMinThickness() )
  1778. continue;
  1779. }
  1780. else
  1781. {
  1782. // We don't currently support via thermal connections *except* in a hatched zone.
  1783. continue;
  1784. }
  1785. int spoke_half_w = spoke_w / 2;
  1786. // Quick test here to possibly save us some work
  1787. BOX2I itemBB = item->GetBoundingBox();
  1788. itemBB.Inflate( thermalReliefGap + epsilon );
  1789. if( !( itemBB.Intersects( zoneBB ) ) )
  1790. continue;
  1791. bool customSpokes = false;
  1792. if( pad && pad->GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
  1793. {
  1794. for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) )
  1795. {
  1796. if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
  1797. {
  1798. customSpokes = true;
  1799. break;
  1800. }
  1801. }
  1802. }
  1803. // Thermal spokes consist of square-ended segments from the pad center to points just
  1804. // outside the thermal relief. The outside end has an extra center point (which must be
  1805. // at idx 3) which is used for testing whether or not the spoke connects to copper in the
  1806. // parent zone.
  1807. auto buildSpokesFromOrigin =
  1808. [&]( const BOX2I& box, EDA_ANGLE angle )
  1809. {
  1810. VECTOR2I center = box.GetCenter();
  1811. VECTOR2I half_size( box.GetWidth() / 2, box.GetHeight() / 2 );
  1812. // Function to find intersection of line with box edge
  1813. auto intersectLineBox =
  1814. [&](const VECTOR2D& direction) -> VECTOR2I
  1815. {
  1816. double dx = direction.x;
  1817. double dy = direction.y;
  1818. // Short-circuit the axis cases because they will be degenerate in the
  1819. // intersection test
  1820. if( direction.x == 0 )
  1821. return VECTOR2I( 0, dy * half_size.y );
  1822. else if( direction.y == 0 )
  1823. return VECTOR2I( dx * half_size.x, 0 );
  1824. // We are going to intersect with one side or the other. Whichever
  1825. // we hit first is the fraction of the spoke length we keep
  1826. double tx = std::min( half_size.x / std::abs( dx ),
  1827. half_size.y / std::abs( dy ) );
  1828. return VECTOR2I( dx * tx, dy * tx );
  1829. };
  1830. // Precalculate angles for four cardinal directions
  1831. const EDA_ANGLE angles[4] = {
  1832. EDA_ANGLE( 0.0, DEGREES_T ) + angle, // Right
  1833. EDA_ANGLE( 90.0, DEGREES_T ) + angle, // Up
  1834. EDA_ANGLE( 180.0, DEGREES_T ) + angle, // Left
  1835. EDA_ANGLE( 270.0, DEGREES_T ) + angle // Down
  1836. };
  1837. // Generate four spokes in cardinal directions
  1838. for( const EDA_ANGLE& spokeAngle : angles )
  1839. {
  1840. VECTOR2D direction( spokeAngle.Cos(), spokeAngle.Sin() );
  1841. VECTOR2D perpendicular = direction.Perpendicular();
  1842. VECTOR2I intersection = intersectLineBox( direction );
  1843. VECTOR2I spoke_side = perpendicular.Resize( spoke_half_w );
  1844. SHAPE_LINE_CHAIN spoke;
  1845. spoke.Append( center + spoke_side );
  1846. spoke.Append( center - spoke_side );
  1847. spoke.Append( center + intersection - spoke_side );
  1848. spoke.Append( center + intersection ); // test pt
  1849. spoke.Append( center + intersection + spoke_side );
  1850. spoke.SetClosed( true );
  1851. aSpokesList.push_back( std::move( spoke ) );
  1852. }
  1853. };
  1854. if( customSpokes )
  1855. {
  1856. SHAPE_POLY_SET thermalPoly;
  1857. SHAPE_LINE_CHAIN thermalOutline;
  1858. pad->TransformShapeToPolygon( thermalPoly, aLayer, thermalReliefGap + epsilon,
  1859. m_maxError, ERROR_OUTSIDE );
  1860. if( thermalPoly.OutlineCount() )
  1861. thermalOutline = thermalPoly.Outline( 0 );
  1862. SHAPE_LINE_CHAIN padOutline = pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Outline( 0 );
  1863. auto trimToOutline = [&]( SEG& aSegment )
  1864. {
  1865. SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
  1866. if( padOutline.Intersect( aSegment, intersections ) )
  1867. {
  1868. intersections.clear();
  1869. // Trim the segment to the thermal outline
  1870. if( thermalOutline.Intersect( aSegment, intersections ) )
  1871. {
  1872. aSegment.B = intersections.front().p;
  1873. return true;
  1874. }
  1875. }
  1876. return false;
  1877. };
  1878. for( const std::shared_ptr<PCB_SHAPE>& primitive : pad->GetPrimitives( aLayer ) )
  1879. {
  1880. if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::SEGMENT )
  1881. {
  1882. SEG seg( primitive->GetStart(), primitive->GetEnd() );
  1883. SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
  1884. RotatePoint( seg.A, pad->GetOrientation() );
  1885. RotatePoint( seg.B, pad->GetOrientation() );
  1886. seg.A += pad->ShapePos( aLayer );
  1887. seg.B += pad->ShapePos( aLayer );
  1888. // Make sure seg.A is the origin
  1889. if( !pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Contains( seg.A ) )
  1890. {
  1891. // Do not create this spoke if neither point is in the pad.
  1892. if( !pad->GetEffectivePolygon( aLayer, ERROR_OUTSIDE )->Contains( seg.B ) )
  1893. continue;
  1894. seg.Reverse();
  1895. }
  1896. // Trim segment to pad and thermal outline polygon.
  1897. // If there is no intersection with the pad, don't create the spoke.
  1898. if( trimToOutline( seg ) )
  1899. {
  1900. VECTOR2I direction = ( seg.B - seg.A ).Resize( spoke_half_w );
  1901. VECTOR2I offset = direction.Perpendicular().Resize( spoke_half_w );
  1902. // Extend the spoke edges by half the spoke width to capture convex pad shapes
  1903. // with a maximum of 45 degrees.
  1904. SEG segL( seg.A - direction - offset, seg.B + direction - offset );
  1905. SEG segR( seg.A - direction + offset, seg.B + direction + offset );
  1906. // Only create this spoke if both edges intersect the pad and thermal outline
  1907. if( trimToOutline( segL ) && trimToOutline( segR ) )
  1908. {
  1909. // Extend the spoke by the minimum thickness for the zone to ensure full
  1910. // connection width
  1911. direction = direction.Resize( aZone->GetMinThickness() );
  1912. SHAPE_LINE_CHAIN spoke;
  1913. spoke.Append( seg.A + offset );
  1914. spoke.Append( seg.A - offset );
  1915. spoke.Append( segL.B + direction );
  1916. spoke.Append( seg.B + direction ); // test pt at index 3.
  1917. spoke.Append( segR.B + direction );
  1918. spoke.SetClosed( true );
  1919. aSpokesList.push_back( std::move( spoke ) );
  1920. }
  1921. }
  1922. }
  1923. }
  1924. }
  1925. else
  1926. {
  1927. EDA_ANGLE thermalSpokeAngle;
  1928. if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
  1929. thermalSpokeAngle = aZone->GetHatchOrientation();
  1930. else if( pad )
  1931. thermalSpokeAngle = pad->GetThermalSpokeAngle();
  1932. BOX2I spokesBox;
  1933. VECTOR2I position;
  1934. EDA_ANGLE orientation;
  1935. // Since the bounding-box needs to be correclty rotated we use a dummy pad to keep
  1936. // from dirtying the real pad's cached shapes.
  1937. if( pad )
  1938. {
  1939. PAD dummy_pad( *pad );
  1940. dummy_pad.SetOrientation( ANGLE_0 );
  1941. // Spokes are from center of pad shape, not from hole. So the dummy pad has no shape
  1942. // offset and is at position 0,0
  1943. dummy_pad.SetPosition( VECTOR2I( 0, 0 ) );
  1944. dummy_pad.SetOffset( aLayer, VECTOR2I( 0, 0 ) );
  1945. spokesBox = dummy_pad.GetBoundingBox( aLayer );
  1946. position = pad->ShapePos( aLayer );
  1947. orientation = pad->GetOrientation();
  1948. // Remove group membership from dummy item before deleting
  1949. dummy_pad.SetParentGroup( nullptr );
  1950. }
  1951. else if( via )
  1952. {
  1953. PCB_VIA dummy_via( *via );
  1954. dummy_via.SetPosition( VECTOR2I( 0, 0 ) );
  1955. spokesBox = dummy_via.GetBoundingBox( aLayer );
  1956. position = via->GetPosition();
  1957. }
  1958. // Add the half width of the zone mininum width to the inflate amount to account for
  1959. // the fact that the deflation procedure will shrink the results by half the half the
  1960. // zone min width
  1961. spokesBox.Inflate( thermalReliefGap + epsilon + zone_half_width );
  1962. // This is a touchy case because the bounding box for circles overshoots the mark
  1963. // when rotated at 45 degrees. So we just build spokes at 0 degrees and rotate
  1964. // them later.
  1965. if( circular )
  1966. {
  1967. buildSpokesFromOrigin( spokesBox, ANGLE_0 );
  1968. if( thermalSpokeAngle != ANGLE_0 )
  1969. {
  1970. //Rotate the last four elements of aspokeslist
  1971. for( auto it = aSpokesList.rbegin(); it != aSpokesList.rbegin() + 4; ++it )
  1972. it->Rotate( thermalSpokeAngle );
  1973. }
  1974. }
  1975. else
  1976. {
  1977. buildSpokesFromOrigin( spokesBox, thermalSpokeAngle );
  1978. }
  1979. auto spokeIter = aSpokesList.rbegin();
  1980. for( int ii = 0; ii < 4; ++ii, ++spokeIter )
  1981. {
  1982. spokeIter->Rotate( orientation );
  1983. spokeIter->Move( position );
  1984. }
  1985. }
  1986. }
  1987. for( size_t ii = 0; ii < aSpokesList.size(); ++ii )
  1988. aSpokesList[ii].GenerateBBoxCache();
  1989. }
  1990. bool ZONE_FILLER::addHatchFillTypeOnZone( const ZONE* aZone, PCB_LAYER_ID aLayer,
  1991. PCB_LAYER_ID aDebugLayer, SHAPE_POLY_SET& aFillPolys )
  1992. {
  1993. // Build grid:
  1994. // obviously line thickness must be > zone min thickness.
  1995. // It can happens if a board file was edited by hand by a python script
  1996. // Use 1 micron margin to be *sure* there is no issue in Gerber files
  1997. // (Gbr file unit = 1 or 10 nm) due to some truncation in coordinates or calculations
  1998. // This margin also avoid problems due to rounding coordinates in next calculations
  1999. // that can create incorrect polygons
  2000. int thickness = std::max( aZone->GetHatchThickness(),
  2001. aZone->GetMinThickness() + pcbIUScale.mmToIU( 0.001 ) );
  2002. int gridsize = thickness + aZone->GetHatchGap();
  2003. int maxError = m_board->GetDesignSettings().m_MaxError;
  2004. SHAPE_POLY_SET filledPolys = aFillPolys.CloneDropTriangulation();
  2005. // Use a area that contains the rotated bbox by orientation, and after rotate the result
  2006. // by -orientation.
  2007. if( !aZone->GetHatchOrientation().IsZero() )
  2008. filledPolys.Rotate( - aZone->GetHatchOrientation() );
  2009. BOX2I bbox = filledPolys.BBox( 0 );
  2010. // Build hole shape
  2011. // the hole size is aZone->GetHatchGap(), but because the outline thickness
  2012. // is aZone->GetMinThickness(), the hole shape size must be larger
  2013. SHAPE_LINE_CHAIN hole_base;
  2014. int hole_size = aZone->GetHatchGap() + aZone->GetMinThickness();
  2015. VECTOR2I corner( 0, 0 );;
  2016. hole_base.Append( corner );
  2017. corner.x += hole_size;
  2018. hole_base.Append( corner );
  2019. corner.y += hole_size;
  2020. hole_base.Append( corner );
  2021. corner.x = 0;
  2022. hole_base.Append( corner );
  2023. hole_base.SetClosed( true );
  2024. // Calculate minimal area of a grid hole.
  2025. // All holes smaller than a threshold will be removed
  2026. double minimal_hole_area = hole_base.Area() * aZone->GetHatchHoleMinArea();
  2027. // Now convert this hole to a smoothed shape:
  2028. if( aZone->GetHatchSmoothingLevel() > 0 )
  2029. {
  2030. // the actual size of chamfer, or rounded corner radius is the half size
  2031. // of the HatchFillTypeGap scaled by aZone->GetHatchSmoothingValue()
  2032. // aZone->GetHatchSmoothingValue() = 1.0 is the max value for the chamfer or the
  2033. // radius of corner (radius = half size of the hole)
  2034. int smooth_value = KiROUND( aZone->GetHatchGap()
  2035. * aZone->GetHatchSmoothingValue() / 2 );
  2036. // Minimal optimization:
  2037. // make smoothing only for reasonable smooth values, to avoid a lot of useless segments
  2038. // and if the smooth value is small, use chamfer even if fillet is requested
  2039. #define SMOOTH_MIN_VAL_MM 0.02
  2040. #define SMOOTH_SMALL_VAL_MM 0.04
  2041. if( smooth_value > pcbIUScale.mmToIU( SMOOTH_MIN_VAL_MM ) )
  2042. {
  2043. SHAPE_POLY_SET smooth_hole;
  2044. smooth_hole.AddOutline( hole_base );
  2045. int smooth_level = aZone->GetHatchSmoothingLevel();
  2046. if( smooth_value < pcbIUScale.mmToIU( SMOOTH_SMALL_VAL_MM ) && smooth_level > 1 )
  2047. smooth_level = 1;
  2048. // Use a larger smooth_value to compensate the outline tickness
  2049. // (chamfer is not visible is smooth value < outline thickess)
  2050. smooth_value += aZone->GetMinThickness() / 2;
  2051. // smooth_value cannot be bigger than the half size oh the hole:
  2052. smooth_value = std::min( smooth_value, aZone->GetHatchGap() / 2 );
  2053. // the error to approximate a circle by segments when smoothing corners by a arc
  2054. maxError = std::max( maxError * 2, smooth_value / 20 );
  2055. switch( smooth_level )
  2056. {
  2057. case 1:
  2058. // Chamfer() uses the distance from a corner to create a end point
  2059. // for the chamfer.
  2060. hole_base = smooth_hole.Chamfer( smooth_value ).Outline( 0 );
  2061. break;
  2062. default:
  2063. if( aZone->GetHatchSmoothingLevel() > 2 )
  2064. maxError /= 2; // Force better smoothing
  2065. hole_base = smooth_hole.Fillet( smooth_value, maxError ).Outline( 0 );
  2066. break;
  2067. case 0:
  2068. break;
  2069. };
  2070. }
  2071. }
  2072. // Build holes
  2073. SHAPE_POLY_SET holes;
  2074. VECTOR2I offset_opt = VECTOR2I();
  2075. bool zone_has_offset = false;
  2076. if( aZone->LayerProperties().contains( aLayer ) )
  2077. {
  2078. zone_has_offset = aZone->HatchingOffset( aLayer ).has_value();
  2079. offset_opt = aZone->HatchingOffset( aLayer ).value_or( VECTOR2I( 0, 0 ) );
  2080. }
  2081. if( !zone_has_offset )
  2082. {
  2083. if( m_board->GetDesignSettings().GetDefaultZoneSettings().m_layerProperties.contains(
  2084. aLayer ) )
  2085. {
  2086. const ZONE_LAYER_PROPERTIES& properties =
  2087. m_board->GetDesignSettings().GetDefaultZoneSettings().m_layerProperties.at(
  2088. aLayer );
  2089. offset_opt = properties.hatching_offset.value_or( VECTOR2I( 0, 0 ) );
  2090. }
  2091. }
  2092. int x_offset = bbox.GetX() - ( bbox.GetX() ) % gridsize - gridsize;
  2093. int y_offset = bbox.GetY() - ( bbox.GetY() ) % gridsize - gridsize;
  2094. for( int xx = x_offset; xx <= bbox.GetRight(); xx += gridsize )
  2095. {
  2096. for( int yy = y_offset; yy <= bbox.GetBottom(); yy += gridsize )
  2097. {
  2098. // Generate hole
  2099. SHAPE_LINE_CHAIN hole( hole_base );
  2100. hole.Move( VECTOR2I( xx, yy ) );
  2101. if( !aZone->GetHatchOrientation().IsZero() )
  2102. {
  2103. hole.Rotate( aZone->GetHatchOrientation() );
  2104. }
  2105. hole.Move( VECTOR2I( offset_opt.x % gridsize, offset_opt.y % gridsize ) );
  2106. holes.AddOutline( hole );
  2107. }
  2108. }
  2109. DUMP_POLYS_TO_COPPER_LAYER( holes, In10_Cu, wxT( "hatch-holes" ) );
  2110. int deflated_thickness = aZone->GetHatchThickness() - aZone->GetMinThickness();
  2111. // Don't let thickness drop below maxError * 2 or it might not get reinflated.
  2112. deflated_thickness = std::max( deflated_thickness, maxError * 2 );
  2113. // The fill has already been deflated to ensure GetMinThickness() so we just have to
  2114. // account for anything beyond that.
  2115. SHAPE_POLY_SET deflatedFilledPolys = aFillPolys.CloneDropTriangulation();
  2116. deflatedFilledPolys.Deflate( deflated_thickness, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
  2117. holes.BooleanIntersection( deflatedFilledPolys );
  2118. DUMP_POLYS_TO_COPPER_LAYER( holes, In11_Cu, wxT( "fill-clipped-hatch-holes" ) );
  2119. SHAPE_POLY_SET deflatedOutline = aZone->Outline()->CloneDropTriangulation();
  2120. deflatedOutline.Deflate( aZone->GetMinThickness(), CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
  2121. holes.BooleanIntersection( deflatedOutline );
  2122. DUMP_POLYS_TO_COPPER_LAYER( holes, In12_Cu, wxT( "outline-clipped-hatch-holes" ) );
  2123. // Now filter truncated holes to avoid small holes in pattern
  2124. // It happens for holes near the zone outline
  2125. for( int ii = 0; ii < holes.OutlineCount(); )
  2126. {
  2127. double area = holes.Outline( ii ).Area();
  2128. if( area < minimal_hole_area ) // The current hole is too small: remove it
  2129. holes.DeletePolygon( ii );
  2130. else
  2131. ++ii;
  2132. }
  2133. // create grid. Useto
  2134. // generate strictly simple polygons needed by Gerber files and Fracture()
  2135. aFillPolys.BooleanSubtract( aFillPolys, holes );
  2136. DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In14_Cu, wxT( "after-hatching" ) );
  2137. return true;
  2138. }