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.

1358 lines
49 KiB

Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2014-2017 CERN
  5. * Copyright (C) 2014-2020 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 <thread>
  26. #include <algorithm>
  27. #include <future>
  28. #include <advanced_config.h>
  29. #include <class_board.h>
  30. #include <class_zone.h>
  31. #include <class_module.h>
  32. #include <class_edge_mod.h>
  33. #include <class_drawsegment.h>
  34. #include <class_pcb_text.h>
  35. #include <class_pcb_target.h>
  36. #include <class_track.h>
  37. #include <connectivity/connectivity_data.h>
  38. #include <convert_basic_shapes_to_polygon.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 <math/util.h> // for KiROUND
  48. #include <convert_basic_shapes_to_polygon.h>
  49. #include "zone_filler.h"
  50. class PROGRESS_REPORTER_HIDER
  51. {
  52. public:
  53. PROGRESS_REPORTER_HIDER( WX_PROGRESS_REPORTER* aReporter )
  54. {
  55. m_reporter = aReporter;
  56. if( aReporter )
  57. aReporter->Hide();
  58. }
  59. ~PROGRESS_REPORTER_HIDER()
  60. {
  61. if( m_reporter )
  62. m_reporter->Show();
  63. }
  64. private:
  65. WX_PROGRESS_REPORTER* m_reporter;
  66. };
  67. static const double s_RoundPadThermalSpokeAngle = 450;
  68. static const bool s_DumpZonesWhenFilling = false;
  69. ZONE_FILLER::ZONE_FILLER( BOARD* aBoard, COMMIT* aCommit ) :
  70. m_board( aBoard ),
  71. m_brdOutlinesValid( false ),
  72. m_commit( aCommit ),
  73. m_progressReporter( nullptr ),
  74. m_maxError( ARC_HIGH_DEF )
  75. {
  76. }
  77. ZONE_FILLER::~ZONE_FILLER()
  78. {
  79. }
  80. void ZONE_FILLER::InstallNewProgressReporter( wxWindow* aParent, const wxString& aTitle,
  81. int aNumPhases )
  82. {
  83. m_uniqueReporter = std::make_unique<WX_PROGRESS_REPORTER>( aParent, aTitle, aNumPhases );
  84. m_progressReporter = m_uniqueReporter.get();
  85. }
  86. bool ZONE_FILLER::Fill( const std::vector<ZONE_CONTAINER*>& aZones, bool aCheck )
  87. {
  88. std::vector<std::pair<ZONE_CONTAINER*, PCB_LAYER_ID>> toFill;
  89. std::vector<CN_ZONE_ISOLATED_ISLAND_LIST> islandsList;
  90. auto connectivity = m_board->GetConnectivity();
  91. bool filledPolyWithOutline = not m_board->GetDesignSettings().m_ZoneUseNoOutlineInFill;
  92. std::unique_lock<std::mutex> lock( connectivity->GetLock(), std::try_to_lock );
  93. if( !lock )
  94. return false;
  95. if( m_progressReporter )
  96. {
  97. m_progressReporter->Report( aCheck ? _( "Checking zone fills..." )
  98. : _( "Building zone fills..." ) );
  99. m_progressReporter->SetMaxProgress( aZones.size() );
  100. }
  101. // The board outlines is used to clip solid areas inside the board (when outlines are valid)
  102. m_boardOutline.RemoveAllContours();
  103. m_brdOutlinesValid = m_board->GetBoardPolygonOutlines( m_boardOutline );
  104. // Update the bounding box shape caches in the pads to prevent multi-threaded rebuilds
  105. for( auto module : m_board->Modules() )
  106. {
  107. for( auto pad : module->Pads() )
  108. {
  109. if( pad->IsDirty() )
  110. pad->BuildEffectiveShapes( UNDEFINED_LAYER );
  111. }
  112. }
  113. for( ZONE_CONTAINER* zone : aZones )
  114. {
  115. // Keepout zones are not filled
  116. if( zone->GetIsKeepout() )
  117. continue;
  118. m_commit->Modify( zone );
  119. // calculate the hash value for filled areas. it will be used later
  120. // to know if the current filled areas are up to date
  121. for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
  122. {
  123. zone->BuildHashValue( layer );
  124. // Add the zone to the list of zones to test or refill
  125. toFill.emplace_back( std::make_pair( zone, layer ) );
  126. }
  127. islandsList.emplace_back( CN_ZONE_ISOLATED_ISLAND_LIST( zone ) );
  128. // Remove existing fill first to prevent drawing invalid polygons
  129. // on some platforms
  130. zone->UnFill();
  131. }
  132. std::atomic<size_t> nextItem( 0 );
  133. size_t parallelThreadCount = std::min<size_t>( std::thread::hardware_concurrency(),
  134. aZones.size() );
  135. std::vector<std::future<size_t>> returns( parallelThreadCount );
  136. auto fill_lambda =
  137. [&]( PROGRESS_REPORTER* aReporter ) -> size_t
  138. {
  139. size_t num = 0;
  140. for( size_t i = nextItem++; i < toFill.size(); i = nextItem++ )
  141. {
  142. PCB_LAYER_ID layer = toFill[i].second;
  143. ZONE_CONTAINER* zone = toFill[i].first;
  144. zone->SetFilledPolysUseThickness( filledPolyWithOutline );
  145. SHAPE_POLY_SET rawPolys, finalPolys;
  146. fillSingleZone( zone, layer, rawPolys, finalPolys );
  147. std::unique_lock<std::mutex> zoneLock( zone->GetLock() );
  148. zone->SetRawPolysList( layer, rawPolys );
  149. zone->SetFilledPolysList( layer, finalPolys );
  150. zone->SetIsFilled( true );
  151. if( m_progressReporter )
  152. {
  153. m_progressReporter->AdvanceProgress();
  154. if( m_progressReporter->IsCancelled() )
  155. break;
  156. }
  157. num++;
  158. }
  159. return num;
  160. };
  161. if( parallelThreadCount <= 1 )
  162. fill_lambda( m_progressReporter );
  163. else
  164. {
  165. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  166. returns[ii] = std::async( std::launch::async, fill_lambda, m_progressReporter );
  167. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  168. {
  169. // Here we balance returns with a 100ms timeout to allow UI updating
  170. std::future_status status;
  171. do
  172. {
  173. if( m_progressReporter )
  174. m_progressReporter->KeepRefreshing();
  175. status = returns[ii].wait_for( std::chrono::milliseconds( 100 ) );
  176. } while( status != std::future_status::ready );
  177. }
  178. }
  179. // Now update the connectivity to check for copper islands
  180. if( m_progressReporter )
  181. {
  182. if( m_progressReporter->IsCancelled() )
  183. return false;
  184. m_progressReporter->AdvancePhase();
  185. m_progressReporter->Report( _( "Removing insulated copper islands..." ) );
  186. m_progressReporter->KeepRefreshing();
  187. }
  188. connectivity->SetProgressReporter( m_progressReporter );
  189. connectivity->FindIsolatedCopperIslands( islandsList );
  190. connectivity->SetProgressReporter( nullptr );
  191. if( m_progressReporter && m_progressReporter->IsCancelled() )
  192. return false;
  193. // Re-add not connected zones to list, connectivity->FindIsolatedCopperIslands()
  194. // populates islandsList only with zones having a net
  195. for( ZONE_CONTAINER* zone : aZones )
  196. {
  197. // Keepout zones are not filled
  198. if( zone->GetIsKeepout() || !zone->IsOnCopperLayer() || zone->GetNetCode() > 0 )
  199. continue;
  200. islandsList.emplace_back( CN_ZONE_ISOLATED_ISLAND_LIST( zone ) );
  201. // All filled polygons are "isolated areas". Enter all filled polygons
  202. // to CN_ZONE_ISOLATED_ISLAND_LIST index list
  203. // Later, only areas outside the board outlines will be removed.
  204. CN_ZONE_ISOLATED_ISLAND_LIST& cn_item = islandsList.back();
  205. for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
  206. {
  207. const SHAPE_POLY_SET& polys = zone->GetFilledPolysList( layer );
  208. for( int ii = 0; ii < polys.OutlineCount(); ii++ )
  209. cn_item.m_islands[layer].push_back( ii );
  210. }
  211. }
  212. // Now remove insulated copper islands and islands outside the board edge
  213. bool outOfDate = false;
  214. for( auto& zone : islandsList )
  215. {
  216. for( PCB_LAYER_ID layer : zone.m_zone->GetLayerSet().Seq() )
  217. {
  218. if( !zone.m_islands.count( layer ) )
  219. continue;
  220. std::vector<int>& islands = zone.m_islands.at( layer );
  221. // The list of polygons to delete must be explored from last to first in list,
  222. // to allow deleting a polygon from list without breaking the remaining of the list
  223. std::sort( islands.begin(), islands.end(), std::greater<int>() );
  224. SHAPE_POLY_SET poly = zone.m_zone->GetFilledPolysList( layer );
  225. long long int minArea = zone.m_zone->GetMinIslandArea();
  226. ISLAND_REMOVAL_MODE mode = zone.m_zone->GetIslandRemovalMode();
  227. // Remove solid areas outside the board cutouts and the insulated islands
  228. // only zones with net code > 0 can have insulated islands by definition
  229. if( zone.m_zone->GetNetCode() > 0 )
  230. {
  231. // solid areas outside the board cutouts are also removed, because they are usually
  232. // insulated islands
  233. for( auto idx : islands )
  234. {
  235. if( mode == ISLAND_REMOVAL_MODE::ALWAYS
  236. || ( mode == ISLAND_REMOVAL_MODE::AREA
  237. && poly.Outline( idx ).Area() < minArea )
  238. || !m_boardOutline.Contains( poly.Polygon( idx ).front().CPoint( 0 ) ) )
  239. poly.DeletePolygon( idx );
  240. else
  241. zone.m_zone->SetIsIsland( layer, idx );
  242. }
  243. }
  244. // Zones with no net can have areas outside the board cutouts.
  245. // By definition, Zones with no net have no isolated island
  246. // (in fact any filled area is an isolated island)
  247. // but they can have some areas outside the board cutouts.
  248. // A filled area outside the board cutouts has all points outside cutouts,
  249. // so we only need to check one point for each filled polygon.
  250. // Note also non copper zones are already clipped
  251. else if( m_brdOutlinesValid && zone.m_zone->IsOnCopperLayer() )
  252. {
  253. for( auto idx : islands )
  254. {
  255. if( poly.Polygon( idx ).empty()
  256. || !m_boardOutline.Contains( poly.Polygon( idx ).front().CPoint( 0 ) ) )
  257. {
  258. poly.DeletePolygon( idx );
  259. }
  260. }
  261. }
  262. zone.m_zone->SetFilledPolysList( layer, poly );
  263. zone.m_zone->CalculateFilledArea();
  264. if( aCheck && zone.m_zone->GetHashValue( layer ) != poly.GetHash() )
  265. outOfDate = true;
  266. if( m_progressReporter && m_progressReporter->IsCancelled() )
  267. return false;
  268. }
  269. }
  270. if( aCheck && outOfDate )
  271. {
  272. PROGRESS_REPORTER_HIDER raii( m_progressReporter );
  273. KIDIALOG dlg( m_progressReporter->GetParent(),
  274. _( "Zone fills are out-of-date. Refill?" ),
  275. _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
  276. dlg.SetOKCancelLabels( _( "Refill" ), _( "Continue without Refill" ) );
  277. dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
  278. if( dlg.ShowModal() == wxID_CANCEL )
  279. return false;
  280. }
  281. if( m_progressReporter )
  282. {
  283. m_progressReporter->AdvancePhase();
  284. m_progressReporter->Report( _( "Performing polygon fills..." ) );
  285. m_progressReporter->SetMaxProgress( toFill.size() );
  286. }
  287. nextItem = 0;
  288. auto tri_lambda =
  289. [&]( PROGRESS_REPORTER* aReporter ) -> size_t
  290. {
  291. size_t num = 0;
  292. for( size_t i = nextItem++; i < islandsList.size(); i = nextItem++ )
  293. {
  294. islandsList[i].m_zone->CacheTriangulation();
  295. num++;
  296. if( m_progressReporter )
  297. {
  298. m_progressReporter->AdvanceProgress();
  299. if( m_progressReporter->IsCancelled() )
  300. break;
  301. }
  302. }
  303. return num;
  304. };
  305. if( parallelThreadCount <= 1 )
  306. tri_lambda( m_progressReporter );
  307. else
  308. {
  309. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  310. returns[ii] = std::async( std::launch::async, tri_lambda, m_progressReporter );
  311. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  312. {
  313. // Here we balance returns with a 100ms timeout to allow UI updating
  314. std::future_status status;
  315. do
  316. {
  317. if( m_progressReporter )
  318. {
  319. m_progressReporter->KeepRefreshing();
  320. if( m_progressReporter->IsCancelled() )
  321. break;
  322. }
  323. status = returns[ii].wait_for( std::chrono::milliseconds( 100 ) );
  324. } while( status != std::future_status::ready );
  325. }
  326. }
  327. if( m_progressReporter )
  328. {
  329. if( m_progressReporter->IsCancelled() )
  330. return false;
  331. m_progressReporter->AdvancePhase();
  332. m_progressReporter->Report( _( "Committing changes..." ) );
  333. m_progressReporter->KeepRefreshing();
  334. }
  335. connectivity->SetProgressReporter( nullptr );
  336. return true;
  337. }
  338. /**
  339. * Return true if the given pad has a thermal connection with the given zone.
  340. */
  341. bool hasThermalConnection( D_PAD* pad, const ZONE_CONTAINER* aZone )
  342. {
  343. // Rejects non-standard pads with tht-only thermal reliefs
  344. if( aZone->GetPadConnection( pad ) == ZONE_CONNECTION::THT_THERMAL
  345. && pad->GetAttribute() != PAD_ATTRIB_STANDARD )
  346. {
  347. return false;
  348. }
  349. if( aZone->GetPadConnection( pad ) != ZONE_CONNECTION::THERMAL
  350. && aZone->GetPadConnection( pad ) != ZONE_CONNECTION::THT_THERMAL )
  351. {
  352. return false;
  353. }
  354. if( pad->GetNetCode() != aZone->GetNetCode() || pad->GetNetCode() <= 0 )
  355. return false;
  356. EDA_RECT item_boundingbox = pad->GetBoundingBox();
  357. int thermalGap = aZone->GetThermalReliefGap( pad );
  358. item_boundingbox.Inflate( thermalGap, thermalGap );
  359. return item_boundingbox.Intersects( aZone->GetBoundingBox() );
  360. }
  361. /**
  362. * Setup aDummyPad to have the same size and shape of aPad's hole. This allows us to create
  363. * thermal reliefs and clearances for holes using the pad code.
  364. */
  365. static void setupDummyPadForHole( const D_PAD* aPad, D_PAD& aDummyPad )
  366. {
  367. // People may author clearance rules that look at a bunch of different stuff so we need
  368. // to copy over pretty much everything except the shape info, which we fill from the drill
  369. // info.
  370. aDummyPad.SetLayerSet( aPad->GetLayerSet() );
  371. aDummyPad.SetAttribute( aPad->GetAttribute() );
  372. aDummyPad.SetProperty( aPad->GetProperty() );
  373. aDummyPad.SetNetCode( aPad->GetNetCode() );
  374. aDummyPad.SetLocalClearance( aPad->GetLocalClearance() );
  375. aDummyPad.SetLocalSolderMaskMargin( aPad->GetLocalSolderMaskMargin() );
  376. aDummyPad.SetLocalSolderPasteMargin( aPad->GetLocalSolderPasteMargin() );
  377. aDummyPad.SetLocalSolderPasteMarginRatio( aPad->GetLocalSolderPasteMarginRatio() );
  378. aDummyPad.SetZoneConnection( aPad->GetEffectiveZoneConnection() );
  379. aDummyPad.SetThermalSpokeWidth( aPad->GetEffectiveThermalSpokeWidth() );
  380. aDummyPad.SetThermalGap( aPad->GetEffectiveThermalGap() );
  381. aDummyPad.SetCustomShapeInZoneOpt( aPad->GetCustomShapeInZoneOpt() );
  382. // Note: drill size represents finish size, which means the actual holes size is the
  383. // plating thickness larger.
  384. int platingThickness = 0;
  385. if( aPad->GetAttribute() == PAD_ATTRIB_STANDARD )
  386. platingThickness = aPad->GetBoard()->GetDesignSettings().GetHolePlatingThickness();
  387. aDummyPad.SetOffset( wxPoint( 0, 0 ) );
  388. aDummyPad.SetSize( aPad->GetDrillSize() + wxSize( platingThickness, platingThickness ) );
  389. aDummyPad.SetShape( aPad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ? PAD_SHAPE_OVAL
  390. : PAD_SHAPE_CIRCLE );
  391. aDummyPad.SetOrientation( aPad->GetOrientation() );
  392. aDummyPad.SetPosition( aPad->GetPosition() );
  393. }
  394. /**
  395. * Add a knockout for a pad. The knockout is 'aGap' larger than the pad (which might be
  396. * either the thermal clearance or the electrical clearance).
  397. */
  398. void ZONE_FILLER::addKnockout( D_PAD* aPad, PCB_LAYER_ID aLayer, int aGap, SHAPE_POLY_SET& aHoles )
  399. {
  400. if( aPad->GetShape() == PAD_SHAPE_CUSTOM )
  401. {
  402. SHAPE_POLY_SET poly;
  403. aPad->TransformShapeWithClearanceToPolygon( poly, aLayer, aGap, m_maxError );
  404. // the pad shape in zone can be its convex hull or the shape itself
  405. if( aPad->GetCustomShapeInZoneOpt() == CUST_PAD_SHAPE_IN_ZONE_CONVEXHULL )
  406. {
  407. std::vector<wxPoint> convex_hull;
  408. BuildConvexHull( convex_hull, poly );
  409. aHoles.NewOutline();
  410. for( const wxPoint& pt : convex_hull )
  411. aHoles.Append( pt );
  412. }
  413. else
  414. aHoles.Append( poly );
  415. }
  416. else
  417. {
  418. aPad->TransformShapeWithClearanceToPolygon( aHoles, aLayer, aGap, m_maxError );
  419. }
  420. }
  421. /**
  422. * Add a knockout for a graphic item. The knockout is 'aGap' larger than the item (which
  423. * might be either the electrical clearance or the board edge clearance).
  424. */
  425. void ZONE_FILLER::addKnockout( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aGap,
  426. bool aIgnoreLineWidth, SHAPE_POLY_SET& aHoles )
  427. {
  428. switch( aItem->Type() )
  429. {
  430. case PCB_LINE_T:
  431. {
  432. DRAWSEGMENT* seg = (DRAWSEGMENT*) aItem;
  433. seg->TransformShapeWithClearanceToPolygon( aHoles, aLayer, aGap, m_maxError,
  434. aIgnoreLineWidth );
  435. break;
  436. }
  437. case PCB_TEXT_T:
  438. {
  439. TEXTE_PCB* text = (TEXTE_PCB*) aItem;
  440. text->TransformBoundingBoxWithClearanceToPolygon( &aHoles, aGap );
  441. break;
  442. }
  443. case PCB_MODULE_EDGE_T:
  444. {
  445. EDGE_MODULE* edge = (EDGE_MODULE*) aItem;
  446. edge->TransformShapeWithClearanceToPolygon( aHoles, aLayer, aGap, m_maxError,
  447. aIgnoreLineWidth );
  448. break;
  449. }
  450. case PCB_MODULE_TEXT_T:
  451. {
  452. TEXTE_MODULE* text = (TEXTE_MODULE*) aItem;
  453. if( text->IsVisible() )
  454. text->TransformBoundingBoxWithClearanceToPolygon( &aHoles, aGap );
  455. break;
  456. }
  457. default:
  458. break;
  459. }
  460. }
  461. /**
  462. * Removes thermal reliefs from the shape for any pads connected to the zone. Does NOT add
  463. * in spokes, which must be done later.
  464. */
  465. void ZONE_FILLER::knockoutThermalReliefs( const ZONE_CONTAINER* aZone, PCB_LAYER_ID aLayer,
  466. SHAPE_POLY_SET& aFill )
  467. {
  468. SHAPE_POLY_SET holes;
  469. // Use a dummy pad to calculate relief when a pad has a hole but is not on the zone's
  470. // copper layer. The dummy pad has the size and shape of the original pad's hole. We have
  471. // to give it a parent because some functions expect a non-null parent to find clearance
  472. // data, etc.
  473. MODULE dummymodule( m_board );
  474. D_PAD dummypad( &dummymodule );
  475. for( auto module : m_board->Modules() )
  476. {
  477. for( auto pad : module->Pads() )
  478. {
  479. if( !hasThermalConnection( pad, aZone ) )
  480. continue;
  481. // If the pad isn't on the current layer but has a hole, knock out a thermal relief
  482. // for the hole.
  483. if( !pad->IsOnLayer( aLayer ) )
  484. {
  485. if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 )
  486. continue;
  487. setupDummyPadForHole( pad, dummypad );
  488. pad = &dummypad;
  489. }
  490. addKnockout( pad, aLayer, aZone->GetThermalReliefGap( pad ), holes );
  491. }
  492. }
  493. aFill.BooleanSubtract( holes, SHAPE_POLY_SET::PM_FAST );
  494. }
  495. /**
  496. * Removes clearance from the shape for copper items which share the zone's layer but are
  497. * not connected to it.
  498. */
  499. void ZONE_FILLER::buildCopperItemClearances( const ZONE_CONTAINER* aZone, PCB_LAYER_ID aLayer,
  500. SHAPE_POLY_SET& aHoles )
  501. {
  502. static DRAWSEGMENT dummyEdge;
  503. dummyEdge.SetParent( m_board );
  504. dummyEdge.SetLayer( Edge_Cuts );
  505. // A small extra clearance to be sure actual track clearances are not smaller than
  506. // requested clearance due to many approximations in calculations, like arc to segment
  507. // approx, rounding issues, etc.
  508. // 1 micron is a good value
  509. int extra_margin = Millimeter2iu( ADVANCED_CFG::GetCfg().m_ExtraClearance );
  510. BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
  511. int zone_clearance = aZone->GetLocalClearance();
  512. EDA_RECT zone_boundingbox = aZone->GetBoundingBox();
  513. // Items outside the zone bounding box are skipped, so it needs to be inflated by the
  514. // largest clearance value found in the netclasses and rules
  515. int biggest_clearance = std::max( zone_clearance, bds.GetBiggestClearanceValue() );
  516. zone_boundingbox.Inflate( biggest_clearance + extra_margin );
  517. // Use a dummy pad to calculate hole clearance when a pad has a hole but is not on the
  518. // zone's copper layer. The dummy pad has the size and shape of the original pad's hole.
  519. // We have to give it a parent because some functions expect a non-null parent to find
  520. // clearance data, etc.
  521. MODULE dummymodule( m_board );
  522. D_PAD dummypad( &dummymodule );
  523. // Add non-connected pad clearances
  524. //
  525. for( MODULE* module : m_board->Modules() )
  526. {
  527. for( D_PAD* pad : module->Pads() )
  528. {
  529. if( !pad->IsPadOnLayer( aLayer ) )
  530. {
  531. if( pad->GetDrillSize().x == 0 && pad->GetDrillSize().y == 0 )
  532. continue;
  533. setupDummyPadForHole( pad, dummypad );
  534. pad = &dummypad;
  535. }
  536. if( pad->GetNetCode() != aZone->GetNetCode() || pad->GetNetCode() <= 0
  537. || aZone->GetPadConnection( pad ) == ZONE_CONNECTION::NONE )
  538. {
  539. if( pad->GetBoundingBox().Intersects( zone_boundingbox ) )
  540. {
  541. int gap;
  542. // for pads having the same netcode as the zone, the net clearance has no
  543. // meaning so use the greater of the zone clearance and the thermal relief
  544. if( pad->GetNetCode() > 0 && pad->GetNetCode() == aZone->GetNetCode() )
  545. gap = std::max( zone_clearance, aZone->GetThermalReliefGap( pad ) );
  546. else
  547. gap = aZone->GetClearance( aLayer, pad );
  548. addKnockout( pad, aLayer, gap, aHoles );
  549. }
  550. }
  551. }
  552. }
  553. // Add non-connected track clearances
  554. //
  555. for( TRACK* track : m_board->Tracks() )
  556. {
  557. if( !track->IsOnLayer( aLayer ) )
  558. continue;
  559. if( track->GetNetCode() == aZone->GetNetCode() && ( aZone->GetNetCode() != 0) )
  560. continue;
  561. if( track->GetBoundingBox().Intersects( zone_boundingbox ) )
  562. {
  563. int gap = aZone->GetClearance( aLayer, track ) + extra_margin;
  564. if( track->Type() == PCB_VIA_T )
  565. {
  566. VIA* via = static_cast<VIA*>( track );
  567. if( !via->IsPadOnLayer( aLayer ) )
  568. {
  569. int radius = via->GetDrillValue() / 2 + bds.GetHolePlatingThickness() + gap;
  570. TransformCircleToPolygon( aHoles, via->GetPosition(), radius, m_maxError );
  571. }
  572. else
  573. {
  574. via->TransformShapeWithClearanceToPolygon( aHoles, aLayer, gap, m_maxError );
  575. }
  576. }
  577. else
  578. {
  579. track->TransformShapeWithClearanceToPolygon( aHoles, aLayer, gap, m_maxError );
  580. }
  581. }
  582. }
  583. // Add graphic item clearances. They are by definition unconnected, and have no clearance
  584. // definitions of their own.
  585. //
  586. auto doGraphicItem =
  587. [&]( BOARD_ITEM* aItem )
  588. {
  589. // A item on the Edge_Cuts is always seen as on any layer:
  590. if( !aItem->IsOnLayer( aLayer ) && !aItem->IsOnLayer( Edge_Cuts ) )
  591. return;
  592. if( aItem->GetBoundingBox().Intersects( zone_boundingbox ) )
  593. {
  594. PCB_LAYER_ID layer = aLayer;
  595. bool ignoreLineWidth = false;
  596. if( aItem->IsOnLayer( Edge_Cuts ) )
  597. {
  598. layer = Edge_Cuts;
  599. ignoreLineWidth = true;
  600. }
  601. int gap = aZone->GetClearance( aLayer, aItem ) + extra_margin;
  602. addKnockout( aItem, layer, gap, ignoreLineWidth, aHoles );
  603. }
  604. };
  605. for( MODULE* module : m_board->Modules() )
  606. {
  607. doGraphicItem( &module->Reference() );
  608. doGraphicItem( &module->Value() );
  609. for( BOARD_ITEM* item : module->GraphicalItems() )
  610. doGraphicItem( item );
  611. }
  612. for( BOARD_ITEM* item : m_board->Drawings() )
  613. doGraphicItem( item );
  614. // Add zones outlines having an higher priority and keepout
  615. //
  616. for( ZONE_CONTAINER* zone : m_board->GetZoneList( true ) )
  617. {
  618. // If the zones share no common layers
  619. if( !zone->GetLayerSet().test( aLayer ) )
  620. continue;
  621. if( !zone->GetIsKeepout() && zone->GetPriority() <= aZone->GetPriority() )
  622. continue;
  623. if( zone->GetIsKeepout() && !zone->GetDoNotAllowCopperPour() )
  624. continue;
  625. // A higher priority zone or keepout area is found: remove this area
  626. EDA_RECT item_boundingbox = zone->GetBoundingBox();
  627. if( item_boundingbox.Intersects( zone_boundingbox ) )
  628. {
  629. // Add the zone outline area. Don't use any clearance for keepouts, or for zones
  630. // with the same net (they will be connected but will honor their own clearance,
  631. // thermal connections, etc.).
  632. int gap = 0;
  633. if( !zone->GetIsKeepout() && aZone->GetNetCode() != zone->GetNetCode() )
  634. gap = aZone->GetClearance( aLayer, zone );
  635. zone->TransformOutlinesShapeWithClearanceToPolygon( aHoles, gap );
  636. }
  637. }
  638. aHoles.Simplify( SHAPE_POLY_SET::PM_FAST );
  639. }
  640. /**
  641. * 1 - Creates the main zone outline using a correction to shrink the resulting area by
  642. * m_ZoneMinThickness / 2. The result is areas with a margin of m_ZoneMinThickness / 2
  643. * so that when drawing outline with segments having a thickness of m_ZoneMinThickness the
  644. * outlines will match exactly the initial outlines
  645. * 2 - Knocks out thermal reliefs around thermally-connected pads
  646. * 3 - Builds a set of thermal spoke for the whole zone
  647. * 4 - Knocks out unconnected copper items, deleting any affected spokes
  648. * 5 - Removes unconnected copper islands, deleting any affected spokes
  649. * 6 - Adds in the remaining spokes
  650. */
  651. void ZONE_FILLER::computeRawFilledArea( const ZONE_CONTAINER* aZone, PCB_LAYER_ID aLayer,
  652. const SHAPE_POLY_SET& aSmoothedOutline,
  653. SHAPE_POLY_SET& aRawPolys,
  654. SHAPE_POLY_SET& aFinalPolys )
  655. {
  656. m_maxError = m_board->GetDesignSettings().m_MaxError;
  657. // Features which are min_width should survive pruning; features that are *less* than
  658. // min_width should not. Therefore we subtract epsilon from the min_width when
  659. // deflating/inflating.
  660. int half_min_width = aZone->GetMinThickness() / 2;
  661. int epsilon = Millimeter2iu( 0.001 );
  662. int numSegs = GetArcToSegmentCount( half_min_width, m_maxError, 360.0 );
  663. SHAPE_POLY_SET::CORNER_STRATEGY cornerStrategy;
  664. if( aZone->GetCornerSmoothingType() == ZONE_SETTINGS::SMOOTHING_FILLET )
  665. cornerStrategy = SHAPE_POLY_SET::ROUND_ACUTE_CORNERS;
  666. else
  667. cornerStrategy = SHAPE_POLY_SET::CHAMFER_ACUTE_CORNERS;
  668. std::deque<SHAPE_LINE_CHAIN> thermalSpokes;
  669. SHAPE_POLY_SET clearanceHoles;
  670. std::unique_ptr<SHAPE_FILE_IO> dumper( new SHAPE_FILE_IO(
  671. s_DumpZonesWhenFilling ? "zones_dump.txt" : "", SHAPE_FILE_IO::IOM_APPEND ) );
  672. aRawPolys = aSmoothedOutline;
  673. if( s_DumpZonesWhenFilling )
  674. dumper->BeginGroup( "clipper-zone" );
  675. if( m_progressReporter && m_progressReporter->IsCancelled() )
  676. return;
  677. knockoutThermalReliefs( aZone, aLayer, aRawPolys );
  678. if( s_DumpZonesWhenFilling )
  679. dumper->Write( &aRawPolys, "solid-areas-minus-thermal-reliefs" );
  680. if( m_progressReporter && m_progressReporter->IsCancelled() )
  681. return;
  682. buildCopperItemClearances( aZone, aLayer, clearanceHoles );
  683. if( s_DumpZonesWhenFilling )
  684. dumper->Write( &aRawPolys, "clearance holes" );
  685. if( m_progressReporter && m_progressReporter->IsCancelled() )
  686. return;
  687. buildThermalSpokes( aZone, aLayer, thermalSpokes );
  688. if( m_progressReporter && m_progressReporter->IsCancelled() )
  689. return;
  690. // Create a temporary zone that we can hit-test spoke-ends against. It's only temporary
  691. // because the "real" subtract-clearance-holes has to be done after the spokes are added.
  692. static const bool USE_BBOX_CACHES = true;
  693. SHAPE_POLY_SET testAreas = aRawPolys;
  694. testAreas.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
  695. // Prune features that don't meet minimum-width criteria
  696. if( half_min_width - epsilon > epsilon )
  697. {
  698. testAreas.Deflate( half_min_width - epsilon, numSegs, cornerStrategy );
  699. testAreas.Inflate( half_min_width - epsilon, numSegs, cornerStrategy );
  700. }
  701. if( m_progressReporter && m_progressReporter->IsCancelled() )
  702. return;
  703. // Spoke-end-testing is hugely expensive so we generate cached bounding-boxes to speed
  704. // things up a bit.
  705. testAreas.BuildBBoxCaches();
  706. int interval = 0;
  707. for( const SHAPE_LINE_CHAIN& spoke : thermalSpokes )
  708. {
  709. const VECTOR2I& testPt = spoke.CPoint( 3 );
  710. // Hit-test against zone body
  711. if( testAreas.Contains( testPt, -1, 1, USE_BBOX_CACHES ) )
  712. {
  713. aRawPolys.AddOutline( spoke );
  714. continue;
  715. }
  716. if( interval++ > 400 )
  717. {
  718. if( m_progressReporter && m_progressReporter->IsCancelled() )
  719. return;
  720. interval = 0;
  721. }
  722. // Hit-test against other spokes
  723. for( const SHAPE_LINE_CHAIN& other : thermalSpokes )
  724. {
  725. if( &other != &spoke && other.PointInside( testPt, 1, USE_BBOX_CACHES ) )
  726. {
  727. aRawPolys.AddOutline( spoke );
  728. break;
  729. }
  730. }
  731. }
  732. if( m_progressReporter && m_progressReporter->IsCancelled() )
  733. return;
  734. if( s_DumpZonesWhenFilling )
  735. dumper->Write( &aRawPolys, "solid-areas-with-thermal-spokes" );
  736. if( m_progressReporter && m_progressReporter->IsCancelled() )
  737. return;
  738. aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
  739. // Prune features that don't meet minimum-width criteria
  740. if( half_min_width - epsilon > epsilon )
  741. aRawPolys.Deflate( half_min_width - epsilon, numSegs, cornerStrategy );
  742. if( s_DumpZonesWhenFilling )
  743. dumper->Write( &aRawPolys, "solid-areas-before-hatching" );
  744. if( m_progressReporter && m_progressReporter->IsCancelled() )
  745. return;
  746. // Now remove the non filled areas due to the hatch pattern
  747. if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
  748. addHatchFillTypeOnZone( aZone, aLayer, aRawPolys );
  749. if( s_DumpZonesWhenFilling )
  750. dumper->Write( &aRawPolys, "solid-areas-after-hatching" );
  751. if( m_progressReporter && m_progressReporter->IsCancelled() )
  752. return;
  753. // Re-inflate after pruning of areas that don't meet minimum-width criteria
  754. if( aZone->GetFilledPolysUseThickness() )
  755. {
  756. // If we're stroking the zone with a min_width stroke then this will naturally inflate
  757. // the zone by half_min_width
  758. }
  759. else if( half_min_width - epsilon > epsilon )
  760. {
  761. aRawPolys.Inflate( half_min_width - epsilon, numSegs, cornerStrategy );
  762. }
  763. // Ensure additive changes (thermal stubs and particularly inflating acute corners) do not
  764. // add copper outside the zone boundary or inside the clearance holes
  765. aRawPolys.BooleanIntersection( aSmoothedOutline, SHAPE_POLY_SET::PM_FAST );
  766. aRawPolys.BooleanSubtract( clearanceHoles, SHAPE_POLY_SET::PM_FAST );
  767. aRawPolys.Fracture( SHAPE_POLY_SET::PM_FAST );
  768. if( s_DumpZonesWhenFilling )
  769. dumper->Write( &aRawPolys, "areas_fractured" );
  770. aFinalPolys = aRawPolys;
  771. if( s_DumpZonesWhenFilling )
  772. dumper->EndGroup();
  773. }
  774. /*
  775. * Build the filled solid areas data from real outlines (stored in m_Poly)
  776. * The solid areas can be more than one on copper layers, and do not have holes
  777. * ( holes are linked by overlapping segments to the main outline)
  778. */
  779. bool ZONE_FILLER::fillSingleZone( ZONE_CONTAINER* aZone, PCB_LAYER_ID aLayer,
  780. SHAPE_POLY_SET& aRawPolys, SHAPE_POLY_SET& aFinalPolys )
  781. {
  782. SHAPE_POLY_SET smoothedPoly;
  783. /*
  784. * convert outlines + holes to outlines without holes (adding extra segments if necessary)
  785. * m_Poly data is expected normalized, i.e. NormalizeAreaOutlines was used after building
  786. * this zone
  787. */
  788. if ( !aZone->BuildSmoothedPoly( smoothedPoly, aLayer ) )
  789. return false;
  790. if( m_progressReporter && m_progressReporter->IsCancelled() )
  791. return false;
  792. if( aZone->IsOnCopperLayer() )
  793. {
  794. computeRawFilledArea( aZone, aLayer, smoothedPoly, aRawPolys, aFinalPolys );
  795. }
  796. else
  797. {
  798. // Features which are min_width should survive pruning; features that are *less* than
  799. // min_width should not. Therefore we subtract epsilon from the min_width when
  800. // deflating/inflating.
  801. int half_min_width = aZone->GetMinThickness() / 2;
  802. int epsilon = Millimeter2iu( 0.001 );
  803. int numSegs = GetArcToSegmentCount( half_min_width, m_maxError, 360.0 );
  804. if( m_brdOutlinesValid )
  805. smoothedPoly.BooleanIntersection( m_boardOutline, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  806. smoothedPoly.Deflate( half_min_width - epsilon, numSegs );
  807. // Remove the non filled areas due to the hatch pattern
  808. if( aZone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN )
  809. addHatchFillTypeOnZone( aZone, aLayer, smoothedPoly );
  810. // Re-inflate after pruning of areas that don't meet minimum-width criteria
  811. if( aZone->GetFilledPolysUseThickness() )
  812. {
  813. // If we're stroking the zone with a min_width stroke then this will naturally
  814. // inflate the zone by half_min_width
  815. }
  816. else if( half_min_width - epsilon > epsilon )
  817. smoothedPoly.Deflate( -( half_min_width - epsilon ), numSegs );
  818. aRawPolys = smoothedPoly;
  819. aFinalPolys = smoothedPoly;
  820. aFinalPolys.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  821. }
  822. aZone->SetNeedRefill( false );
  823. return true;
  824. }
  825. /**
  826. * Function buildThermalSpokes
  827. */
  828. void ZONE_FILLER::buildThermalSpokes( const ZONE_CONTAINER* aZone, PCB_LAYER_ID aLayer,
  829. std::deque<SHAPE_LINE_CHAIN>& aSpokesList )
  830. {
  831. auto zoneBB = aZone->GetBoundingBox();
  832. int zone_clearance = aZone->GetLocalClearance();
  833. int biggest_clearance = m_board->GetDesignSettings().GetBiggestClearanceValue();
  834. biggest_clearance = std::max( biggest_clearance, zone_clearance );
  835. zoneBB.Inflate( biggest_clearance );
  836. // Is a point on the boundary of the polygon inside or outside? This small epsilon lets
  837. // us avoid the question.
  838. int epsilon = KiROUND( IU_PER_MM * 0.04 ); // about 1.5 mil
  839. for( auto module : m_board->Modules() )
  840. {
  841. for( auto pad : module->Pads() )
  842. {
  843. if( !hasThermalConnection( pad, aZone ) )
  844. continue;
  845. // We currently only connect to pads, not pad holes
  846. if( !pad->IsOnLayer( aLayer ) )
  847. continue;
  848. int thermalReliefGap = aZone->GetThermalReliefGap( pad );
  849. // Calculate thermal bridge half width
  850. int spoke_w = aZone->GetThermalReliefCopperBridge( pad );
  851. // Avoid spoke_w bigger than the smaller pad size, because
  852. // it is not possible to create stubs bigger than the pad.
  853. // Possible refinement: have a separate size for vertical and horizontal stubs
  854. spoke_w = std::min( spoke_w, pad->GetSize().x );
  855. spoke_w = std::min( spoke_w, pad->GetSize().y );
  856. // Cannot create stubs having a width < zone min thickness
  857. if( spoke_w <= aZone->GetMinThickness() )
  858. continue;
  859. int spoke_half_w = spoke_w / 2;
  860. // Quick test here to possibly save us some work
  861. BOX2I itemBB = pad->GetBoundingBox();
  862. itemBB.Inflate( thermalReliefGap + epsilon );
  863. if( !( itemBB.Intersects( zoneBB ) ) )
  864. continue;
  865. // Thermal spokes consist of segments from the pad center to points just outside
  866. // the thermal relief.
  867. //
  868. // We use the bounding-box to lay out the spokes, but for this to work the
  869. // bounding box has to be built at the same rotation as the spokes.
  870. // We have to use a dummy pad to avoid dirtying the cached shapes
  871. wxPoint shapePos = pad->ShapePos();
  872. double padAngle = pad->GetOrientation();
  873. D_PAD dummy_pad( *pad );
  874. dummy_pad.SetOrientation( 0.0 );
  875. dummy_pad.SetPosition( { 0, 0 } );
  876. BOX2I reliefBB = dummy_pad.GetBoundingBox();
  877. reliefBB.Inflate( thermalReliefGap + epsilon );
  878. // For circle pads, the thermal spoke orientation is 45 deg
  879. if( pad->GetShape() == PAD_SHAPE_CIRCLE )
  880. padAngle = s_RoundPadThermalSpokeAngle;
  881. for( int i = 0; i < 4; i++ )
  882. {
  883. SHAPE_LINE_CHAIN spoke;
  884. switch( i )
  885. {
  886. case 0: // lower stub
  887. spoke.Append( +spoke_half_w, -spoke_half_w );
  888. spoke.Append( -spoke_half_w, -spoke_half_w );
  889. spoke.Append( -spoke_half_w, reliefBB.GetBottom() );
  890. spoke.Append( 0, reliefBB.GetBottom() ); // test pt
  891. spoke.Append( +spoke_half_w, reliefBB.GetBottom() );
  892. break;
  893. case 1: // upper stub
  894. spoke.Append( +spoke_half_w, spoke_half_w );
  895. spoke.Append( -spoke_half_w, spoke_half_w );
  896. spoke.Append( -spoke_half_w, reliefBB.GetTop() );
  897. spoke.Append( 0, reliefBB.GetTop() ); // test pt
  898. spoke.Append( +spoke_half_w, reliefBB.GetTop() );
  899. break;
  900. case 2: // right stub
  901. spoke.Append( -spoke_half_w, spoke_half_w );
  902. spoke.Append( -spoke_half_w, -spoke_half_w );
  903. spoke.Append( reliefBB.GetRight(), -spoke_half_w );
  904. spoke.Append( reliefBB.GetRight(), 0 ); // test pt
  905. spoke.Append( reliefBB.GetRight(), spoke_half_w );
  906. break;
  907. case 3: // left stub
  908. spoke.Append( spoke_half_w, spoke_half_w );
  909. spoke.Append( spoke_half_w, -spoke_half_w );
  910. spoke.Append( reliefBB.GetLeft(), -spoke_half_w );
  911. spoke.Append( reliefBB.GetLeft(), 0 ); // test pt
  912. spoke.Append( reliefBB.GetLeft(), spoke_half_w );
  913. break;
  914. }
  915. spoke.Rotate( -DECIDEG2RAD( padAngle ) );
  916. spoke.Move( shapePos );
  917. spoke.SetClosed( true );
  918. spoke.GenerateBBoxCache();
  919. aSpokesList.push_back( std::move( spoke ) );
  920. }
  921. }
  922. }
  923. }
  924. void ZONE_FILLER::addHatchFillTypeOnZone( const ZONE_CONTAINER* aZone, PCB_LAYER_ID aLayer,
  925. SHAPE_POLY_SET& aRawPolys )
  926. {
  927. // Build grid:
  928. // obviously line thickness must be > zone min thickness.
  929. // It can happens if a board file was edited by hand by a python script
  930. // Use 1 micron margin to be *sure* there is no issue in Gerber files
  931. // (Gbr file unit = 1 or 10 nm) due to some truncation in coordinates or calculations
  932. // This margin also avoid problems due to rounding coordinates in next calculations
  933. // that can create incorrect polygons
  934. int thickness = std::max( aZone->GetHatchThickness(),
  935. aZone->GetMinThickness() + Millimeter2iu( 0.001 ) );
  936. int linethickness = thickness - aZone->GetMinThickness();
  937. int gridsize = thickness + aZone->GetHatchGap();
  938. double orientation = aZone->GetHatchOrientation();
  939. SHAPE_POLY_SET filledPolys = aRawPolys;
  940. // Use a area that contains the rotated bbox by orientation,
  941. // and after rotate the result by -orientation.
  942. if( orientation != 0.0 )
  943. filledPolys.Rotate( M_PI/180.0 * orientation, VECTOR2I( 0,0 ) );
  944. BOX2I bbox = filledPolys.BBox( 0 );
  945. // Build hole shape
  946. // the hole size is aZone->GetHatchGap(), but because the outline thickness
  947. // is aZone->GetMinThickness(), the hole shape size must be larger
  948. SHAPE_LINE_CHAIN hole_base;
  949. int hole_size = aZone->GetHatchGap() + aZone->GetMinThickness();
  950. VECTOR2I corner( 0, 0 );;
  951. hole_base.Append( corner );
  952. corner.x += hole_size;
  953. hole_base.Append( corner );
  954. corner.y += hole_size;
  955. hole_base.Append( corner );
  956. corner.x = 0;
  957. hole_base.Append( corner );
  958. hole_base.SetClosed( true );
  959. // Calculate minimal area of a grid hole.
  960. // All holes smaller than a threshold will be removed
  961. double minimal_hole_area = hole_base.Area() * aZone->GetHatchHoleMinArea();
  962. // Now convert this hole to a smoothed shape:
  963. if( aZone->GetHatchSmoothingLevel() > 0 )
  964. {
  965. // the actual size of chamfer, or rounded corner radius is the half size
  966. // of the HatchFillTypeGap scaled by aZone->GetHatchSmoothingValue()
  967. // aZone->GetHatchSmoothingValue() = 1.0 is the max value for the chamfer or the
  968. // radius of corner (radius = half size of the hole)
  969. int smooth_value = KiROUND( aZone->GetHatchGap()
  970. * aZone->GetHatchSmoothingValue() / 2 );
  971. // Minimal optimization:
  972. // make smoothing only for reasonnable smooth values, to avoid a lot of useless segments
  973. // and if the smooth value is small, use chamfer even if fillet is requested
  974. #define SMOOTH_MIN_VAL_MM 0.02
  975. #define SMOOTH_SMALL_VAL_MM 0.04
  976. if( smooth_value > Millimeter2iu( SMOOTH_MIN_VAL_MM ) )
  977. {
  978. SHAPE_POLY_SET smooth_hole;
  979. smooth_hole.AddOutline( hole_base );
  980. int smooth_level = aZone->GetHatchSmoothingLevel();
  981. if( smooth_value < Millimeter2iu( SMOOTH_SMALL_VAL_MM ) && smooth_level > 1 )
  982. smooth_level = 1;
  983. // Use a larger smooth_value to compensate the outline tickness
  984. // (chamfer is not visible is smooth value < outline thickess)
  985. smooth_value += aZone->GetMinThickness() / 2;
  986. // smooth_value cannot be bigger than the half size oh the hole:
  987. smooth_value = std::min( smooth_value, aZone->GetHatchGap() / 2 );
  988. // the error to approximate a circle by segments when smoothing corners by a arc
  989. int error_max = std::max( Millimeter2iu( 0.01 ), smooth_value / 20 );
  990. switch( smooth_level )
  991. {
  992. case 1:
  993. // Chamfer() uses the distance from a corner to create a end point
  994. // for the chamfer.
  995. hole_base = smooth_hole.Chamfer( smooth_value ).Outline( 0 );
  996. break;
  997. default:
  998. if( aZone->GetHatchSmoothingLevel() > 2 )
  999. error_max /= 2; // Force better smoothing
  1000. hole_base = smooth_hole.Fillet( smooth_value, error_max ).Outline( 0 );
  1001. break;
  1002. case 0:
  1003. break;
  1004. };
  1005. }
  1006. }
  1007. // Build holes
  1008. SHAPE_POLY_SET holes;
  1009. for( int xx = 0; ; xx++ )
  1010. {
  1011. int xpos = xx * gridsize;
  1012. if( xpos > bbox.GetWidth() )
  1013. break;
  1014. for( int yy = 0; ; yy++ )
  1015. {
  1016. int ypos = yy * gridsize;
  1017. if( ypos > bbox.GetHeight() )
  1018. break;
  1019. // Generate hole
  1020. SHAPE_LINE_CHAIN hole( hole_base );
  1021. hole.Move( VECTOR2I( xpos, ypos ) );
  1022. holes.AddOutline( hole );
  1023. }
  1024. }
  1025. holes.Move( bbox.GetPosition() );
  1026. // We must buffer holes by at least aZone->GetMinThickness() to guarantee that thermal
  1027. // reliefs can be built (and to give the zone a solid outline). However, it looks more
  1028. // visually consistent if the buffer width is the same as the hatch width.
  1029. int outline_margin = KiROUND( aZone->GetMinThickness() * 1.1 );
  1030. if( aZone->GetHatchBorderAlgorithm() )
  1031. outline_margin = std::max( outline_margin, aZone->GetHatchThickness() );
  1032. if( outline_margin > linethickness / 2 )
  1033. filledPolys.Deflate( outline_margin - linethickness / 2, 16 );
  1034. holes.BooleanIntersection( filledPolys, SHAPE_POLY_SET::PM_FAST );
  1035. if( orientation != 0.0 )
  1036. holes.Rotate( -M_PI/180.0 * orientation, VECTOR2I( 0,0 ) );
  1037. if( aZone->GetNetCode() != 0 )
  1038. {
  1039. // Vias and pads connected to the zone must not be allowed to become isolated inside
  1040. // one of the holes. Effectively this means their copper outline needs to be expanded
  1041. // to be at least as wide as the gap so that it is guaranteed to touch at least one
  1042. // edge.
  1043. EDA_RECT zone_boundingbox = aZone->GetBoundingBox();
  1044. SHAPE_POLY_SET aprons;
  1045. int min_apron_radius = ( aZone->GetHatchGap() * 10 ) / 19;
  1046. for( TRACK* track : m_board->Tracks() )
  1047. {
  1048. if( track->Type() == PCB_VIA_T )
  1049. {
  1050. VIA* via = static_cast<VIA*>( track );
  1051. if( via->GetNetCode() == aZone->GetNetCode()
  1052. && via->IsOnLayer( aLayer )
  1053. && via->GetBoundingBox().Intersects( zone_boundingbox ) )
  1054. {
  1055. int r = std::max( min_apron_radius,
  1056. via->GetDrillValue() / 2 + outline_margin );
  1057. TransformCircleToPolygon( aprons, via->GetPosition(), r, ARC_HIGH_DEF );
  1058. }
  1059. }
  1060. }
  1061. for( MODULE* module : m_board->Modules() )
  1062. {
  1063. for( D_PAD* pad : module->Pads() )
  1064. {
  1065. if( pad->GetNetCode() == aZone->GetNetCode()
  1066. && pad->IsOnLayer( aLayer )
  1067. && pad->GetBoundingBox().Intersects( zone_boundingbox ) )
  1068. {
  1069. // What we want is to bulk up the pad shape so that the narrowest bit of
  1070. // copper between the hole and the apron edge is at least outline_margin
  1071. // wide (and that the apron itself meets min_apron_radius. But that would
  1072. // take a lot of code and math, and the following approximation is close
  1073. // enough.
  1074. int pad_width = std::min( pad->GetSize().x, pad->GetSize().y );
  1075. int slot_width = std::min( pad->GetDrillSize().x, pad->GetDrillSize().y );
  1076. int min_annulus = ( pad_width - slot_width ) / 2;
  1077. int clearance = std::max( min_apron_radius - pad_width / 2,
  1078. outline_margin - min_annulus );
  1079. clearance = std::max( 0, clearance - linethickness / 2 );
  1080. pad->TransformShapeWithClearanceToPolygon( aprons, aLayer, clearance,
  1081. ARC_HIGH_DEF );
  1082. }
  1083. }
  1084. }
  1085. holes.BooleanSubtract( aprons, SHAPE_POLY_SET::PM_FAST );
  1086. }
  1087. // Now filter truncated holes to avoid small holes in pattern
  1088. // It happens for holes near the zone outline
  1089. for( int ii = 0; ii < holes.OutlineCount(); )
  1090. {
  1091. double area = holes.Outline( ii ).Area();
  1092. if( area < minimal_hole_area ) // The current hole is too small: remove it
  1093. holes.DeletePolygon( ii );
  1094. else
  1095. ++ii;
  1096. }
  1097. // create grid. Use SHAPE_POLY_SET::PM_STRICTLY_SIMPLE to
  1098. // generate strictly simple polygons needed by Gerber files and Fracture()
  1099. aRawPolys.BooleanSubtract( aRawPolys, holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  1100. }