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.

1499 lines
45 KiB

4 years ago
5 years ago
7 years ago
7 years ago
7 years ago
4 years ago
3 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
  6. * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your 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 <bitmaps.h>
  26. #include <geometry/geometry_utils.h>
  27. #include <geometry/shape_null.h>
  28. #include <pcb_edit_frame.h>
  29. #include <pcb_screen.h>
  30. #include <board.h>
  31. #include <board_design_settings.h>
  32. #include <pad.h>
  33. #include <zone.h>
  34. #include <string_utils.h>
  35. #include <math_for_graphics.h>
  36. #include <settings/color_settings.h>
  37. #include <settings/settings_manager.h>
  38. #include <trigo.h>
  39. #include <i18n_utility.h>
  40. ZONE::ZONE( BOARD_ITEM_CONTAINER* aParent, bool aInFP ) :
  41. BOARD_CONNECTED_ITEM( aParent, aInFP ? PCB_FP_ZONE_T : PCB_ZONE_T ),
  42. m_area( 0.0 ),
  43. m_outlinearea( 0.0 )
  44. {
  45. m_Poly = new SHAPE_POLY_SET(); // Outlines
  46. m_cornerSmoothingType = ZONE_SETTINGS::SMOOTHING_NONE;
  47. m_cornerRadius = 0;
  48. m_zoneName = wxEmptyString;
  49. m_CornerSelection = nullptr; // no corner is selected
  50. m_isFilled = false; // fill status : true when the zone is filled
  51. m_teardropType = TEARDROP_TYPE::TD_NONE;
  52. m_islandRemovalMode = ISLAND_REMOVAL_MODE::ALWAYS;
  53. m_borderStyle = ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE;
  54. m_borderHatchPitch = GetDefaultHatchPitch();
  55. m_priority = 0;
  56. SetIsRuleArea( aInFP ); // Zones living in footprints have the rule area option
  57. SetLocalFlags( 0 ); // flags temporary used in zone calculations
  58. m_fillVersion = 5; // set the "old" way to build filled polygon areas (< 6.0.x)
  59. m_ZoneMinThickness = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, ZONE_THICKNESS_MIL );
  60. m_thermalReliefSpokeWidth = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, ZONE_THERMAL_RELIEF_COPPER_WIDTH_MIL );
  61. m_thermalReliefGap = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, ZONE_THERMAL_RELIEF_GAP_MIL );
  62. aParent->GetZoneSettings().ExportSetting( *this );
  63. m_needRefill = false; // True only after edits.
  64. }
  65. ZONE::ZONE( const ZONE& aZone ) :
  66. BOARD_CONNECTED_ITEM( aZone ),
  67. m_Poly( nullptr ),
  68. m_CornerSelection( nullptr )
  69. {
  70. InitDataFromSrcInCopyCtor( aZone );
  71. }
  72. ZONE& ZONE::operator=( const ZONE& aOther )
  73. {
  74. BOARD_CONNECTED_ITEM::operator=( aOther );
  75. InitDataFromSrcInCopyCtor( aOther );
  76. return *this;
  77. }
  78. ZONE::~ZONE()
  79. {
  80. delete m_Poly;
  81. delete m_CornerSelection;
  82. }
  83. void ZONE::InitDataFromSrcInCopyCtor( const ZONE& aZone )
  84. {
  85. // members are expected non initialize in this.
  86. // InitDataFromSrcInCopyCtor() is expected to be called only from a copy constructor.
  87. // Copy only useful EDA_ITEM flags:
  88. m_flags = aZone.m_flags;
  89. m_forceVisible = aZone.m_forceVisible;
  90. // Replace the outlines for aZone outlines.
  91. delete m_Poly;
  92. m_Poly = new SHAPE_POLY_SET( *aZone.m_Poly );
  93. m_cornerSmoothingType = aZone.m_cornerSmoothingType;
  94. m_cornerRadius = aZone.m_cornerRadius;
  95. m_zoneName = aZone.m_zoneName;
  96. m_priority = aZone.m_priority;
  97. m_isRuleArea = aZone.m_isRuleArea;
  98. SetLayerSet( aZone.GetLayerSet() );
  99. m_doNotAllowCopperPour = aZone.m_doNotAllowCopperPour;
  100. m_doNotAllowVias = aZone.m_doNotAllowVias;
  101. m_doNotAllowTracks = aZone.m_doNotAllowTracks;
  102. m_doNotAllowPads = aZone.m_doNotAllowPads;
  103. m_doNotAllowFootprints = aZone.m_doNotAllowFootprints;
  104. m_PadConnection = aZone.m_PadConnection;
  105. m_ZoneClearance = aZone.m_ZoneClearance; // clearance value
  106. m_ZoneMinThickness = aZone.m_ZoneMinThickness;
  107. m_fillVersion = aZone.m_fillVersion;
  108. m_islandRemovalMode = aZone.m_islandRemovalMode;
  109. m_minIslandArea = aZone.m_minIslandArea;
  110. m_isFilled = aZone.m_isFilled;
  111. m_needRefill = aZone.m_needRefill;
  112. m_teardropType = aZone.m_teardropType;
  113. m_thermalReliefGap = aZone.m_thermalReliefGap;
  114. m_thermalReliefSpokeWidth = aZone.m_thermalReliefSpokeWidth;
  115. m_fillMode = aZone.m_fillMode; // solid vs. hatched
  116. m_hatchThickness = aZone.m_hatchThickness;
  117. m_hatchGap = aZone.m_hatchGap;
  118. m_hatchOrientation = aZone.m_hatchOrientation;
  119. m_hatchSmoothingLevel = aZone.m_hatchSmoothingLevel;
  120. m_hatchSmoothingValue = aZone.m_hatchSmoothingValue;
  121. m_hatchBorderAlgorithm = aZone.m_hatchBorderAlgorithm;
  122. m_hatchHoleMinArea = aZone.m_hatchHoleMinArea;
  123. // For corner moving, corner index to drag, or nullptr if no selection
  124. delete m_CornerSelection;
  125. m_CornerSelection = nullptr;
  126. for( PCB_LAYER_ID layer : aZone.GetLayerSet().Seq() )
  127. {
  128. std::shared_ptr<SHAPE_POLY_SET> fill = aZone.m_FilledPolysList.at( layer );
  129. if( fill )
  130. m_FilledPolysList[layer] = std::make_shared<SHAPE_POLY_SET>( *fill );
  131. else
  132. m_FilledPolysList[layer] = std::make_shared<SHAPE_POLY_SET>();
  133. m_filledPolysHash[layer] = aZone.m_filledPolysHash.at( layer );
  134. m_insulatedIslands[layer] = aZone.m_insulatedIslands.at( layer );
  135. }
  136. m_borderStyle = aZone.m_borderStyle;
  137. m_borderHatchPitch = aZone.m_borderHatchPitch;
  138. m_borderHatchLines = aZone.m_borderHatchLines;
  139. SetLocalFlags( aZone.GetLocalFlags() );
  140. m_netinfo = aZone.m_netinfo;
  141. m_area = aZone.m_area;
  142. m_outlinearea = aZone.m_outlinearea;
  143. }
  144. EDA_ITEM* ZONE::Clone() const
  145. {
  146. return new ZONE( *this );
  147. }
  148. bool ZONE::HigherPriority( const ZONE* aOther ) const
  149. {
  150. // Teardrops are always higher priority than regular zones, so if one zone is a teardrop
  151. // and the other is not, then return higher priority as the teardrop
  152. if( ( m_teardropType == TEARDROP_TYPE::TD_NONE )
  153. ^ ( aOther->m_teardropType == TEARDROP_TYPE::TD_NONE ) )
  154. {
  155. return static_cast<int>( m_teardropType ) > static_cast<int>( aOther->m_teardropType );
  156. }
  157. if( m_priority != aOther->m_priority )
  158. return m_priority > aOther->m_priority;
  159. return m_Uuid > aOther->m_Uuid;
  160. }
  161. bool ZONE::SameNet( const ZONE* aOther ) const
  162. {
  163. return GetNetCode() == aOther->GetNetCode();
  164. }
  165. bool ZONE::UnFill()
  166. {
  167. bool change = false;
  168. for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& pair : m_FilledPolysList )
  169. {
  170. change |= !pair.second->IsEmpty();
  171. m_insulatedIslands[pair.first].clear();
  172. pair.second->RemoveAllContours();
  173. }
  174. m_isFilled = false;
  175. m_fillFlags.reset();
  176. return change;
  177. }
  178. bool ZONE::IsConflicting() const
  179. {
  180. return HasFlag( COURTYARD_CONFLICT );
  181. }
  182. VECTOR2I ZONE::GetPosition() const
  183. {
  184. return GetCornerPosition( 0 );
  185. }
  186. PCB_LAYER_ID ZONE::GetLayer() const
  187. {
  188. return BOARD_ITEM::GetLayer();
  189. }
  190. PCB_LAYER_ID ZONE::GetFirstLayer() const
  191. {
  192. if( m_layerSet.size() )
  193. return m_layerSet.UIOrder()[0];
  194. else
  195. return UNDEFINED_LAYER;
  196. }
  197. bool ZONE::IsOnCopperLayer() const
  198. {
  199. return ( m_layerSet & LSET::AllCuMask() ).count() > 0;
  200. }
  201. void ZONE::SetLayer( PCB_LAYER_ID aLayer )
  202. {
  203. SetLayerSet( LSET( aLayer ) );
  204. }
  205. void ZONE::SetLayerSet( LSET aLayerSet )
  206. {
  207. if( aLayerSet.count() == 0 )
  208. return;
  209. if( m_layerSet != aLayerSet )
  210. {
  211. SetNeedRefill( true );
  212. UnFill();
  213. m_FilledPolysList.clear();
  214. m_filledPolysHash.clear();
  215. m_insulatedIslands.clear();
  216. for( PCB_LAYER_ID layer : aLayerSet.Seq() )
  217. {
  218. m_FilledPolysList[layer] = std::make_shared<SHAPE_POLY_SET>();
  219. m_filledPolysHash[layer] = {};
  220. m_insulatedIslands[layer] = {};
  221. }
  222. }
  223. m_layerSet = aLayerSet;
  224. }
  225. void ZONE::ViewGetLayers( int aLayers[], int& aCount ) const
  226. {
  227. aCount = 0;
  228. LSEQ layers = m_layerSet.Seq();
  229. for( PCB_LAYER_ID layer : m_layerSet.Seq() )
  230. {
  231. aLayers[ aCount++ ] = layer; // For outline (always full opacity)
  232. aLayers[ aCount++ ] = layer + LAYER_ZONE_START; // For fill (obeys global zone opacity)
  233. }
  234. if( IsConflicting() )
  235. aLayers[ aCount++ ] = LAYER_CONFLICTS_SHADOW;
  236. }
  237. double ZONE::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
  238. {
  239. constexpr double HIDE = std::numeric_limits<double>::max();
  240. return aView->IsLayerVisible( LAYER_ZONES ) ? 0.0 : HIDE;
  241. }
  242. bool ZONE::IsOnLayer( PCB_LAYER_ID aLayer ) const
  243. {
  244. return m_layerSet.test( aLayer );
  245. }
  246. const BOX2I ZONE::GetBoundingBox() const
  247. {
  248. if( const BOARD* board = GetBoard() )
  249. {
  250. std::unordered_map<const ZONE*, BOX2I>& cache = board->m_ZoneBBoxCache;
  251. auto cacheIter = cache.find( this );
  252. if( cacheIter != cache.end() )
  253. return cacheIter->second;
  254. BOX2I bbox = m_Poly->BBox();
  255. std::unique_lock<std::mutex> cacheLock( const_cast<BOARD*>( board )->m_CachesMutex );
  256. cache[ this ] = bbox;
  257. return bbox;
  258. }
  259. return m_Poly->BBox();
  260. }
  261. void ZONE::CacheBoundingBox()
  262. {
  263. BOARD* board = GetBoard();
  264. std::unordered_map<const ZONE*, BOX2I>& cache = board->m_ZoneBBoxCache;
  265. auto cacheIter = cache.find( this );
  266. if( cacheIter == cache.end() )
  267. {
  268. std::unique_lock<std::mutex> cacheLock( board->m_CachesMutex );
  269. cache[ this ] = m_Poly->BBox();
  270. }
  271. }
  272. int ZONE::GetThermalReliefGap( PAD* aPad, wxString* aSource ) const
  273. {
  274. if( aPad->GetLocalThermalGapOverride() == 0 )
  275. {
  276. if( aSource )
  277. *aSource = _( "zone" );
  278. return m_thermalReliefGap;
  279. }
  280. return aPad->GetLocalThermalGapOverride( aSource );
  281. }
  282. void ZONE::SetCornerRadius( unsigned int aRadius )
  283. {
  284. if( m_cornerRadius != aRadius )
  285. SetNeedRefill( true );
  286. m_cornerRadius = aRadius;
  287. }
  288. static SHAPE_POLY_SET g_nullPoly;
  289. MD5_HASH ZONE::GetHashValue( PCB_LAYER_ID aLayer )
  290. {
  291. if( !m_filledPolysHash.count( aLayer ) )
  292. return g_nullPoly.GetHash();
  293. else
  294. return m_filledPolysHash.at( aLayer );
  295. }
  296. void ZONE::BuildHashValue( PCB_LAYER_ID aLayer )
  297. {
  298. if( !m_FilledPolysList.count( aLayer ) )
  299. m_filledPolysHash[aLayer] = g_nullPoly.GetHash();
  300. else
  301. m_filledPolysHash[aLayer] = m_FilledPolysList.at( aLayer )->GetHash();
  302. }
  303. bool ZONE::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
  304. {
  305. // When looking for an "exact" hit aAccuracy will be 0 which works poorly for very thin
  306. // lines. Give it a floor.
  307. int accuracy = std::max( aAccuracy, pcbIUScale.mmToIU( 0.1 ) );
  308. return HitTestForCorner( aPosition, accuracy * 2 ) || HitTestForEdge( aPosition, accuracy );
  309. }
  310. bool ZONE::HitTestForCorner( const VECTOR2I& refPos, int aAccuracy,
  311. SHAPE_POLY_SET::VERTEX_INDEX* aCornerHit ) const
  312. {
  313. return m_Poly->CollideVertex( VECTOR2I( refPos ), aCornerHit, aAccuracy );
  314. }
  315. bool ZONE::HitTestForEdge( const VECTOR2I& refPos, int aAccuracy,
  316. SHAPE_POLY_SET::VERTEX_INDEX* aCornerHit ) const
  317. {
  318. return m_Poly->CollideEdge( VECTOR2I( refPos ), aCornerHit, aAccuracy );
  319. }
  320. bool ZONE::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
  321. {
  322. // Calculate bounding box for zone
  323. BOX2I bbox = GetBoundingBox();
  324. bbox.Normalize();
  325. BOX2I arect = aRect;
  326. arect.Normalize();
  327. arect.Inflate( aAccuracy );
  328. if( aContained )
  329. {
  330. return arect.Contains( bbox );
  331. }
  332. else
  333. {
  334. // Fast test: if aBox is outside the polygon bounding box, rectangles cannot intersect
  335. if( !arect.Intersects( bbox ) )
  336. return false;
  337. int count = m_Poly->TotalVertices();
  338. for( int ii = 0; ii < count; ii++ )
  339. {
  340. VECTOR2I vertex = m_Poly->CVertex( ii );
  341. VECTOR2I vertexNext = m_Poly->CVertex( ( ii + 1 ) % count );
  342. // Test if the point is within the rect
  343. if( arect.Contains( vertex ) )
  344. return true;
  345. // Test if this edge intersects the rect
  346. if( arect.Intersects( vertex, vertexNext ) )
  347. return true;
  348. }
  349. return false;
  350. }
  351. }
  352. int ZONE::GetLocalClearance( wxString* aSource ) const
  353. {
  354. if( m_isRuleArea )
  355. return 0;
  356. if( aSource )
  357. *aSource = _( "zone" );
  358. return m_ZoneClearance;
  359. }
  360. bool ZONE::HitTestFilledArea( PCB_LAYER_ID aLayer, const VECTOR2I& aRefPos, int aAccuracy ) const
  361. {
  362. // Rule areas have no filled area, but it's generally nice to treat their interior as if it were
  363. // filled so that people don't have to select them by their outline (which is min-width)
  364. if( GetIsRuleArea() )
  365. return m_Poly->Contains( aRefPos, -1, aAccuracy );
  366. if( !m_FilledPolysList.count( aLayer ) )
  367. return false;
  368. return m_FilledPolysList.at( aLayer )->Contains( aRefPos, -1, aAccuracy );
  369. }
  370. bool ZONE::HitTestCutout( const VECTOR2I& aRefPos, int* aOutlineIdx, int* aHoleIdx ) const
  371. {
  372. // Iterate over each outline polygon in the zone and then iterate over
  373. // each hole it has to see if the point is in it.
  374. for( int i = 0; i < m_Poly->OutlineCount(); i++ )
  375. {
  376. for( int j = 0; j < m_Poly->HoleCount( i ); j++ )
  377. {
  378. if( m_Poly->Hole( i, j ).PointInside( aRefPos ) )
  379. {
  380. if( aOutlineIdx )
  381. *aOutlineIdx = i;
  382. if( aHoleIdx )
  383. *aHoleIdx = j;
  384. return true;
  385. }
  386. }
  387. }
  388. return false;
  389. }
  390. void ZONE::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  391. {
  392. wxString msg = GetFriendlyName();
  393. // Display Cutout instead of Outline for holes inside a zone (i.e. when num contour !=0).
  394. // Check whether the selected corner is in a hole; i.e., in any contour but the first one.
  395. if( m_CornerSelection != nullptr && m_CornerSelection->m_contour > 0 )
  396. msg << wxT( " " ) << _( "Cutout" );
  397. aList.emplace_back( _( "Type" ), msg );
  398. if( GetIsRuleArea() )
  399. {
  400. msg.Empty();
  401. if( GetDoNotAllowVias() )
  402. AccumulateDescription( msg, _( "No vias" ) );
  403. if( GetDoNotAllowTracks() )
  404. AccumulateDescription( msg, _( "No tracks" ) );
  405. if( GetDoNotAllowPads() )
  406. AccumulateDescription( msg, _( "No pads" ) );
  407. if( GetDoNotAllowCopperPour() )
  408. AccumulateDescription( msg, _( "No copper zones" ) );
  409. if( GetDoNotAllowFootprints() )
  410. AccumulateDescription( msg, _( "No footprints" ) );
  411. if( !msg.IsEmpty() )
  412. aList.emplace_back( _( "Restrictions" ), msg );
  413. }
  414. else if( IsOnCopperLayer() )
  415. {
  416. if( aFrame->GetName() == PCB_EDIT_FRAME_NAME )
  417. {
  418. aList.emplace_back( _( "Net" ), UnescapeString( GetNetname() ) );
  419. aList.emplace_back( _( "Resolved Netclass" ),
  420. UnescapeString( GetEffectiveNetClass()->GetName() ) );
  421. }
  422. // Display priority level
  423. aList.emplace_back( _( "Priority" ),
  424. wxString::Format( wxT( "%d" ), GetAssignedPriority() ) );
  425. }
  426. if( aFrame->GetName() == PCB_EDIT_FRAME_NAME )
  427. {
  428. if( IsLocked() )
  429. aList.emplace_back( _( "Status" ), _( "Locked" ) );
  430. }
  431. wxString layerDesc;
  432. int count = 0;
  433. for( PCB_LAYER_ID layer : m_layerSet.Seq() )
  434. {
  435. if( count == 0 )
  436. layerDesc = GetBoard()->GetLayerName( layer );
  437. count++;
  438. }
  439. if( count > 1 )
  440. layerDesc.Printf( _( "%s and %d more" ), layerDesc, count - 1 );
  441. aList.emplace_back( _( "Layer" ), layerDesc );
  442. if( !m_zoneName.empty() )
  443. aList.emplace_back( _( "Name" ), m_zoneName );
  444. switch( m_fillMode )
  445. {
  446. case ZONE_FILL_MODE::POLYGONS: msg = _( "Solid" ); break;
  447. case ZONE_FILL_MODE::HATCH_PATTERN: msg = _( "Hatched" ); break;
  448. default: msg = _( "Unknown" ); break;
  449. }
  450. aList.emplace_back( _( "Fill Mode" ), msg );
  451. aList.emplace_back( _( "Filled Area" ),
  452. aFrame->MessageTextFromValue( m_area, true, EDA_DATA_TYPE::AREA ) );
  453. wxString source;
  454. int clearance = GetOwnClearance( UNDEFINED_LAYER, &source );
  455. if( !source.IsEmpty() )
  456. {
  457. aList.emplace_back( wxString::Format( _( "Min Clearance: %s" ),
  458. aFrame->MessageTextFromValue( clearance ) ),
  459. wxString::Format( _( "(from %s)" ),
  460. source ) );
  461. }
  462. if( !m_FilledPolysList.empty() )
  463. {
  464. count = 0;
  465. for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& ii: m_FilledPolysList )
  466. count += ii.second->TotalVertices();
  467. aList.emplace_back( _( "Corner Count" ), wxString::Format( wxT( "%d" ), count ) );
  468. }
  469. }
  470. void ZONE::Move( const VECTOR2I& offset )
  471. {
  472. /* move outlines */
  473. m_Poly->Move( offset );
  474. HatchBorder();
  475. /* move fills */
  476. for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& pair : m_FilledPolysList )
  477. pair.second->Move( offset );
  478. /*
  479. * move boundingbox cache
  480. *
  481. * While the cache will get nuked at the conclusion of the operation, we use it for some
  482. * things (such as drawing the parent group) during the move.
  483. */
  484. if( GetBoard() )
  485. {
  486. auto it = GetBoard()->m_ZoneBBoxCache.find( this );
  487. if( it != GetBoard()->m_ZoneBBoxCache.end() )
  488. it->second.Move( offset );
  489. }
  490. }
  491. wxString ZONE::GetFriendlyName() const
  492. {
  493. if( GetIsRuleArea() )
  494. return _( "Rule Area" );
  495. else if( IsTeardropArea() )
  496. return _( "Teardrop Area" );
  497. else if( IsOnCopperLayer() )
  498. return _( "Copper Zone" );
  499. else
  500. return _( "Non-copper Zone" );
  501. }
  502. void ZONE::MoveEdge( const VECTOR2I& offset, int aEdge )
  503. {
  504. int next_corner;
  505. if( m_Poly->GetNeighbourIndexes( aEdge, nullptr, &next_corner ) )
  506. {
  507. m_Poly->SetVertex( aEdge, m_Poly->CVertex( aEdge ) + VECTOR2I( offset ) );
  508. m_Poly->SetVertex( next_corner, m_Poly->CVertex( next_corner ) + VECTOR2I( offset ) );
  509. HatchBorder();
  510. SetNeedRefill( true );
  511. }
  512. }
  513. void ZONE::Rotate( const VECTOR2I& aCentre, const EDA_ANGLE& aAngle )
  514. {
  515. m_Poly->Rotate( aAngle, VECTOR2I( aCentre ) );
  516. HatchBorder();
  517. /* rotate filled areas: */
  518. for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& pair : m_FilledPolysList )
  519. pair.second->Rotate( aAngle, aCentre );
  520. }
  521. void ZONE::Flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
  522. {
  523. Mirror( aCentre, aFlipLeftRight );
  524. SetLayerSet( FlipLayerMask( GetLayerSet(), GetBoard()->GetCopperLayerCount() ) );
  525. }
  526. void ZONE::Mirror( const VECTOR2I& aMirrorRef, bool aMirrorLeftRight )
  527. {
  528. m_Poly->Mirror( aMirrorLeftRight, !aMirrorLeftRight, aMirrorRef );
  529. HatchBorder();
  530. for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& pair : m_FilledPolysList )
  531. pair.second->Mirror( aMirrorLeftRight, !aMirrorLeftRight, aMirrorRef );
  532. }
  533. void ZONE::RemoveCutout( int aOutlineIdx, int aHoleIdx )
  534. {
  535. // Ensure the requested cutout is valid
  536. if( m_Poly->OutlineCount() < aOutlineIdx || m_Poly->HoleCount( aOutlineIdx ) < aHoleIdx )
  537. return;
  538. SHAPE_POLY_SET cutPoly( m_Poly->Hole( aOutlineIdx, aHoleIdx ) );
  539. // Add the cutout back to the zone
  540. m_Poly->BooleanAdd( cutPoly, SHAPE_POLY_SET::PM_FAST );
  541. SetNeedRefill( true );
  542. }
  543. void ZONE::AddPolygon( const SHAPE_LINE_CHAIN& aPolygon )
  544. {
  545. wxASSERT( aPolygon.IsClosed() );
  546. // Add the outline as a new polygon in the polygon set
  547. if( m_Poly->OutlineCount() == 0 )
  548. m_Poly->AddOutline( aPolygon );
  549. else
  550. m_Poly->AddHole( aPolygon );
  551. SetNeedRefill( true );
  552. }
  553. void ZONE::AddPolygon( std::vector<VECTOR2I>& aPolygon )
  554. {
  555. if( aPolygon.empty() )
  556. return;
  557. SHAPE_LINE_CHAIN outline;
  558. // Create an outline and populate it with the points of aPolygon
  559. for( const VECTOR2I& pt : aPolygon )
  560. outline.Append( pt );
  561. outline.SetClosed( true );
  562. AddPolygon( outline );
  563. }
  564. bool ZONE::AppendCorner( VECTOR2I aPosition, int aHoleIdx, bool aAllowDuplication )
  565. {
  566. // Ensure the main outline exists:
  567. if( m_Poly->OutlineCount() == 0 )
  568. m_Poly->NewOutline();
  569. // If aHoleIdx >= 0, the corner musty be added to the hole, index aHoleIdx.
  570. // (remember: the index of the first hole is 0)
  571. // Return error if it does not exist.
  572. if( aHoleIdx >= m_Poly->HoleCount( 0 ) )
  573. return false;
  574. m_Poly->Append( aPosition.x, aPosition.y, -1, aHoleIdx, aAllowDuplication );
  575. SetNeedRefill( true );
  576. return true;
  577. }
  578. wxString ZONE::GetSelectMenuText( UNITS_PROVIDER* aUnitsProvider ) const
  579. {
  580. wxString layerDesc;
  581. int count = 0;
  582. for( PCB_LAYER_ID layer : m_layerSet.Seq() )
  583. {
  584. if( count == 0 )
  585. layerDesc = GetBoard()->GetLayerName( layer );
  586. count++;
  587. }
  588. if( count > 1 )
  589. layerDesc.Printf( _( "%s and %d more" ), layerDesc, count - 1 );
  590. // Check whether the selected contour is a hole (contour index > 0)
  591. if( m_CornerSelection != nullptr && m_CornerSelection->m_contour > 0 )
  592. {
  593. if( GetIsRuleArea() )
  594. return wxString::Format( _( "Rule Area Cutout on %s" ), layerDesc );
  595. else
  596. return wxString::Format( _( "Zone Cutout on %s" ), layerDesc );
  597. }
  598. else
  599. {
  600. if( GetIsRuleArea() )
  601. return wxString::Format( _( "Rule Area on %s" ), layerDesc );
  602. else
  603. return wxString::Format( _( "Zone %s on %s" ), GetNetnameMsg(), layerDesc );
  604. }
  605. }
  606. int ZONE::GetBorderHatchPitch() const
  607. {
  608. return m_borderHatchPitch;
  609. }
  610. void ZONE::SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE aBorderHatchStyle,
  611. int aBorderHatchPitch, bool aRebuildBorderHatch )
  612. {
  613. aBorderHatchPitch = std::max( aBorderHatchPitch,
  614. pcbIUScale.mmToIU( ZONE_BORDER_HATCH_MINDIST_MM ) );
  615. aBorderHatchPitch = std::min( aBorderHatchPitch,
  616. pcbIUScale.mmToIU( ZONE_BORDER_HATCH_MAXDIST_MM ) );
  617. SetBorderHatchPitch( aBorderHatchPitch );
  618. m_borderStyle = aBorderHatchStyle;
  619. if( aRebuildBorderHatch )
  620. HatchBorder();
  621. }
  622. void ZONE::SetBorderHatchPitch( int aPitch )
  623. {
  624. m_borderHatchPitch = aPitch;
  625. }
  626. void ZONE::UnHatchBorder()
  627. {
  628. m_borderHatchLines.clear();
  629. }
  630. // Creates hatch lines inside the outline of the complex polygon
  631. // sort function used in ::HatchBorder to sort points by descending VECTOR2I.x values
  632. bool sortEndsByDescendingX( const VECTOR2I& ref, const VECTOR2I& tst )
  633. {
  634. return tst.x < ref.x;
  635. }
  636. void ZONE::HatchBorder()
  637. {
  638. UnHatchBorder();
  639. if( m_borderStyle == ZONE_BORDER_DISPLAY_STYLE::NO_HATCH
  640. || m_borderHatchPitch == 0
  641. || m_Poly->IsEmpty() )
  642. {
  643. return;
  644. }
  645. // define range for hatch lines
  646. int min_x = m_Poly->CVertex( 0 ).x;
  647. int max_x = m_Poly->CVertex( 0 ).x;
  648. int min_y = m_Poly->CVertex( 0 ).y;
  649. int max_y = m_Poly->CVertex( 0 ).y;
  650. for( auto iterator = m_Poly->IterateWithHoles(); iterator; iterator++ )
  651. {
  652. if( iterator->x < min_x )
  653. min_x = iterator->x;
  654. if( iterator->x > max_x )
  655. max_x = iterator->x;
  656. if( iterator->y < min_y )
  657. min_y = iterator->y;
  658. if( iterator->y > max_y )
  659. max_y = iterator->y;
  660. }
  661. // Calculate spacing between 2 hatch lines
  662. int spacing;
  663. if( m_borderStyle == ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE )
  664. spacing = m_borderHatchPitch;
  665. else
  666. spacing = m_borderHatchPitch * 2;
  667. // set the "length" of hatch lines (the length on horizontal axis)
  668. int hatch_line_len = m_borderHatchPitch;
  669. // To have a better look, give a slope depending on the layer
  670. int layer = GetFirstLayer();
  671. int slope_flag = (layer & 1) ? 1 : -1; // 1 or -1
  672. double slope = 0.707106 * slope_flag; // 45 degrees slope
  673. int max_a, min_a;
  674. if( slope_flag == 1 )
  675. {
  676. max_a = KiROUND( max_y - slope * min_x );
  677. min_a = KiROUND( min_y - slope * max_x );
  678. }
  679. else
  680. {
  681. max_a = KiROUND( max_y - slope * max_x );
  682. min_a = KiROUND( min_y - slope * min_x );
  683. }
  684. min_a = (min_a / spacing) * spacing;
  685. // calculate an offset depending on layer number,
  686. // for a better look of hatches on a multilayer board
  687. int offset = (layer * 7) / 8;
  688. min_a += offset;
  689. // loop through hatch lines
  690. std::vector<VECTOR2I> pointbuffer;
  691. pointbuffer.reserve( 256 );
  692. for( int a = min_a; a < max_a; a += spacing )
  693. {
  694. pointbuffer.clear();
  695. // Iterate through all vertices
  696. for( auto iterator = m_Poly->IterateSegmentsWithHoles(); iterator; iterator++ )
  697. {
  698. const SEG seg = *iterator;
  699. double x, y;
  700. if( FindLineSegmentIntersection( a, slope, seg.A.x, seg.A.y, seg.B.x, seg.B.y, x, y ) )
  701. pointbuffer.emplace_back( KiROUND( x ), KiROUND( y ) );
  702. }
  703. // sort points in order of descending x (if more than 2) to
  704. // ensure the starting point and the ending point of the same segment
  705. // are stored one just after the other.
  706. if( pointbuffer.size() > 2 )
  707. sort( pointbuffer.begin(), pointbuffer.end(), sortEndsByDescendingX );
  708. // creates lines or short segments inside the complex polygon
  709. for( size_t ip = 0; ip + 1 < pointbuffer.size(); ip += 2 )
  710. {
  711. int dx = pointbuffer[ip + 1].x - pointbuffer[ip].x;
  712. // Push only one line for diagonal hatch,
  713. // or for small lines < twice the line length
  714. // else push 2 small lines
  715. if( m_borderStyle == ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_FULL
  716. || std::abs( dx ) < 2 * hatch_line_len )
  717. {
  718. m_borderHatchLines.emplace_back( SEG( pointbuffer[ip], pointbuffer[ ip + 1] ) );
  719. }
  720. else
  721. {
  722. double dy = pointbuffer[ip + 1].y - pointbuffer[ip].y;
  723. slope = dy / dx;
  724. if( dx > 0 )
  725. dx = hatch_line_len;
  726. else
  727. dx = -hatch_line_len;
  728. int x1 = KiROUND( pointbuffer[ip].x + dx );
  729. int x2 = KiROUND( pointbuffer[ip + 1].x - dx );
  730. int y1 = KiROUND( pointbuffer[ip].y + dx * slope );
  731. int y2 = KiROUND( pointbuffer[ip + 1].y - dx * slope );
  732. m_borderHatchLines.emplace_back( SEG( pointbuffer[ip].x, pointbuffer[ip].y,
  733. x1, y1 ) );
  734. m_borderHatchLines.emplace_back( SEG( pointbuffer[ip+1].x, pointbuffer[ip+1].y,
  735. x2, y2 ) );
  736. }
  737. }
  738. }
  739. }
  740. int ZONE::GetDefaultHatchPitch()
  741. {
  742. return EDA_UNIT_UTILS::Mils2IU( pcbIUScale, ZONE_BORDER_HATCH_DIST_MIL );
  743. }
  744. BITMAPS ZONE::GetMenuImage() const
  745. {
  746. return BITMAPS::add_zone;
  747. }
  748. void ZONE::swapData( BOARD_ITEM* aImage )
  749. {
  750. assert( aImage->Type() == PCB_ZONE_T || aImage->Type() == PCB_FP_ZONE_T );
  751. std::swap( *static_cast<ZONE*>( this ), *static_cast<ZONE*>( aImage) );
  752. }
  753. void ZONE::CacheTriangulation( PCB_LAYER_ID aLayer )
  754. {
  755. if( aLayer == UNDEFINED_LAYER )
  756. {
  757. for( auto& [ layer, poly ] : m_FilledPolysList )
  758. poly->CacheTriangulation();
  759. m_Poly->CacheTriangulation( false );
  760. }
  761. else
  762. {
  763. if( m_FilledPolysList.count( aLayer ) )
  764. m_FilledPolysList[ aLayer ]->CacheTriangulation();
  765. }
  766. }
  767. bool ZONE::IsIsland( PCB_LAYER_ID aLayer, int aPolyIdx ) const
  768. {
  769. if( GetNetCode() < 1 )
  770. return true;
  771. if( !m_insulatedIslands.count( aLayer ) )
  772. return false;
  773. return m_insulatedIslands.at( aLayer ).count( aPolyIdx );
  774. }
  775. void ZONE::GetInteractingZones( PCB_LAYER_ID aLayer, std::vector<ZONE*>* aSameNetCollidingZones,
  776. std::vector<ZONE*>* aOtherNetIntersectingZones ) const
  777. {
  778. int epsilon = pcbIUScale.mmToIU( 0.001 );
  779. BOX2I bbox = GetBoundingBox();
  780. bbox.Inflate( epsilon );
  781. for( ZONE* candidate : GetBoard()->Zones() )
  782. {
  783. if( candidate == this )
  784. continue;
  785. if( !candidate->GetLayerSet().test( aLayer ) )
  786. continue;
  787. if( candidate->GetIsRuleArea() || candidate->IsTeardropArea() )
  788. continue;
  789. if( !candidate->GetBoundingBox().Intersects( bbox ) )
  790. continue;
  791. if( candidate->GetNetCode() == GetNetCode() )
  792. {
  793. if( m_Poly->Collide( candidate->m_Poly ) )
  794. aSameNetCollidingZones->push_back( candidate );
  795. }
  796. else
  797. {
  798. aOtherNetIntersectingZones->push_back( candidate );
  799. }
  800. }
  801. }
  802. bool ZONE::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly, PCB_LAYER_ID aLayer,
  803. SHAPE_POLY_SET* aBoardOutline,
  804. SHAPE_POLY_SET* aSmoothedPolyWithApron ) const
  805. {
  806. if( GetNumCorners() <= 2 ) // malformed zone. polygon calculations will not like it ...
  807. return false;
  808. // Processing of arc shapes in zones is not yet supported because Clipper can't do boolean
  809. // operations on them. The poly outline must be converted to segments first.
  810. SHAPE_POLY_SET flattened = m_Poly->CloneDropTriangulation();
  811. flattened.ClearArcs();
  812. if( GetIsRuleArea() )
  813. {
  814. // We like keepouts just the way they are....
  815. aSmoothedPoly = flattened;
  816. return true;
  817. }
  818. const BOARD* board = GetBoard();
  819. int maxError = ARC_HIGH_DEF;
  820. bool keepExternalFillets = false;
  821. bool smooth_requested = m_cornerSmoothingType == ZONE_SETTINGS::SMOOTHING_CHAMFER
  822. || m_cornerSmoothingType == ZONE_SETTINGS::SMOOTHING_FILLET;
  823. if( IsTeardropArea() )
  824. {
  825. // We use teardrop shapes with no smoothing; these shapes are already optimized
  826. smooth_requested = false;
  827. }
  828. if( board )
  829. {
  830. BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
  831. maxError = bds.m_MaxError;
  832. keepExternalFillets = bds.m_ZoneKeepExternalFillets;
  833. }
  834. auto smooth = [&]( SHAPE_POLY_SET& aPoly )
  835. {
  836. if( !smooth_requested )
  837. return;
  838. switch( m_cornerSmoothingType )
  839. {
  840. case ZONE_SETTINGS::SMOOTHING_CHAMFER:
  841. aPoly = aPoly.Chamfer( (int) m_cornerRadius );
  842. break;
  843. case ZONE_SETTINGS::SMOOTHING_FILLET:
  844. {
  845. aPoly = aPoly.Fillet( (int) m_cornerRadius, maxError );
  846. break;
  847. }
  848. default:
  849. break;
  850. }
  851. };
  852. SHAPE_POLY_SET* maxExtents = &flattened;
  853. SHAPE_POLY_SET withFillets;
  854. aSmoothedPoly = flattened;
  855. // Should external fillets (that is, those applied to concave corners) be kept? While it
  856. // seems safer to never have copper extend outside the zone outline, 5.1.x and prior did
  857. // indeed fill them so we leave the mode available.
  858. if( keepExternalFillets && smooth_requested )
  859. {
  860. withFillets = flattened;
  861. smooth( withFillets );
  862. withFillets.BooleanAdd( flattened, SHAPE_POLY_SET::PM_FAST );
  863. maxExtents = &withFillets;
  864. }
  865. // We now add in the areas of any same-net, intersecting zones. This keeps us from smoothing
  866. // corners at an intersection (which often produces undesired divots between the intersecting
  867. // zones -- see #2752).
  868. //
  869. // After smoothing, we'll subtract back out everything outside of our zone.
  870. std::vector<ZONE*> sameNetCollidingZones;
  871. std::vector<ZONE*> otherNetIntersectingZones;
  872. GetInteractingZones( aLayer, &sameNetCollidingZones, &otherNetIntersectingZones );
  873. for( ZONE* sameNetZone : sameNetCollidingZones )
  874. {
  875. BOX2I sameNetBoundingBox = sameNetZone->GetBoundingBox();
  876. SHAPE_POLY_SET sameNetPoly = sameNetZone->Outline()->CloneDropTriangulation();
  877. sameNetPoly.ClearArcs();
  878. // Of course there's always a wrinkle. The same-net intersecting zone *might* get knocked
  879. // out along the border by a higher-priority, different-net zone. #12797
  880. for( ZONE* otherNetZone : otherNetIntersectingZones )
  881. {
  882. if( otherNetZone->HigherPriority( sameNetZone )
  883. && otherNetZone->GetBoundingBox().Intersects( sameNetBoundingBox ) )
  884. {
  885. sameNetPoly.BooleanSubtract( *otherNetZone->Outline(), SHAPE_POLY_SET::PM_FAST );
  886. }
  887. }
  888. aSmoothedPoly.BooleanAdd( sameNetPoly, SHAPE_POLY_SET::PM_FAST );
  889. }
  890. if( aBoardOutline )
  891. aSmoothedPoly.BooleanIntersection( *aBoardOutline, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  892. smooth( aSmoothedPoly );
  893. if( aSmoothedPolyWithApron )
  894. {
  895. SHAPE_POLY_SET poly = maxExtents->CloneDropTriangulation();
  896. poly.Inflate( m_ZoneMinThickness, 64 );
  897. *aSmoothedPolyWithApron = aSmoothedPoly;
  898. aSmoothedPolyWithApron->BooleanIntersection( poly, SHAPE_POLY_SET::PM_FAST );
  899. }
  900. aSmoothedPoly.BooleanIntersection( *maxExtents, SHAPE_POLY_SET::PM_FAST );
  901. return true;
  902. }
  903. double ZONE::CalculateFilledArea()
  904. {
  905. m_area = 0.0;
  906. // Iterate over each outline polygon in the zone and then iterate over
  907. // each hole it has to compute the total area.
  908. for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& pair : m_FilledPolysList )
  909. {
  910. std::shared_ptr<SHAPE_POLY_SET>& poly = pair.second;
  911. for( int i = 0; i < poly->OutlineCount(); i++ )
  912. {
  913. m_area += poly->Outline( i ).Area();
  914. for( int j = 0; j < poly->HoleCount( i ); j++ )
  915. m_area -= poly->Hole( i, j ).Area();
  916. }
  917. }
  918. return m_area;
  919. }
  920. double ZONE::CalculateOutlineArea()
  921. {
  922. m_outlinearea = std::abs( m_Poly->Area() );
  923. return m_outlinearea;
  924. }
  925. void ZONE::TransformSmoothedOutlineToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance,
  926. int aMaxError, ERROR_LOC aErrorLoc,
  927. SHAPE_POLY_SET* aBoardOutline ) const
  928. {
  929. // Creates the zone outline polygon (with holes if any)
  930. SHAPE_POLY_SET polybuffer;
  931. // TODO: using GetFirstLayer() means it only works for single-layer zones....
  932. BuildSmoothedPoly( polybuffer, GetFirstLayer(), aBoardOutline );
  933. // Calculate the polygon with clearance
  934. // holes are linked to the main outline, so only one polygon is created.
  935. if( aClearance )
  936. {
  937. const BOARD* board = GetBoard();
  938. int maxError = ARC_HIGH_DEF;
  939. if( board )
  940. maxError = board->GetDesignSettings().m_MaxError;
  941. int segCount = GetArcToSegmentCount( aClearance, maxError, FULL_CIRCLE );
  942. if( aErrorLoc == ERROR_OUTSIDE )
  943. aClearance += aMaxError;
  944. polybuffer.Inflate( aClearance, segCount );
  945. }
  946. polybuffer.Fracture( SHAPE_POLY_SET::PM_FAST );
  947. aBuffer.Append( polybuffer );
  948. }
  949. FP_ZONE::FP_ZONE( BOARD_ITEM_CONTAINER* aParent ) :
  950. ZONE( aParent, true )
  951. {
  952. // in a footprint, net classes are not managed.
  953. // so set the net to NETINFO_LIST::ORPHANED_ITEM
  954. SetNetCode( -1, true );
  955. }
  956. FP_ZONE::FP_ZONE( const FP_ZONE& aZone ) :
  957. ZONE( aZone )
  958. {
  959. InitDataFromSrcInCopyCtor( aZone );
  960. }
  961. FP_ZONE& FP_ZONE::operator=( const FP_ZONE& aOther )
  962. {
  963. ZONE::operator=( aOther );
  964. return *this;
  965. }
  966. EDA_ITEM* FP_ZONE::Clone() const
  967. {
  968. return new FP_ZONE( *this );
  969. }
  970. double FP_ZONE::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
  971. {
  972. constexpr double HIDE = (double)std::numeric_limits<double>::max();
  973. if( !aView )
  974. return 0;
  975. if( !aView->IsLayerVisible( LAYER_ZONES ) )
  976. return HIDE;
  977. bool flipped = GetParent() && GetParent()->GetLayer() == B_Cu;
  978. // Handle Render tab switches
  979. if( !flipped && !aView->IsLayerVisible( LAYER_MOD_FR ) )
  980. return HIDE;
  981. if( flipped && !aView->IsLayerVisible( LAYER_MOD_BK ) )
  982. return HIDE;
  983. // Other layers are shown without any conditions
  984. return 0.0;
  985. }
  986. std::shared_ptr<SHAPE> ZONE::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const
  987. {
  988. if( m_FilledPolysList.find( aLayer ) == m_FilledPolysList.end() )
  989. return std::make_shared<SHAPE_NULL>();
  990. else
  991. return m_FilledPolysList.at( aLayer );
  992. }
  993. void ZONE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance,
  994. int aError, ERROR_LOC aErrorLoc, bool aIgnoreLineWidth ) const
  995. {
  996. wxASSERT_MSG( !aIgnoreLineWidth, wxT( "IgnoreLineWidth has no meaning for zones." ) );
  997. if( !m_FilledPolysList.count( aLayer ) )
  998. return;
  999. if( !aClearance )
  1000. {
  1001. aBuffer.Append( *m_FilledPolysList.at( aLayer ) );
  1002. return;
  1003. }
  1004. SHAPE_POLY_SET temp_buf = m_FilledPolysList.at( aLayer )->CloneDropTriangulation();
  1005. // Rebuild filled areas only if clearance is not 0
  1006. int numSegs = GetArcToSegmentCount( aClearance, aError, FULL_CIRCLE );
  1007. if( aErrorLoc == ERROR_OUTSIDE )
  1008. aClearance += aError;
  1009. temp_buf.InflateWithLinkedHoles( aClearance, numSegs, SHAPE_POLY_SET::PM_FAST );
  1010. aBuffer.Append( temp_buf );
  1011. }
  1012. void ZONE::TransformSolidAreasShapesToPolygon( PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aBuffer ) const
  1013. {
  1014. if( m_FilledPolysList.count( aLayer ) && !m_FilledPolysList.at( aLayer )->IsEmpty() )
  1015. aBuffer.Append( *m_FilledPolysList.at( aLayer ) );
  1016. }
  1017. static struct ZONE_DESC
  1018. {
  1019. ZONE_DESC()
  1020. {
  1021. ENUM_MAP<ZONE_CONNECTION>& zcMap = ENUM_MAP<ZONE_CONNECTION>::Instance();
  1022. if( zcMap.Choices().GetCount() == 0 )
  1023. {
  1024. zcMap.Undefined( ZONE_CONNECTION::INHERITED );
  1025. zcMap.Map( ZONE_CONNECTION::INHERITED, _HKI( "Inherited" ) )
  1026. .Map( ZONE_CONNECTION::NONE, _HKI( "None" ) )
  1027. .Map( ZONE_CONNECTION::THERMAL, _HKI( "Thermal reliefs" ) )
  1028. .Map( ZONE_CONNECTION::FULL, _HKI( "Solid" ) )
  1029. .Map( ZONE_CONNECTION::THT_THERMAL, _HKI( "Thermal reliefs for PTH" ) );
  1030. }
  1031. ENUM_MAP<ZONE_FILL_MODE>& zfmMap = ENUM_MAP<ZONE_FILL_MODE>::Instance();
  1032. if( zfmMap.Choices().GetCount() == 0 )
  1033. {
  1034. zfmMap.Undefined( ZONE_FILL_MODE::POLYGONS );
  1035. zfmMap.Map( ZONE_FILL_MODE::POLYGONS, _HKI( "Solid fill" ) )
  1036. .Map( ZONE_FILL_MODE::HATCH_PATTERN, _HKI( "Hatch pattern" ) );
  1037. }
  1038. PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
  1039. REGISTER_TYPE( ZONE );
  1040. propMgr.InheritsAfter( TYPE_HASH( ZONE ), TYPE_HASH( BOARD_CONNECTED_ITEM ) );
  1041. // Mask layer and position properties; they aren't useful in current form
  1042. auto posX = new PROPERTY<ZONE, int>( _HKI( "Position X" ),
  1043. NO_SETTER( ZONE, int ),
  1044. reinterpret_cast<int (ZONE::*)() const>( &ZONE::GetX ),
  1045. PROPERTY_DISPLAY::PT_COORD,
  1046. ORIGIN_TRANSFORMS::ABS_X_COORD );
  1047. posX->SetIsInternal( true );
  1048. auto posY = new PROPERTY<ZONE, int>( _HKI( "Position Y" ), NO_SETTER( ZONE, int ),
  1049. reinterpret_cast<int (ZONE::*)() const>( &ZONE::GetY ),
  1050. PROPERTY_DISPLAY::PT_COORD,
  1051. ORIGIN_TRANSFORMS::ABS_Y_COORD );
  1052. posY->SetIsInternal( true );
  1053. propMgr.ReplaceProperty( TYPE_HASH( BOARD_ITEM ), _HKI( "Position X" ), posX );
  1054. propMgr.ReplaceProperty( TYPE_HASH( BOARD_ITEM ), _HKI( "Position Y" ), posY );
  1055. auto isCopperZone =
  1056. []( INSPECTABLE* aItem ) -> bool
  1057. {
  1058. if( ZONE* zone = dynamic_cast<ZONE*>( aItem ) )
  1059. return !zone->GetIsRuleArea() && IsCopperLayer( zone->GetFirstLayer() );
  1060. return false;
  1061. };
  1062. auto isHatchedFill =
  1063. []( INSPECTABLE* aItem ) -> bool
  1064. {
  1065. if( ZONE* zone = dynamic_cast<ZONE*>( aItem ) )
  1066. return zone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN;
  1067. return false;
  1068. };
  1069. auto layer = new PROPERTY_ENUM<ZONE, PCB_LAYER_ID>( _HKI( "Layer" ),
  1070. &ZONE::SetLayer, &ZONE::GetLayer );
  1071. layer->SetIsInternal( true );
  1072. propMgr.ReplaceProperty( TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Layer" ), layer );
  1073. propMgr.OverrideAvailability( TYPE_HASH( ZONE ), TYPE_HASH( BOARD_CONNECTED_ITEM ),
  1074. _HKI( "Net" ), isCopperZone );
  1075. propMgr.OverrideAvailability( TYPE_HASH( ZONE ), TYPE_HASH( BOARD_CONNECTED_ITEM ),
  1076. _HKI( "Net Class" ), isCopperZone );
  1077. auto priority = new PROPERTY<ZONE, unsigned>( _HKI( "Priority" ),
  1078. &ZONE::SetAssignedPriority, &ZONE::GetAssignedPriority );
  1079. priority->SetAvailableFunc( isCopperZone );
  1080. propMgr.AddProperty( priority );
  1081. propMgr.AddProperty( new PROPERTY<ZONE, wxString>( _HKI( "Name" ),
  1082. &ZONE::SetZoneName, &ZONE::GetZoneName ) );
  1083. const wxString groupFill = _HKI( "Fill Style" );
  1084. propMgr.AddProperty( new PROPERTY_ENUM<ZONE, ZONE_FILL_MODE>( _HKI( "Fill Mode" ),
  1085. &ZONE::SetFillMode, &ZONE::GetFillMode ), groupFill );
  1086. auto hatchOrientation = new PROPERTY<ZONE, EDA_ANGLE>( _HKI( "Orientation" ),
  1087. &ZONE::SetHatchOrientation, &ZONE::GetHatchOrientation,
  1088. PROPERTY_DISPLAY::PT_DEGREE );
  1089. hatchOrientation->SetWriteableFunc( isHatchedFill );
  1090. propMgr.AddProperty( hatchOrientation, groupFill );
  1091. //TODO: Switch to translated
  1092. auto hatchWidth = new PROPERTY<ZONE, int>( wxT( "Hatch Width" ),
  1093. &ZONE::SetHatchThickness, &ZONE::GetHatchThickness,
  1094. PROPERTY_DISPLAY::PT_SIZE );
  1095. hatchWidth->SetWriteableFunc( isHatchedFill );
  1096. propMgr.AddProperty( hatchWidth, groupFill );
  1097. //TODO: Switch to translated
  1098. auto hatchGap = new PROPERTY<ZONE, int>( wxT( "Hatch Gap" ),
  1099. &ZONE::SetHatchGap, &ZONE::GetHatchGap,
  1100. PROPERTY_DISPLAY::PT_SIZE );
  1101. hatchGap->SetWriteableFunc( isHatchedFill );
  1102. propMgr.AddProperty( hatchGap, groupFill );
  1103. // TODO: Smoothing effort needs to change to enum (in dialog too)
  1104. // TODO: Smoothing amount (double)
  1105. // Unexposed properties (HatchHoleMinArea / HatchBorderAlgorithm)?
  1106. const wxString groupOverrides = _HKI( "Overrides" );
  1107. auto clearanceOverride = new PROPERTY<ZONE, int>( _HKI( "Clearance Override" ),
  1108. &ZONE::SetLocalClearance, &ZONE::GetLocalClearance,
  1109. PROPERTY_DISPLAY::PT_SIZE );
  1110. clearanceOverride->SetAvailableFunc( isCopperZone );
  1111. auto minWidth = new PROPERTY<ZONE, int>( _HKI( "Minimum Width" ),
  1112. &ZONE::SetMinThickness, &ZONE::GetMinThickness,
  1113. PROPERTY_DISPLAY::PT_SIZE );
  1114. minWidth->SetAvailableFunc( isCopperZone );
  1115. auto padConnections = new PROPERTY_ENUM<ZONE, ZONE_CONNECTION>( _HKI( "Pad Connections" ),
  1116. &ZONE::SetPadConnection, &ZONE::GetPadConnection );
  1117. padConnections->SetAvailableFunc( isCopperZone );
  1118. auto thermalGap = new PROPERTY<ZONE, int>( _HKI( "Thermal Relief Gap" ),
  1119. &ZONE::SetThermalReliefGap, &ZONE::GetThermalReliefGap,
  1120. PROPERTY_DISPLAY::PT_SIZE );
  1121. thermalGap->SetAvailableFunc( isCopperZone );
  1122. auto thermalSpokeWidth = new PROPERTY<ZONE, int>( _HKI( "Thermal Relief Spoke Width" ),
  1123. &ZONE::SetThermalReliefSpokeWidth, &ZONE::GetThermalReliefSpokeWidth,
  1124. PROPERTY_DISPLAY::PT_SIZE );
  1125. thermalSpokeWidth->SetAvailableFunc( isCopperZone );
  1126. propMgr.AddProperty( clearanceOverride, groupOverrides );
  1127. propMgr.AddProperty( minWidth, groupOverrides );
  1128. propMgr.AddProperty( padConnections, groupOverrides );
  1129. propMgr.AddProperty( thermalGap, groupOverrides );
  1130. propMgr.AddProperty( thermalSpokeWidth, groupOverrides );
  1131. }
  1132. } _ZONE_DESC;
  1133. IMPLEMENT_ENUM_TO_WXANY( ZONE_CONNECTION )
  1134. IMPLEMENT_ENUM_TO_WXANY( ZONE_FILL_MODE )