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.

1146 lines
41 KiB

  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-2019 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 <cstdint>
  26. #include <thread>
  27. #include <mutex>
  28. #include <algorithm>
  29. #include <future>
  30. #include <class_board.h>
  31. #include <class_zone.h>
  32. #include <class_module.h>
  33. #include <class_edge_mod.h>
  34. #include <class_drawsegment.h>
  35. #include <class_track.h>
  36. #include <class_pcb_text.h>
  37. #include <class_pcb_target.h>
  38. #include <connectivity/connectivity_data.h>
  39. #include <board_commit.h>
  40. #include <widgets/progress_reporter.h>
  41. #include <geometry/shape_poly_set.h>
  42. #include <geometry/shape_file_io.h>
  43. #include <geometry/convex_hull.h>
  44. #include <geometry/geometry_utils.h>
  45. #include <confirm.h>
  46. #include <convert_to_biu.h>
  47. #include "zone_filler.h"
  48. #include <advanced_config.h> // To be removed later, when the zone fill option will be always allowed
  49. class PROGRESS_REPORTER_HIDER
  50. {
  51. public:
  52. PROGRESS_REPORTER_HIDER( WX_PROGRESS_REPORTER* aReporter )
  53. {
  54. m_reporter = aReporter;
  55. if( aReporter )
  56. aReporter->Hide();
  57. }
  58. ~PROGRESS_REPORTER_HIDER()
  59. {
  60. if( m_reporter )
  61. m_reporter->Show();
  62. }
  63. private:
  64. WX_PROGRESS_REPORTER* m_reporter;
  65. };
  66. static const double s_RoundPadThermalSpokeAngle = 450;
  67. static const bool s_DumpZonesWhenFilling = false;
  68. ZONE_FILLER::ZONE_FILLER( BOARD* aBoard, COMMIT* aCommit ) :
  69. m_board( aBoard ), m_brdOutlinesValid( false ), m_commit( aCommit ),
  70. m_progressReporter( nullptr )
  71. {
  72. }
  73. ZONE_FILLER::~ZONE_FILLER()
  74. {
  75. }
  76. void ZONE_FILLER::InstallNewProgressReporter( wxWindow* aParent, const wxString& aTitle,
  77. int aNumPhases )
  78. {
  79. m_uniqueReporter = std::make_unique<WX_PROGRESS_REPORTER>( aParent, aTitle, aNumPhases );
  80. m_progressReporter = m_uniqueReporter.get();
  81. }
  82. bool ZONE_FILLER::Fill( const std::vector<ZONE_CONTAINER*>& aZones, bool aCheck )
  83. {
  84. std::vector<CN_ZONE_ISOLATED_ISLAND_LIST> toFill;
  85. auto connectivity = m_board->GetConnectivity();
  86. bool filledPolyWithOutline = not m_board->GetDesignSettings().m_ZoneUseNoOutlineInFill;
  87. std::unique_lock<std::mutex> lock( connectivity->GetLock(), std::try_to_lock );
  88. if( !lock )
  89. return false;
  90. if( m_progressReporter )
  91. {
  92. m_progressReporter->Report( aCheck ? _( "Checking zone fills..." ) : _( "Building zone fills..." ) );
  93. m_progressReporter->SetMaxProgress( toFill.size() );
  94. }
  95. // The board outlines is used to clip solid areas inside the board (when outlines are valid)
  96. m_boardOutline.RemoveAllContours();
  97. m_brdOutlinesValid = m_board->GetBoardPolygonOutlines( m_boardOutline );
  98. for( auto zone : aZones )
  99. {
  100. // Keepout zones are not filled
  101. if( zone->GetIsKeepout() )
  102. continue;
  103. if( m_commit )
  104. m_commit->Modify( zone );
  105. // calculate the hash value for filled areas. it will be used later
  106. // to know if the current filled areas are up to date
  107. zone->BuildHashValue();
  108. // Add the zone to the list of zones to test or refill
  109. toFill.emplace_back( CN_ZONE_ISOLATED_ISLAND_LIST(zone) );
  110. // Remove existing fill first to prevent drawing invalid polygons
  111. // on some platforms
  112. zone->UnFill();
  113. }
  114. std::atomic<size_t> nextItem( 0 );
  115. size_t parallelThreadCount =
  116. std::min<size_t>( std::thread::hardware_concurrency(), aZones.size() );
  117. std::vector<std::future<size_t>> returns( parallelThreadCount );
  118. auto fill_lambda = [&] ( PROGRESS_REPORTER* aReporter ) -> size_t
  119. {
  120. size_t num = 0;
  121. for( size_t i = nextItem++; i < toFill.size(); i = nextItem++ )
  122. {
  123. ZONE_CONTAINER* zone = toFill[i].m_zone;
  124. zone->SetFilledPolysUseThickness( filledPolyWithOutline );
  125. SHAPE_POLY_SET rawPolys, finalPolys;
  126. fillSingleZone( zone, rawPolys, finalPolys );
  127. zone->SetRawPolysList( rawPolys );
  128. zone->SetFilledPolysList( finalPolys );
  129. zone->SetIsFilled( true );
  130. if( m_progressReporter )
  131. m_progressReporter->AdvanceProgress();
  132. num++;
  133. }
  134. return num;
  135. };
  136. if( parallelThreadCount <= 1 )
  137. fill_lambda( m_progressReporter );
  138. else
  139. {
  140. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  141. returns[ii] = std::async( std::launch::async, fill_lambda, m_progressReporter );
  142. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  143. {
  144. // Here we balance returns with a 100ms timeout to allow UI updating
  145. std::future_status status;
  146. do
  147. {
  148. if( m_progressReporter )
  149. m_progressReporter->KeepRefreshing();
  150. status = returns[ii].wait_for( std::chrono::milliseconds( 100 ) );
  151. } while( status != std::future_status::ready );
  152. }
  153. }
  154. // Now update the connectivity to check for copper islands
  155. if( m_progressReporter )
  156. {
  157. m_progressReporter->AdvancePhase();
  158. m_progressReporter->Report( _( "Removing insulated copper islands..." ) );
  159. m_progressReporter->KeepRefreshing();
  160. }
  161. connectivity->SetProgressReporter( m_progressReporter );
  162. connectivity->FindIsolatedCopperIslands( toFill );
  163. // Now remove insulated copper islands and islands outside the board edge
  164. bool outOfDate = false;
  165. for( auto& zone : toFill )
  166. {
  167. std::sort( zone.m_islands.begin(), zone.m_islands.end(), std::greater<int>() );
  168. SHAPE_POLY_SET poly = zone.m_zone->GetFilledPolysList();
  169. // Remove solid areas outside the board cutouts and the insulated islands
  170. // only zones with net code > 0 can have insulated islands by definition
  171. if( zone.m_zone->GetNetCode() > 0 )
  172. {
  173. // solid areas outside the board cutouts are also removed, because they are usually
  174. // insulated islands
  175. for( auto idx : zone.m_islands )
  176. {
  177. poly.DeletePolygon( idx );
  178. }
  179. }
  180. // Zones with no net can have areas outside the board cutouts.
  181. // By definition, Zones with no net have no isolated island
  182. // (in fact all filled areas are isolated islands)
  183. // but they can have some areas outside the board cutouts.
  184. // A filled area outside the board cutouts has all points outside cutouts,
  185. // so we only need to check one point for each filled polygon.
  186. // Note also non copper zones are already clipped
  187. else if( m_brdOutlinesValid && zone.m_zone->IsOnCopperLayer() )
  188. {
  189. for( int idx = 0; idx < poly.OutlineCount(); )
  190. {
  191. if( poly.Polygon( idx ).empty() ||
  192. !m_boardOutline.Contains( poly.Polygon( idx ).front().CPoint( 0 ) ) )
  193. {
  194. poly.DeletePolygon( idx );
  195. }
  196. else
  197. idx++;
  198. }
  199. }
  200. zone.m_zone->SetFilledPolysList( poly );
  201. if( aCheck && zone.m_zone->GetHashValue() != poly.GetHash() )
  202. outOfDate = true;
  203. }
  204. if( aCheck && outOfDate )
  205. {
  206. PROGRESS_REPORTER_HIDER raii( m_progressReporter );
  207. KIDIALOG dlg( m_progressReporter->GetParent(),
  208. _( "Zone fills are out-of-date. Refill?" ),
  209. _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
  210. dlg.SetOKCancelLabels( _( "Refill" ), _( "Continue without Refill" ) );
  211. dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
  212. if( dlg.ShowModal() == wxID_CANCEL )
  213. {
  214. if( m_commit )
  215. m_commit->Revert();
  216. connectivity->SetProgressReporter( nullptr );
  217. return false;
  218. }
  219. }
  220. if( m_progressReporter )
  221. {
  222. m_progressReporter->AdvancePhase();
  223. m_progressReporter->Report( _( "Performing polygon fills..." ) );
  224. m_progressReporter->SetMaxProgress( toFill.size() );
  225. }
  226. nextItem = 0;
  227. auto tri_lambda = [&] ( PROGRESS_REPORTER* aReporter ) -> size_t
  228. {
  229. size_t num = 0;
  230. for( size_t i = nextItem++; i < toFill.size(); i = nextItem++ )
  231. {
  232. toFill[i].m_zone->CacheTriangulation();
  233. num++;
  234. if( m_progressReporter )
  235. m_progressReporter->AdvanceProgress();
  236. }
  237. return num;
  238. };
  239. if( parallelThreadCount <= 1 )
  240. tri_lambda( m_progressReporter );
  241. else
  242. {
  243. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  244. returns[ii] = std::async( std::launch::async, tri_lambda, m_progressReporter );
  245. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  246. {
  247. // Here we balance returns with a 100ms timeout to allow UI updating
  248. std::future_status status;
  249. do
  250. {
  251. if( m_progressReporter )
  252. m_progressReporter->KeepRefreshing();
  253. status = returns[ii].wait_for( std::chrono::milliseconds( 100 ) );
  254. } while( status != std::future_status::ready );
  255. }
  256. }
  257. if( m_progressReporter )
  258. {
  259. m_progressReporter->AdvancePhase();
  260. m_progressReporter->Report( _( "Committing changes..." ) );
  261. m_progressReporter->KeepRefreshing();
  262. }
  263. connectivity->SetProgressReporter( nullptr );
  264. if( m_commit )
  265. {
  266. m_commit->Push( _( "Fill Zone(s)" ), false );
  267. }
  268. else
  269. {
  270. for( auto& i : toFill )
  271. connectivity->Update( i.m_zone );
  272. connectivity->RecalculateRatsnest();
  273. }
  274. return true;
  275. }
  276. /**
  277. * Return true if the given pad has a thermal connection with the given zone.
  278. */
  279. bool hasThermalConnection( D_PAD* pad, const ZONE_CONTAINER* aZone )
  280. {
  281. // Rejects non-standard pads with tht-only thermal reliefs
  282. if( aZone->GetPadConnection( pad ) == PAD_ZONE_CONN_THT_THERMAL
  283. && pad->GetAttribute() != PAD_ATTRIB_STANDARD )
  284. {
  285. return false;
  286. }
  287. if( aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_THERMAL
  288. && aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_THT_THERMAL )
  289. {
  290. return false;
  291. }
  292. if( pad->GetNetCode() != aZone->GetNetCode() || pad->GetNetCode() <= 0 )
  293. return false;
  294. EDA_RECT item_boundingbox = pad->GetBoundingBox();
  295. int thermalGap = aZone->GetThermalReliefGap( pad );
  296. item_boundingbox.Inflate( thermalGap, thermalGap );
  297. return item_boundingbox.Intersects( aZone->GetBoundingBox() );
  298. }
  299. /**
  300. * Setup aDummyPad to have the same size and shape of aPad's hole. This allows us to create
  301. * thermal reliefs and clearances for holes using the pad code.
  302. */
  303. static void setupDummyPadForHole( const D_PAD* aPad, D_PAD& aDummyPad )
  304. {
  305. aDummyPad.SetNetCode( aPad->GetNetCode() );
  306. aDummyPad.SetSize( aPad->GetDrillSize() );
  307. aDummyPad.SetOrientation( aPad->GetOrientation() );
  308. aDummyPad.SetShape( aPad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? PAD_SHAPE_OVAL
  309. : PAD_SHAPE_CIRCLE );
  310. aDummyPad.SetPosition( aPad->GetPosition() );
  311. }
  312. /**
  313. * Add a knockout for a pad. The knockout is 'aGap' larger than the pad (which might be
  314. * either the thermal clearance or the electrical clearance).
  315. */
  316. void ZONE_FILLER::addKnockout( D_PAD* aPad, int aGap, SHAPE_POLY_SET& aHoles )
  317. {
  318. if( aPad->GetShape() == PAD_SHAPE_CUSTOM )
  319. {
  320. // the pad shape in zone can be its convex hull or the shape itself
  321. SHAPE_POLY_SET outline( aPad->GetCustomShapeAsPolygon() );
  322. int numSegs = std::max( GetArcToSegmentCount( aGap, m_high_def, 360.0 ), 6 );
  323. double correction = GetCircletoPolyCorrectionFactor( numSegs );
  324. outline.Inflate( KiROUND( aGap * correction ), numSegs );
  325. aPad->CustomShapeAsPolygonToBoardPosition( &outline, aPad->GetPosition(),
  326. aPad->GetOrientation() );
  327. if( aPad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL )
  328. {
  329. std::vector<wxPoint> convex_hull;
  330. BuildConvexHull( convex_hull, outline );
  331. aHoles.NewOutline();
  332. for( const wxPoint& pt : convex_hull )
  333. aHoles.Append( pt );
  334. }
  335. else
  336. aHoles.Append( outline );
  337. }
  338. else
  339. {
  340. // Optimizing polygon vertex count: the high definition is used for round
  341. // and oval pads (pads with large arcs) but low def for other shapes (with
  342. // small arcs)
  343. if( aPad->GetShape() == PAD_SHAPE_CIRCLE || aPad->GetShape() == PAD_SHAPE_OVAL ||
  344. ( aPad->GetShape() == PAD_SHAPE_ROUNDRECT && aPad->GetRoundRectRadiusRatio() > 0.4 ) )
  345. aPad->TransformShapeWithClearanceToPolygon( aHoles, aGap, m_high_def );
  346. else
  347. aPad->TransformShapeWithClearanceToPolygon( aHoles, aGap, m_low_def );
  348. }
  349. }
  350. /**
  351. * Add a knockout for a graphic item. The knockout is 'aGap' larger than the item (which
  352. * might be either the electrical clearance or the board edge clearance).
  353. */
  354. void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, int aGap, bool aIgnoreLineWidth,
  355. SHAPE_POLY_SET& aHoles )
  356. {
  357. switch( aItem->Type() )
  358. {
  359. case PCB_LINE_T:
  360. {
  361. DRAWSEGMENT* seg = (DRAWSEGMENT*) aItem;
  362. seg->TransformShapeWithClearanceToPolygon( aHoles, aGap, m_high_def, aIgnoreLineWidth );
  363. break;
  364. }
  365. case PCB_TEXT_T:
  366. {
  367. TEXTE_PCB* text = (TEXTE_PCB*) aItem;
  368. text->TransformBoundingBoxWithClearanceToPolygon( &aHoles, aGap );
  369. break;
  370. }
  371. case PCB_MODULE_EDGE_T:
  372. {
  373. EDGE_MODULE* edge = (EDGE_MODULE*) aItem;
  374. edge->TransformShapeWithClearanceToPolygon( aHoles, aGap, m_high_def, aIgnoreLineWidth );
  375. break;
  376. }
  377. case PCB_MODULE_TEXT_T:
  378. {
  379. TEXTE_MODULE* text = (TEXTE_MODULE*) aItem;
  380. if( text->IsVisible() )
  381. text->TransformBoundingBoxWithClearanceToPolygon( &aHoles, aGap );
  382. break;
  383. }
  384. default:
  385. break;
  386. }
  387. }
  388. /**
  389. * Removes thermal reliefs from the shape for any pads connected to the zone. Does NOT add
  390. * in spokes, which must be done later.
  391. */
  392. void ZONE_FILLER::knockoutThermalReliefs( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aFill )
  393. {
  394. SHAPE_POLY_SET holes;
  395. // Use a dummy pad to calculate relief when a pad has a hole but is not on the zone's
  396. // copper layer. The dummy pad has the size and shape of the original pad's hole. We have
  397. // to give it a parent because some functions expect a non-null parent to find clearance
  398. // data, etc.
  399. MODULE dummymodule( m_board );
  400. D_PAD dummypad( &dummymodule );
  401. for( auto module : m_board->Modules() )
  402. {
  403. for( auto pad : module->Pads() )
  404. {
  405. if( !hasThermalConnection( pad, aZone ) )
  406. continue;
  407. // If the pad isn't on the current layer but has a hole, knock out a thermal relief
  408. // for the hole.
  409. if( !pad->IsOnLayer( aZone->GetLayer() ) )
  410. {
  411. if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 )
  412. continue;
  413. setupDummyPadForHole( pad, dummypad );
  414. pad = &dummypad;
  415. }
  416. addKnockout( pad, aZone->GetThermalReliefGap( pad ), holes );
  417. }
  418. }
  419. holes.Simplify( SHAPE_POLY_SET::PM_FAST );
  420. aFill.BooleanSubtract( holes, SHAPE_POLY_SET::PM_FAST );
  421. }
  422. /**
  423. * Removes clearance from the shape for copper items which share the zone's layer but are
  424. * not connected to it.
  425. */
  426. void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aHoles )
  427. {
  428. int zone_clearance = aZone->GetClearance();
  429. int edgeClearance = m_board->GetDesignSettings().m_CopperEdgeClearance;
  430. int zone_to_edgecut_clearance = std::max( aZone->GetZoneClearance(), edgeClearance );
  431. // items outside the zone bounding box are skipped
  432. // the bounding box is the zone bounding box + the biggest clearance found in Netclass list
  433. EDA_RECT zone_boundingbox = aZone->GetBoundingBox();
  434. int biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue();
  435. biggest_clearance = std::max( biggest_clearance, zone_clearance );
  436. zone_boundingbox.Inflate( biggest_clearance );
  437. // Use a dummy pad to calculate hole clearance when a pad has a hole but is not on the
  438. // zone's copper layer. The dummy pad has the size and shape of the original pad's hole.
  439. // We have to give it a parent because some functions expect a non-null parent to find
  440. // clearance data, etc.
  441. MODULE dummymodule( m_board );
  442. D_PAD dummypad( &dummymodule );
  443. // Add non-connected pad clearances
  444. //
  445. for( auto module : m_board->Modules() )
  446. {
  447. for( auto pad : module->Pads() )
  448. {
  449. if( !pad->IsOnLayer( aZone->GetLayer() ) )
  450. {
  451. if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 )
  452. continue;
  453. setupDummyPadForHole( pad, dummypad );
  454. pad = &dummypad;
  455. }
  456. if( pad->GetNetCode() != aZone->GetNetCode()
  457. || pad->GetNetCode() <= 0
  458. || aZone->GetPadConnection( pad ) == PAD_ZONE_CONN_NONE )
  459. {
  460. int gap = std::max( zone_clearance, pad->GetClearance() );
  461. EDA_RECT item_boundingbox = pad->GetBoundingBox();
  462. item_boundingbox.Inflate( pad->GetClearance() );
  463. if( item_boundingbox.Intersects( zone_boundingbox ) )
  464. addKnockout( pad, gap, aHoles );
  465. }
  466. }
  467. }
  468. // Add non-connected track clearances
  469. //
  470. for( auto track : m_board->Tracks() )
  471. {
  472. if( !track->IsOnLayer( aZone->GetLayer() ) )
  473. continue;
  474. if( track->GetNetCode() == aZone->GetNetCode() && ( aZone->GetNetCode() != 0) )
  475. continue;
  476. int gap = std::max( zone_clearance, track->GetClearance() );
  477. EDA_RECT item_boundingbox = track->GetBoundingBox();
  478. if( item_boundingbox.Intersects( zone_boundingbox ) )
  479. track->TransformShapeWithClearanceToPolygon( aHoles, gap, m_low_def );
  480. }
  481. // Add graphic item clearances. They are by definition unconnected, and have no clearance
  482. // definitions of their own.
  483. //
  484. auto doGraphicItem = [&]( BOARD_ITEM* aItem )
  485. {
  486. // A item on the Edge_Cuts is always seen as on any layer:
  487. if( !aItem->IsOnLayer( aZone->GetLayer() ) && !aItem->IsOnLayer( Edge_Cuts ) )
  488. return;
  489. if( !aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
  490. return;
  491. bool ignoreLineWidth = false;
  492. int gap = zone_clearance;
  493. if( aItem->IsOnLayer( Edge_Cuts ) )
  494. {
  495. gap = zone_to_edgecut_clearance;
  496. // edge cuts by definition don't have a width
  497. ignoreLineWidth = true;
  498. }
  499. addKnockout( aItem, gap, ignoreLineWidth, aHoles );
  500. };
  501. for( auto module : m_board->Modules() )
  502. {
  503. doGraphicItem( &module->Reference() );
  504. doGraphicItem( &module->Value() );
  505. for( auto item : module->GraphicalItems() )
  506. doGraphicItem( item );
  507. }
  508. for( auto item : m_board->Drawings() )
  509. doGraphicItem( item );
  510. // Add zones outlines having an higher priority and keepout
  511. //
  512. for( ZONE_CONTAINER* zone : m_board->GetZoneList( true ) )
  513. {
  514. // If the zones share no common layers
  515. if( !aZone->CommonLayerExists( zone->GetLayerSet() ) )
  516. continue;
  517. if( !zone->GetIsKeepout() && zone->GetPriority() <= aZone->GetPriority() )
  518. continue;
  519. if( zone->GetIsKeepout() && !zone->GetDoNotAllowCopperPour() )
  520. continue;
  521. // A higher priority zone or keepout area is found: remove this area
  522. EDA_RECT item_boundingbox = zone->GetBoundingBox();
  523. if( !item_boundingbox.Intersects( zone_boundingbox ) )
  524. continue;
  525. // Add the zone outline area. Don't use any clearance for keepouts, or for zones with
  526. // the same net (they will be connected but will honor their own clearance, thermal
  527. // connections, etc.).
  528. bool sameNet = aZone->GetNetCode() == zone->GetNetCode();
  529. bool useNetClearance = true;
  530. int minClearance = zone_clearance;
  531. // The final clearance is obviously the max value of each zone clearance
  532. minClearance = std::max( minClearance, zone->GetClearance() );
  533. if( zone->GetIsKeepout() || sameNet )
  534. {
  535. minClearance = 0;
  536. useNetClearance = false;
  537. }
  538. zone->TransformOutlinesShapeWithClearanceToPolygon( aHoles, minClearance, useNetClearance );
  539. }
  540. aHoles.Simplify( SHAPE_POLY_SET::PM_FAST );
  541. }
  542. /**
  543. * 1 - Creates the main zone outline using a correction to shrink the resulting area by
  544. * m_ZoneMinThickness / 2. The result is areas with a margin of m_ZoneMinThickness / 2
  545. * so that when drawing outline with segments having a thickness of m_ZoneMinThickness the
  546. * outlines will match exactly the initial outlines
  547. * 2 - Knocks out thermal reliefs around thermally-connected pads
  548. * 3 - Builds a set of thermal spoke for the whole zone
  549. * 4 - Knocks out unconnected copper items, deleting any affected spokes
  550. * 5 - Removes unconnected copper islands, deleting any affected spokes
  551. * 6 - Adds in the remaining spokes
  552. */
  553. void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone,
  554. const SHAPE_POLY_SET& aSmoothedOutline,
  555. std::set<VECTOR2I>* aPreserveCorners,
  556. SHAPE_POLY_SET& aRawPolys,
  557. SHAPE_POLY_SET& aFinalPolys )
  558. {
  559. m_high_def = m_board->GetDesignSettings().m_MaxError;
  560. m_low_def = std::min( ARC_LOW_DEF, int( m_high_def*1.5 ) ); // Reasonable value
  561. // Features which are min_width should survive pruning; features that are *less* than
  562. // min_width should not. Therefore we subtract epsilon from the min_width when
  563. // deflating/inflating.
  564. int half_min_width = aZone->GetMinThickness() / 2;
  565. int epsilon = Millimeter2iu( 0.001 );
  566. int numSegs = std::max( GetArcToSegmentCount( half_min_width, m_high_def, 360.0 ), 6 );
  567. SHAPE_POLY_SET::CORNER_STRATEGY cornerStrategy = SHAPE_POLY_SET::CHOP_ACUTE_CORNERS;
  568. if( aZone->GetCornerSmoothingType() == ZONE_SETTINGS::SMOOTHING_FILLET )
  569. cornerStrategy = SHAPE_POLY_SET::ROUND_ACUTE_CORNERS;
  570. std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
  571. SHAPE_POLY_SET clearanceHoles;
  572. std::unique_ptr<SHAPE_FILE_IO> dumper( new SHAPE_FILE_IO(
  573. s_DumpZonesWhenFilling ? "zones_dump.txt" : "", SHAPE_FILE_IO::IOM_APPEND ) );
  574. aRawPolys = aSmoothedOutline;
  575. if( s_DumpZonesWhenFilling )
  576. dumper->BeginGroup( "clipper-zone" );
  577. knockoutThermalReliefs( aZone, aRawPolys );
  578. if( s_DumpZonesWhenFilling )
  579. dumper->Write( &aRawPolys, "solid-areas-minus-thermal-reliefs" );
  580. buildCopperItemClearances( aZone, clearanceHoles );
  581. if( s_DumpZonesWhenFilling )
  582. dumper->Write( &aRawPolys, "clearance holes" );
  583. buildThermalSpokes( aZone, thermalSpokes );
  584. // Create a temporary zone that we can hit-test spoke-ends against. It's only temporary
  585. // because the "real" subtract-clearance-holes has to be done after the spokes are added.
  586. static const bool USE_BBOX_CACHES = true;
  587. SHAPE_POLY_SET testAreas = aRawPolys;
  588. testAreas.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
  589. // Prune features that don't meet minimum-width criteria
  590. if( half_min_width - epsilon > epsilon )
  591. {
  592. testAreas.Deflate( half_min_width - epsilon, numSegs, cornerStrategy );
  593. testAreas.Inflate( half_min_width - epsilon, numSegs, cornerStrategy );
  594. }
  595. // Spoke-end-testing is hugely expensive so we generate cached bounding-boxes to speed
  596. // things up a bit.
  597. testAreas.BuildBBoxCaches();
  598. for( const SHAPE_LINE_CHAIN& spoke : thermalSpokes )
  599. {
  600. const VECTOR2I& testPt = spoke.CPoint( 3 );
  601. // Hit-test against zone body
  602. if( testAreas.Contains( testPt, -1, 1, USE_BBOX_CACHES ) )
  603. {
  604. aRawPolys.AddOutline( spoke );
  605. continue;
  606. }
  607. // Hit-test against other spokes
  608. for( const SHAPE_LINE_CHAIN& other : thermalSpokes )
  609. {
  610. if( &other != &spoke && other.PointInside( testPt, 1, USE_BBOX_CACHES ) )
  611. {
  612. aRawPolys.AddOutline( spoke );
  613. break;
  614. }
  615. }
  616. }
  617. // Ensure previous changes (adding thermal stubs) do not add
  618. // filled areas outside the zone boundary
  619. aRawPolys.BooleanIntersection( aSmoothedOutline, SHAPE_POLY_SET::PM_FAST );
  620. aRawPolys.Simplify( SHAPE_POLY_SET::PM_FAST );
  621. if( s_DumpZonesWhenFilling )
  622. dumper->Write( &aRawPolys, "solid-areas-with-thermal-spokes" );
  623. aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
  624. // Prune features that don't meet minimum-width criteria
  625. if( half_min_width - epsilon > epsilon )
  626. aRawPolys.Deflate( half_min_width - epsilon, numSegs, cornerStrategy );
  627. if( s_DumpZonesWhenFilling )
  628. dumper->Write( &aRawPolys, "solid-areas-before-hatching" );
  629. // Now remove the non filled areas due to the hatch pattern
  630. if( aZone->GetFillMode() == ZFM_HATCH_PATTERN )
  631. addHatchFillTypeOnZone( aZone, aRawPolys );
  632. if( s_DumpZonesWhenFilling )
  633. dumper->Write( &aRawPolys, "solid-areas-after-hatching" );
  634. // Re-inflate after pruning of areas that don't meet minimum-width criteria
  635. if( aZone->GetFilledPolysUseThickness() )
  636. {
  637. // If we're stroking the zone with a min_width stroke then this will naturally
  638. // inflate the zone by half_min_width
  639. }
  640. else if( half_min_width - epsilon > epsilon )
  641. {
  642. aRawPolys.Simplify( SHAPE_POLY_SET::PM_FAST );
  643. aRawPolys.Inflate( half_min_width - epsilon, numSegs, cornerStrategy );
  644. // If we've deflated/inflated by something near our corner radius then we will have
  645. // ended up with too-sharp corners. Apply outline smoothing again.
  646. if( aZone->GetMinThickness() > (int)aZone->GetCornerRadius() )
  647. aRawPolys.BooleanIntersection( aSmoothedOutline, SHAPE_POLY_SET::PM_FAST );
  648. }
  649. aRawPolys.Fracture( SHAPE_POLY_SET::PM_FAST );
  650. if( s_DumpZonesWhenFilling )
  651. dumper->Write( &aRawPolys, "areas_fractured" );
  652. aFinalPolys = aRawPolys;
  653. if( s_DumpZonesWhenFilling )
  654. dumper->EndGroup();
  655. }
  656. /*
  657. * Build the filled solid areas data from real outlines (stored in m_Poly)
  658. * The solid areas can be more than one on copper layers, and do not have holes
  659. * ( holes are linked by overlapping segments to the main outline)
  660. */
  661. bool ZONE_FILLER::fillSingleZone( ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPolys,
  662. SHAPE_POLY_SET& aFinalPolys )
  663. {
  664. SHAPE_POLY_SET smoothedPoly;
  665. std::set<VECTOR2I> colinearCorners;
  666. aZone->GetColinearCorners( m_board, colinearCorners );
  667. /*
  668. * convert outlines + holes to outlines without holes (adding extra segments if necessary)
  669. * m_Poly data is expected normalized, i.e. NormalizeAreaOutlines was used after building
  670. * this zone
  671. */
  672. if ( !aZone->BuildSmoothedPoly( smoothedPoly, &colinearCorners ) )
  673. return false;
  674. if( aZone->IsOnCopperLayer() )
  675. {
  676. computeRawFilledArea( aZone, smoothedPoly, &colinearCorners, aRawPolys, aFinalPolys );
  677. }
  678. else
  679. {
  680. // Features which are min_width should survive pruning; features that are *less* than
  681. // min_width should not. Therefore we subtract epsilon from the min_width when
  682. // deflating/inflating.
  683. int half_min_width = aZone->GetMinThickness() / 2;
  684. int epsilon = Millimeter2iu( 0.001 );
  685. int numSegs = std::max( GetArcToSegmentCount( half_min_width, m_high_def, 360.0 ), 6 );
  686. if( m_brdOutlinesValid )
  687. smoothedPoly.BooleanIntersection( m_boardOutline, SHAPE_POLY_SET::PM_FAST );
  688. smoothedPoly.Deflate( half_min_width - epsilon, numSegs );
  689. // Remove the non filled areas due to the hatch pattern
  690. if( aZone->GetFillMode() == ZFM_HATCH_PATTERN )
  691. addHatchFillTypeOnZone( aZone, smoothedPoly );
  692. // Re-inflate after pruning of areas that don't meet minimum-width criteria
  693. if( aZone->GetFilledPolysUseThickness() )
  694. {
  695. // If we're stroking the zone with a min_width stroke then this will naturally
  696. // inflate the zone by half_min_width
  697. }
  698. else if( half_min_width - epsilon > epsilon )
  699. smoothedPoly.Deflate( -( half_min_width - epsilon ), numSegs );
  700. aRawPolys = smoothedPoly;
  701. aFinalPolys = smoothedPoly;
  702. aFinalPolys.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  703. }
  704. aZone->SetNeedRefill( false );
  705. return true;
  706. }
  707. /**
  708. * Function buildThermalSpokes
  709. */
  710. void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone,
  711. std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
  712. {
  713. auto zoneBB = aZone->GetBoundingBox();
  714. int zone_clearance = aZone->GetZoneClearance();
  715. int biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue();
  716. biggest_clearance = std::max( biggest_clearance, zone_clearance );
  717. zoneBB.Inflate( biggest_clearance );
  718. // Is a point on the boundary of the polygon inside or outside? This small epsilon lets
  719. // us avoid the question.
  720. int epsilon = KiROUND( IU_PER_MM * 0.04 ); // about 1.5 mil
  721. for( auto module : m_board->Modules() )
  722. {
  723. for( auto pad : module->Pads() )
  724. {
  725. if( !hasThermalConnection( pad, aZone ) )
  726. continue;
  727. // We currently only connect to pads, not pad holes
  728. if( !pad->IsOnLayer( aZone->GetLayer() ) )
  729. continue;
  730. int thermalReliefGap = aZone->GetThermalReliefGap( pad );
  731. // Calculate thermal bridge half width
  732. int spoke_w = aZone->GetThermalReliefCopperBridge( pad );
  733. // Avoid spoke_w bigger than the smaller pad size, because
  734. // it is not possible to create stubs bigger than the pad.
  735. // Possible refinement: have a separate size for vertical and horizontal stubs
  736. spoke_w = std::min( spoke_w, pad->GetSize().x );
  737. spoke_w = std::min( spoke_w, pad->GetSize().y );
  738. // Cannot create stubs having a width < zone min thickness
  739. if( spoke_w <= aZone->GetMinThickness() )
  740. continue;
  741. int spoke_half_w = spoke_w / 2;
  742. // Quick test here to possibly save us some work
  743. BOX2I itemBB = pad->GetBoundingBox();
  744. itemBB.Inflate( thermalReliefGap + epsilon );
  745. if( !( itemBB.Intersects( zoneBB ) ) )
  746. continue;
  747. // Thermal spokes consist of segments from the pad center to points just outside
  748. // the thermal relief.
  749. //
  750. // We use the bounding-box to lay out the spokes, but for this to work the
  751. // bounding box has to be built at the same rotation as the spokes.
  752. wxPoint shapePos = pad->ShapePos();
  753. wxPoint padPos = pad->GetPosition();
  754. double padAngle = pad->GetOrientation();
  755. pad->SetOrientation( 0.0 );
  756. pad->SetPosition( { 0, 0 } );
  757. BOX2I reliefBB = pad->GetBoundingBox();
  758. pad->SetPosition( padPos );
  759. pad->SetOrientation( padAngle );
  760. reliefBB.Inflate( thermalReliefGap + epsilon );
  761. // For circle pads, the thermal spoke orientation is 45 deg
  762. if( pad->GetShape() == PAD_SHAPE_CIRCLE )
  763. padAngle = s_RoundPadThermalSpokeAngle;
  764. for( int i = 0; i < 4; i++ )
  765. {
  766. SHAPE_LINE_CHAIN spoke;
  767. switch( i )
  768. {
  769. case 0: // lower stub
  770. spoke.Append( +spoke_half_w, -spoke_half_w );
  771. spoke.Append( -spoke_half_w, -spoke_half_w );
  772. spoke.Append( -spoke_half_w, reliefBB.GetBottom() );
  773. spoke.Append( 0, reliefBB.GetBottom() ); // test pt
  774. spoke.Append( +spoke_half_w, reliefBB.GetBottom() );
  775. break;
  776. case 1: // upper stub
  777. spoke.Append( +spoke_half_w, spoke_half_w );
  778. spoke.Append( -spoke_half_w, spoke_half_w );
  779. spoke.Append( -spoke_half_w, reliefBB.GetTop() );
  780. spoke.Append( 0, reliefBB.GetTop() ); // test pt
  781. spoke.Append( +spoke_half_w, reliefBB.GetTop() );
  782. break;
  783. case 2: // right stub
  784. spoke.Append( -spoke_half_w, spoke_half_w );
  785. spoke.Append( -spoke_half_w, -spoke_half_w );
  786. spoke.Append( reliefBB.GetRight(), -spoke_half_w );
  787. spoke.Append( reliefBB.GetRight(), 0 ); // test pt
  788. spoke.Append( reliefBB.GetRight(), spoke_half_w );
  789. break;
  790. case 3: // left stub
  791. spoke.Append( spoke_half_w, spoke_half_w );
  792. spoke.Append( spoke_half_w, -spoke_half_w );
  793. spoke.Append( reliefBB.GetLeft(), -spoke_half_w );
  794. spoke.Append( reliefBB.GetLeft(), 0 ); // test pt
  795. spoke.Append( reliefBB.GetLeft(), spoke_half_w );
  796. break;
  797. }
  798. for( int j = 0; j < spoke.PointCount(); j++ )
  799. {
  800. RotatePoint( spoke.Point( j ), padAngle );
  801. spoke.Point( j ) += shapePos;
  802. }
  803. spoke.SetClosed( true );
  804. spoke.GenerateBBoxCache();
  805. aSpokesList.push_back( std::move( spoke ) );
  806. }
  807. }
  808. }
  809. }
  810. void ZONE_FILLER::addHatchFillTypeOnZone( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPolys )
  811. {
  812. // Build grid:
  813. // obviously line thickness must be > zone min thickness.
  814. // It can happens if a board file was edited by hand by a python script
  815. // Use 1 micron margin to be *sure* there is no issue in Gerber files
  816. // (Gbr file unit = 1 or 10 nm) due to some truncation in coordinates or calculations
  817. // This margin also avoid problems due to rounding coordinates in next calculations
  818. // that can create incorrect polygons
  819. int thickness = std::max( aZone->GetHatchFillTypeThickness(),
  820. aZone->GetMinThickness()+Millimeter2iu( 0.001 ) );
  821. int linethickness = thickness - aZone->GetMinThickness();
  822. int gridsize = thickness + aZone->GetHatchFillTypeGap();
  823. double orientation = aZone->GetHatchFillTypeOrientation();
  824. SHAPE_POLY_SET filledPolys = aRawPolys;
  825. // Use a area that contains the rotated bbox by orientation,
  826. // and after rotate the result by -orientation.
  827. if( orientation != 0.0 )
  828. {
  829. filledPolys.Rotate( M_PI/180.0 * orientation, VECTOR2I( 0,0 ) );
  830. }
  831. BOX2I bbox = filledPolys.BBox( 0 );
  832. // Build hole shape
  833. // the hole size is aZone->GetHatchFillTypeGap(), but because the outline thickness
  834. // is aZone->GetMinThickness(), the hole shape size must be larger
  835. SHAPE_LINE_CHAIN hole_base;
  836. int hole_size = aZone->GetHatchFillTypeGap() + aZone->GetMinThickness();
  837. VECTOR2I corner( 0, 0 );;
  838. hole_base.Append( corner );
  839. corner.x += hole_size;
  840. hole_base.Append( corner );
  841. corner.y += hole_size;
  842. hole_base.Append( corner );
  843. corner.x = 0;
  844. hole_base.Append( corner );
  845. hole_base.SetClosed( true );
  846. // Calculate minimal area of a grid hole.
  847. // All holes smaller than a threshold will be removed
  848. double minimal_hole_area = hole_base.Area() / 2;
  849. // Now convert this hole to a smoothed shape:
  850. if( aZone->GetHatchFillTypeSmoothingLevel() > 0 )
  851. {
  852. // the actual size of chamfer, or rounded corner radius is the half size
  853. // of the HatchFillTypeGap scaled by aZone->GetHatchFillTypeSmoothingValue()
  854. // aZone->GetHatchFillTypeSmoothingValue() = 1.0 is the max value for the chamfer or the
  855. // radius of corner (radius = half size of the hole)
  856. int smooth_value = KiROUND( aZone->GetHatchFillTypeGap()
  857. * aZone->GetHatchFillTypeSmoothingValue() / 2 );
  858. // Minimal optimization:
  859. // make smoothing only for reasonnable smooth values, to avoid a lot of useless segments
  860. // and if the smooth value is small, use chamfer even if fillet is requested
  861. #define SMOOTH_MIN_VAL_MM 0.02
  862. #define SMOOTH_SMALL_VAL_MM 0.04
  863. if( smooth_value > Millimeter2iu( SMOOTH_MIN_VAL_MM ) )
  864. {
  865. SHAPE_POLY_SET smooth_hole;
  866. smooth_hole.AddOutline( hole_base );
  867. int smooth_level = aZone->GetHatchFillTypeSmoothingLevel();
  868. if( smooth_value < Millimeter2iu( SMOOTH_SMALL_VAL_MM ) && smooth_level > 1 )
  869. smooth_level = 1;
  870. // Use a larger smooth_value to compensate the outline tickness
  871. // (chamfer is not visible is smooth value < outline thickess)
  872. smooth_value += aZone->GetMinThickness()/2;
  873. // smooth_value cannot be bigger than the half size oh the hole:
  874. smooth_value = std::min( smooth_value, aZone->GetHatchFillTypeGap()/2 );
  875. // the error to approximate a circle by segments when smoothing corners by a arc
  876. int error_max = std::max( Millimeter2iu( 0.01), smooth_value/20 );
  877. switch( smooth_level )
  878. {
  879. case 1:
  880. // Chamfer() uses the distance from a corner to create a end point
  881. // for the chamfer.
  882. hole_base = smooth_hole.Chamfer( smooth_value ).Outline( 0 );
  883. break;
  884. default:
  885. if( aZone->GetHatchFillTypeSmoothingLevel() > 2 )
  886. error_max /= 2; // Force better smoothing
  887. hole_base = smooth_hole.Fillet( smooth_value, error_max ).Outline( 0 );
  888. break;
  889. case 0:
  890. break;
  891. };
  892. }
  893. }
  894. // Build holes
  895. SHAPE_POLY_SET holes;
  896. for( int xx = 0; ; xx++ )
  897. {
  898. int xpos = xx * gridsize;
  899. if( xpos > bbox.GetWidth() )
  900. break;
  901. for( int yy = 0; ; yy++ )
  902. {
  903. int ypos = yy * gridsize;
  904. if( ypos > bbox.GetHeight() )
  905. break;
  906. // Generate hole
  907. SHAPE_LINE_CHAIN hole( hole_base );
  908. hole.Move( VECTOR2I( xpos, ypos ) );
  909. holes.AddOutline( hole );
  910. }
  911. }
  912. holes.Move( bbox.GetPosition() );
  913. // Clamp holes to the area of filled zones with a outline thickness
  914. // > aZone->GetMinThickness() to be sure the thermal pads can be built
  915. int outline_margin = std::max( (aZone->GetMinThickness()*10)/9, linethickness/2 );
  916. filledPolys.Deflate( outline_margin, 16 );
  917. holes.BooleanIntersection( filledPolys, SHAPE_POLY_SET::PM_FAST );
  918. if( orientation != 0.0 )
  919. holes.Rotate( -M_PI/180.0 * orientation, VECTOR2I( 0,0 ) );
  920. // Now filter truncated holes to avoid small holes in pattern
  921. // It happens for holes near the zone outline
  922. for( int ii = 0; ii < holes.OutlineCount(); )
  923. {
  924. double area = holes.Outline( ii ).Area();
  925. if( area < minimal_hole_area ) // The current hole is too small: remove it
  926. holes.DeletePolygon( ii );
  927. else
  928. ++ii;
  929. }
  930. // create grid. Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to
  931. // generate strictly simple polygons needed by Gerber files and Fracture()
  932. aRawPolys.BooleanSubtract( aRawPolys, holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  933. }