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.

1166 lines
43 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. extern void CreateThermalReliefPadPolygon( SHAPE_POLY_SET& aCornerBuffer,
  48. const D_PAD& aPad,
  49. int aThermalGap,
  50. int aCopperThickness,
  51. int aMinThicknessValue,
  52. int aCircleToSegmentsCount,
  53. double aCorrectionFactor,
  54. double aThermalRot );
  55. static double s_thermalRot = 450; // angle of stubs in thermal reliefs for round pads
  56. static const bool s_DumpZonesWhenFilling = false;
  57. ZONE_FILLER::ZONE_FILLER( BOARD* aBoard, COMMIT* aCommit ) :
  58. m_board( aBoard ), m_commit( aCommit ), m_progressReporter( nullptr )
  59. {
  60. }
  61. ZONE_FILLER::~ZONE_FILLER()
  62. {
  63. }
  64. void ZONE_FILLER::SetProgressReporter( WX_PROGRESS_REPORTER* aReporter )
  65. {
  66. m_progressReporter = aReporter;
  67. }
  68. bool ZONE_FILLER::Fill( std::vector<ZONE_CONTAINER*> aZones, bool aCheck )
  69. {
  70. std::vector<CN_ZONE_ISOLATED_ISLAND_LIST> toFill;
  71. auto connectivity = m_board->GetConnectivity();
  72. std::unique_lock<std::mutex> lock( connectivity->GetLock(), std::try_to_lock );
  73. if( !lock )
  74. return false;
  75. for( auto zone : aZones )
  76. {
  77. // Keepout zones are not filled
  78. if( zone->GetIsKeepout() )
  79. continue;
  80. toFill.emplace_back( CN_ZONE_ISOLATED_ISLAND_LIST(zone) );
  81. }
  82. for( unsigned i = 0; i < toFill.size(); i++ )
  83. {
  84. if( m_commit )
  85. {
  86. m_commit->Modify( toFill[i].m_zone );
  87. }
  88. }
  89. if( m_progressReporter )
  90. {
  91. m_progressReporter->Report( _( "Checking zone fills..." ) );
  92. m_progressReporter->SetMaxProgress( toFill.size() );
  93. }
  94. // Remove deprecaded segment zones (only found in very old boards)
  95. m_board->m_SegZoneDeprecated.DeleteAll();
  96. std::atomic<size_t> nextItem( 0 );
  97. size_t parallelThreadCount = std::min<size_t>( std::thread::hardware_concurrency(), toFill.size() );
  98. std::vector<std::future<size_t>> returns( parallelThreadCount );
  99. auto fill_lambda = [&] ( PROGRESS_REPORTER* aReporter ) -> size_t
  100. {
  101. size_t num = 0;
  102. for( size_t i = nextItem++; i < toFill.size(); i = nextItem++ )
  103. {
  104. ZONE_CONTAINER* zone = toFill[i].m_zone;
  105. if( zone->GetFillMode() == ZFM_SEGMENTS )
  106. {
  107. ZONE_SEGMENT_FILL segFill;
  108. fillZoneWithSegments( zone, zone->GetFilledPolysList(), segFill );
  109. zone->SetFillSegments( segFill );
  110. }
  111. else
  112. {
  113. SHAPE_POLY_SET rawPolys, finalPolys;
  114. fillSingleZone( zone, rawPolys, finalPolys );
  115. zone->SetRawPolysList( rawPolys );
  116. zone->SetFilledPolysList( finalPolys );
  117. }
  118. zone->SetIsFilled( true );
  119. if( m_progressReporter )
  120. m_progressReporter->AdvanceProgress();
  121. num++;
  122. }
  123. return num;
  124. };
  125. if( parallelThreadCount <= 1 )
  126. fill_lambda( m_progressReporter );
  127. else
  128. {
  129. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  130. returns[ii] = std::async( std::launch::async, fill_lambda, m_progressReporter );
  131. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  132. {
  133. // Here we balance returns with a 100ms timeout to allow UI updating
  134. std::future_status status;
  135. do
  136. {
  137. if( m_progressReporter )
  138. m_progressReporter->KeepRefreshing();
  139. status = returns[ii].wait_for( std::chrono::milliseconds( 100 ) );
  140. } while( status != std::future_status::ready );
  141. }
  142. }
  143. // Now update the connectivity to check for copper islands
  144. if( m_progressReporter )
  145. {
  146. m_progressReporter->AdvancePhase();
  147. m_progressReporter->Report( _( "Removing insulated copper islands..." ) );
  148. m_progressReporter->KeepRefreshing();
  149. }
  150. connectivity->SetProgressReporter( m_progressReporter );
  151. connectivity->FindIsolatedCopperIslands( toFill );
  152. // Now remove insulated copper islands
  153. bool outOfDate = false;
  154. for( auto& zone : toFill )
  155. {
  156. std::sort( zone.m_islands.begin(), zone.m_islands.end(), std::greater<int>() );
  157. SHAPE_POLY_SET poly = zone.m_zone->GetFilledPolysList();
  158. // only zones with net code > 0 can have islands to remove by definition
  159. if( zone.m_zone->GetNetCode() > 0 )
  160. {
  161. for( auto idx : zone.m_islands )
  162. {
  163. poly.DeletePolygon( idx );
  164. }
  165. }
  166. zone.m_zone->SetFilledPolysList( poly );
  167. if( aCheck && zone.m_lastPolys.GetHash() != poly.GetHash() )
  168. outOfDate = true;
  169. }
  170. if( aCheck )
  171. {
  172. bool refill = false;
  173. wxCHECK( m_progressReporter, false );
  174. if( outOfDate )
  175. {
  176. m_progressReporter->Hide();
  177. KIDIALOG dlg( m_progressReporter->GetParent(),
  178. _( "Zone fills are out-of-date. Refill?" ),
  179. _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
  180. dlg.SetOKCancelLabels( _( "Refill" ), _( "Continue without Refill" ) );
  181. dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
  182. refill = ( dlg.ShowModal() == wxID_OK );
  183. m_progressReporter->Show();
  184. }
  185. if( !refill )
  186. {
  187. if( m_commit )
  188. m_commit->Revert();
  189. connectivity->SetProgressReporter( nullptr );
  190. return false;
  191. }
  192. }
  193. if( m_progressReporter )
  194. {
  195. m_progressReporter->AdvancePhase();
  196. m_progressReporter->Report( _( "Performing polygon fills..." ) );
  197. m_progressReporter->SetMaxProgress( toFill.size() );
  198. }
  199. nextItem = 0;
  200. auto tri_lambda = [&] ( PROGRESS_REPORTER* aReporter ) -> size_t
  201. {
  202. size_t num = 0;
  203. for( size_t i = nextItem++; i < toFill.size(); i = nextItem++ )
  204. {
  205. toFill[i].m_zone->CacheTriangulation();
  206. num++;
  207. if( m_progressReporter )
  208. m_progressReporter->AdvanceProgress();
  209. }
  210. return num;
  211. };
  212. if( parallelThreadCount <= 1 )
  213. tri_lambda( m_progressReporter );
  214. else
  215. {
  216. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  217. returns[ii] = std::async( std::launch::async, tri_lambda, m_progressReporter );
  218. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  219. {
  220. // Here we balance returns with a 100ms timeout to allow UI updating
  221. std::future_status status;
  222. do
  223. {
  224. if( m_progressReporter )
  225. m_progressReporter->KeepRefreshing();
  226. status = returns[ii].wait_for( std::chrono::milliseconds( 100 ) );
  227. } while( status != std::future_status::ready );
  228. }
  229. }
  230. if( m_progressReporter )
  231. {
  232. m_progressReporter->AdvancePhase();
  233. m_progressReporter->Report( _( "Committing changes..." ) );
  234. m_progressReporter->KeepRefreshing();
  235. }
  236. connectivity->SetProgressReporter( nullptr );
  237. if( m_commit )
  238. {
  239. m_commit->Push( _( "Fill Zone(s)" ), false );
  240. }
  241. else
  242. {
  243. for( unsigned i = 0; i < toFill.size(); i++ )
  244. {
  245. connectivity->Update( toFill[i].m_zone );
  246. }
  247. connectivity->RecalculateRatsnest();
  248. }
  249. return true;
  250. }
  251. void ZONE_FILLER::buildZoneFeatureHoleList( const ZONE_CONTAINER* aZone,
  252. SHAPE_POLY_SET& aFeatures ) const
  253. {
  254. // Set the number of segments in arc approximations
  255. // Since we can no longer edit the segment count in pcbnew, we set
  256. // the fill to our high-def count to avoid jagged knock-outs
  257. // However, if the user has edited their zone to increase the segment count,
  258. // we keep this preference
  259. int segsPerCircle = std::max( aZone->GetArcSegmentCount(), ARC_APPROX_SEGMENTS_COUNT_HIGH_DEF );
  260. /* calculates the coeff to compensate radius reduction of holes clearance
  261. * due to the segment approx.
  262. * For a circle the min radius is radius * cos( 2PI / segsPerCircle / 2)
  263. * correctionFactor is 1 /cos( PI/segsPerCircle )
  264. */
  265. double correctionFactor = GetCircletoPolyCorrectionFactor( segsPerCircle );
  266. aFeatures.RemoveAllContours();
  267. int outline_half_thickness = aZone->GetMinThickness() / 2;
  268. // When removing holes, the holes must be expanded by outline_half_thickness
  269. // to take in account the thickness of the zone outlines
  270. int zone_clearance = aZone->GetClearance() + outline_half_thickness;
  271. // When holes are created by non copper items (edge cut items), use only
  272. // the m_ZoneClearance parameter (zone clearance with no netclass clearance)
  273. int zone_to_edgecut_clearance = aZone->GetZoneClearance() + outline_half_thickness;
  274. /* store holes (i.e. tracks and pads areas as polygons outlines)
  275. * in a polygon list
  276. */
  277. /* items ouside the zone bounding box are skipped
  278. * the bounding box is the zone bounding box + the biggest clearance found in Netclass list
  279. */
  280. EDA_RECT item_boundingbox;
  281. EDA_RECT zone_boundingbox = aZone->GetBoundingBox();
  282. int biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue();
  283. biggest_clearance = std::max( biggest_clearance, zone_clearance );
  284. zone_boundingbox.Inflate( biggest_clearance );
  285. /*
  286. * First : Add pads. Note: pads having the same net as zone are left in zone.
  287. * Thermal shapes will be created later if necessary
  288. */
  289. /* Use a dummy pad to calculate hole clearance when a pad is not on all copper layers
  290. * and this pad has a hole
  291. * This dummy pad has the size and shape of the hole
  292. * Therefore, this dummy pad is a circle or an oval.
  293. * A pad must have a parent because some functions expect a non null parent
  294. * to find the parent board, and some other data
  295. */
  296. MODULE dummymodule( m_board ); // Creates a dummy parent
  297. D_PAD dummypad( &dummymodule );
  298. for( MODULE* module = m_board->m_Modules; module; module = module->Next() )
  299. {
  300. D_PAD* nextpad;
  301. for( D_PAD* pad = module->PadsList(); pad != NULL; pad = nextpad )
  302. {
  303. nextpad = pad->Next(); // pad pointer can be modified by next code, so
  304. // calculate the next pad here
  305. if( !pad->IsOnLayer( aZone->GetLayer() ) )
  306. {
  307. /* Test for pads that are on top or bottom only and have a hole.
  308. * There are curious pads but they can be used for some components that are
  309. * inside the board (in fact inside the hole. Some photo diodes and Leds are
  310. * like this)
  311. */
  312. if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 )
  313. continue;
  314. // Use a dummy pad to calculate a hole shape that have the same dimension as
  315. // the pad hole
  316. dummypad.SetSize( pad->GetDrillSize() );
  317. dummypad.SetOrientation( pad->GetOrientation() );
  318. dummypad.SetShape( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ?
  319. PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE );
  320. dummypad.SetPosition( pad->GetPosition() );
  321. pad = &dummypad;
  322. }
  323. // Note: netcode <=0 means not connected item
  324. if( ( pad->GetNetCode() != aZone->GetNetCode() ) || ( pad->GetNetCode() <= 0 ) )
  325. {
  326. int item_clearance = pad->GetClearance() + outline_half_thickness;
  327. item_boundingbox = pad->GetBoundingBox();
  328. item_boundingbox.Inflate( item_clearance );
  329. if( item_boundingbox.Intersects( zone_boundingbox ) )
  330. {
  331. int clearance = std::max( zone_clearance, item_clearance );
  332. // PAD_SHAPE_CUSTOM can have a specific keepout, to avoid to break the shape
  333. if( pad->GetShape() == PAD_SHAPE_CUSTOM
  334. && pad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL )
  335. {
  336. // the pad shape in zone can be its convex hull or
  337. // the shape itself
  338. SHAPE_POLY_SET outline( pad->GetCustomShapeAsPolygon() );
  339. outline.Inflate( KiROUND( clearance * correctionFactor ), segsPerCircle );
  340. pad->CustomShapeAsPolygonToBoardPosition( &outline,
  341. pad->GetPosition(), pad->GetOrientation() );
  342. if( pad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL )
  343. {
  344. std::vector<wxPoint> convex_hull;
  345. BuildConvexHull( convex_hull, outline );
  346. aFeatures.NewOutline();
  347. for( unsigned ii = 0; ii < convex_hull.size(); ++ii )
  348. aFeatures.Append( convex_hull[ii] );
  349. }
  350. else
  351. aFeatures.Append( outline );
  352. }
  353. else
  354. pad->TransformShapeWithClearanceToPolygon( aFeatures,
  355. clearance,
  356. segsPerCircle,
  357. correctionFactor );
  358. }
  359. continue;
  360. }
  361. // Pads are removed from zone if the setup is PAD_ZONE_CONN_NONE
  362. // or if they have a custom shape and not PAD_ZONE_CONN_FULL,
  363. // because a thermal relief will break
  364. // the shape
  365. if( aZone->GetPadConnection( pad ) == PAD_ZONE_CONN_NONE
  366. || ( pad->GetShape() == PAD_SHAPE_CUSTOM && aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_FULL ) )
  367. {
  368. int gap = zone_clearance;
  369. int thermalGap = aZone->GetThermalReliefGap( pad );
  370. gap = std::max( gap, thermalGap );
  371. item_boundingbox = pad->GetBoundingBox();
  372. item_boundingbox.Inflate( gap );
  373. if( item_boundingbox.Intersects( zone_boundingbox ) )
  374. {
  375. // PAD_SHAPE_CUSTOM has a specific keepout, to avoid to break the shape
  376. // the pad shape in zone can be its convex hull or the shape itself
  377. if( pad->GetShape() == PAD_SHAPE_CUSTOM
  378. && pad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL )
  379. {
  380. // the pad shape in zone can be its convex hull or
  381. // the shape itself
  382. SHAPE_POLY_SET outline( pad->GetCustomShapeAsPolygon() );
  383. outline.Inflate( KiROUND( gap * correctionFactor ), segsPerCircle );
  384. pad->CustomShapeAsPolygonToBoardPosition( &outline,
  385. pad->GetPosition(), pad->GetOrientation() );
  386. std::vector<wxPoint> convex_hull;
  387. BuildConvexHull( convex_hull, outline );
  388. aFeatures.NewOutline();
  389. for( unsigned ii = 0; ii < convex_hull.size(); ++ii )
  390. aFeatures.Append( convex_hull[ii] );
  391. }
  392. else
  393. pad->TransformShapeWithClearanceToPolygon( aFeatures,
  394. gap, segsPerCircle, correctionFactor );
  395. }
  396. }
  397. }
  398. }
  399. /* Add holes (i.e. tracks and vias areas as polygons outlines)
  400. * in cornerBufferPolysToSubstract
  401. */
  402. for( auto track : m_board->Tracks() )
  403. {
  404. if( !track->IsOnLayer( aZone->GetLayer() ) )
  405. continue;
  406. if( track->GetNetCode() == aZone->GetNetCode() && ( aZone->GetNetCode() != 0) )
  407. continue;
  408. int item_clearance = track->GetClearance() + outline_half_thickness;
  409. item_boundingbox = track->GetBoundingBox();
  410. if( item_boundingbox.Intersects( zone_boundingbox ) )
  411. {
  412. int clearance = std::max( zone_clearance, item_clearance );
  413. track->TransformShapeWithClearanceToPolygon( aFeatures,
  414. clearance, segsPerCircle, correctionFactor );
  415. }
  416. }
  417. /* Add graphic items that are on copper layers. These have no net, so we just
  418. * use the zone clearance (or edge clearance).
  419. */
  420. auto doGraphicItem = [&]( BOARD_ITEM* aItem )
  421. {
  422. // A item on the Edge_Cuts is always seen as on any layer:
  423. if( !aItem->IsOnLayer( aZone->GetLayer() ) && !aItem->IsOnLayer( Edge_Cuts ) )
  424. return;
  425. if( !aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
  426. return;
  427. bool ignoreLineWidth = false;
  428. int zclearance = zone_clearance;
  429. if( aItem->IsOnLayer( Edge_Cuts ) )
  430. {
  431. // use only the m_ZoneClearance, not the clearance using
  432. // the netclass value, because we do not have a copper item
  433. zclearance = zone_to_edgecut_clearance;
  434. #if 0
  435. // 6.0 TODO: we're leaving this off for 5.1 so that people can continue to use the board
  436. // edge width as a hack for edge clearance.
  437. // edge cuts by definition don't have a width
  438. ignoreLineWidth = true;
  439. #endif
  440. }
  441. switch( aItem->Type() )
  442. {
  443. case PCB_LINE_T:
  444. static_cast<DRAWSEGMENT*>( aItem )->TransformShapeWithClearanceToPolygon(
  445. aFeatures, zclearance, segsPerCircle, correctionFactor, ignoreLineWidth );
  446. break;
  447. case PCB_TEXT_T:
  448. static_cast<TEXTE_PCB*>( aItem )->TransformBoundingBoxWithClearanceToPolygon(
  449. &aFeatures, zclearance );
  450. break;
  451. case PCB_MODULE_EDGE_T:
  452. static_cast<EDGE_MODULE*>( aItem )->TransformShapeWithClearanceToPolygon(
  453. aFeatures, zclearance, segsPerCircle, correctionFactor, ignoreLineWidth );
  454. break;
  455. case PCB_MODULE_TEXT_T:
  456. if( static_cast<TEXTE_MODULE*>( aItem )->IsVisible() )
  457. {
  458. static_cast<TEXTE_MODULE*>( aItem )->TransformBoundingBoxWithClearanceToPolygon(
  459. &aFeatures, zclearance );
  460. }
  461. break;
  462. default:
  463. break;
  464. }
  465. };
  466. for( auto module : m_board->Modules() )
  467. {
  468. doGraphicItem( &module->Reference() );
  469. doGraphicItem( &module->Value() );
  470. for( auto item : module->GraphicalItems() )
  471. doGraphicItem( item );
  472. }
  473. for( auto item : m_board->Drawings() )
  474. doGraphicItem( item );
  475. /* Add zones outlines having an higher priority and keepout
  476. */
  477. for( int ii = 0; ii < m_board->GetAreaCount(); ii++ )
  478. {
  479. ZONE_CONTAINER* zone = m_board->GetArea( ii );
  480. // If the zones share no common layers
  481. if( !aZone->CommonLayerExists( zone->GetLayerSet() ) )
  482. continue;
  483. if( !zone->GetIsKeepout() && zone->GetPriority() <= aZone->GetPriority() )
  484. continue;
  485. if( zone->GetIsKeepout() && !zone->GetDoNotAllowCopperPour() )
  486. continue;
  487. // A highter priority zone or keepout area is found: remove this area
  488. item_boundingbox = zone->GetBoundingBox();
  489. if( !item_boundingbox.Intersects( zone_boundingbox ) )
  490. continue;
  491. // Add the zone outline area.
  492. // However if the zone has the same net as the current zone,
  493. // do not add any clearance.
  494. // the zone will be connected to the current zone, but filled areas
  495. // will use different parameters (clearance, thermal shapes )
  496. bool same_net = aZone->GetNetCode() == zone->GetNetCode();
  497. bool use_net_clearance = true;
  498. int min_clearance = zone_clearance;
  499. // Do not forget to make room to draw the thick outlines
  500. // of the hole created by the area of the zone to remove
  501. int holeclearance = zone->GetClearance() + outline_half_thickness;
  502. // The final clearance is obviously the max value of each zone clearance
  503. min_clearance = std::max( min_clearance, holeclearance );
  504. if( zone->GetIsKeepout() || same_net )
  505. {
  506. // Just take in account the fact the outline has a thickness, so
  507. // the actual area to substract is inflated to take in account this fact
  508. min_clearance = outline_half_thickness;
  509. use_net_clearance = false;
  510. }
  511. zone->TransformOutlinesShapeWithClearanceToPolygon(
  512. aFeatures, min_clearance, use_net_clearance );
  513. }
  514. /* Remove thermal symbols
  515. */
  516. for( auto module : m_board->Modules() )
  517. {
  518. for( auto pad : module->Pads() )
  519. {
  520. // Rejects non-standard pads with tht-only thermal reliefs
  521. if( aZone->GetPadConnection( pad ) == PAD_ZONE_CONN_THT_THERMAL
  522. && pad->GetAttribute() != PAD_ATTRIB_STANDARD )
  523. continue;
  524. if( aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_THERMAL
  525. && aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_THT_THERMAL )
  526. continue;
  527. if( !pad->IsOnLayer( aZone->GetLayer() ) )
  528. continue;
  529. if( pad->GetNetCode() != aZone->GetNetCode() )
  530. continue;
  531. if( pad->GetNetCode() <= 0 )
  532. continue;
  533. item_boundingbox = pad->GetBoundingBox();
  534. int thermalGap = aZone->GetThermalReliefGap( pad );
  535. item_boundingbox.Inflate( thermalGap, thermalGap );
  536. if( item_boundingbox.Intersects( zone_boundingbox ) )
  537. {
  538. CreateThermalReliefPadPolygon( aFeatures,
  539. *pad, thermalGap,
  540. aZone->GetThermalReliefCopperBridge( pad ),
  541. aZone->GetMinThickness(),
  542. segsPerCircle,
  543. correctionFactor, s_thermalRot );
  544. }
  545. }
  546. }
  547. }
  548. /**
  549. * Function ComputeRawFilledAreas
  550. * Supports a min thickness area constraint.
  551. * Add non copper areas polygons (pads and tracks with clearance)
  552. * to the filled copper area found
  553. * in BuildFilledPolysListData after calculating filled areas in a zone
  554. * Non filled copper areas are pads and track and their clearance areas
  555. * The filled copper area must be computed just before.
  556. * BuildFilledPolysListData() call this function just after creating the
  557. * filled copper area polygon (without clearance areas)
  558. * to do that this function:
  559. * 1 - Creates the main outline (zone outline) using a correction to shrink the resulting area
  560. * with m_ZoneMinThickness/2 value.
  561. * The result is areas with a margin of m_ZoneMinThickness/2
  562. * When drawing outline with segments having a thickness of m_ZoneMinThickness, the
  563. * outlines will match exactly the initial outlines
  564. * 3 - Add all non filled areas (pads, tracks) in group B with a clearance of m_Clearance +
  565. * m_ZoneMinThickness/2
  566. * in a buffer
  567. * - If Thermal shapes are wanted, add non filled area, in order to create these thermal shapes
  568. * 4 - calculates the polygon A - B
  569. * 5 - put resulting list of polygons (filled areas) in m_FilledPolysList
  570. * This zone contains pads with the same net.
  571. * 6 - Remove insulated copper islands
  572. * 7 - If Thermal shapes are wanted, remove unconnected stubs in thermal shapes:
  573. * creates a buffer of polygons corresponding to stubs to remove
  574. * sub them to the filled areas.
  575. * Remove new insulated copper islands
  576. */
  577. void ZONE_FILLER::computeRawFilledAreas( const ZONE_CONTAINER* aZone,
  578. const SHAPE_POLY_SET& aSmoothedOutline,
  579. SHAPE_POLY_SET& aRawPolys,
  580. SHAPE_POLY_SET& aFinalPolys ) const
  581. {
  582. int outline_half_thickness = aZone->GetMinThickness() / 2;
  583. std::unique_ptr<SHAPE_FILE_IO> dumper( new SHAPE_FILE_IO(
  584. s_DumpZonesWhenFilling ? "zones_dump.txt" : "", SHAPE_FILE_IO::IOM_APPEND ) );
  585. // Set the number of segments in arc approximations
  586. int segsPerCircle = std::max( aZone->GetArcSegmentCount(), ARC_APPROX_SEGMENTS_COUNT_HIGH_DEF );
  587. /* calculates the coeff to compensate radius reduction of holes clearance
  588. */
  589. double correctionFactor = GetCircletoPolyCorrectionFactor( segsPerCircle );
  590. if( s_DumpZonesWhenFilling )
  591. dumper->BeginGroup( "clipper-zone" );
  592. SHAPE_POLY_SET solidAreas = aSmoothedOutline;
  593. solidAreas.Inflate( -outline_half_thickness, segsPerCircle );
  594. solidAreas.Simplify( SHAPE_POLY_SET::PM_FAST );
  595. SHAPE_POLY_SET holes;
  596. if( s_DumpZonesWhenFilling )
  597. dumper->Write( &solidAreas, "solid-areas" );
  598. buildZoneFeatureHoleList( aZone, holes );
  599. if( s_DumpZonesWhenFilling )
  600. dumper->Write( &holes, "feature-holes" );
  601. holes.Simplify( SHAPE_POLY_SET::PM_FAST );
  602. if( s_DumpZonesWhenFilling )
  603. dumper->Write( &holes, "feature-holes-postsimplify" );
  604. // Generate the filled areas (currently, without thermal shapes, which will
  605. // be created later).
  606. // Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to generate strictly simple polygons
  607. // needed by Gerber files and Fracture()
  608. solidAreas.BooleanSubtract( holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  609. if( s_DumpZonesWhenFilling )
  610. dumper->Write( &solidAreas, "solid-areas-minus-holes" );
  611. SHAPE_POLY_SET areas_fractured = solidAreas;
  612. areas_fractured.Fracture( SHAPE_POLY_SET::PM_FAST );
  613. if( s_DumpZonesWhenFilling )
  614. dumper->Write( &areas_fractured, "areas_fractured" );
  615. aFinalPolys = areas_fractured;
  616. SHAPE_POLY_SET thermalHoles;
  617. // Test thermal stubs connections and add polygons to remove unconnected stubs.
  618. // (this is a refinement for thermal relief shapes)
  619. if( aZone->GetNetCode() > 0 )
  620. {
  621. buildUnconnectedThermalStubsPolygonList( thermalHoles, aZone, aFinalPolys,
  622. correctionFactor, s_thermalRot );
  623. }
  624. // remove copper areas corresponding to not connected stubs
  625. if( !thermalHoles.IsEmpty() )
  626. {
  627. thermalHoles.Simplify( SHAPE_POLY_SET::PM_FAST );
  628. // Remove unconnected stubs. Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to
  629. // generate strictly simple polygons
  630. // needed by Gerber files and Fracture()
  631. solidAreas.BooleanSubtract( thermalHoles, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  632. if( s_DumpZonesWhenFilling )
  633. dumper->Write( &thermalHoles, "thermal-holes" );
  634. // put these areas in m_FilledPolysList
  635. SHAPE_POLY_SET th_fractured = solidAreas;
  636. th_fractured.Fracture( SHAPE_POLY_SET::PM_FAST );
  637. if( s_DumpZonesWhenFilling )
  638. dumper->Write( &th_fractured, "th_fractured" );
  639. aFinalPolys = th_fractured;
  640. }
  641. aRawPolys = aFinalPolys;
  642. if( s_DumpZonesWhenFilling )
  643. dumper->EndGroup();
  644. }
  645. /* Build the filled solid areas data from real outlines (stored in m_Poly)
  646. * The solid areas can be more than one on copper layers, and do not have holes
  647. * ( holes are linked by overlapping segments to the main outline)
  648. */
  649. bool ZONE_FILLER::fillSingleZone( const ZONE_CONTAINER* aZone, SHAPE_POLY_SET& aRawPolys,
  650. SHAPE_POLY_SET& aFinalPolys ) const
  651. {
  652. SHAPE_POLY_SET smoothedPoly;
  653. /* convert outlines + holes to outlines without holes (adding extra segments if necessary)
  654. * m_Poly data is expected normalized, i.e. NormalizeAreaOutlines was used after building
  655. * this zone
  656. */
  657. if ( !aZone->BuildSmoothedPoly( smoothedPoly ) )
  658. return false;
  659. if( aZone->IsOnCopperLayer() )
  660. {
  661. computeRawFilledAreas( aZone, smoothedPoly, aRawPolys, aFinalPolys );
  662. }
  663. else
  664. {
  665. aRawPolys = smoothedPoly;
  666. aFinalPolys = smoothedPoly;
  667. aFinalPolys.Inflate( -aZone->GetMinThickness() / 2, ARC_APPROX_SEGMENTS_COUNT_HIGH_DEF );
  668. aFinalPolys.Fracture( SHAPE_POLY_SET::PM_FAST );
  669. }
  670. return true;
  671. }
  672. bool ZONE_FILLER::fillZoneWithSegments( const ZONE_CONTAINER* aZone,
  673. const SHAPE_POLY_SET& aFilledPolys,
  674. ZONE_SEGMENT_FILL& aFillSegs ) const
  675. {
  676. bool success = true;
  677. // segments are on something like a grid. Give it a minimal size
  678. // to avoid too many segments, and use the m_ZoneMinThickness when (this is usually the case)
  679. // the size is > mingrid_size.
  680. // This is not perfect, but the actual purpose of this code
  681. // is to allow filling zones on a grid, with grid size > m_ZoneMinThickness,
  682. // in order to have really a grid.
  683. //
  684. // Using a user selectable grid size is for future Kicad versions.
  685. // For now the area is fully filled.
  686. int mingrid_size = Millimeter2iu( 0.05 );
  687. int grid_size = std::max( mingrid_size, aZone->GetMinThickness() );
  688. // Make segments slightly overlapping to ensure a good full filling
  689. grid_size -= grid_size/20;
  690. // Creates the horizontal segments
  691. for ( int index = 0; index < aFilledPolys.OutlineCount(); index++ )
  692. {
  693. const SHAPE_LINE_CHAIN& outline0 = aFilledPolys.COutline( index );
  694. success = fillPolygonWithHorizontalSegments( outline0, aFillSegs, grid_size );
  695. if( !success )
  696. break;
  697. // Creates the vertical segments. Because the filling algo creates horizontal segments,
  698. // to reuse the fillPolygonWithHorizontalSegments function, we rotate the polygons to fill
  699. // then fill them, then inverse rotate the result
  700. SHAPE_LINE_CHAIN outline90;
  701. outline90.Append( outline0 );
  702. // Rotate 90 degrees the outline:
  703. for( int ii = 0; ii < outline90.PointCount(); ii++ )
  704. {
  705. VECTOR2I& point = outline90.Point( ii );
  706. std::swap( point.x, point.y );
  707. point.y = -point.y;
  708. }
  709. int first_point = aFillSegs.size();
  710. success = fillPolygonWithHorizontalSegments( outline90, aFillSegs, grid_size );
  711. if( !success )
  712. break;
  713. // Rotate -90 degrees the segments:
  714. for( unsigned ii = first_point; ii < aFillSegs.size(); ii++ )
  715. {
  716. SEG& segm = aFillSegs[ii];
  717. std::swap( segm.A.x, segm.A.y );
  718. std::swap( segm.B.x, segm.B.y );
  719. segm.A.x = - segm.A.x;
  720. segm.B.x = - segm.B.x;
  721. }
  722. }
  723. return success;
  724. }
  725. /** Helper function fillPolygonWithHorizontalSegments
  726. * fills a polygon with horizontal segments.
  727. * It can be used for any angle, if the zone outline to fill is rotated by this angle
  728. * and the result is rotated by -angle
  729. * @param aPolygon = a SHAPE_LINE_CHAIN polygon to fill
  730. * @param aFillSegmList = a std::vector\<SEGMENT\> which will be populated by filling segments
  731. * @param aStep = the horizontal grid size
  732. */
  733. bool ZONE_FILLER::fillPolygonWithHorizontalSegments( const SHAPE_LINE_CHAIN& aPolygon,
  734. ZONE_SEGMENT_FILL& aFillSegmList, int aStep ) const
  735. {
  736. std::vector <int> x_coordinates;
  737. bool success = true;
  738. // Creates the horizontal segments
  739. const SHAPE_LINE_CHAIN& outline = aPolygon;
  740. const BOX2I& rect = outline.BBox();
  741. // Calculate the y limits of the zone
  742. for( int refy = rect.GetY(), endy = rect.GetBottom(); refy < endy; refy += aStep )
  743. {
  744. // find all intersection points of an infinite line with polyline sides
  745. x_coordinates.clear();
  746. for( int v = 0; v < outline.PointCount(); v++ )
  747. {
  748. int seg_startX = outline.CPoint( v ).x;
  749. int seg_startY = outline.CPoint( v ).y;
  750. int seg_endX = outline.CPoint( v + 1 ).x;
  751. int seg_endY = outline.CPoint( v + 1 ).y;
  752. /* Trivial cases: skip if ref above or below the segment to test */
  753. if( ( seg_startY > refy ) && ( seg_endY > refy ) )
  754. continue;
  755. // segment below ref point, or its Y end pos on Y coordinate ref point: skip
  756. if( ( seg_startY <= refy ) && (seg_endY <= refy ) )
  757. continue;
  758. /* at this point refy is between seg_startY and seg_endY
  759. * see if an horizontal line at Y = refy is intersecting this segment
  760. */
  761. // calculate the x position of the intersection of this segment and the
  762. // infinite line this is more easier if we move the X,Y axis origin to
  763. // the segment start point:
  764. seg_endX -= seg_startX;
  765. seg_endY -= seg_startY;
  766. double newrefy = (double) ( refy - seg_startY );
  767. double intersec_x;
  768. if ( seg_endY == 0 ) // horizontal segment on the same line: skip
  769. continue;
  770. // Now calculate the x intersection coordinate of the horizontal line at
  771. // y = newrefy and the segment from (0,0) to (seg_endX,seg_endY) with the
  772. // horizontal line at the new refy position the line slope is:
  773. // slope = seg_endY/seg_endX; and inv_slope = seg_endX/seg_endY
  774. // and the x pos relative to the new origin is:
  775. // intersec_x = refy/slope = refy * inv_slope
  776. // Note: because horizontal segments are already tested and skipped, slope
  777. // exists (seg_end_y not O)
  778. double inv_slope = (double) seg_endX / seg_endY;
  779. intersec_x = newrefy * inv_slope;
  780. x_coordinates.push_back( (int) intersec_x + seg_startX );
  781. }
  782. // A line scan is finished: build list of segments
  783. // Sort intersection points by increasing x value:
  784. // So 2 consecutive points are the ends of a segment
  785. std::sort( x_coordinates.begin(), x_coordinates.end() );
  786. // An even number of coordinates is expected, because a segment has 2 ends.
  787. // An if this algorithm always works, it must always find an even count.
  788. if( ( x_coordinates.size() & 1 ) != 0 )
  789. {
  790. success = false;
  791. break;
  792. }
  793. // Create segments having the same Y coordinate
  794. int iimax = x_coordinates.size() - 1;
  795. for( int ii = 0; ii < iimax; ii += 2 )
  796. {
  797. VECTOR2I seg_start, seg_end;
  798. seg_start.x = x_coordinates[ii];
  799. seg_start.y = refy;
  800. seg_end.x = x_coordinates[ii + 1];
  801. seg_end.y = refy;
  802. SEG segment( seg_start, seg_end );
  803. aFillSegmList.push_back( segment );
  804. }
  805. } // End examine segments in one area
  806. return success;
  807. }
  808. /**
  809. * Function buildUnconnectedThermalStubsPolygonList
  810. * Creates a set of polygons corresponding to stubs created by thermal shapes on pads
  811. * which are not connected to a zone (dangling bridges)
  812. * @param aCornerBuffer = a SHAPE_POLY_SET where to store polygons
  813. * @param aZone = a pointer to the ZONE_CONTAINER to examine.
  814. * @param aArcCorrection = arc correction factor.
  815. * @param aRoundPadThermalRotation = the rotation in 1.0 degree for thermal stubs in round pads
  816. */
  817. void ZONE_FILLER::buildUnconnectedThermalStubsPolygonList( SHAPE_POLY_SET& aCornerBuffer,
  818. const ZONE_CONTAINER* aZone,
  819. const SHAPE_POLY_SET& aRawFilledArea,
  820. double aArcCorrection,
  821. double aRoundPadThermalRotation ) const
  822. {
  823. SHAPE_LINE_CHAIN spokes;
  824. BOX2I itemBB;
  825. VECTOR2I ptTest[4];
  826. auto zoneBB = aRawFilledArea.BBox();
  827. int zone_clearance = aZone->GetZoneClearance();
  828. int biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue();
  829. biggest_clearance = std::max( biggest_clearance, zone_clearance );
  830. zoneBB.Inflate( biggest_clearance );
  831. // half size of the pen used to draw/plot zones outlines
  832. int pen_radius = aZone->GetMinThickness() / 2;
  833. for( auto module : m_board->Modules() )
  834. {
  835. for( auto pad : module->Pads() )
  836. {
  837. // Rejects non-standard pads with tht-only thermal reliefs
  838. if( aZone->GetPadConnection( pad ) == PAD_ZONE_CONN_THT_THERMAL
  839. && pad->GetAttribute() != PAD_ATTRIB_STANDARD )
  840. continue;
  841. if( aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_THERMAL
  842. && aZone->GetPadConnection( pad ) != PAD_ZONE_CONN_THT_THERMAL )
  843. continue;
  844. if( !pad->IsOnLayer( aZone->GetLayer() ) )
  845. continue;
  846. if( pad->GetNetCode() != aZone->GetNetCode() )
  847. continue;
  848. // Calculate thermal bridge half width
  849. int thermalBridgeWidth = aZone->GetThermalReliefCopperBridge( pad )
  850. - aZone->GetMinThickness();
  851. if( thermalBridgeWidth <= 0 )
  852. continue;
  853. // we need the thermal bridge half width
  854. // with a small extra size to be sure we create a stub
  855. // slightly larger than the actual stub
  856. thermalBridgeWidth = ( thermalBridgeWidth + 4 ) / 2;
  857. int thermalReliefGap = aZone->GetThermalReliefGap( pad );
  858. itemBB = pad->GetBoundingBox();
  859. itemBB.Inflate( thermalReliefGap );
  860. if( !( itemBB.Intersects( zoneBB ) ) )
  861. continue;
  862. // Thermal bridges are like a segment from a starting point inside the pad
  863. // to an ending point outside the pad
  864. // calculate the ending point of the thermal pad, outside the pad
  865. VECTOR2I endpoint;
  866. endpoint.x = ( pad->GetSize().x / 2 ) + thermalReliefGap;
  867. endpoint.y = ( pad->GetSize().y / 2 ) + thermalReliefGap;
  868. // Calculate the starting point of the thermal stub
  869. // inside the pad
  870. VECTOR2I startpoint;
  871. int copperThickness = aZone->GetThermalReliefCopperBridge( pad )
  872. - aZone->GetMinThickness();
  873. if( copperThickness < 0 )
  874. copperThickness = 0;
  875. // Leave a small extra size to the copper area inside to pad
  876. copperThickness += KiROUND( IU_PER_MM * 0.04 );
  877. startpoint.x = std::min( pad->GetSize().x, copperThickness );
  878. startpoint.y = std::min( pad->GetSize().y, copperThickness );
  879. startpoint.x /= 2;
  880. startpoint.y /= 2;
  881. // This is a CIRCLE pad tweak
  882. // for circle pads, the thermal stubs orientation is 45 deg
  883. double fAngle = pad->GetOrientation();
  884. if( pad->GetShape() == PAD_SHAPE_CIRCLE )
  885. {
  886. endpoint.x = KiROUND( endpoint.x * aArcCorrection );
  887. endpoint.y = endpoint.x;
  888. fAngle = aRoundPadThermalRotation;
  889. }
  890. // contour line width has to be taken into calculation to avoid "thermal stub bleed"
  891. endpoint.x += pen_radius;
  892. endpoint.y += pen_radius;
  893. // compute north, south, west and east points for zone connection.
  894. ptTest[0] = VECTOR2I( 0, endpoint.y ); // lower point
  895. ptTest[1] = VECTOR2I( 0, -endpoint.y ); // upper point
  896. ptTest[2] = VECTOR2I( endpoint.x, 0 ); // right point
  897. ptTest[3] = VECTOR2I( -endpoint.x, 0 ); // left point
  898. // Test all sides
  899. for( int i = 0; i < 4; i++ )
  900. {
  901. // rotate point
  902. RotatePoint( ptTest[i], fAngle );
  903. // translate point
  904. ptTest[i] += pad->ShapePos();
  905. if( aRawFilledArea.Contains( ptTest[i] ) )
  906. continue;
  907. spokes.Clear();
  908. // polygons are rectangles with width of copper bridge value
  909. switch( i )
  910. {
  911. case 0: // lower stub
  912. spokes.Append( -thermalBridgeWidth, endpoint.y );
  913. spokes.Append( +thermalBridgeWidth, endpoint.y );
  914. spokes.Append( +thermalBridgeWidth, startpoint.y );
  915. spokes.Append( -thermalBridgeWidth, startpoint.y );
  916. break;
  917. case 1: // upper stub
  918. spokes.Append( -thermalBridgeWidth, -endpoint.y );
  919. spokes.Append( +thermalBridgeWidth, -endpoint.y );
  920. spokes.Append( +thermalBridgeWidth, -startpoint.y );
  921. spokes.Append( -thermalBridgeWidth, -startpoint.y );
  922. break;
  923. case 2: // right stub
  924. spokes.Append( endpoint.x, -thermalBridgeWidth );
  925. spokes.Append( endpoint.x, thermalBridgeWidth );
  926. spokes.Append( +startpoint.x, thermalBridgeWidth );
  927. spokes.Append( +startpoint.x, -thermalBridgeWidth );
  928. break;
  929. case 3: // left stub
  930. spokes.Append( -endpoint.x, -thermalBridgeWidth );
  931. spokes.Append( -endpoint.x, thermalBridgeWidth );
  932. spokes.Append( -startpoint.x, thermalBridgeWidth );
  933. spokes.Append( -startpoint.x, -thermalBridgeWidth );
  934. break;
  935. }
  936. aCornerBuffer.NewOutline();
  937. // add computed polygon to list
  938. for( int ic = 0; ic < spokes.PointCount(); ic++ )
  939. {
  940. auto cpos = spokes.CPoint( ic );
  941. RotatePoint( cpos, fAngle ); // Rotate according to module orientation
  942. cpos += pad->ShapePos(); // Shift origin to position
  943. aCornerBuffer.Append( cpos );
  944. }
  945. }
  946. }
  947. }
  948. }