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.

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