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.

1761 lines
55 KiB

4 years ago
5 years ago
1 year ago
1 year ago
7 years ago
7 years ago
7 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-2024 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 <footprint.h>
  35. #include <string_utils.h>
  36. #include <math_for_graphics.h>
  37. #include <properties/property_validators.h>
  38. #include <settings/color_settings.h>
  39. #include <settings/settings_manager.h>
  40. #include <trigo.h>
  41. #include <i18n_utility.h>
  42. #include <mutex>
  43. ZONE::ZONE( BOARD_ITEM_CONTAINER* aParent ) :
  44. BOARD_CONNECTED_ITEM( aParent, PCB_ZONE_T ),
  45. m_Poly( nullptr ),
  46. m_teardropType( TEARDROP_TYPE::TD_NONE ),
  47. m_isFilled( false ),
  48. m_CornerSelection( nullptr ),
  49. m_area( 0.0 ),
  50. m_outlinearea( 0.0 )
  51. {
  52. m_Poly = new SHAPE_POLY_SET(); // Outlines
  53. SetLocalFlags( 0 ); // flags temporary used in zone calculations
  54. m_fillVersion = 5; // set the "old" way to build filled polygon areas (< 6.0.x)
  55. if( GetParentFootprint() )
  56. SetIsRuleArea( true ); // Zones living in footprints have the rule area option
  57. if( aParent->GetBoard() )
  58. aParent->GetBoard()->GetDesignSettings().GetDefaultZoneSettings().ExportSetting( *this );
  59. else
  60. ZONE_SETTINGS().ExportSetting( *this );
  61. m_needRefill = false; // True only after edits.
  62. }
  63. ZONE::ZONE( const ZONE& aZone ) :
  64. BOARD_CONNECTED_ITEM( aZone ),
  65. m_Poly( nullptr ),
  66. m_CornerSelection( nullptr )
  67. {
  68. InitDataFromSrcInCopyCtor( aZone );
  69. }
  70. ZONE& ZONE::operator=( const ZONE& aOther )
  71. {
  72. BOARD_CONNECTED_ITEM::operator=( aOther );
  73. InitDataFromSrcInCopyCtor( aOther );
  74. return *this;
  75. }
  76. ZONE::~ZONE()
  77. {
  78. delete m_Poly;
  79. delete m_CornerSelection;
  80. if( BOARD* board = GetBoard() )
  81. board->IncrementTimeStamp();
  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. aZone.GetLayerSet().RunOnLayers(
  127. [&]( PCB_LAYER_ID layer )
  128. {
  129. std::shared_ptr<SHAPE_POLY_SET> fill = aZone.m_FilledPolysList.at( layer );
  130. if( fill )
  131. m_FilledPolysList[layer] = std::make_shared<SHAPE_POLY_SET>( *fill );
  132. else
  133. m_FilledPolysList[layer] = std::make_shared<SHAPE_POLY_SET>();
  134. m_filledPolysHash[layer] = aZone.m_filledPolysHash.at( layer );
  135. m_insulatedIslands[layer] = aZone.m_insulatedIslands.at( layer );
  136. } );
  137. m_borderStyle = aZone.m_borderStyle;
  138. m_borderHatchPitch = aZone.m_borderHatchPitch;
  139. m_borderHatchLines = aZone.m_borderHatchLines;
  140. SetLocalFlags( aZone.GetLocalFlags() );
  141. m_netinfo = aZone.m_netinfo;
  142. m_area = aZone.m_area;
  143. m_outlinearea = aZone.m_outlinearea;
  144. }
  145. EDA_ITEM* ZONE::Clone() const
  146. {
  147. return new ZONE( *this );
  148. }
  149. bool ZONE::HigherPriority( const ZONE* aOther ) const
  150. {
  151. // Teardrops are always higher priority than regular zones, so if one zone is a teardrop
  152. // and the other is not, then return higher priority as the teardrop
  153. if( ( m_teardropType == TEARDROP_TYPE::TD_NONE )
  154. ^ ( aOther->m_teardropType == TEARDROP_TYPE::TD_NONE ) )
  155. {
  156. return static_cast<int>( m_teardropType ) > static_cast<int>( aOther->m_teardropType );
  157. }
  158. if( m_priority != aOther->m_priority )
  159. return m_priority > aOther->m_priority;
  160. return m_Uuid > aOther->m_Uuid;
  161. }
  162. bool ZONE::SameNet( const ZONE* aOther ) const
  163. {
  164. return GetNetCode() == aOther->GetNetCode();
  165. }
  166. bool ZONE::UnFill()
  167. {
  168. bool change = false;
  169. for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& pair : m_FilledPolysList )
  170. {
  171. change |= !pair.second->IsEmpty();
  172. m_insulatedIslands[pair.first].clear();
  173. pair.second->RemoveAllContours();
  174. }
  175. m_isFilled = false;
  176. m_fillFlags.reset();
  177. return change;
  178. }
  179. bool ZONE::IsConflicting() const
  180. {
  181. return HasFlag( COURTYARD_CONFLICT );
  182. }
  183. VECTOR2I ZONE::GetPosition() const
  184. {
  185. return GetCornerPosition( 0 );
  186. }
  187. PCB_LAYER_ID ZONE::GetLayer() const
  188. {
  189. if( m_layerSet.count() == 1 )
  190. return m_layerSet.UIOrder()[0];
  191. else
  192. return UNDEFINED_LAYER;
  193. }
  194. PCB_LAYER_ID ZONE::GetFirstLayer() const
  195. {
  196. if( m_layerSet.count() )
  197. return m_layerSet.UIOrder()[0];
  198. else
  199. return UNDEFINED_LAYER;
  200. }
  201. bool ZONE::IsOnCopperLayer() const
  202. {
  203. return ( m_layerSet & LSET::AllCuMask() ).count() > 0;
  204. }
  205. void ZONE::SetLayer( PCB_LAYER_ID aLayer )
  206. {
  207. SetLayerSet( LSET( aLayer ) );
  208. }
  209. void ZONE::SetLayerSet( LSET aLayerSet )
  210. {
  211. if( aLayerSet.count() == 0 )
  212. return;
  213. if( m_layerSet != aLayerSet )
  214. {
  215. SetNeedRefill( true );
  216. UnFill();
  217. m_FilledPolysList.clear();
  218. m_filledPolysHash.clear();
  219. m_insulatedIslands.clear();
  220. aLayerSet.RunOnLayers(
  221. [&]( PCB_LAYER_ID layer )
  222. {
  223. m_FilledPolysList[layer] = std::make_shared<SHAPE_POLY_SET>();
  224. m_filledPolysHash[layer] = {};
  225. m_insulatedIslands[layer] = {};
  226. } );
  227. }
  228. m_layerSet = aLayerSet;
  229. }
  230. void ZONE::ViewGetLayers( int aLayers[], int& aCount ) const
  231. {
  232. aCount = 0;
  233. m_layerSet.RunOnLayers(
  234. [&]( PCB_LAYER_ID layer )
  235. {
  236. aLayers[ aCount++ ] = layer;
  237. aLayers[ aCount++ ] = layer + static_cast<int>( LAYER_ZONE_START );
  238. } );
  239. if( IsConflicting() )
  240. aLayers[ aCount++ ] = LAYER_CONFLICTS_SHADOW;
  241. }
  242. double ZONE::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
  243. {
  244. constexpr double HIDE = std::numeric_limits<double>::max();
  245. if( !aView )
  246. return 0;
  247. if( !aView->IsLayerVisible( LAYER_ZONES ) )
  248. return HIDE;
  249. if( FOOTPRINT* parentFP = GetParentFootprint() )
  250. {
  251. bool flipped = parentFP->GetLayer() == B_Cu;
  252. // Handle Render tab switches
  253. if( !flipped && !aView->IsLayerVisible( LAYER_FOOTPRINTS_FR ) )
  254. return HIDE;
  255. if( flipped && !aView->IsLayerVisible( LAYER_FOOTPRINTS_BK ) )
  256. return HIDE;
  257. }
  258. // Other layers are shown without any conditions
  259. return 0.0;
  260. }
  261. bool ZONE::IsOnLayer( PCB_LAYER_ID aLayer ) const
  262. {
  263. return m_layerSet.test( aLayer );
  264. }
  265. const BOX2I ZONE::GetBoundingBox() const
  266. {
  267. if( const BOARD* board = GetBoard() )
  268. {
  269. std::unordered_map<const ZONE*, BOX2I>& cache = board->m_ZoneBBoxCache;
  270. {
  271. std::shared_lock<std::shared_mutex> readLock( board->m_CachesMutex );
  272. auto cacheIter = cache.find( this );
  273. if( cacheIter != cache.end() )
  274. return cacheIter->second;
  275. }
  276. BOX2I bbox = m_Poly->BBox();
  277. {
  278. std::unique_lock<std::shared_mutex> writeLock( board->m_CachesMutex );
  279. cache[ this ] = bbox;
  280. }
  281. return bbox;
  282. }
  283. return m_Poly->BBox();
  284. }
  285. void ZONE::CacheBoundingBox()
  286. {
  287. // GetBoundingBox() will cache it for us, and there's no sense duplicating the somewhat tricky
  288. // locking code.
  289. GetBoundingBox();
  290. }
  291. int ZONE::GetThermalReliefGap( PAD* aPad, wxString* aSource ) const
  292. {
  293. if( aPad->GetLocalThermalGapOverride() == 0 )
  294. {
  295. if( aSource )
  296. *aSource = _( "zone" );
  297. return m_thermalReliefGap;
  298. }
  299. return aPad->GetLocalThermalGapOverride( aSource );
  300. }
  301. void ZONE::SetCornerRadius( unsigned int aRadius )
  302. {
  303. if( m_cornerRadius != aRadius )
  304. SetNeedRefill( true );
  305. m_cornerRadius = aRadius;
  306. }
  307. static SHAPE_POLY_SET g_nullPoly;
  308. HASH_128 ZONE::GetHashValue( PCB_LAYER_ID aLayer )
  309. {
  310. if( !m_filledPolysHash.count( aLayer ) )
  311. return g_nullPoly.GetHash();
  312. else
  313. return m_filledPolysHash.at( aLayer );
  314. }
  315. void ZONE::BuildHashValue( PCB_LAYER_ID aLayer )
  316. {
  317. if( !m_FilledPolysList.count( aLayer ) )
  318. m_filledPolysHash[aLayer] = g_nullPoly.GetHash();
  319. else
  320. m_filledPolysHash[aLayer] = m_FilledPolysList.at( aLayer )->GetHash();
  321. }
  322. bool ZONE::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
  323. {
  324. // When looking for an "exact" hit aAccuracy will be 0 which works poorly for very thin
  325. // lines. Give it a floor.
  326. int accuracy = std::max( aAccuracy, pcbIUScale.mmToIU( 0.1 ) );
  327. return HitTestForCorner( aPosition, accuracy * 2 ) || HitTestForEdge( aPosition, accuracy );
  328. }
  329. bool ZONE::HitTestForCorner( const VECTOR2I& refPos, int aAccuracy,
  330. SHAPE_POLY_SET::VERTEX_INDEX* aCornerHit ) const
  331. {
  332. return m_Poly->CollideVertex( VECTOR2I( refPos ), aCornerHit, aAccuracy );
  333. }
  334. bool ZONE::HitTestForEdge( const VECTOR2I& refPos, int aAccuracy,
  335. SHAPE_POLY_SET::VERTEX_INDEX* aCornerHit ) const
  336. {
  337. return m_Poly->CollideEdge( VECTOR2I( refPos ), aCornerHit, aAccuracy );
  338. }
  339. bool ZONE::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
  340. {
  341. // Calculate bounding box for zone
  342. BOX2I bbox = GetBoundingBox();
  343. bbox.Normalize();
  344. BOX2I arect = aRect;
  345. arect.Normalize();
  346. arect.Inflate( aAccuracy );
  347. if( aContained )
  348. {
  349. return arect.Contains( bbox );
  350. }
  351. else
  352. {
  353. // Fast test: if aBox is outside the polygon bounding box, rectangles cannot intersect
  354. if( !arect.Intersects( bbox ) )
  355. return false;
  356. int count = m_Poly->TotalVertices();
  357. for( int ii = 0; ii < count; ii++ )
  358. {
  359. VECTOR2I vertex = m_Poly->CVertex( ii );
  360. VECTOR2I vertexNext = m_Poly->CVertex( ( ii + 1 ) % count );
  361. // Test if the point is within the rect
  362. if( arect.Contains( vertex ) )
  363. return true;
  364. // Test if this edge intersects the rect
  365. if( arect.Intersects( vertex, vertexNext ) )
  366. return true;
  367. }
  368. return false;
  369. }
  370. }
  371. std::optional<int> ZONE::GetLocalClearance() const
  372. {
  373. return m_isRuleArea ? 0 : m_ZoneClearance;
  374. }
  375. bool ZONE::HitTestFilledArea( PCB_LAYER_ID aLayer, const VECTOR2I& aRefPos, int aAccuracy ) const
  376. {
  377. // Rule areas have no filled area, but it's generally nice to treat their interior as if it were
  378. // filled so that people don't have to select them by their outline (which is min-width)
  379. if( GetIsRuleArea() )
  380. return m_Poly->Contains( aRefPos, -1, aAccuracy );
  381. if( !m_FilledPolysList.count( aLayer ) )
  382. return false;
  383. return m_FilledPolysList.at( aLayer )->Contains( aRefPos, -1, aAccuracy );
  384. }
  385. bool ZONE::HitTestCutout( const VECTOR2I& aRefPos, int* aOutlineIdx, int* aHoleIdx ) const
  386. {
  387. // Iterate over each outline polygon in the zone and then iterate over
  388. // each hole it has to see if the point is in it.
  389. for( int i = 0; i < m_Poly->OutlineCount(); i++ )
  390. {
  391. for( int j = 0; j < m_Poly->HoleCount( i ); j++ )
  392. {
  393. if( m_Poly->Hole( i, j ).PointInside( aRefPos ) )
  394. {
  395. if( aOutlineIdx )
  396. *aOutlineIdx = i;
  397. if( aHoleIdx )
  398. *aHoleIdx = j;
  399. return true;
  400. }
  401. }
  402. }
  403. return false;
  404. }
  405. void ZONE::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  406. {
  407. wxString msg = GetFriendlyName();
  408. // Display Cutout instead of Outline for holes inside a zone (i.e. when num contour !=0).
  409. // Check whether the selected corner is in a hole; i.e., in any contour but the first one.
  410. if( m_CornerSelection != nullptr && m_CornerSelection->m_contour > 0 )
  411. msg << wxT( " " ) << _( "Cutout" );
  412. aList.emplace_back( _( "Type" ), msg );
  413. if( GetIsRuleArea() )
  414. {
  415. msg.Empty();
  416. if( GetDoNotAllowVias() )
  417. AccumulateDescription( msg, _( "No vias" ) );
  418. if( GetDoNotAllowTracks() )
  419. AccumulateDescription( msg, _( "No tracks" ) );
  420. if( GetDoNotAllowPads() )
  421. AccumulateDescription( msg, _( "No pads" ) );
  422. if( GetDoNotAllowCopperPour() )
  423. AccumulateDescription( msg, _( "No copper zones" ) );
  424. if( GetDoNotAllowFootprints() )
  425. AccumulateDescription( msg, _( "No footprints" ) );
  426. if( !msg.IsEmpty() )
  427. aList.emplace_back( _( "Restrictions" ), msg );
  428. }
  429. else if( IsOnCopperLayer() )
  430. {
  431. if( aFrame->GetName() == PCB_EDIT_FRAME_NAME )
  432. {
  433. aList.emplace_back( _( "Net" ), UnescapeString( GetNetname() ) );
  434. aList.emplace_back( _( "Resolved Netclass" ),
  435. UnescapeString( GetEffectiveNetClass()->GetName() ) );
  436. }
  437. // Display priority level
  438. aList.emplace_back( _( "Priority" ),
  439. wxString::Format( wxT( "%d" ), GetAssignedPriority() ) );
  440. }
  441. if( aFrame->GetName() == PCB_EDIT_FRAME_NAME )
  442. {
  443. if( IsLocked() )
  444. aList.emplace_back( _( "Status" ), _( "Locked" ) );
  445. }
  446. LSEQ layers = m_layerSet.Seq();
  447. wxString layerDesc;
  448. if( layers.size() == 1 )
  449. {
  450. layerDesc.Printf( _( "%s" ), GetBoard()->GetLayerName( layers[0] ) );
  451. }
  452. else if (layers.size() == 2 )
  453. {
  454. layerDesc.Printf( _( "%s and %s" ),
  455. GetBoard()->GetLayerName( layers[0] ),
  456. GetBoard()->GetLayerName( layers[1] ) );
  457. }
  458. else if (layers.size() == 3 )
  459. {
  460. layerDesc.Printf( _( "%s, %s and %s" ),
  461. GetBoard()->GetLayerName( layers[0] ),
  462. GetBoard()->GetLayerName( layers[1] ),
  463. GetBoard()->GetLayerName( layers[2] ) );
  464. }
  465. else if( layers.size() > 3 )
  466. {
  467. layerDesc.Printf( _( "%s, %s and %d more" ),
  468. GetBoard()->GetLayerName( layers[0] ),
  469. GetBoard()->GetLayerName( layers[1] ),
  470. static_cast<int>( layers.size() - 2 ) );
  471. }
  472. aList.emplace_back( _( "Layer" ), layerDesc );
  473. if( !m_zoneName.empty() )
  474. aList.emplace_back( _( "Name" ), m_zoneName );
  475. switch( m_fillMode )
  476. {
  477. case ZONE_FILL_MODE::POLYGONS: msg = _( "Solid" ); break;
  478. case ZONE_FILL_MODE::HATCH_PATTERN: msg = _( "Hatched" ); break;
  479. default: msg = _( "Unknown" ); break;
  480. }
  481. aList.emplace_back( _( "Fill Mode" ), msg );
  482. aList.emplace_back( _( "Filled Area" ),
  483. aFrame->MessageTextFromValue( m_area, true, EDA_DATA_TYPE::AREA ) );
  484. wxString source;
  485. int clearance = GetOwnClearance( UNDEFINED_LAYER, &source );
  486. if( !source.IsEmpty() )
  487. {
  488. aList.emplace_back( wxString::Format( _( "Min Clearance: %s" ),
  489. aFrame->MessageTextFromValue( clearance ) ),
  490. wxString::Format( _( "(from %s)" ),
  491. source ) );
  492. }
  493. if( !m_FilledPolysList.empty() )
  494. {
  495. int count = 0;
  496. for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& ii: m_FilledPolysList )
  497. count += ii.second->TotalVertices();
  498. aList.emplace_back( _( "Corner Count" ), wxString::Format( wxT( "%d" ), count ) );
  499. }
  500. }
  501. void ZONE::Move( const VECTOR2I& offset )
  502. {
  503. /* move outlines */
  504. m_Poly->Move( offset );
  505. HatchBorder();
  506. /* move fills */
  507. for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& pair : m_FilledPolysList )
  508. pair.second->Move( offset );
  509. /*
  510. * move boundingbox cache
  511. *
  512. * While the cache will get nuked at the conclusion of the operation, we use it for some
  513. * things (such as drawing the parent group) during the move.
  514. */
  515. if( GetBoard() )
  516. {
  517. auto it = GetBoard()->m_ZoneBBoxCache.find( this );
  518. if( it != GetBoard()->m_ZoneBBoxCache.end() )
  519. it->second.Move( offset );
  520. }
  521. }
  522. wxString ZONE::GetFriendlyName() const
  523. {
  524. if( GetIsRuleArea() )
  525. return _( "Rule Area" );
  526. else if( IsTeardropArea() )
  527. return _( "Teardrop Area" );
  528. else if( IsOnCopperLayer() )
  529. return _( "Copper Zone" );
  530. else
  531. return _( "Non-copper Zone" );
  532. }
  533. void ZONE::MoveEdge( const VECTOR2I& offset, int aEdge )
  534. {
  535. int next_corner;
  536. if( m_Poly->GetNeighbourIndexes( aEdge, nullptr, &next_corner ) )
  537. {
  538. m_Poly->SetVertex( aEdge, m_Poly->CVertex( aEdge ) + VECTOR2I( offset ) );
  539. m_Poly->SetVertex( next_corner, m_Poly->CVertex( next_corner ) + VECTOR2I( offset ) );
  540. HatchBorder();
  541. SetNeedRefill( true );
  542. }
  543. }
  544. void ZONE::Rotate( const VECTOR2I& aCentre, const EDA_ANGLE& aAngle )
  545. {
  546. m_Poly->Rotate( aAngle, aCentre );
  547. HatchBorder();
  548. /* rotate filled areas: */
  549. for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& pair : m_FilledPolysList )
  550. pair.second->Rotate( aAngle, aCentre );
  551. }
  552. void ZONE::Flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
  553. {
  554. Mirror( aCentre, aFlipLeftRight );
  555. std::map<PCB_LAYER_ID, SHAPE_POLY_SET> fillsCopy;
  556. for( auto& [oldLayer, shapePtr] : m_FilledPolysList )
  557. {
  558. fillsCopy[oldLayer] = *shapePtr;
  559. }
  560. SetLayerSet( FlipLayerMask( GetLayerSet(), GetBoard()->GetCopperLayerCount() ) );
  561. for( auto& [oldLayer, shape] : fillsCopy )
  562. {
  563. PCB_LAYER_ID newLayer = GetBoard()->FlipLayer( oldLayer );
  564. SetFilledPolysList( newLayer, shape );
  565. }
  566. }
  567. void ZONE::Mirror( const VECTOR2I& aMirrorRef, bool aMirrorLeftRight )
  568. {
  569. m_Poly->Mirror( aMirrorLeftRight, !aMirrorLeftRight, aMirrorRef );
  570. HatchBorder();
  571. for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& pair : m_FilledPolysList )
  572. pair.second->Mirror( aMirrorLeftRight, !aMirrorLeftRight, aMirrorRef );
  573. }
  574. void ZONE::RemoveCutout( int aOutlineIdx, int aHoleIdx )
  575. {
  576. // Ensure the requested cutout is valid
  577. if( m_Poly->OutlineCount() < aOutlineIdx || m_Poly->HoleCount( aOutlineIdx ) < aHoleIdx )
  578. return;
  579. SHAPE_POLY_SET cutPoly( m_Poly->Hole( aOutlineIdx, aHoleIdx ) );
  580. // Add the cutout back to the zone
  581. m_Poly->BooleanAdd( cutPoly, SHAPE_POLY_SET::PM_FAST );
  582. SetNeedRefill( true );
  583. }
  584. void ZONE::AddPolygon( const SHAPE_LINE_CHAIN& aPolygon )
  585. {
  586. wxASSERT( aPolygon.IsClosed() );
  587. // Add the outline as a new polygon in the polygon set
  588. if( m_Poly->OutlineCount() == 0 )
  589. m_Poly->AddOutline( aPolygon );
  590. else
  591. m_Poly->AddHole( aPolygon );
  592. SetNeedRefill( true );
  593. }
  594. void ZONE::AddPolygon( std::vector<VECTOR2I>& aPolygon )
  595. {
  596. if( aPolygon.empty() )
  597. return;
  598. SHAPE_LINE_CHAIN outline;
  599. // Create an outline and populate it with the points of aPolygon
  600. for( const VECTOR2I& pt : aPolygon )
  601. outline.Append( pt );
  602. outline.SetClosed( true );
  603. AddPolygon( outline );
  604. }
  605. bool ZONE::AppendCorner( VECTOR2I aPosition, int aHoleIdx, bool aAllowDuplication )
  606. {
  607. // Ensure the main outline exists:
  608. if( m_Poly->OutlineCount() == 0 )
  609. m_Poly->NewOutline();
  610. // If aHoleIdx >= 0, the corner musty be added to the hole, index aHoleIdx.
  611. // (remember: the index of the first hole is 0)
  612. // Return error if it does not exist.
  613. if( aHoleIdx >= m_Poly->HoleCount( 0 ) )
  614. return false;
  615. m_Poly->Append( aPosition.x, aPosition.y, -1, aHoleIdx, aAllowDuplication );
  616. SetNeedRefill( true );
  617. return true;
  618. }
  619. wxString ZONE::GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const
  620. {
  621. LSEQ layers = m_layerSet.Seq();
  622. wxString layerDesc;
  623. if( layers.size() == 1 )
  624. {
  625. layerDesc.Printf( _( "on %s" ), GetBoard()->GetLayerName( layers[0] ) );
  626. }
  627. else if (layers.size() == 2 )
  628. {
  629. layerDesc.Printf( _( "on %s and %s" ),
  630. GetBoard()->GetLayerName( layers[0] ),
  631. GetBoard()->GetLayerName( layers[1] ) );
  632. }
  633. else if (layers.size() == 3 )
  634. {
  635. layerDesc.Printf( _( "on %s, %s and %s" ),
  636. GetBoard()->GetLayerName( layers[0] ),
  637. GetBoard()->GetLayerName( layers[1] ),
  638. GetBoard()->GetLayerName( layers[2] ) );
  639. }
  640. else if( layers.size() > 3 )
  641. {
  642. layerDesc.Printf( _( "on %s, %s and %zu more" ),
  643. GetBoard()->GetLayerName( layers[0] ),
  644. GetBoard()->GetLayerName( layers[1] ),
  645. layers.size() - 2 );
  646. }
  647. // Check whether the selected contour is a hole (contour index > 0)
  648. if( m_CornerSelection != nullptr && m_CornerSelection->m_contour > 0 )
  649. {
  650. if( GetIsRuleArea() )
  651. return wxString::Format( _( "Rule Area Cutout %s" ), layerDesc );
  652. else
  653. return wxString::Format( _( "Zone Cutout %s" ), layerDesc );
  654. }
  655. else
  656. {
  657. if( GetIsRuleArea() )
  658. return wxString::Format( _( "Rule Area %s" ), layerDesc );
  659. else
  660. return wxString::Format( _( "Zone %s %s" ), GetNetnameMsg(), layerDesc );
  661. }
  662. }
  663. int ZONE::GetBorderHatchPitch() const
  664. {
  665. return m_borderHatchPitch;
  666. }
  667. void ZONE::SetBorderDisplayStyle( ZONE_BORDER_DISPLAY_STYLE aBorderHatchStyle,
  668. int aBorderHatchPitch, bool aRebuildBorderHatch )
  669. {
  670. aBorderHatchPitch = std::max( aBorderHatchPitch,
  671. pcbIUScale.mmToIU( ZONE_BORDER_HATCH_MINDIST_MM ) );
  672. aBorderHatchPitch = std::min( aBorderHatchPitch,
  673. pcbIUScale.mmToIU( ZONE_BORDER_HATCH_MAXDIST_MM ) );
  674. SetBorderHatchPitch( aBorderHatchPitch );
  675. m_borderStyle = aBorderHatchStyle;
  676. if( aRebuildBorderHatch )
  677. HatchBorder();
  678. }
  679. void ZONE::SetBorderHatchPitch( int aPitch )
  680. {
  681. m_borderHatchPitch = aPitch;
  682. }
  683. void ZONE::UnHatchBorder()
  684. {
  685. m_borderHatchLines.clear();
  686. }
  687. // Creates hatch lines inside the outline of the complex polygon
  688. // sort function used in ::HatchBorder to sort points by descending VECTOR2I.x values
  689. bool sortEndsByDescendingX( const VECTOR2I& ref, const VECTOR2I& tst )
  690. {
  691. return tst.x < ref.x;
  692. }
  693. void ZONE::HatchBorder()
  694. {
  695. UnHatchBorder();
  696. if( m_borderStyle == ZONE_BORDER_DISPLAY_STYLE::NO_HATCH
  697. || m_borderHatchPitch == 0
  698. || m_Poly->IsEmpty() )
  699. {
  700. return;
  701. }
  702. // define range for hatch lines
  703. int min_x = m_Poly->CVertex( 0 ).x;
  704. int max_x = m_Poly->CVertex( 0 ).x;
  705. int min_y = m_Poly->CVertex( 0 ).y;
  706. int max_y = m_Poly->CVertex( 0 ).y;
  707. for( auto iterator = m_Poly->IterateWithHoles(); iterator; iterator++ )
  708. {
  709. if( iterator->x < min_x )
  710. min_x = iterator->x;
  711. if( iterator->x > max_x )
  712. max_x = iterator->x;
  713. if( iterator->y < min_y )
  714. min_y = iterator->y;
  715. if( iterator->y > max_y )
  716. max_y = iterator->y;
  717. }
  718. // Calculate spacing between 2 hatch lines
  719. int spacing;
  720. if( m_borderStyle == ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_EDGE )
  721. spacing = m_borderHatchPitch;
  722. else
  723. spacing = m_borderHatchPitch * 2;
  724. // set the "length" of hatch lines (the length on horizontal axis)
  725. int hatch_line_len = m_borderHatchPitch;
  726. // To have a better look, give a slope depending on the layer
  727. int layer = GetFirstLayer();
  728. std::vector<int> slope_flags;
  729. if( IsTeardropArea() )
  730. slope_flags = { 1, -1 };
  731. else if( layer & 1 )
  732. slope_flags = { 1 };
  733. else
  734. slope_flags = { -1 };
  735. for( int slope_flag : slope_flags )
  736. {
  737. double slope = 0.707106 * slope_flag; // 45 degrees slope
  738. int64_t max_a, min_a;
  739. if( slope_flag == 1 )
  740. {
  741. max_a = KiROUND<double, int64_t>( max_y - slope * min_x );
  742. min_a = KiROUND<double, int64_t>( min_y - slope * max_x );
  743. }
  744. else
  745. {
  746. max_a = KiROUND<double, int64_t>( max_y - slope * max_x );
  747. min_a = KiROUND<double, int64_t>( min_y - slope * min_x );
  748. }
  749. min_a = (min_a / spacing) * spacing;
  750. // calculate an offset depending on layer number,
  751. // for a better look of hatches on a multilayer board
  752. int offset = (layer * 7) / 8;
  753. min_a += offset;
  754. // loop through hatch lines
  755. std::vector<VECTOR2I> pointbuffer;
  756. pointbuffer.reserve( 256 );
  757. for( int64_t a = min_a; a < max_a; a += spacing )
  758. {
  759. pointbuffer.clear();
  760. // Iterate through all vertices
  761. for( auto iterator = m_Poly->IterateSegmentsWithHoles(); iterator; iterator++ )
  762. {
  763. const SEG seg = *iterator;
  764. double x, y;
  765. if( FindLineSegmentIntersection( a, slope, seg.A.x, seg.A.y, seg.B.x, seg.B.y, x, y ) )
  766. pointbuffer.emplace_back( KiROUND( x ), KiROUND( y ) );
  767. }
  768. // sort points in order of descending x (if more than 2) to
  769. // ensure the starting point and the ending point of the same segment
  770. // are stored one just after the other.
  771. if( pointbuffer.size() > 2 )
  772. sort( pointbuffer.begin(), pointbuffer.end(), sortEndsByDescendingX );
  773. // creates lines or short segments inside the complex polygon
  774. for( size_t ip = 0; ip + 1 < pointbuffer.size(); ip += 2 )
  775. {
  776. int dx = pointbuffer[ip + 1].x - pointbuffer[ip].x;
  777. // Push only one line for diagonal hatch,
  778. // or for small lines < twice the line length
  779. // else push 2 small lines
  780. if( m_borderStyle == ZONE_BORDER_DISPLAY_STYLE::DIAGONAL_FULL
  781. || std::abs( dx ) < 2 * hatch_line_len )
  782. {
  783. m_borderHatchLines.emplace_back( SEG( pointbuffer[ip], pointbuffer[ ip + 1] ) );
  784. }
  785. else
  786. {
  787. double dy = pointbuffer[ip + 1].y - pointbuffer[ip].y;
  788. slope = dy / dx;
  789. if( dx > 0 )
  790. dx = hatch_line_len;
  791. else
  792. dx = -hatch_line_len;
  793. int x1 = KiROUND( pointbuffer[ip].x + dx );
  794. int x2 = KiROUND( pointbuffer[ip + 1].x - dx );
  795. int y1 = KiROUND( pointbuffer[ip].y + dx * slope );
  796. int y2 = KiROUND( pointbuffer[ip + 1].y - dx * slope );
  797. m_borderHatchLines.emplace_back( SEG( pointbuffer[ip].x, pointbuffer[ip].y,
  798. x1, y1 ) );
  799. m_borderHatchLines.emplace_back( SEG( pointbuffer[ip+1].x, pointbuffer[ip+1].y,
  800. x2, y2 ) );
  801. }
  802. }
  803. }
  804. }
  805. }
  806. int ZONE::GetDefaultHatchPitch()
  807. {
  808. return pcbIUScale.mmToIU( ZONE_BORDER_HATCH_DIST_MM );
  809. }
  810. BITMAPS ZONE::GetMenuImage() const
  811. {
  812. return BITMAPS::add_zone;
  813. }
  814. void ZONE::swapData( BOARD_ITEM* aImage )
  815. {
  816. assert( aImage->Type() == PCB_ZONE_T );
  817. std::swap( *static_cast<ZONE*>( this ), *static_cast<ZONE*>( aImage) );
  818. }
  819. void ZONE::CacheTriangulation( PCB_LAYER_ID aLayer )
  820. {
  821. if( aLayer == UNDEFINED_LAYER )
  822. {
  823. for( auto& [ layer, poly ] : m_FilledPolysList )
  824. poly->CacheTriangulation();
  825. m_Poly->CacheTriangulation( false );
  826. }
  827. else
  828. {
  829. if( m_FilledPolysList.count( aLayer ) )
  830. m_FilledPolysList[ aLayer ]->CacheTriangulation();
  831. }
  832. }
  833. bool ZONE::IsIsland( PCB_LAYER_ID aLayer, int aPolyIdx ) const
  834. {
  835. if( GetNetCode() < 1 )
  836. return true;
  837. if( !m_insulatedIslands.count( aLayer ) )
  838. return false;
  839. return m_insulatedIslands.at( aLayer ).count( aPolyIdx );
  840. }
  841. void ZONE::GetInteractingZones( PCB_LAYER_ID aLayer, std::vector<ZONE*>* aSameNetCollidingZones,
  842. std::vector<ZONE*>* aOtherNetIntersectingZones ) const
  843. {
  844. int epsilon = pcbIUScale.mmToIU( 0.001 );
  845. BOX2I bbox = GetBoundingBox();
  846. bbox.Inflate( epsilon );
  847. for( ZONE* candidate : GetBoard()->Zones() )
  848. {
  849. if( candidate == this )
  850. continue;
  851. if( !candidate->GetLayerSet().test( aLayer ) )
  852. continue;
  853. if( candidate->GetIsRuleArea() || candidate->IsTeardropArea() )
  854. continue;
  855. if( !candidate->GetBoundingBox().Intersects( bbox ) )
  856. continue;
  857. if( candidate->GetNetCode() == GetNetCode() )
  858. {
  859. if( m_Poly->Collide( candidate->m_Poly ) )
  860. aSameNetCollidingZones->push_back( candidate );
  861. }
  862. else
  863. {
  864. aOtherNetIntersectingZones->push_back( candidate );
  865. }
  866. }
  867. }
  868. bool ZONE::BuildSmoothedPoly( SHAPE_POLY_SET& aSmoothedPoly, PCB_LAYER_ID aLayer,
  869. SHAPE_POLY_SET* aBoardOutline,
  870. SHAPE_POLY_SET* aSmoothedPolyWithApron ) const
  871. {
  872. if( GetNumCorners() <= 2 ) // malformed zone. polygon calculations will not like it ...
  873. return false;
  874. // Processing of arc shapes in zones is not yet supported because Clipper can't do boolean
  875. // operations on them. The poly outline must be converted to segments first.
  876. SHAPE_POLY_SET flattened = m_Poly->CloneDropTriangulation();
  877. flattened.ClearArcs();
  878. if( GetIsRuleArea() )
  879. {
  880. // We like keepouts just the way they are....
  881. aSmoothedPoly = flattened;
  882. return true;
  883. }
  884. const BOARD* board = GetBoard();
  885. int maxError = ARC_HIGH_DEF;
  886. bool keepExternalFillets = false;
  887. bool smooth_requested = m_cornerSmoothingType == ZONE_SETTINGS::SMOOTHING_CHAMFER
  888. || m_cornerSmoothingType == ZONE_SETTINGS::SMOOTHING_FILLET;
  889. if( IsTeardropArea() )
  890. {
  891. // We use teardrop shapes with no smoothing; these shapes are already optimized
  892. smooth_requested = false;
  893. }
  894. if( board )
  895. {
  896. BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
  897. maxError = bds.m_MaxError;
  898. keepExternalFillets = bds.m_ZoneKeepExternalFillets;
  899. }
  900. auto smooth = [&]( SHAPE_POLY_SET& aPoly )
  901. {
  902. if( !smooth_requested )
  903. return;
  904. switch( m_cornerSmoothingType )
  905. {
  906. case ZONE_SETTINGS::SMOOTHING_CHAMFER:
  907. aPoly = aPoly.Chamfer( (int) m_cornerRadius );
  908. break;
  909. case ZONE_SETTINGS::SMOOTHING_FILLET:
  910. {
  911. aPoly = aPoly.Fillet( (int) m_cornerRadius, maxError );
  912. break;
  913. }
  914. default:
  915. break;
  916. }
  917. };
  918. SHAPE_POLY_SET* maxExtents = &flattened;
  919. SHAPE_POLY_SET withFillets;
  920. aSmoothedPoly = flattened;
  921. // Should external fillets (that is, those applied to concave corners) be kept? While it
  922. // seems safer to never have copper extend outside the zone outline, 5.1.x and prior did
  923. // indeed fill them so we leave the mode available.
  924. if( keepExternalFillets && smooth_requested )
  925. {
  926. withFillets = flattened;
  927. smooth( withFillets );
  928. withFillets.BooleanAdd( flattened, SHAPE_POLY_SET::PM_FAST );
  929. maxExtents = &withFillets;
  930. }
  931. // We now add in the areas of any same-net, intersecting zones. This keeps us from smoothing
  932. // corners at an intersection (which often produces undesired divots between the intersecting
  933. // zones -- see #2752).
  934. //
  935. // After smoothing, we'll subtract back out everything outside of our zone.
  936. std::vector<ZONE*> sameNetCollidingZones;
  937. std::vector<ZONE*> diffNetIntersectingZones;
  938. GetInteractingZones( aLayer, &sameNetCollidingZones, &diffNetIntersectingZones );
  939. for( ZONE* sameNetZone : sameNetCollidingZones )
  940. {
  941. BOX2I sameNetBoundingBox = sameNetZone->GetBoundingBox();
  942. // Note: a two-pass algorithm could use sameNetZone's actual fill instead of its outline.
  943. // This would obviate the need for the below wrinkles, in addition to fixing both issues
  944. // in #16095.
  945. // (And we wouldn't need to collect all the diffNetIntersectingZones either.)
  946. SHAPE_POLY_SET sameNetPoly = sameNetZone->Outline()->CloneDropTriangulation();
  947. SHAPE_POLY_SET diffNetPoly;
  948. // Of course there's always a wrinkle. The same-net intersecting zone *might* get knocked
  949. // out along the border by a higher-priority, different-net zone. #12797
  950. for( ZONE* diffNetZone : diffNetIntersectingZones )
  951. {
  952. if( diffNetZone->HigherPriority( sameNetZone )
  953. && diffNetZone->GetBoundingBox().Intersects( sameNetBoundingBox ) )
  954. {
  955. diffNetPoly.BooleanAdd( *diffNetZone->Outline(), SHAPE_POLY_SET::PM_FAST );
  956. }
  957. }
  958. // Second wrinkle. After unioning the higher priority, different net zones together, we
  959. // need to check to see if they completely enclose our zone. If they do, then we need to
  960. // treat the enclosed zone as isolated, not connected to the outer zone. #13915
  961. bool isolated = false;
  962. if( diffNetPoly.OutlineCount() )
  963. {
  964. SHAPE_POLY_SET thisPoly = Outline()->CloneDropTriangulation();
  965. thisPoly.BooleanSubtract( diffNetPoly, SHAPE_POLY_SET::PM_FAST );
  966. isolated = thisPoly.OutlineCount() == 0;
  967. }
  968. if( !isolated )
  969. {
  970. sameNetPoly.ClearArcs();
  971. aSmoothedPoly.BooleanAdd( sameNetPoly, SHAPE_POLY_SET::PM_FAST );
  972. }
  973. }
  974. if( aBoardOutline )
  975. aSmoothedPoly.BooleanIntersection( *aBoardOutline, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  976. smooth( aSmoothedPoly );
  977. if( aSmoothedPolyWithApron )
  978. {
  979. SHAPE_POLY_SET poly = maxExtents->CloneDropTriangulation();
  980. poly.Inflate( m_ZoneMinThickness, CORNER_STRATEGY::ROUND_ALL_CORNERS, maxError );
  981. *aSmoothedPolyWithApron = aSmoothedPoly;
  982. aSmoothedPolyWithApron->BooleanIntersection( poly, SHAPE_POLY_SET::PM_FAST );
  983. }
  984. aSmoothedPoly.BooleanIntersection( *maxExtents, SHAPE_POLY_SET::PM_FAST );
  985. return true;
  986. }
  987. double ZONE::CalculateFilledArea()
  988. {
  989. m_area = 0.0;
  990. // Iterate over each outline polygon in the zone and then iterate over
  991. // each hole it has to compute the total area.
  992. for( std::pair<const PCB_LAYER_ID, std::shared_ptr<SHAPE_POLY_SET>>& pair : m_FilledPolysList )
  993. {
  994. std::shared_ptr<SHAPE_POLY_SET>& poly = pair.second;
  995. for( int i = 0; i < poly->OutlineCount(); i++ )
  996. {
  997. m_area += poly->Outline( i ).Area();
  998. for( int j = 0; j < poly->HoleCount( i ); j++ )
  999. m_area -= poly->Hole( i, j ).Area();
  1000. }
  1001. }
  1002. return m_area;
  1003. }
  1004. double ZONE::CalculateOutlineArea()
  1005. {
  1006. m_outlinearea = std::abs( m_Poly->Area() );
  1007. return m_outlinearea;
  1008. }
  1009. void ZONE::TransformSmoothedOutlineToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance,
  1010. int aMaxError, ERROR_LOC aErrorLoc,
  1011. SHAPE_POLY_SET* aBoardOutline ) const
  1012. {
  1013. // Creates the zone outline polygon (with holes if any)
  1014. SHAPE_POLY_SET polybuffer;
  1015. // TODO: using GetFirstLayer() means it only works for single-layer zones....
  1016. BuildSmoothedPoly( polybuffer, GetFirstLayer(), aBoardOutline );
  1017. // Calculate the polygon with clearance
  1018. // holes are linked to the main outline, so only one polygon is created.
  1019. if( aClearance )
  1020. {
  1021. const BOARD* board = GetBoard();
  1022. int maxError = ARC_HIGH_DEF;
  1023. if( board )
  1024. maxError = board->GetDesignSettings().m_MaxError;
  1025. if( aErrorLoc == ERROR_OUTSIDE )
  1026. aClearance += maxError;
  1027. polybuffer.Inflate( aClearance, CORNER_STRATEGY::ROUND_ALL_CORNERS, maxError );
  1028. }
  1029. polybuffer.Fracture( SHAPE_POLY_SET::PM_FAST );
  1030. aBuffer.Append( polybuffer );
  1031. }
  1032. std::shared_ptr<SHAPE> ZONE::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const
  1033. {
  1034. if( m_FilledPolysList.find( aLayer ) == m_FilledPolysList.end() )
  1035. return std::make_shared<SHAPE_NULL>();
  1036. else
  1037. return m_FilledPolysList.at( aLayer );
  1038. }
  1039. void ZONE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance,
  1040. int aError, ERROR_LOC aErrorLoc, bool aIgnoreLineWidth ) const
  1041. {
  1042. wxASSERT_MSG( !aIgnoreLineWidth, wxT( "IgnoreLineWidth has no meaning for zones." ) );
  1043. if( !m_FilledPolysList.count( aLayer ) )
  1044. return;
  1045. if( !aClearance )
  1046. {
  1047. aBuffer.Append( *m_FilledPolysList.at( aLayer ) );
  1048. return;
  1049. }
  1050. SHAPE_POLY_SET temp_buf = m_FilledPolysList.at( aLayer )->CloneDropTriangulation();
  1051. // Rebuild filled areas only if clearance is not 0
  1052. if( aClearance > 0 || aErrorLoc == ERROR_OUTSIDE )
  1053. {
  1054. if( aErrorLoc == ERROR_OUTSIDE )
  1055. aClearance += aError;
  1056. temp_buf.InflateWithLinkedHoles( aClearance, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError,
  1057. SHAPE_POLY_SET::PM_FAST );
  1058. }
  1059. aBuffer.Append( temp_buf );
  1060. }
  1061. void ZONE::TransformSolidAreasShapesToPolygon( PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aBuffer ) const
  1062. {
  1063. if( m_FilledPolysList.count( aLayer ) && !m_FilledPolysList.at( aLayer )->IsEmpty() )
  1064. aBuffer.Append( *m_FilledPolysList.at( aLayer ) );
  1065. }
  1066. bool ZONE::operator==( const BOARD_ITEM& aOther ) const
  1067. {
  1068. if( aOther.Type() != Type() )
  1069. return false;
  1070. const ZONE& other = static_cast<const ZONE&>( aOther );
  1071. return *this == other;
  1072. }
  1073. bool ZONE::operator==( const ZONE& aOther ) const
  1074. {
  1075. if( GetIsRuleArea() != aOther.GetIsRuleArea() )
  1076. return false;
  1077. if( GetLayerSet() != aOther.GetLayerSet() )
  1078. return false;
  1079. if( GetNetCode() != aOther.GetNetCode() )
  1080. return false;
  1081. if( GetIsRuleArea() )
  1082. {
  1083. if( GetDoNotAllowCopperPour() != aOther.GetDoNotAllowCopperPour() )
  1084. return false;
  1085. if( GetDoNotAllowTracks() != aOther.GetDoNotAllowTracks() )
  1086. return false;
  1087. if( GetDoNotAllowVias() != aOther.GetDoNotAllowVias() )
  1088. return false;
  1089. if( GetDoNotAllowFootprints() != aOther.GetDoNotAllowFootprints() )
  1090. return false;
  1091. if( GetDoNotAllowPads() != aOther.GetDoNotAllowPads() )
  1092. return false;
  1093. }
  1094. else
  1095. {
  1096. if( GetAssignedPriority() != aOther.GetAssignedPriority() )
  1097. return false;
  1098. if( GetMinThickness() != aOther.GetMinThickness() )
  1099. return false;
  1100. if( GetCornerSmoothingType() != aOther.GetCornerSmoothingType() )
  1101. return false;
  1102. if( GetCornerRadius() != aOther.GetCornerRadius() )
  1103. return false;
  1104. if( GetTeardropParams() != aOther.GetTeardropParams() )
  1105. return false;
  1106. }
  1107. if( GetNumCorners() != aOther.GetNumCorners() )
  1108. return false;
  1109. for( int ii = 0; ii < GetNumCorners(); ii++ )
  1110. {
  1111. if( GetCornerPosition( ii ) != aOther.GetCornerPosition( ii ) )
  1112. return false;
  1113. }
  1114. return true;
  1115. }
  1116. double ZONE::Similarity( const BOARD_ITEM& aOther ) const
  1117. {
  1118. if( aOther.Type() != Type() )
  1119. return 0.0;
  1120. const ZONE& other = static_cast<const ZONE&>( aOther );
  1121. if( GetIsRuleArea() != other.GetIsRuleArea() )
  1122. return 0.0;
  1123. double similarity = 1.0;
  1124. if( GetLayerSet() != other.GetLayerSet() )
  1125. similarity *= 0.9;
  1126. if( GetNetCode() != other.GetNetCode() )
  1127. similarity *= 0.9;
  1128. if( !GetIsRuleArea() )
  1129. {
  1130. if( GetAssignedPriority() != other.GetAssignedPriority() )
  1131. similarity *= 0.9;
  1132. if( GetMinThickness() != other.GetMinThickness() )
  1133. similarity *= 0.9;
  1134. if( GetCornerSmoothingType() != other.GetCornerSmoothingType() )
  1135. similarity *= 0.9;
  1136. if( GetCornerRadius() != other.GetCornerRadius() )
  1137. similarity *= 0.9;
  1138. if( GetTeardropParams() != other.GetTeardropParams() )
  1139. similarity *= 0.9;
  1140. }
  1141. else
  1142. {
  1143. if( GetDoNotAllowCopperPour() != other.GetDoNotAllowCopperPour() )
  1144. similarity *= 0.9;
  1145. if( GetDoNotAllowTracks() != other.GetDoNotAllowTracks() )
  1146. similarity *= 0.9;
  1147. if( GetDoNotAllowVias() != other.GetDoNotAllowVias() )
  1148. similarity *= 0.9;
  1149. if( GetDoNotAllowFootprints() != other.GetDoNotAllowFootprints() )
  1150. similarity *= 0.9;
  1151. if( GetDoNotAllowPads() != other.GetDoNotAllowPads() )
  1152. similarity *= 0.9;
  1153. }
  1154. std::vector<VECTOR2I> corners;
  1155. std::vector<VECTOR2I> otherCorners;
  1156. VECTOR2I lastCorner( 0, 0 );
  1157. for( int ii = 0; ii < GetNumCorners(); ii++ )
  1158. {
  1159. corners.push_back( lastCorner - GetCornerPosition( ii ) );
  1160. lastCorner = GetCornerPosition( ii );
  1161. }
  1162. lastCorner = VECTOR2I( 0, 0 );
  1163. for( int ii = 0; ii < other.GetNumCorners(); ii++ )
  1164. {
  1165. otherCorners.push_back( lastCorner - other.GetCornerPosition( ii ) );
  1166. lastCorner = other.GetCornerPosition( ii );
  1167. }
  1168. size_t longest = alg::longest_common_subset( corners, otherCorners );
  1169. similarity *= std::pow( 0.9, GetNumCorners() + other.GetNumCorners() - 2 * longest );
  1170. return similarity;
  1171. }
  1172. static struct ZONE_DESC
  1173. {
  1174. ZONE_DESC()
  1175. {
  1176. ENUM_MAP<PCB_LAYER_ID>& layerEnum = ENUM_MAP<PCB_LAYER_ID>::Instance();
  1177. if( layerEnum.Choices().GetCount() == 0 )
  1178. {
  1179. layerEnum.Undefined( UNDEFINED_LAYER );
  1180. for( LSEQ seq = LSET::AllLayersMask().Seq(); seq; ++seq )
  1181. layerEnum.Map( *seq, LSET::Name( *seq ) );
  1182. }
  1183. ENUM_MAP<ZONE_CONNECTION>& zcMap = ENUM_MAP<ZONE_CONNECTION>::Instance();
  1184. if( zcMap.Choices().GetCount() == 0 )
  1185. {
  1186. zcMap.Undefined( ZONE_CONNECTION::INHERITED );
  1187. zcMap.Map( ZONE_CONNECTION::INHERITED, _HKI( "Inherited" ) )
  1188. .Map( ZONE_CONNECTION::NONE, _HKI( "None" ) )
  1189. .Map( ZONE_CONNECTION::THERMAL, _HKI( "Thermal reliefs" ) )
  1190. .Map( ZONE_CONNECTION::FULL, _HKI( "Solid" ) )
  1191. .Map( ZONE_CONNECTION::THT_THERMAL, _HKI( "Thermal reliefs for PTH" ) );
  1192. }
  1193. ENUM_MAP<ZONE_FILL_MODE>& zfmMap = ENUM_MAP<ZONE_FILL_MODE>::Instance();
  1194. if( zfmMap.Choices().GetCount() == 0 )
  1195. {
  1196. zfmMap.Undefined( ZONE_FILL_MODE::POLYGONS );
  1197. zfmMap.Map( ZONE_FILL_MODE::POLYGONS, _HKI( "Solid fill" ) )
  1198. .Map( ZONE_FILL_MODE::HATCH_PATTERN, _HKI( "Hatch pattern" ) );
  1199. }
  1200. ENUM_MAP<ISLAND_REMOVAL_MODE>& irmMap = ENUM_MAP<ISLAND_REMOVAL_MODE>::Instance();
  1201. if( irmMap.Choices().GetCount() == 0 )
  1202. {
  1203. irmMap.Undefined( ISLAND_REMOVAL_MODE::ALWAYS );
  1204. irmMap.Map( ISLAND_REMOVAL_MODE::ALWAYS, _HKI( "Always" ) )
  1205. .Map( ISLAND_REMOVAL_MODE::NEVER, _HKI( "Never" ) )
  1206. .Map( ISLAND_REMOVAL_MODE::AREA, _HKI( "Below area limit" ) );
  1207. }
  1208. PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
  1209. REGISTER_TYPE( ZONE );
  1210. propMgr.InheritsAfter( TYPE_HASH( ZONE ), TYPE_HASH( BOARD_CONNECTED_ITEM ) );
  1211. // Mask layer and position properties; they aren't useful in current form
  1212. auto posX = new PROPERTY<ZONE, int>( _HKI( "Position X" ), NO_SETTER( ZONE, int ),
  1213. static_cast<int ( ZONE::* )() const>( &ZONE::GetX ),
  1214. PROPERTY_DISPLAY::PT_COORD,
  1215. ORIGIN_TRANSFORMS::ABS_X_COORD );
  1216. posX->SetIsHiddenFromPropertiesManager();
  1217. auto posY = new PROPERTY<ZONE, int>( _HKI( "Position Y" ), NO_SETTER( ZONE, int ),
  1218. static_cast<int ( ZONE::* )() const>( &ZONE::GetY ),
  1219. PROPERTY_DISPLAY::PT_COORD,
  1220. ORIGIN_TRANSFORMS::ABS_Y_COORD );
  1221. posY->SetIsHiddenFromPropertiesManager();
  1222. propMgr.ReplaceProperty( TYPE_HASH( BOARD_ITEM ), _HKI( "Position X" ), posX );
  1223. propMgr.ReplaceProperty( TYPE_HASH( BOARD_ITEM ), _HKI( "Position Y" ), posY );
  1224. auto isCopperZone =
  1225. []( INSPECTABLE* aItem ) -> bool
  1226. {
  1227. if( ZONE* zone = dynamic_cast<ZONE*>( aItem ) )
  1228. return !zone->GetIsRuleArea() && IsCopperLayer( zone->GetFirstLayer() );
  1229. return false;
  1230. };
  1231. auto isHatchedFill =
  1232. []( INSPECTABLE* aItem ) -> bool
  1233. {
  1234. if( ZONE* zone = dynamic_cast<ZONE*>( aItem ) )
  1235. return zone->GetFillMode() == ZONE_FILL_MODE::HATCH_PATTERN;
  1236. return false;
  1237. };
  1238. auto isAreaBasedIslandRemoval =
  1239. []( INSPECTABLE* aItem ) -> bool
  1240. {
  1241. if( ZONE* zone = dynamic_cast<ZONE*>( aItem ) )
  1242. return zone->GetIslandRemovalMode() == ISLAND_REMOVAL_MODE::AREA;
  1243. return false;
  1244. };
  1245. // Layer property is hidden because it only holds a single layer and zones actually use
  1246. // a layer set
  1247. propMgr.ReplaceProperty( TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Layer" ),
  1248. new PROPERTY_ENUM<ZONE, PCB_LAYER_ID>( _HKI( "Layer" ),
  1249. &ZONE::SetLayer,
  1250. &ZONE::GetLayer ) )
  1251. .SetIsHiddenFromPropertiesManager();
  1252. propMgr.OverrideAvailability( TYPE_HASH( ZONE ), TYPE_HASH( BOARD_CONNECTED_ITEM ),
  1253. _HKI( "Net" ), isCopperZone );
  1254. propMgr.OverrideAvailability( TYPE_HASH( ZONE ), TYPE_HASH( BOARD_CONNECTED_ITEM ),
  1255. _HKI( "Net Class" ), isCopperZone );
  1256. propMgr.AddProperty( new PROPERTY<ZONE, unsigned>( _HKI( "Priority" ),
  1257. &ZONE::SetAssignedPriority, &ZONE::GetAssignedPriority ) )
  1258. .SetAvailableFunc( isCopperZone );
  1259. propMgr.AddProperty( new PROPERTY<ZONE, wxString>( _HKI( "Name" ),
  1260. &ZONE::SetZoneName, &ZONE::GetZoneName ) );
  1261. const wxString groupFill = _HKI( "Fill Style" );
  1262. propMgr.AddProperty( new PROPERTY_ENUM<ZONE, ZONE_FILL_MODE>( _HKI( "Fill Mode" ),
  1263. &ZONE::SetFillMode, &ZONE::GetFillMode ),
  1264. groupFill )
  1265. .SetAvailableFunc( isCopperZone );
  1266. propMgr.AddProperty( new PROPERTY<ZONE, EDA_ANGLE>( _HKI( "Orientation" ),
  1267. &ZONE::SetHatchOrientation, &ZONE::GetHatchOrientation,
  1268. PROPERTY_DISPLAY::PT_DEGREE ),
  1269. groupFill )
  1270. .SetAvailableFunc( isCopperZone )
  1271. .SetWriteableFunc( isHatchedFill );
  1272. auto atLeastMinWidthValidator =
  1273. []( const wxAny&& aValue, EDA_ITEM* aZone ) -> VALIDATOR_RESULT
  1274. {
  1275. int val = aValue.As<int>();
  1276. ZONE* zone = dynamic_cast<ZONE*>( aZone );
  1277. wxCHECK( zone, std::nullopt );
  1278. if( val < zone->GetMinThickness() )
  1279. {
  1280. return std::make_unique<VALIDATION_ERROR_MSG>(
  1281. _( "Cannot be less than zone minimum width" ) );
  1282. }
  1283. return std::nullopt;
  1284. };
  1285. propMgr.AddProperty( new PROPERTY<ZONE, int>( _HKI( "Hatch Width" ),
  1286. &ZONE::SetHatchThickness, &ZONE::GetHatchThickness, PROPERTY_DISPLAY::PT_SIZE ),
  1287. groupFill )
  1288. .SetAvailableFunc( isCopperZone )
  1289. .SetWriteableFunc( isHatchedFill )
  1290. .SetValidator( atLeastMinWidthValidator );
  1291. propMgr.AddProperty( new PROPERTY<ZONE, int>( _HKI( "Hatch Gap" ),
  1292. &ZONE::SetHatchGap, &ZONE::GetHatchGap, PROPERTY_DISPLAY::PT_SIZE ),
  1293. groupFill )
  1294. .SetAvailableFunc( isCopperZone )
  1295. .SetWriteableFunc( isHatchedFill )
  1296. .SetValidator( atLeastMinWidthValidator );
  1297. propMgr.AddProperty( new PROPERTY<ZONE, double>( _HKI( "Hatch Minimum Hole Ratio" ),
  1298. &ZONE::SetHatchHoleMinArea, &ZONE::GetHatchHoleMinArea ),
  1299. groupFill )
  1300. .SetAvailableFunc( isCopperZone )
  1301. .SetWriteableFunc( isHatchedFill )
  1302. .SetValidator( PROPERTY_VALIDATORS::PositiveRatioValidator );
  1303. // TODO: Smoothing effort needs to change to enum (in dialog too)
  1304. propMgr.AddProperty( new PROPERTY<ZONE, int>( _HKI( "Smoothing Effort" ),
  1305. &ZONE::SetHatchSmoothingLevel, &ZONE::GetHatchSmoothingLevel ),
  1306. groupFill )
  1307. .SetAvailableFunc( isCopperZone )
  1308. .SetWriteableFunc( isHatchedFill );
  1309. propMgr.AddProperty( new PROPERTY<ZONE, double>( _HKI( "Smoothing Amount" ),
  1310. &ZONE::SetHatchSmoothingValue, &ZONE::GetHatchSmoothingValue ),
  1311. groupFill )
  1312. .SetAvailableFunc( isCopperZone )
  1313. .SetWriteableFunc( isHatchedFill );
  1314. propMgr.AddProperty( new PROPERTY_ENUM<ZONE, ISLAND_REMOVAL_MODE>( _HKI( "Remove Islands" ),
  1315. &ZONE::SetIslandRemovalMode, &ZONE::GetIslandRemovalMode ),
  1316. groupFill )
  1317. .SetAvailableFunc( isCopperZone );
  1318. propMgr.AddProperty( new PROPERTY<ZONE, long long int>( _HKI( "Minimum Island Area" ),
  1319. &ZONE::SetMinIslandArea, &ZONE::GetMinIslandArea, PROPERTY_DISPLAY::PT_AREA ),
  1320. groupFill )
  1321. .SetAvailableFunc( isCopperZone )
  1322. .SetWriteableFunc( isAreaBasedIslandRemoval );
  1323. const wxString groupElectrical = _HKI( "Electrical" );
  1324. auto clearanceOverride = new PROPERTY<ZONE, std::optional<int>>( _HKI( "Clearance" ),
  1325. &ZONE::SetLocalClearance, &ZONE::GetLocalClearance,
  1326. PROPERTY_DISPLAY::PT_SIZE );
  1327. clearanceOverride->SetAvailableFunc( isCopperZone );
  1328. constexpr int maxClearance = pcbIUScale.mmToIU( ZONE_CLEARANCE_MAX_VALUE_MM );
  1329. clearanceOverride->SetValidator( PROPERTY_VALIDATORS::RangeIntValidator<0, maxClearance> );
  1330. auto minWidth = new PROPERTY<ZONE, int>( _HKI( "Minimum Width" ),
  1331. &ZONE::SetMinThickness, &ZONE::GetMinThickness,
  1332. PROPERTY_DISPLAY::PT_SIZE );
  1333. minWidth->SetAvailableFunc( isCopperZone );
  1334. constexpr int minMinWidth = pcbIUScale.mmToIU( ZONE_THICKNESS_MIN_VALUE_MM );
  1335. clearanceOverride->SetValidator( PROPERTY_VALIDATORS::RangeIntValidator<minMinWidth,
  1336. INT_MAX> );
  1337. auto padConnections = new PROPERTY_ENUM<ZONE, ZONE_CONNECTION>( _HKI( "Pad Connections" ),
  1338. &ZONE::SetPadConnection, &ZONE::GetPadConnection );
  1339. padConnections->SetAvailableFunc( isCopperZone );
  1340. auto thermalGap = new PROPERTY<ZONE, int>( _HKI( "Thermal Relief Gap" ),
  1341. &ZONE::SetThermalReliefGap, &ZONE::GetThermalReliefGap,
  1342. PROPERTY_DISPLAY::PT_SIZE );
  1343. thermalGap->SetAvailableFunc( isCopperZone );
  1344. thermalGap->SetValidator( PROPERTY_VALIDATORS::PositiveIntValidator );
  1345. auto thermalSpokeWidth = new PROPERTY<ZONE, int>( _HKI( "Thermal Relief Spoke Width" ),
  1346. &ZONE::SetThermalReliefSpokeWidth, &ZONE::GetThermalReliefSpokeWidth,
  1347. PROPERTY_DISPLAY::PT_SIZE );
  1348. thermalSpokeWidth->SetAvailableFunc( isCopperZone );
  1349. thermalSpokeWidth->SetValidator( atLeastMinWidthValidator );
  1350. propMgr.AddProperty( clearanceOverride, groupElectrical );
  1351. propMgr.AddProperty( minWidth, groupElectrical );
  1352. propMgr.AddProperty( padConnections, groupElectrical );
  1353. propMgr.AddProperty( thermalGap, groupElectrical );
  1354. propMgr.AddProperty( thermalSpokeWidth, groupElectrical );
  1355. }
  1356. } _ZONE_DESC;
  1357. IMPLEMENT_ENUM_TO_WXANY( ZONE_CONNECTION )
  1358. IMPLEMENT_ENUM_TO_WXANY( ZONE_FILL_MODE )
  1359. IMPLEMENT_ENUM_TO_WXANY( ISLAND_REMOVAL_MODE )