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.

2747 lines
92 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
5 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
18 years ago
5 years ago
18 years ago
14 years ago
18 years ago
18 years ago
5 years ago
Introduction of Graphics Abstraction Layer based rendering for pcbnew. New classes: - VIEW - represents view that is seen by user, takes care of layer ordering & visibility and how it is displayed (which location, how much zoomed, etc.) - VIEW_ITEM - Base class for every item that can be displayed on VIEW (the biggest change is that now it may be necessary to override ViewBBox & ViewGetLayers method for derived classes). - EDA_DRAW_PANEL_GAL - Inherits after EDA_DRAW_PANEL, displays VIEW output, right now it is not editable (in opposite to usual EDA_DRAW_PANEL). - GAL/OPENGL_GAL/CAIRO_GAL - Base Graphics Abstraction Layer class + two different flavours (Cairo is not fully supported yet), that offers methods to draw primitives using different libraries. - WX_VIEW_CONTROLS - Controller for VIEW, handles user events, allows zooming, panning, etc. - PAINTER/PCB_PAINTER - Classes that uses GAL interface to draw items (as you may have already guessed - PCB_PAINTER is a class for drawing PCB specific object, PAINTER is an abstract class). Its methods are invoked by VIEW, when an item has to be drawn. To display a new type of item - you need to implement draw(ITEM_TYPE*) method that draws it using GAL methods. - STROKE_FONT - Implements stroke font drawing using GAL methods. Most important changes to Kicad original code: * EDA_ITEM now inherits from VIEW_ITEM, which is a base class for all drawable objects. * EDA_DRAW_FRAME contains both usual EDA_DRAW_PANEL and new EDA_DRAW_PANEL_GAL, that can be switched anytime. * There are some new layers for displaying multilayer pads, vias & pads holes (these are not shown yet on the right sidebar in pcbnew) * Display order of layers is different than in previous versions (if you are curious - you may check m_galLayerOrder@pcbnew/basepcbframe.cpp). Preserving usual order would result in not very natural display, such as showing silkscreen texts on the bottom. * Introduced new hotkey (Alt+F12) and new menu option (View->Switch canvas) for switching canvas during runtime. * Some of classes (mostly derived from BOARD_ITEM) now includes ViewBBox & ViewGetLayers methods. * Removed tools/class_painter.h, as now it is extended and included in source code. Build changes: * GAL-based rendering option is turned on by a new compilation CMake option KICAD_GAL. * When compiling with CMake option KICAD_GAL=ON, GLEW and Cairo libraries are required. * GAL-related code is compiled into a static library (common/libgal). * Build with KICAD_GAL=OFF should not need any new libraries and should come out as a standard version of Kicad Currently most of items in pcbnew can be displayed using OpenGL (to be done are DIMENSIONS and MARKERS). More details about GAL can be found in: http://www.ohwr.org/attachments/1884/view-spec.pdf
13 years ago
Introduction of Graphics Abstraction Layer based rendering for pcbnew. New classes: - VIEW - represents view that is seen by user, takes care of layer ordering & visibility and how it is displayed (which location, how much zoomed, etc.) - VIEW_ITEM - Base class for every item that can be displayed on VIEW (the biggest change is that now it may be necessary to override ViewBBox & ViewGetLayers method for derived classes). - EDA_DRAW_PANEL_GAL - Inherits after EDA_DRAW_PANEL, displays VIEW output, right now it is not editable (in opposite to usual EDA_DRAW_PANEL). - GAL/OPENGL_GAL/CAIRO_GAL - Base Graphics Abstraction Layer class + two different flavours (Cairo is not fully supported yet), that offers methods to draw primitives using different libraries. - WX_VIEW_CONTROLS - Controller for VIEW, handles user events, allows zooming, panning, etc. - PAINTER/PCB_PAINTER - Classes that uses GAL interface to draw items (as you may have already guessed - PCB_PAINTER is a class for drawing PCB specific object, PAINTER is an abstract class). Its methods are invoked by VIEW, when an item has to be drawn. To display a new type of item - you need to implement draw(ITEM_TYPE*) method that draws it using GAL methods. - STROKE_FONT - Implements stroke font drawing using GAL methods. Most important changes to Kicad original code: * EDA_ITEM now inherits from VIEW_ITEM, which is a base class for all drawable objects. * EDA_DRAW_FRAME contains both usual EDA_DRAW_PANEL and new EDA_DRAW_PANEL_GAL, that can be switched anytime. * There are some new layers for displaying multilayer pads, vias & pads holes (these are not shown yet on the right sidebar in pcbnew) * Display order of layers is different than in previous versions (if you are curious - you may check m_galLayerOrder@pcbnew/basepcbframe.cpp). Preserving usual order would result in not very natural display, such as showing silkscreen texts on the bottom. * Introduced new hotkey (Alt+F12) and new menu option (View->Switch canvas) for switching canvas during runtime. * Some of classes (mostly derived from BOARD_ITEM) now includes ViewBBox & ViewGetLayers methods. * Removed tools/class_painter.h, as now it is extended and included in source code. Build changes: * GAL-based rendering option is turned on by a new compilation CMake option KICAD_GAL. * When compiling with CMake option KICAD_GAL=ON, GLEW and Cairo libraries are required. * GAL-related code is compiled into a static library (common/libgal). * Build with KICAD_GAL=OFF should not need any new libraries and should come out as a standard version of Kicad Currently most of items in pcbnew can be displayed using OpenGL (to be done are DIMENSIONS and MARKERS). More details about GAL can be found in: http://www.ohwr.org/attachments/1884/view-spec.pdf
13 years ago
Introduction of Graphics Abstraction Layer based rendering for pcbnew. New classes: - VIEW - represents view that is seen by user, takes care of layer ordering & visibility and how it is displayed (which location, how much zoomed, etc.) - VIEW_ITEM - Base class for every item that can be displayed on VIEW (the biggest change is that now it may be necessary to override ViewBBox & ViewGetLayers method for derived classes). - EDA_DRAW_PANEL_GAL - Inherits after EDA_DRAW_PANEL, displays VIEW output, right now it is not editable (in opposite to usual EDA_DRAW_PANEL). - GAL/OPENGL_GAL/CAIRO_GAL - Base Graphics Abstraction Layer class + two different flavours (Cairo is not fully supported yet), that offers methods to draw primitives using different libraries. - WX_VIEW_CONTROLS - Controller for VIEW, handles user events, allows zooming, panning, etc. - PAINTER/PCB_PAINTER - Classes that uses GAL interface to draw items (as you may have already guessed - PCB_PAINTER is a class for drawing PCB specific object, PAINTER is an abstract class). Its methods are invoked by VIEW, when an item has to be drawn. To display a new type of item - you need to implement draw(ITEM_TYPE*) method that draws it using GAL methods. - STROKE_FONT - Implements stroke font drawing using GAL methods. Most important changes to Kicad original code: * EDA_ITEM now inherits from VIEW_ITEM, which is a base class for all drawable objects. * EDA_DRAW_FRAME contains both usual EDA_DRAW_PANEL and new EDA_DRAW_PANEL_GAL, that can be switched anytime. * There are some new layers for displaying multilayer pads, vias & pads holes (these are not shown yet on the right sidebar in pcbnew) * Display order of layers is different than in previous versions (if you are curious - you may check m_galLayerOrder@pcbnew/basepcbframe.cpp). Preserving usual order would result in not very natural display, such as showing silkscreen texts on the bottom. * Introduced new hotkey (Alt+F12) and new menu option (View->Switch canvas) for switching canvas during runtime. * Some of classes (mostly derived from BOARD_ITEM) now includes ViewBBox & ViewGetLayers methods. * Removed tools/class_painter.h, as now it is extended and included in source code. Build changes: * GAL-based rendering option is turned on by a new compilation CMake option KICAD_GAL. * When compiling with CMake option KICAD_GAL=ON, GLEW and Cairo libraries are required. * GAL-related code is compiled into a static library (common/libgal). * Build with KICAD_GAL=OFF should not need any new libraries and should come out as a standard version of Kicad Currently most of items in pcbnew can be displayed using OpenGL (to be done are DIMENSIONS and MARKERS). More details about GAL can be found in: http://www.ohwr.org/attachments/1884/view-spec.pdf
13 years ago
Introduction of Graphics Abstraction Layer based rendering for pcbnew. New classes: - VIEW - represents view that is seen by user, takes care of layer ordering & visibility and how it is displayed (which location, how much zoomed, etc.) - VIEW_ITEM - Base class for every item that can be displayed on VIEW (the biggest change is that now it may be necessary to override ViewBBox & ViewGetLayers method for derived classes). - EDA_DRAW_PANEL_GAL - Inherits after EDA_DRAW_PANEL, displays VIEW output, right now it is not editable (in opposite to usual EDA_DRAW_PANEL). - GAL/OPENGL_GAL/CAIRO_GAL - Base Graphics Abstraction Layer class + two different flavours (Cairo is not fully supported yet), that offers methods to draw primitives using different libraries. - WX_VIEW_CONTROLS - Controller for VIEW, handles user events, allows zooming, panning, etc. - PAINTER/PCB_PAINTER - Classes that uses GAL interface to draw items (as you may have already guessed - PCB_PAINTER is a class for drawing PCB specific object, PAINTER is an abstract class). Its methods are invoked by VIEW, when an item has to be drawn. To display a new type of item - you need to implement draw(ITEM_TYPE*) method that draws it using GAL methods. - STROKE_FONT - Implements stroke font drawing using GAL methods. Most important changes to Kicad original code: * EDA_ITEM now inherits from VIEW_ITEM, which is a base class for all drawable objects. * EDA_DRAW_FRAME contains both usual EDA_DRAW_PANEL and new EDA_DRAW_PANEL_GAL, that can be switched anytime. * There are some new layers for displaying multilayer pads, vias & pads holes (these are not shown yet on the right sidebar in pcbnew) * Display order of layers is different than in previous versions (if you are curious - you may check m_galLayerOrder@pcbnew/basepcbframe.cpp). Preserving usual order would result in not very natural display, such as showing silkscreen texts on the bottom. * Introduced new hotkey (Alt+F12) and new menu option (View->Switch canvas) for switching canvas during runtime. * Some of classes (mostly derived from BOARD_ITEM) now includes ViewBBox & ViewGetLayers methods. * Removed tools/class_painter.h, as now it is extended and included in source code. Build changes: * GAL-based rendering option is turned on by a new compilation CMake option KICAD_GAL. * When compiling with CMake option KICAD_GAL=ON, GLEW and Cairo libraries are required. * GAL-related code is compiled into a static library (common/libgal). * Build with KICAD_GAL=OFF should not need any new libraries and should come out as a standard version of Kicad Currently most of items in pcbnew can be displayed using OpenGL (to be done are DIMENSIONS and MARKERS). More details about GAL can be found in: http://www.ohwr.org/attachments/1884/view-spec.pdf
13 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
  6. * Copyright The 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 <base_units.h>
  26. #include <bitmaps.h>
  27. #include <math/util.h> // for KiROUND
  28. #include <eda_draw_frame.h>
  29. #include <geometry/shape_circle.h>
  30. #include <geometry/shape_segment.h>
  31. #include <geometry/shape_simple.h>
  32. #include <geometry/shape_rect.h>
  33. #include <geometry/shape_compound.h>
  34. #include <geometry/shape_null.h>
  35. #include <layer_range.h>
  36. #include <string_utils.h>
  37. #include <i18n_utility.h>
  38. #include <view/view.h>
  39. #include <board.h>
  40. #include <board_connected_item.h>
  41. #include <board_design_settings.h>
  42. #include <footprint.h>
  43. #include <lset.h>
  44. #include <pad.h>
  45. #include <pad_utils.h>
  46. #include <pcb_shape.h>
  47. #include <connectivity/connectivity_data.h>
  48. #include <eda_units.h>
  49. #include <convert_basic_shapes_to_polygon.h>
  50. #include <widgets/msgpanel.h>
  51. #include <pcb_painter.h>
  52. #include <properties/property_validators.h>
  53. #include <wx/log.h>
  54. #include <api/api_enums.h>
  55. #include <api/api_utils.h>
  56. #include <api/api_pcb_utils.h>
  57. #include <api/board/board_types.pb.h>
  58. #include <memory>
  59. #include <macros.h>
  60. #include <magic_enum.hpp>
  61. #include <drc/drc_item.h>
  62. #include "kiface_base.h"
  63. #include "pcbnew_settings.h"
  64. using KIGFX::PCB_PAINTER;
  65. using KIGFX::PCB_RENDER_SETTINGS;
  66. PAD::PAD( FOOTPRINT* parent ) :
  67. BOARD_CONNECTED_ITEM( parent, PCB_PAD_T ),
  68. m_padStack( this )
  69. {
  70. VECTOR2I& drill = m_padStack.Drill().size;
  71. m_padStack.SetSize( { EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 60 ),
  72. EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 60 ) },
  73. PADSTACK::ALL_LAYERS );
  74. drill.x = drill.y = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 30 ); // Default drill size 30 mils.
  75. m_lengthPadToDie = 0;
  76. if( m_parent && m_parent->Type() == PCB_FOOTPRINT_T )
  77. m_pos = GetParent()->GetPosition();
  78. SetShape( F_Cu, PAD_SHAPE::CIRCLE ); // Default pad shape is PAD_CIRCLE.
  79. SetAnchorPadShape( F_Cu, PAD_SHAPE::CIRCLE ); // Default shape for custom shaped pads
  80. // is PAD_CIRCLE.
  81. SetDrillShape( PAD_DRILL_SHAPE::CIRCLE ); // Default pad drill shape is a circle.
  82. m_attribute = PAD_ATTRIB::PTH; // Default pad type is plated through hole
  83. SetProperty( PAD_PROP::NONE ); // no special fabrication property
  84. // Parameters for round rect only:
  85. m_padStack.SetRoundRectRadiusRatio( 0.25, F_Cu ); // from IPC-7351C standard
  86. // Parameters for chamfered rect only:
  87. m_padStack.SetChamferRatio( 0.2, F_Cu );
  88. m_padStack.SetChamferPositions( RECT_NO_CHAMFER, F_Cu );
  89. // Set layers mask to default for a standard thru hole pad.
  90. m_padStack.SetLayerSet( PTHMask() );
  91. SetSubRatsnest( 0 ); // used in ratsnest calculations
  92. SetDirty();
  93. m_effectiveBoundingRadius = 0;
  94. for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, BoardCopperLayerCount() ) )
  95. m_zoneLayerOverrides[layer] = ZLO_NONE;
  96. }
  97. PAD::PAD( const PAD& aOther ) :
  98. BOARD_CONNECTED_ITEM( aOther.GetParent(), PCB_PAD_T ),
  99. m_padStack( this )
  100. {
  101. PAD::operator=( aOther );
  102. const_cast<KIID&>( m_Uuid ) = aOther.m_Uuid;
  103. }
  104. PAD& PAD::operator=( const PAD &aOther )
  105. {
  106. BOARD_CONNECTED_ITEM::operator=( aOther );
  107. ImportSettingsFrom( aOther );
  108. SetPadToDieLength( aOther.GetPadToDieLength() );
  109. SetPosition( aOther.GetPosition() );
  110. SetNumber( aOther.GetNumber() );
  111. SetPinType( aOther.GetPinType() );
  112. SetPinFunction( aOther.GetPinFunction() );
  113. SetSubRatsnest( aOther.GetSubRatsnest() );
  114. m_effectiveBoundingRadius = aOther.m_effectiveBoundingRadius;
  115. return *this;
  116. }
  117. void PAD::Serialize( google::protobuf::Any &aContainer ) const
  118. {
  119. using namespace kiapi::board::types;
  120. Pad pad;
  121. pad.mutable_id()->set_value( m_Uuid.AsStdString() );
  122. kiapi::common::PackVector2( *pad.mutable_position(), GetPosition() );
  123. pad.set_locked( IsLocked() ? kiapi::common::types::LockedState::LS_LOCKED
  124. : kiapi::common::types::LockedState::LS_UNLOCKED );
  125. pad.mutable_net()->mutable_code()->set_value( GetNetCode() );
  126. pad.mutable_net()->set_name( GetNetname() );
  127. pad.set_number( GetNumber().ToUTF8() );
  128. pad.set_type( ToProtoEnum<PAD_ATTRIB, PadType>( GetAttribute() ) );
  129. google::protobuf::Any padStackMsg;
  130. m_padStack.Serialize( padStackMsg );
  131. padStackMsg.UnpackTo( pad.mutable_pad_stack() );
  132. if( GetLocalClearance().has_value() )
  133. pad.mutable_copper_clearance_override()->set_value_nm( *GetLocalClearance() );
  134. aContainer.PackFrom( pad );
  135. }
  136. bool PAD::Deserialize( const google::protobuf::Any &aContainer )
  137. {
  138. kiapi::board::types::Pad pad;
  139. if( !aContainer.UnpackTo( &pad ) )
  140. return false;
  141. const_cast<KIID&>( m_Uuid ) = KIID( pad.id().value() );
  142. SetPosition( kiapi::common::UnpackVector2( pad.position() ) );
  143. SetNetCode( pad.net().code().value() );
  144. SetLocked( pad.locked() == kiapi::common::types::LockedState::LS_LOCKED );
  145. SetAttribute( FromProtoEnum<PAD_ATTRIB>( pad.type() ) );
  146. SetNumber( wxString::FromUTF8( pad.number() ) );
  147. google::protobuf::Any padStackWrapper;
  148. padStackWrapper.PackFrom( pad.pad_stack() );
  149. m_padStack.Deserialize( padStackWrapper );
  150. SetLayer( m_padStack.StartLayer() );
  151. if( pad.has_copper_clearance_override() )
  152. SetLocalClearance( pad.copper_clearance_override().value_nm() );
  153. else
  154. SetLocalClearance( std::nullopt );
  155. return true;
  156. }
  157. void PAD::ClearZoneLayerOverrides()
  158. {
  159. std::unique_lock<std::mutex> cacheLock( m_zoneLayerOverridesMutex );
  160. for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, BoardCopperLayerCount() ) )
  161. m_zoneLayerOverrides[layer] = ZLO_NONE;
  162. }
  163. const ZONE_LAYER_OVERRIDE& PAD::GetZoneLayerOverride( PCB_LAYER_ID aLayer ) const
  164. {
  165. static const ZONE_LAYER_OVERRIDE defaultOverride = ZLO_NONE;
  166. auto it = m_zoneLayerOverrides.find( aLayer );
  167. return it != m_zoneLayerOverrides.end() ? it->second : defaultOverride;
  168. }
  169. void PAD::SetZoneLayerOverride( PCB_LAYER_ID aLayer, ZONE_LAYER_OVERRIDE aOverride )
  170. {
  171. std::unique_lock<std::mutex> cacheLock( m_zoneLayerOverridesMutex );
  172. m_zoneLayerOverrides[aLayer] = aOverride;
  173. }
  174. bool PAD::CanHaveNumber() const
  175. {
  176. // Aperture pads don't get a number
  177. if( IsAperturePad() )
  178. return false;
  179. // NPTH pads don't get numbers
  180. if( GetAttribute() == PAD_ATTRIB::NPTH )
  181. return false;
  182. return true;
  183. }
  184. bool PAD::IsLocked() const
  185. {
  186. if( GetParent() && GetParent()->IsLocked() )
  187. return true;
  188. return BOARD_ITEM::IsLocked();
  189. };
  190. bool PAD::SharesNetTieGroup( const PAD* aOther ) const
  191. {
  192. FOOTPRINT* parentFp = GetParentFootprint();
  193. if( parentFp && parentFp->IsNetTie() && aOther->GetParentFootprint() == parentFp )
  194. {
  195. std::map<wxString, int> padToNetTieGroupMap = parentFp->MapPadNumbersToNetTieGroups();
  196. int thisNetTieGroup = padToNetTieGroupMap[ GetNumber() ];
  197. int otherNetTieGroup = padToNetTieGroupMap[ aOther->GetNumber() ];
  198. return thisNetTieGroup >= 0 && thisNetTieGroup == otherNetTieGroup;
  199. }
  200. return false;
  201. }
  202. bool PAD::IsNoConnectPad() const
  203. {
  204. return m_pinType.Contains( wxT( "no_connect" ) );
  205. }
  206. bool PAD::IsFreePad() const
  207. {
  208. return GetShortNetname().StartsWith( wxT( "unconnected-(" ) )
  209. && m_pinType == wxT( "free" );
  210. }
  211. LSET PAD::PTHMask()
  212. {
  213. static LSET saved = LSET::AllCuMask() | LSET( { F_Mask, B_Mask } );
  214. return saved;
  215. }
  216. LSET PAD::SMDMask()
  217. {
  218. static LSET saved( { F_Cu, F_Paste, F_Mask } );
  219. return saved;
  220. }
  221. LSET PAD::ConnSMDMask()
  222. {
  223. static LSET saved( { F_Cu, F_Mask } );
  224. return saved;
  225. }
  226. LSET PAD::UnplatedHoleMask()
  227. {
  228. static LSET saved = LSET( { F_Cu, B_Cu, F_Mask, B_Mask } );
  229. return saved;
  230. }
  231. LSET PAD::ApertureMask()
  232. {
  233. static LSET saved( { F_Paste } );
  234. return saved;
  235. }
  236. bool PAD::IsFlipped() const
  237. {
  238. FOOTPRINT* parent = GetParentFootprint();
  239. return ( parent && parent->GetLayer() == B_Cu );
  240. }
  241. PCB_LAYER_ID PAD::GetLayer() const
  242. {
  243. return BOARD_ITEM::GetLayer();
  244. }
  245. PCB_LAYER_ID PAD::GetPrincipalLayer() const
  246. {
  247. if( m_attribute == PAD_ATTRIB::SMD || m_attribute == PAD_ATTRIB::CONN || GetLayerSet().none() )
  248. return m_layer;
  249. else
  250. return GetLayerSet().Seq().front();
  251. }
  252. bool PAD::FlashLayer( LSET aLayers ) const
  253. {
  254. for( PCB_LAYER_ID layer : aLayers.Seq() )
  255. {
  256. if( FlashLayer( layer ) )
  257. return true;
  258. }
  259. return false;
  260. }
  261. bool PAD::FlashLayer( int aLayer, bool aOnlyCheckIfPermitted ) const
  262. {
  263. if( aLayer == UNDEFINED_LAYER )
  264. return true;
  265. // Sometimes this is called with GAL layers and should just return true
  266. if( aLayer > PCB_LAYER_ID_COUNT )
  267. return true;
  268. const PCB_LAYER_ID& layer = static_cast<PCB_LAYER_ID>( aLayer );
  269. if( !IsOnLayer( layer ) )
  270. return false;
  271. if( GetAttribute() == PAD_ATTRIB::NPTH && IsCopperLayer( aLayer ) )
  272. {
  273. if( GetShape( layer ) == PAD_SHAPE::CIRCLE && GetDrillShape() == PAD_DRILL_SHAPE::CIRCLE )
  274. {
  275. if( GetOffset( layer ) == VECTOR2I( 0, 0 ) && GetDrillSize().x >= GetSize( layer ).x )
  276. return false;
  277. }
  278. else if( GetShape( layer ) == PAD_SHAPE::OVAL
  279. && GetDrillShape() == PAD_DRILL_SHAPE::OBLONG )
  280. {
  281. if( GetOffset( layer ) == VECTOR2I( 0, 0 )
  282. && GetDrillSize().x >= GetSize( layer ).x
  283. && GetDrillSize().y >= GetSize( layer ).y )
  284. {
  285. return false;
  286. }
  287. }
  288. }
  289. if( LSET::FrontBoardTechMask().test( aLayer ) )
  290. aLayer = F_Cu;
  291. else if( LSET::BackBoardTechMask().test( aLayer ) )
  292. aLayer = B_Cu;
  293. if( GetAttribute() == PAD_ATTRIB::PTH && IsCopperLayer( aLayer ) )
  294. {
  295. PADSTACK::UNCONNECTED_LAYER_MODE mode = m_padStack.UnconnectedLayerMode();
  296. if( mode == PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL )
  297. return true;
  298. // Plated through hole pads need copper on the top/bottom layers for proper soldering
  299. // Unless the user has removed them in the pad dialog
  300. if( mode == PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END
  301. && ( aLayer == F_Cu || aLayer == B_Cu ) )
  302. {
  303. return true;
  304. }
  305. if( const BOARD* board = GetBoard() )
  306. {
  307. // Must be static to keep from raising its ugly head in performance profiles
  308. static std::initializer_list<KICAD_T> types = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T,
  309. PCB_PAD_T };
  310. if( auto it = m_zoneLayerOverrides.find( static_cast<PCB_LAYER_ID>( aLayer ) );
  311. it != m_zoneLayerOverrides.end() && it->second == ZLO_FORCE_FLASHED )
  312. {
  313. return true;
  314. }
  315. else if( aOnlyCheckIfPermitted )
  316. {
  317. return true;
  318. }
  319. else
  320. {
  321. return board->GetConnectivity()->IsConnectedOnLayer( this, aLayer, types );
  322. }
  323. }
  324. }
  325. return true;
  326. }
  327. void PAD::SetDrillSizeX( const int aX )
  328. {
  329. m_padStack.Drill().size.x = aX;
  330. if( GetDrillShape() == PAD_DRILL_SHAPE::CIRCLE )
  331. SetDrillSizeY( aX );
  332. SetDirty();
  333. }
  334. void PAD::SetDrillShape( PAD_DRILL_SHAPE aShape )
  335. {
  336. m_padStack.Drill().shape = aShape;
  337. if( aShape == PAD_DRILL_SHAPE::CIRCLE )
  338. SetDrillSizeY( GetDrillSizeX() );
  339. m_shapesDirty = true;
  340. }
  341. int PAD::GetRoundRectCornerRadius( PCB_LAYER_ID aLayer ) const
  342. {
  343. return m_padStack.RoundRectRadius( aLayer );
  344. }
  345. void PAD::SetRoundRectCornerRadius( PCB_LAYER_ID aLayer, double aRadius )
  346. {
  347. m_padStack.SetRoundRectRadius( aRadius, aLayer );
  348. }
  349. void PAD::SetRoundRectRadiusRatio( PCB_LAYER_ID aLayer, double aRadiusScale )
  350. {
  351. m_padStack.SetRoundRectRadiusRatio( std::clamp( aRadiusScale, 0.0, 0.5 ), aLayer );
  352. SetDirty();
  353. }
  354. void PAD::SetFrontRoundRectRadiusRatio( double aRadiusScale )
  355. {
  356. wxASSERT_MSG( m_padStack.Mode() == PADSTACK::MODE::NORMAL,
  357. "Set front radius only meaningful for normal padstacks" );
  358. m_padStack.SetRoundRectRadiusRatio( std::clamp( aRadiusScale, 0.0, 0.5 ), F_Cu );
  359. SetDirty();
  360. }
  361. void PAD::SetFrontRoundRectRadiusSize( int aRadius )
  362. {
  363. const VECTOR2I size = m_padStack.Size( F_Cu );
  364. const int minSize = std::min( size.x, size.y );
  365. const double newRatio = aRadius / double( minSize );
  366. SetFrontRoundRectRadiusRatio( newRatio );
  367. }
  368. int PAD::GetFrontRoundRectRadiusSize() const
  369. {
  370. const VECTOR2I size = m_padStack.Size( F_Cu );
  371. const int minSize = std::min( size.x, size.y );
  372. const double ratio = GetFrontRoundRectRadiusRatio();
  373. return KiROUND( ratio * minSize );
  374. }
  375. void PAD::SetChamferRectRatio( PCB_LAYER_ID aLayer, double aChamferScale )
  376. {
  377. m_padStack.SetChamferRatio( std::clamp( aChamferScale, 0.0, 0.5 ), aLayer );
  378. SetDirty();
  379. }
  380. const std::shared_ptr<SHAPE_POLY_SET>& PAD::GetEffectivePolygon( PCB_LAYER_ID aLayer,
  381. ERROR_LOC aErrorLoc ) const
  382. {
  383. if( m_polyDirty[ aErrorLoc ] )
  384. BuildEffectivePolygon( aErrorLoc );
  385. aLayer = Padstack().EffectiveLayerFor( aLayer );
  386. return m_effectivePolygons[ aLayer ][ aErrorLoc ];
  387. }
  388. std::shared_ptr<SHAPE> PAD::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING flashPTHPads ) const
  389. {
  390. if( aLayer == Edge_Cuts )
  391. {
  392. std::shared_ptr<SHAPE_COMPOUND> effective_compund = std::make_shared<SHAPE_COMPOUND>();
  393. if( GetAttribute() == PAD_ATTRIB::PTH || GetAttribute() == PAD_ATTRIB::NPTH )
  394. {
  395. effective_compund->AddShape( GetEffectiveHoleShape() );
  396. return effective_compund;
  397. }
  398. else
  399. {
  400. effective_compund->AddShape( std::make_shared<SHAPE_NULL>() );
  401. return effective_compund;
  402. }
  403. }
  404. if( GetAttribute() == PAD_ATTRIB::PTH )
  405. {
  406. bool flash;
  407. std::shared_ptr<SHAPE_COMPOUND> effective_compund = std::make_shared<SHAPE_COMPOUND>();
  408. if( flashPTHPads == FLASHING::NEVER_FLASHED )
  409. flash = false;
  410. else if( flashPTHPads == FLASHING::ALWAYS_FLASHED )
  411. flash = true;
  412. else
  413. flash = FlashLayer( aLayer );
  414. if( !flash )
  415. {
  416. if( GetAttribute() == PAD_ATTRIB::PTH )
  417. {
  418. effective_compund->AddShape( GetEffectiveHoleShape() );
  419. return effective_compund;
  420. }
  421. else
  422. {
  423. effective_compund->AddShape( std::make_shared<SHAPE_NULL>() );
  424. return effective_compund;
  425. }
  426. }
  427. }
  428. if( m_shapesDirty )
  429. BuildEffectiveShapes();
  430. aLayer = Padstack().EffectiveLayerFor( aLayer );
  431. wxASSERT_MSG( m_effectiveShapes.contains( aLayer ) && m_effectiveShapes.at( aLayer ),
  432. wxT( "Null shape in PAD::GetEffectiveShape!" ) );
  433. return m_effectiveShapes[aLayer];
  434. }
  435. std::shared_ptr<SHAPE_SEGMENT> PAD::GetEffectiveHoleShape() const
  436. {
  437. if( m_shapesDirty )
  438. BuildEffectiveShapes();
  439. return m_effectiveHoleShape;
  440. }
  441. int PAD::GetBoundingRadius() const
  442. {
  443. if( m_polyDirty[ ERROR_OUTSIDE ] )
  444. BuildEffectivePolygon( ERROR_OUTSIDE );
  445. return m_effectiveBoundingRadius;
  446. }
  447. void PAD::BuildEffectiveShapes() const
  448. {
  449. std::lock_guard<std::mutex> RAII_lock( m_shapesBuildingLock );
  450. // If we had to wait for the lock then we were probably waiting for someone else to
  451. // finish rebuilding the shapes. So check to see if they're clean now.
  452. if( !m_shapesDirty )
  453. return;
  454. m_effectiveBoundingBox = BOX2I();
  455. Padstack().ForEachUniqueLayer(
  456. [&]( PCB_LAYER_ID aLayer )
  457. {
  458. const SHAPE_COMPOUND& layerShape = buildEffectiveShape( aLayer );
  459. m_effectiveBoundingBox.Merge( layerShape.BBox() );
  460. } );
  461. // Hole shape
  462. m_effectiveHoleShape = nullptr;
  463. VECTOR2I half_size = m_padStack.Drill().size / 2;
  464. int half_width = std::min( half_size.x, half_size.y );
  465. VECTOR2I half_len( half_size.x - half_width, half_size.y - half_width );
  466. RotatePoint( half_len, GetOrientation() );
  467. m_effectiveHoleShape = std::make_shared<SHAPE_SEGMENT>( m_pos - half_len, m_pos + half_len,
  468. half_width * 2 );
  469. m_effectiveBoundingBox.Merge( m_effectiveHoleShape->BBox() );
  470. // All done
  471. m_shapesDirty = false;
  472. }
  473. const SHAPE_COMPOUND& PAD::buildEffectiveShape( PCB_LAYER_ID aLayer ) const
  474. {
  475. const BOARD* board = GetBoard();
  476. int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF;
  477. m_effectiveShapes[aLayer] = std::make_shared<SHAPE_COMPOUND>();
  478. auto add = [this, aLayer]( SHAPE* aShape )
  479. {
  480. m_effectiveShapes[aLayer]->AddShape( aShape );
  481. };
  482. VECTOR2I shapePos = ShapePos( aLayer ); // Fetch only once; rotation involves trig
  483. PAD_SHAPE effectiveShape = GetShape( aLayer );
  484. const VECTOR2I& size = m_padStack.Size( aLayer );
  485. if( effectiveShape == PAD_SHAPE::CUSTOM )
  486. effectiveShape = GetAnchorPadShape( aLayer );
  487. switch( effectiveShape )
  488. {
  489. case PAD_SHAPE::CIRCLE:
  490. add( new SHAPE_CIRCLE( shapePos, size.x / 2 ) );
  491. break;
  492. case PAD_SHAPE::OVAL:
  493. if( size.x == size.y ) // the oval pad is in fact a circle
  494. {
  495. add( new SHAPE_CIRCLE( shapePos, size.x / 2 ) );
  496. }
  497. else
  498. {
  499. VECTOR2I half_size = size / 2;
  500. int half_width = std::min( half_size.x, half_size.y );
  501. VECTOR2I half_len( half_size.x - half_width, half_size.y - half_width );
  502. RotatePoint( half_len, GetOrientation() );
  503. add( new SHAPE_SEGMENT( shapePos - half_len, shapePos + half_len, half_width * 2 ) );
  504. }
  505. break;
  506. case PAD_SHAPE::RECTANGLE:
  507. case PAD_SHAPE::TRAPEZOID:
  508. case PAD_SHAPE::ROUNDRECT:
  509. {
  510. int r = ( effectiveShape == PAD_SHAPE::ROUNDRECT ) ? GetRoundRectCornerRadius( aLayer ) : 0;
  511. VECTOR2I half_size( size.x / 2, size.y / 2 );
  512. VECTOR2I trap_delta( 0, 0 );
  513. if( r )
  514. {
  515. half_size -= VECTOR2I( r, r );
  516. // Avoid degenerated shapes (0 length segments) that always create issues
  517. // For roundrect pad very near a circle, use only a circle
  518. const int min_len = pcbIUScale.mmToIU( 0.0001 );
  519. if( half_size.x < min_len && half_size.y < min_len )
  520. {
  521. add( new SHAPE_CIRCLE( shapePos, r ) );
  522. break;
  523. }
  524. }
  525. else if( effectiveShape == PAD_SHAPE::TRAPEZOID )
  526. {
  527. trap_delta = m_padStack.TrapezoidDeltaSize( aLayer ) / 2;
  528. }
  529. SHAPE_LINE_CHAIN corners;
  530. corners.Append( -half_size.x - trap_delta.y, half_size.y + trap_delta.x );
  531. corners.Append( half_size.x + trap_delta.y, half_size.y - trap_delta.x );
  532. corners.Append( half_size.x - trap_delta.y, -half_size.y + trap_delta.x );
  533. corners.Append( -half_size.x + trap_delta.y, -half_size.y - trap_delta.x );
  534. corners.Rotate( GetOrientation() );
  535. corners.Move( shapePos );
  536. // GAL renders rectangles faster than 4-point polygons so it's worth checking if our
  537. // body shape is a rectangle.
  538. if( corners.PointCount() == 4
  539. &&
  540. ( ( corners.CPoint( 0 ).y == corners.CPoint( 1 ).y
  541. && corners.CPoint( 1 ).x == corners.CPoint( 2 ).x
  542. && corners.CPoint( 2 ).y == corners.CPoint( 3 ).y
  543. && corners.CPoint( 3 ).x == corners.CPoint( 0 ).x )
  544. ||
  545. ( corners.CPoint( 0 ).x == corners.CPoint( 1 ).x
  546. && corners.CPoint( 1 ).y == corners.CPoint( 2 ).y
  547. && corners.CPoint( 2 ).x == corners.CPoint( 3 ).x
  548. && corners.CPoint( 3 ).y == corners.CPoint( 0 ).y )
  549. )
  550. )
  551. {
  552. int width = std::abs( corners.CPoint( 2 ).x - corners.CPoint( 0 ).x );
  553. int height = std::abs( corners.CPoint( 2 ).y - corners.CPoint( 0 ).y );
  554. VECTOR2I pos( std::min( corners.CPoint( 2 ).x, corners.CPoint( 0 ).x ),
  555. std::min( corners.CPoint( 2 ).y, corners.CPoint( 0 ).y ) );
  556. add( new SHAPE_RECT( pos, width, height ) );
  557. }
  558. else
  559. {
  560. add( new SHAPE_SIMPLE( corners ) );
  561. }
  562. if( r )
  563. {
  564. add( new SHAPE_SEGMENT( corners.CPoint( 0 ), corners.CPoint( 1 ), r * 2 ) );
  565. add( new SHAPE_SEGMENT( corners.CPoint( 1 ), corners.CPoint( 2 ), r * 2 ) );
  566. add( new SHAPE_SEGMENT( corners.CPoint( 2 ), corners.CPoint( 3 ), r * 2 ) );
  567. add( new SHAPE_SEGMENT( corners.CPoint( 3 ), corners.CPoint( 0 ), r * 2 ) );
  568. }
  569. }
  570. break;
  571. case PAD_SHAPE::CHAMFERED_RECT:
  572. {
  573. SHAPE_POLY_SET outline;
  574. TransformRoundChamferedRectToPolygon( outline, shapePos, GetSize( aLayer ),
  575. GetOrientation(), GetRoundRectCornerRadius( aLayer ),
  576. GetChamferRectRatio( aLayer ),
  577. GetChamferPositions( aLayer ), 0, maxError,
  578. ERROR_INSIDE );
  579. add( new SHAPE_SIMPLE( outline.COutline( 0 ) ) );
  580. }
  581. break;
  582. default:
  583. wxFAIL_MSG( wxT( "PAD::buildEffectiveShapes: Unsupported pad shape: PAD_SHAPE::" )
  584. + wxString( std::string( magic_enum::enum_name( effectiveShape ) ) ) );
  585. break;
  586. }
  587. if( GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
  588. {
  589. for( const std::shared_ptr<PCB_SHAPE>& primitive : m_padStack.Primitives( aLayer ) )
  590. {
  591. if( !primitive->IsProxyItem() )
  592. {
  593. for( SHAPE* shape : primitive->MakeEffectiveShapes() )
  594. {
  595. shape->Rotate( GetOrientation() );
  596. shape->Move( shapePos );
  597. add( shape );
  598. }
  599. }
  600. }
  601. }
  602. return *m_effectiveShapes[aLayer];
  603. }
  604. void PAD::BuildEffectivePolygon( ERROR_LOC aErrorLoc ) const
  605. {
  606. std::lock_guard<std::mutex> RAII_lock( m_polyBuildingLock );
  607. // If we had to wait for the lock then we were probably waiting for someone else to
  608. // finish rebuilding the shapes. So check to see if they're clean now.
  609. if( !m_polyDirty[ aErrorLoc ] )
  610. return;
  611. const BOARD* board = GetBoard();
  612. int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF;
  613. Padstack().ForEachUniqueLayer(
  614. [&]( PCB_LAYER_ID aLayer )
  615. {
  616. // Polygon
  617. std::shared_ptr<SHAPE_POLY_SET>& effectivePolygon =
  618. m_effectivePolygons[ aLayer ][ aErrorLoc ];
  619. effectivePolygon = std::make_shared<SHAPE_POLY_SET>();
  620. TransformShapeToPolygon( *effectivePolygon, aLayer, 0, maxError, aErrorLoc );
  621. // Bounding radius
  622. if( aErrorLoc == ERROR_OUTSIDE )
  623. {
  624. m_effectiveBoundingRadius = 0;
  625. for( int cnt = 0; cnt < effectivePolygon->OutlineCount(); ++cnt )
  626. {
  627. const SHAPE_LINE_CHAIN& poly = effectivePolygon->COutline( cnt );
  628. for( int ii = 0; ii < poly.PointCount(); ++ii )
  629. {
  630. int dist = KiROUND( ( poly.CPoint( ii ) - m_pos ).EuclideanNorm() );
  631. m_effectiveBoundingRadius = std::max( m_effectiveBoundingRadius, dist );
  632. }
  633. }
  634. }
  635. } );
  636. // All done
  637. m_polyDirty[ aErrorLoc ] = false;
  638. }
  639. const BOX2I PAD::GetBoundingBox() const
  640. {
  641. if( m_shapesDirty )
  642. BuildEffectiveShapes();
  643. return m_effectiveBoundingBox;
  644. }
  645. void PAD::SetAttribute( PAD_ATTRIB aAttribute )
  646. {
  647. if( m_attribute != aAttribute )
  648. {
  649. m_attribute = aAttribute;
  650. LSET& layerMask = m_padStack.LayerSet();
  651. switch( aAttribute )
  652. {
  653. case PAD_ATTRIB::PTH:
  654. // Plump up to all copper layers
  655. layerMask |= LSET::AllCuMask();
  656. break;
  657. case PAD_ATTRIB::SMD:
  658. case PAD_ATTRIB::CONN:
  659. {
  660. // Trim down to no more than one copper layer
  661. LSET copperLayers = layerMask & LSET::AllCuMask();
  662. if( copperLayers.count() > 1 )
  663. {
  664. layerMask &= ~LSET::AllCuMask();
  665. if( copperLayers.test( B_Cu ) )
  666. layerMask.set( B_Cu );
  667. else
  668. layerMask.set( copperLayers.Seq().front() );
  669. }
  670. // No hole
  671. m_padStack.Drill().size = VECTOR2I( 0, 0 );
  672. break;
  673. }
  674. case PAD_ATTRIB::NPTH:
  675. // No number; no net
  676. m_number = wxEmptyString;
  677. SetNetCode( NETINFO_LIST::UNCONNECTED );
  678. break;
  679. }
  680. }
  681. SetDirty();
  682. }
  683. void PAD::SetFrontShape( PAD_SHAPE aShape )
  684. {
  685. const bool wasRoundable = PAD_UTILS::PadHasMeaningfulRoundingRadius( *this, F_Cu );
  686. m_padStack.SetShape( aShape, F_Cu );
  687. const bool isRoundable = PAD_UTILS::PadHasMeaningfulRoundingRadius( *this, F_Cu );
  688. // If we have become roundable, set a sensible rounding default using the IPC rules.
  689. if( !wasRoundable && isRoundable )
  690. {
  691. const double ipcRadiusRatio = PAD_UTILS::GetDefaultIpcRoundingRatio( *this, F_Cu );
  692. m_padStack.SetRoundRectRadiusRatio( ipcRadiusRatio, F_Cu );
  693. }
  694. SetDirty();
  695. }
  696. void PAD::SetProperty( PAD_PROP aProperty )
  697. {
  698. m_property = aProperty;
  699. SetDirty();
  700. }
  701. void PAD::SetOrientation( const EDA_ANGLE& aAngle )
  702. {
  703. m_padStack.SetOrientation( aAngle );
  704. SetDirty();
  705. }
  706. void PAD::SetFPRelativeOrientation( const EDA_ANGLE& aAngle )
  707. {
  708. if( FOOTPRINT* parentFP = GetParentFootprint() )
  709. SetOrientation( aAngle + parentFP->GetOrientation() );
  710. else
  711. SetOrientation( aAngle );
  712. }
  713. EDA_ANGLE PAD::GetFPRelativeOrientation() const
  714. {
  715. if( FOOTPRINT* parentFP = GetParentFootprint() )
  716. return GetOrientation() - parentFP->GetOrientation();
  717. else
  718. return GetOrientation();
  719. }
  720. void PAD::Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
  721. {
  722. MIRROR( m_pos, aCentre, aFlipDirection );
  723. m_padStack.ForEachUniqueLayer(
  724. [&]( PCB_LAYER_ID aLayer )
  725. {
  726. MIRROR( m_padStack.Offset( aLayer ), VECTOR2I{ 0, 0 }, aFlipDirection );
  727. MIRROR( m_padStack.TrapezoidDeltaSize( aLayer ), VECTOR2I{ 0, 0 }, aFlipDirection );
  728. } );
  729. SetFPRelativeOrientation( -GetFPRelativeOrientation() );
  730. auto mirrorBitFlags = []( int& aBitfield, int a, int b )
  731. {
  732. bool temp = aBitfield & a;
  733. if( aBitfield & b )
  734. aBitfield |= a;
  735. else
  736. aBitfield &= ~a;
  737. if( temp )
  738. aBitfield |= b;
  739. else
  740. aBitfield &= ~b;
  741. };
  742. Padstack().ForEachUniqueLayer(
  743. [&]( PCB_LAYER_ID aLayer )
  744. {
  745. if( aFlipDirection == FLIP_DIRECTION::LEFT_RIGHT )
  746. {
  747. mirrorBitFlags( m_padStack.ChamferPositions( aLayer ), RECT_CHAMFER_TOP_LEFT,
  748. RECT_CHAMFER_TOP_RIGHT );
  749. mirrorBitFlags( m_padStack.ChamferPositions( aLayer ), RECT_CHAMFER_BOTTOM_LEFT,
  750. RECT_CHAMFER_BOTTOM_RIGHT );
  751. }
  752. else
  753. {
  754. mirrorBitFlags( m_padStack.ChamferPositions( aLayer ), RECT_CHAMFER_TOP_LEFT,
  755. RECT_CHAMFER_BOTTOM_LEFT );
  756. mirrorBitFlags( m_padStack.ChamferPositions( aLayer ), RECT_CHAMFER_TOP_RIGHT,
  757. RECT_CHAMFER_BOTTOM_RIGHT );
  758. }
  759. } );
  760. // Flip padstack geometry
  761. int copperLayerCount = BoardCopperLayerCount();
  762. m_padStack.FlipLayers( copperLayerCount );
  763. // Flip pads layers after padstack geometry
  764. LSET layerSet = m_padStack.LayerSet();
  765. SetLayerSet( layerSet.Flip( copperLayerCount ) );
  766. // Flip the basic shapes, in custom pads
  767. FlipPrimitives( aFlipDirection );
  768. SetDirty();
  769. }
  770. void PAD::FlipPrimitives( FLIP_DIRECTION aFlipDirection )
  771. {
  772. Padstack().ForEachUniqueLayer(
  773. [&]( PCB_LAYER_ID aLayer )
  774. {
  775. for( std::shared_ptr<PCB_SHAPE>& primitive : m_padStack.Primitives( aLayer ) )
  776. {
  777. // Ensure the primitive parent is up to date. Flip uses GetBoard() that
  778. // imply primitive parent is valid
  779. primitive->SetParent(this);
  780. primitive->Flip( VECTOR2I( 0, 0 ), aFlipDirection );
  781. }
  782. } );
  783. SetDirty();
  784. }
  785. VECTOR2I PAD::ShapePos( PCB_LAYER_ID aLayer ) const
  786. {
  787. VECTOR2I loc_offset = m_padStack.Offset( aLayer );
  788. if( loc_offset.x == 0 && loc_offset.y == 0 )
  789. return m_pos;
  790. RotatePoint( loc_offset, GetOrientation() );
  791. VECTOR2I shape_pos = m_pos + loc_offset;
  792. return shape_pos;
  793. }
  794. bool PAD::IsOnCopperLayer() const
  795. {
  796. if( GetAttribute() == PAD_ATTRIB::NPTH )
  797. {
  798. // NPTH pads have no plated hole cylinder. If their annular ring size is 0 or
  799. // negative, then they have no annular ring either.
  800. bool hasAnnularRing = true;
  801. Padstack().ForEachUniqueLayer(
  802. [&]( PCB_LAYER_ID aLayer )
  803. {
  804. switch( GetShape( aLayer ) )
  805. {
  806. case PAD_SHAPE::CIRCLE:
  807. if( m_padStack.Offset( aLayer ) == VECTOR2I( 0, 0 )
  808. && m_padStack.Size( aLayer ).x <= m_padStack.Drill().size.x )
  809. {
  810. hasAnnularRing = false;
  811. }
  812. break;
  813. case PAD_SHAPE::OVAL:
  814. if( m_padStack.Offset( aLayer ) == VECTOR2I( 0, 0 )
  815. && m_padStack.Size( aLayer ).x <= m_padStack.Drill().size.x
  816. && m_padStack.Size( aLayer ).y <= m_padStack.Drill().size.y )
  817. {
  818. hasAnnularRing = false;
  819. }
  820. break;
  821. default:
  822. // We could subtract the hole polygon from the shape polygon for these, but it
  823. // would be expensive and we're probably well out of the common use cases....
  824. break;
  825. }
  826. } );
  827. if( !hasAnnularRing )
  828. return false;
  829. }
  830. return ( GetLayerSet() & LSET::AllCuMask() ).any();
  831. }
  832. std::optional<int> PAD::GetLocalClearance( wxString* aSource ) const
  833. {
  834. if( m_padStack.Clearance().has_value() && aSource )
  835. *aSource = _( "pad" );
  836. return m_padStack.Clearance();
  837. }
  838. std::optional<int> PAD::GetClearanceOverrides( wxString* aSource ) const
  839. {
  840. if( m_padStack.Clearance().has_value() )
  841. return GetLocalClearance( aSource );
  842. if( FOOTPRINT* parentFootprint = GetParentFootprint() )
  843. return parentFootprint->GetClearanceOverrides( aSource );
  844. return std::optional<int>();
  845. }
  846. int PAD::GetOwnClearance( PCB_LAYER_ID aLayer, wxString* aSource ) const
  847. {
  848. DRC_CONSTRAINT c;
  849. if( GetBoard() && GetBoard()->GetDesignSettings().m_DRCEngine )
  850. {
  851. BOARD_DESIGN_SETTINGS& bds = GetBoard()->GetDesignSettings();
  852. if( GetAttribute() == PAD_ATTRIB::NPTH )
  853. c = bds.m_DRCEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, this, nullptr, aLayer );
  854. else
  855. c = bds.m_DRCEngine->EvalRules( CLEARANCE_CONSTRAINT, this, nullptr, aLayer );
  856. }
  857. if( c.Value().HasMin() )
  858. {
  859. if( aSource )
  860. *aSource = c.GetName();
  861. return c.Value().Min();
  862. }
  863. return 0;
  864. }
  865. int PAD::GetSolderMaskExpansion( PCB_LAYER_ID aLayer ) const
  866. {
  867. // Pads defined only on mask layers (and perhaps on other tech layers) use the shape
  868. // defined by the pad settings only. ALL other pads, even those that don't actually have
  869. // any copper (such as NPTH pads with holes the same size as the pad) get mask expansion.
  870. if( ( m_padStack.LayerSet() & LSET::AllCuMask() ).none() )
  871. return 0;
  872. if( IsFrontLayer( aLayer ) )
  873. aLayer = F_Mask;
  874. else if( IsBackLayer( aLayer ) )
  875. aLayer = B_Mask;
  876. else
  877. return 0;
  878. std::optional<int> margin = m_padStack.SolderMaskMargin( aLayer );
  879. if( !margin.has_value() )
  880. {
  881. if( FOOTPRINT* parentFootprint = GetParentFootprint() )
  882. margin = parentFootprint->GetLocalSolderMaskMargin();
  883. }
  884. if( !margin.has_value() )
  885. {
  886. if( const BOARD* brd = GetBoard() )
  887. margin = brd->GetDesignSettings().m_SolderMaskExpansion;
  888. }
  889. int marginValue = margin.value_or( 0 );
  890. PCB_LAYER_ID cuLayer = ( aLayer == B_Mask ) ? B_Cu : F_Cu;
  891. // ensure mask have a size always >= 0
  892. if( marginValue < 0 )
  893. {
  894. int minsize = -std::min( m_padStack.Size( cuLayer ).x, m_padStack.Size( cuLayer ).y ) / 2;
  895. if( marginValue < minsize )
  896. marginValue = minsize;
  897. }
  898. return marginValue;
  899. }
  900. VECTOR2I PAD::GetSolderPasteMargin( PCB_LAYER_ID aLayer ) const
  901. {
  902. // Pads defined only on mask layers (and perhaps on other tech layers) use the shape
  903. // defined by the pad settings only. ALL other pads, even those that don't actually have
  904. // any copper (such as NPTH pads with holes the same size as the pad) get paste expansion.
  905. if( ( m_padStack.LayerSet() & LSET::AllCuMask() ).none() )
  906. return VECTOR2I( 0, 0 );
  907. if( IsFrontLayer( aLayer ) )
  908. aLayer = F_Paste;
  909. else if( IsBackLayer( aLayer ) )
  910. aLayer = B_Paste;
  911. else
  912. return VECTOR2I( 0, 0 );
  913. std::optional<int> margin = m_padStack.SolderPasteMargin( aLayer );
  914. std::optional<double> mratio = m_padStack.SolderPasteMarginRatio( aLayer );
  915. if( !margin.has_value() )
  916. {
  917. if( FOOTPRINT* parentFootprint = GetParentFootprint() )
  918. margin = parentFootprint->GetLocalSolderPasteMargin();
  919. }
  920. if( !margin.has_value() )
  921. {
  922. if( const BOARD* board = GetBoard() )
  923. margin = board->GetDesignSettings().m_SolderPasteMargin;
  924. }
  925. if( !mratio.has_value() )
  926. {
  927. if( FOOTPRINT* parentFootprint = GetParentFootprint() )
  928. mratio = parentFootprint->GetLocalSolderPasteMarginRatio();
  929. }
  930. if( !mratio.has_value() )
  931. {
  932. if( const BOARD* board = GetBoard() )
  933. mratio = board->GetDesignSettings().m_SolderPasteMarginRatio;
  934. }
  935. PCB_LAYER_ID cuLayer = ( aLayer == B_Paste ) ? B_Cu : F_Cu;
  936. VECTOR2I padSize = m_padStack.Size( cuLayer );
  937. VECTOR2I pad_margin;
  938. pad_margin.x = margin.value_or( 0 ) + KiROUND( padSize.x * mratio.value_or( 0 ) );
  939. pad_margin.y = margin.value_or( 0 ) + KiROUND( padSize.y * mratio.value_or( 0 ) );
  940. // ensure mask have a size always >= 0
  941. if( m_padStack.Shape( aLayer ) != PAD_SHAPE::CUSTOM )
  942. {
  943. if( pad_margin.x < -padSize.x / 2 )
  944. pad_margin.x = -padSize.x / 2;
  945. if( pad_margin.y < -padSize.y / 2 )
  946. pad_margin.y = -padSize.y / 2;
  947. }
  948. return pad_margin;
  949. }
  950. ZONE_CONNECTION PAD::GetZoneConnectionOverrides( wxString* aSource ) const
  951. {
  952. ZONE_CONNECTION connection = m_padStack.ZoneConnection().value_or( ZONE_CONNECTION::INHERITED );
  953. if( connection != ZONE_CONNECTION::INHERITED )
  954. {
  955. if( aSource )
  956. *aSource = _( "pad" );
  957. }
  958. if( connection == ZONE_CONNECTION::INHERITED )
  959. {
  960. if( FOOTPRINT* parentFootprint = GetParentFootprint() )
  961. connection = parentFootprint->GetZoneConnectionOverrides( aSource );
  962. }
  963. return connection;
  964. }
  965. int PAD::GetLocalSpokeWidthOverride( wxString* aSource ) const
  966. {
  967. if( m_padStack.ThermalSpokeWidth().has_value() && aSource )
  968. *aSource = _( "pad" );
  969. return m_padStack.ThermalSpokeWidth().value_or( 0 );
  970. }
  971. int PAD::GetLocalThermalGapOverride( wxString* aSource ) const
  972. {
  973. if( m_padStack.ThermalGap().has_value() && aSource )
  974. *aSource = _( "pad" );
  975. return GetLocalThermalGapOverride().value_or( 0 );
  976. }
  977. void PAD::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  978. {
  979. wxString msg;
  980. FOOTPRINT* parentFootprint = static_cast<FOOTPRINT*>( m_parent );
  981. if( aFrame->GetName() == PCB_EDIT_FRAME_NAME )
  982. {
  983. if( parentFootprint )
  984. aList.emplace_back( _( "Footprint" ), parentFootprint->GetReference() );
  985. }
  986. aList.emplace_back( _( "Pad" ), m_number );
  987. if( !GetPinFunction().IsEmpty() )
  988. aList.emplace_back( _( "Pin Name" ), GetPinFunction() );
  989. if( !GetPinType().IsEmpty() )
  990. aList.emplace_back( _( "Pin Type" ), GetPinType() );
  991. if( aFrame->GetName() == PCB_EDIT_FRAME_NAME )
  992. {
  993. aList.emplace_back( _( "Net" ), UnescapeString( GetNetname() ) );
  994. aList.emplace_back( _( "Resolved Netclass" ),
  995. UnescapeString( GetEffectiveNetClass()->GetHumanReadableName() ) );
  996. if( IsLocked() )
  997. aList.emplace_back( _( "Status" ), _( "Locked" ) );
  998. }
  999. if( GetAttribute() == PAD_ATTRIB::SMD || GetAttribute() == PAD_ATTRIB::CONN )
  1000. aList.emplace_back( _( "Layer" ), layerMaskDescribe() );
  1001. if( aFrame->GetName() == FOOTPRINT_EDIT_FRAME_NAME )
  1002. {
  1003. if( GetAttribute() == PAD_ATTRIB::SMD )
  1004. {
  1005. // TOOD(JE) padstacks
  1006. const std::shared_ptr<SHAPE_POLY_SET>& poly = GetEffectivePolygon( PADSTACK::ALL_LAYERS );
  1007. double area = poly->Area();
  1008. aList.emplace_back( _( "Area" ), aFrame->MessageTextFromValue( area, true, EDA_DATA_TYPE::AREA ) );
  1009. }
  1010. }
  1011. // Show the pad shape, attribute and property
  1012. wxString props = ShowPadAttr();
  1013. if( GetProperty() != PAD_PROP::NONE )
  1014. props += ',';
  1015. switch( GetProperty() )
  1016. {
  1017. case PAD_PROP::NONE: break;
  1018. case PAD_PROP::BGA: props += _( "BGA" ); break;
  1019. case PAD_PROP::FIDUCIAL_GLBL: props += _( "Fiducial global" ); break;
  1020. case PAD_PROP::FIDUCIAL_LOCAL: props += _( "Fiducial local" ); break;
  1021. case PAD_PROP::TESTPOINT: props += _( "Test point" ); break;
  1022. case PAD_PROP::HEATSINK: props += _( "Heat sink" ); break;
  1023. case PAD_PROP::CASTELLATED: props += _( "Castellated" ); break;
  1024. case PAD_PROP::MECHANICAL: props += _( "Mechanical" ); break;
  1025. }
  1026. // TODO(JE) How to show complex padstack info in the message panel
  1027. aList.emplace_back( ShowPadShape( PADSTACK::ALL_LAYERS ), props );
  1028. PAD_SHAPE padShape = GetShape( PADSTACK::ALL_LAYERS );
  1029. VECTOR2I padSize = m_padStack.Size( PADSTACK::ALL_LAYERS );
  1030. if( ( padShape == PAD_SHAPE::CIRCLE || padShape == PAD_SHAPE::OVAL )
  1031. && padSize.x == padSize.y )
  1032. {
  1033. aList.emplace_back( _( "Diameter" ), aFrame->MessageTextFromValue( padSize.x ) );
  1034. }
  1035. else
  1036. {
  1037. aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( padSize.x ) );
  1038. aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( padSize.y ) );
  1039. }
  1040. EDA_ANGLE fp_orient = parentFootprint ? parentFootprint->GetOrientation() : ANGLE_0;
  1041. EDA_ANGLE pad_orient = GetOrientation() - fp_orient;
  1042. pad_orient.Normalize180();
  1043. if( !fp_orient.IsZero() )
  1044. msg.Printf( wxT( "%g(+ %g)" ), pad_orient.AsDegrees(), fp_orient.AsDegrees() );
  1045. else
  1046. msg.Printf( wxT( "%g" ), GetOrientation().AsDegrees() );
  1047. aList.emplace_back( _( "Rotation" ), msg );
  1048. if( GetPadToDieLength() )
  1049. {
  1050. aList.emplace_back( _( "Length in Package" ),
  1051. aFrame->MessageTextFromValue( GetPadToDieLength() ) );
  1052. }
  1053. const VECTOR2I& drill = m_padStack.Drill().size;
  1054. if( drill.x > 0 || drill.y > 0 )
  1055. {
  1056. if( GetDrillShape() == PAD_DRILL_SHAPE::CIRCLE )
  1057. {
  1058. aList.emplace_back( _( "Hole" ),
  1059. wxString::Format( wxT( "%s" ),
  1060. aFrame->MessageTextFromValue( drill.x ) ) );
  1061. }
  1062. else
  1063. {
  1064. aList.emplace_back( _( "Hole X / Y" ),
  1065. wxString::Format( wxT( "%s / %s" ),
  1066. aFrame->MessageTextFromValue( drill.x ),
  1067. aFrame->MessageTextFromValue( drill.y ) ) );
  1068. }
  1069. }
  1070. wxString source;
  1071. int clearance = GetOwnClearance( UNDEFINED_LAYER, &source );
  1072. if( !source.IsEmpty() )
  1073. {
  1074. aList.emplace_back( wxString::Format( _( "Min Clearance: %s" ),
  1075. aFrame->MessageTextFromValue( clearance ) ),
  1076. wxString::Format( _( "(from %s)" ),
  1077. source ) );
  1078. }
  1079. #if 0
  1080. // useful for debug only
  1081. aList.emplace_back( wxT( "UUID" ), m_Uuid.AsString() );
  1082. #endif
  1083. }
  1084. bool PAD::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
  1085. {
  1086. VECTOR2I delta = aPosition - GetPosition();
  1087. int boundingRadius = GetBoundingRadius() + aAccuracy;
  1088. if( delta.SquaredEuclideanNorm() > SEG::Square( boundingRadius ) )
  1089. return false;
  1090. bool contains = false;
  1091. Padstack().ForEachUniqueLayer(
  1092. [&]( PCB_LAYER_ID l )
  1093. {
  1094. if( contains )
  1095. return;
  1096. if( GetEffectivePolygon( l, ERROR_INSIDE )->Contains( aPosition, -1, aAccuracy ) )
  1097. contains = true;
  1098. } );
  1099. return contains;
  1100. }
  1101. bool PAD::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
  1102. {
  1103. BOX2I arect = aRect;
  1104. arect.Normalize();
  1105. arect.Inflate( aAccuracy );
  1106. BOX2I bbox = GetBoundingBox();
  1107. if( aContained )
  1108. {
  1109. return arect.Contains( bbox );
  1110. }
  1111. else
  1112. {
  1113. // Fast test: if aRect is outside the polygon bounding box,
  1114. // rectangles cannot intersect
  1115. if( !arect.Intersects( bbox ) )
  1116. return false;
  1117. bool hit = false;
  1118. Padstack().ForEachUniqueLayer(
  1119. [&]( PCB_LAYER_ID aLayer )
  1120. {
  1121. if( hit )
  1122. return;
  1123. const std::shared_ptr<SHAPE_POLY_SET>& poly =
  1124. GetEffectivePolygon( aLayer, ERROR_INSIDE );
  1125. int count = poly->TotalVertices();
  1126. for( int ii = 0; ii < count; ii++ )
  1127. {
  1128. VECTOR2I vertex = poly->CVertex( ii );
  1129. VECTOR2I vertexNext = poly->CVertex( ( ii + 1 ) % count );
  1130. // Test if the point is within aRect
  1131. if( arect.Contains( vertex ) )
  1132. {
  1133. hit = true;
  1134. break;
  1135. }
  1136. // Test if this edge intersects aRect
  1137. if( arect.Intersects( vertex, vertexNext ) )
  1138. {
  1139. hit = true;
  1140. break;
  1141. }
  1142. }
  1143. } );
  1144. return hit;
  1145. }
  1146. }
  1147. int PAD::Compare( const PAD* aPadRef, const PAD* aPadCmp )
  1148. {
  1149. int diff;
  1150. if( ( diff = static_cast<int>( aPadRef->m_attribute ) -
  1151. static_cast<int>( aPadCmp->m_attribute ) ) != 0 )
  1152. return diff;
  1153. // Dick: specctra_export needs this
  1154. // Lorenzo: gencad also needs it to implement padstacks!
  1155. return PADSTACK::Compare( &aPadRef->Padstack(), &aPadCmp->Padstack() );
  1156. }
  1157. void PAD::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
  1158. {
  1159. RotatePoint( m_pos, aRotCentre, aAngle );
  1160. m_padStack.SetOrientation( m_padStack.GetOrientation() + aAngle );
  1161. SetDirty();
  1162. }
  1163. wxString PAD::ShowPadShape( PCB_LAYER_ID aLayer ) const
  1164. {
  1165. switch( GetShape( aLayer ) )
  1166. {
  1167. case PAD_SHAPE::CIRCLE: return _( "Circle" );
  1168. case PAD_SHAPE::OVAL: return _( "Oval" );
  1169. case PAD_SHAPE::RECTANGLE: return _( "Rect" );
  1170. case PAD_SHAPE::TRAPEZOID: return _( "Trap" );
  1171. case PAD_SHAPE::ROUNDRECT: return _( "Roundrect" );
  1172. case PAD_SHAPE::CHAMFERED_RECT: return _( "Chamferedrect" );
  1173. case PAD_SHAPE::CUSTOM: return _( "CustomShape" );
  1174. default: return wxT( "???" );
  1175. }
  1176. }
  1177. wxString PAD::ShowPadAttr() const
  1178. {
  1179. switch( GetAttribute() )
  1180. {
  1181. case PAD_ATTRIB::PTH: return _( "PTH" );
  1182. case PAD_ATTRIB::SMD: return _( "SMD" );
  1183. case PAD_ATTRIB::CONN: return _( "Conn" );
  1184. case PAD_ATTRIB::NPTH: return _( "NPTH" );
  1185. default: return wxT( "???" );
  1186. }
  1187. }
  1188. wxString PAD::GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const
  1189. {
  1190. FOOTPRINT* parentFP = nullptr;
  1191. if( EDA_DRAW_FRAME* frame = dynamic_cast<EDA_DRAW_FRAME*>( aUnitsProvider ) )
  1192. {
  1193. if( frame->GetName() == PCB_EDIT_FRAME_NAME )
  1194. parentFP = GetParentFootprint();
  1195. }
  1196. if( GetAttribute() == PAD_ATTRIB::NPTH )
  1197. {
  1198. if( parentFP )
  1199. {
  1200. return wxString::Format( _( "NPTH pad of %s" ),
  1201. parentFP->GetReference() );
  1202. }
  1203. else
  1204. {
  1205. return _( "NPTH pad" );
  1206. }
  1207. }
  1208. else if( GetNumber().IsEmpty() )
  1209. {
  1210. if( GetAttribute() == PAD_ATTRIB::SMD || GetAttribute() == PAD_ATTRIB::CONN )
  1211. {
  1212. if( parentFP )
  1213. {
  1214. return wxString::Format( _( "Pad %s of %s on %s" ),
  1215. GetNetnameMsg(),
  1216. parentFP->GetReference(),
  1217. layerMaskDescribe() );
  1218. }
  1219. else
  1220. {
  1221. return wxString::Format( _( "Pad on %s" ),
  1222. layerMaskDescribe() );
  1223. }
  1224. }
  1225. else
  1226. {
  1227. if( parentFP )
  1228. {
  1229. return wxString::Format( _( "PTH pad %s of %s" ),
  1230. GetNetnameMsg(),
  1231. parentFP->GetReference() );
  1232. }
  1233. else
  1234. {
  1235. return _( "PTH pad" );
  1236. }
  1237. }
  1238. }
  1239. else
  1240. {
  1241. if( GetAttribute() == PAD_ATTRIB::SMD || GetAttribute() == PAD_ATTRIB::CONN )
  1242. {
  1243. if( parentFP )
  1244. {
  1245. return wxString::Format( _( "Pad %s %s of %s on %s" ),
  1246. GetNumber(),
  1247. GetNetnameMsg(),
  1248. parentFP->GetReference(),
  1249. layerMaskDescribe() );
  1250. }
  1251. else
  1252. {
  1253. return wxString::Format( _( "Pad %s on %s" ),
  1254. GetNumber(),
  1255. layerMaskDescribe() );
  1256. }
  1257. }
  1258. else
  1259. {
  1260. if( parentFP )
  1261. {
  1262. return wxString::Format( _( "PTH pad %s %s of %s" ),
  1263. GetNumber(),
  1264. GetNetnameMsg(),
  1265. parentFP->GetReference() );
  1266. }
  1267. else
  1268. {
  1269. return wxString::Format( _( "PTH pad %s" ),
  1270. GetNumber() );
  1271. }
  1272. }
  1273. }
  1274. }
  1275. BITMAPS PAD::GetMenuImage() const
  1276. {
  1277. return BITMAPS::pad;
  1278. }
  1279. EDA_ITEM* PAD::Clone() const
  1280. {
  1281. PAD* cloned = new PAD( *this );
  1282. // Ensure the cloned primitives of the pad stack have the right parent
  1283. cloned->Padstack().ForEachUniqueLayer(
  1284. [&]( PCB_LAYER_ID aLayer )
  1285. {
  1286. for( std::shared_ptr<PCB_SHAPE>& primitive : cloned->m_padStack.Primitives( aLayer ) )
  1287. {
  1288. primitive->SetParent( cloned );
  1289. }
  1290. } );
  1291. return cloned;
  1292. }
  1293. std::vector<int> PAD::ViewGetLayers() const
  1294. {
  1295. std::vector<int> layers;
  1296. layers.reserve( 64 );
  1297. // These 2 types of pads contain a hole
  1298. if( m_attribute == PAD_ATTRIB::PTH )
  1299. {
  1300. layers.push_back( LAYER_PAD_PLATEDHOLES );
  1301. layers.push_back( LAYER_PAD_HOLEWALLS );
  1302. }
  1303. if( m_attribute == PAD_ATTRIB::NPTH )
  1304. layers.push_back( LAYER_NON_PLATEDHOLES );
  1305. LSET cuLayers = ( m_padStack.LayerSet() & LSET::AllCuMask() );
  1306. // Don't spend cycles rendering layers that aren't visible
  1307. if( const BOARD* board = GetBoard() )
  1308. cuLayers &= board->GetEnabledLayers();
  1309. if( cuLayers.count() > 1 )
  1310. {
  1311. // Multi layer pad
  1312. for( PCB_LAYER_ID layer : cuLayers.Seq() )
  1313. {
  1314. layers.push_back( LAYER_PAD_COPPER_START + layer );
  1315. layers.push_back( LAYER_CLEARANCE_START + layer );
  1316. }
  1317. layers.push_back( LAYER_PAD_NETNAMES );
  1318. }
  1319. else if( IsOnLayer( F_Cu ) )
  1320. {
  1321. layers.push_back( LAYER_PAD_COPPER_START );
  1322. layers.push_back( LAYER_CLEARANCE_START );
  1323. // Is this a PTH pad that has only front copper? If so, we need to also display the
  1324. // net name on the PTH netname layer so that it isn't blocked by the drill hole.
  1325. if( m_attribute == PAD_ATTRIB::PTH )
  1326. layers.push_back( LAYER_PAD_NETNAMES );
  1327. else
  1328. layers.push_back( LAYER_PAD_FR_NETNAMES );
  1329. }
  1330. else if( IsOnLayer( B_Cu ) )
  1331. {
  1332. layers.push_back( LAYER_PAD_COPPER_START + B_Cu );
  1333. layers.push_back( LAYER_CLEARANCE_START + B_Cu );
  1334. // Is this a PTH pad that has only back copper? If so, we need to also display the
  1335. // net name on the PTH netname layer so that it isn't blocked by the drill hole.
  1336. if( m_attribute == PAD_ATTRIB::PTH )
  1337. layers.push_back( LAYER_PAD_NETNAMES );
  1338. else
  1339. layers.push_back( LAYER_PAD_BK_NETNAMES );
  1340. }
  1341. // Check non-copper layers. This list should include all the layers that the
  1342. // footprint editor allows a pad to be placed on.
  1343. static const PCB_LAYER_ID layers_mech[] = { F_Mask, B_Mask, F_Paste, B_Paste,
  1344. F_Adhes, B_Adhes, F_SilkS, B_SilkS, Dwgs_User, Eco1_User, Eco2_User };
  1345. for( PCB_LAYER_ID each_layer : layers_mech )
  1346. {
  1347. if( IsOnLayer( each_layer ) )
  1348. layers.push_back( each_layer );
  1349. }
  1350. return layers;
  1351. }
  1352. double PAD::ViewGetLOD( int aLayer, const KIGFX::VIEW* aView ) const
  1353. {
  1354. PCB_PAINTER& painter = static_cast<PCB_PAINTER&>( *aView->GetPainter() );
  1355. PCB_RENDER_SETTINGS& renderSettings = *painter.GetSettings();
  1356. const BOARD* board = GetBoard();
  1357. // Meta control for hiding all pads
  1358. if( !aView->IsLayerVisible( LAYER_PADS ) )
  1359. return LOD_HIDE;
  1360. // Handle Render tab switches
  1361. const PCB_LAYER_ID& pcbLayer = static_cast<PCB_LAYER_ID>( aLayer );
  1362. if( !IsFlipped() && !aView->IsLayerVisible( LAYER_FOOTPRINTS_FR ) )
  1363. return LOD_HIDE;
  1364. if( IsFlipped() && !aView->IsLayerVisible( LAYER_FOOTPRINTS_BK ) )
  1365. return LOD_HIDE;
  1366. LSET visible = board->GetVisibleLayers() & board->GetEnabledLayers();
  1367. if( IsHoleLayer( aLayer ) )
  1368. {
  1369. if( !( visible & LSET::PhysicalLayersMask() ).any() )
  1370. return LOD_HIDE;
  1371. }
  1372. else if( IsNetnameLayer( aLayer ) )
  1373. {
  1374. if( renderSettings.GetHighContrast() )
  1375. {
  1376. // Hide netnames unless pad is flashed to a high-contrast layer
  1377. if( !FlashLayer( renderSettings.GetPrimaryHighContrastLayer() ) )
  1378. return LOD_HIDE;
  1379. }
  1380. else
  1381. {
  1382. // Hide netnames unless pad is flashed to a visible layer
  1383. if( !FlashLayer( visible ) )
  1384. return LOD_HIDE;
  1385. }
  1386. // Netnames will be shown only if zoom is appropriate
  1387. const int minSize = std::min( GetBoundingBox().GetWidth(), GetBoundingBox().GetHeight() );
  1388. return lodScaleForThreshold( minSize, pcbIUScale.mmToIU( 0.5 ) );
  1389. }
  1390. VECTOR2L padSize = GetShape( pcbLayer ) != PAD_SHAPE::CUSTOM
  1391. ? VECTOR2L( GetSize( pcbLayer ) ) : GetBoundingBox().GetSize();
  1392. int64_t minSide = std::min( padSize.x, padSize.y );
  1393. if( minSide > 0 )
  1394. return std::min( (double) pcbIUScale.mmToIU( 0.2 ) / minSide, 3.5 );
  1395. return LOD_SHOW;
  1396. }
  1397. const BOX2I PAD::ViewBBox() const
  1398. {
  1399. // Bounding box includes soldermask too. Remember mask and/or paste margins can be < 0
  1400. int solderMaskMargin = 0;
  1401. VECTOR2I solderPasteMargin;
  1402. Padstack().ForEachUniqueLayer(
  1403. [&]( PCB_LAYER_ID aLayer )
  1404. {
  1405. solderMaskMargin = std::max( solderMaskMargin,
  1406. std::max( GetSolderMaskExpansion( aLayer ), 0 ) );
  1407. VECTOR2I layerMargin = GetSolderPasteMargin( aLayer );
  1408. solderPasteMargin.x = std::max( solderPasteMargin.x, layerMargin.x );
  1409. solderPasteMargin.y = std::max( solderPasteMargin.y, layerMargin.y );
  1410. } );
  1411. BOX2I bbox = GetBoundingBox();
  1412. int clearance = 0;
  1413. // If we're drawing clearance lines then get the biggest possible clearance
  1414. if( PCBNEW_SETTINGS* cfg = dynamic_cast<PCBNEW_SETTINGS*>( Kiface().KifaceSettings() ) )
  1415. {
  1416. if( cfg && cfg->m_Display.m_PadClearance && GetBoard() )
  1417. clearance = GetBoard()->GetMaxClearanceValue();
  1418. }
  1419. // Look for the biggest possible bounding box
  1420. int xMargin = std::max( solderMaskMargin, solderPasteMargin.x ) + clearance;
  1421. int yMargin = std::max( solderMaskMargin, solderPasteMargin.y ) + clearance;
  1422. return BOX2I( VECTOR2I( bbox.GetOrigin() ) - VECTOR2I( xMargin, yMargin ),
  1423. VECTOR2I( bbox.GetSize() ) + VECTOR2I( 2 * xMargin, 2 * yMargin ) );
  1424. }
  1425. void PAD::ImportSettingsFrom( const PAD& aMasterPad )
  1426. {
  1427. SetPadstack( aMasterPad.Padstack() );
  1428. // Layer Set should be updated before calling SetAttribute()
  1429. SetLayerSet( aMasterPad.GetLayerSet() );
  1430. SetAttribute( aMasterPad.GetAttribute() );
  1431. // Unfortunately, SetAttribute() can change m_layerMask.
  1432. // Be sure we keep the original mask by calling SetLayerSet() after SetAttribute()
  1433. SetLayerSet( aMasterPad.GetLayerSet() );
  1434. SetProperty( aMasterPad.GetProperty() );
  1435. // Must be after setting attribute and layerSet
  1436. if( !CanHaveNumber() )
  1437. SetNumber( wxEmptyString );
  1438. // I am not sure the m_LengthPadToDie should be imported, because this is a parameter
  1439. // really specific to a given pad (JPC).
  1440. #if 0
  1441. SetPadToDieLength( aMasterPad.GetPadToDieLength() );
  1442. #endif
  1443. // The pad orientation, for historical reasons is the pad rotation + parent rotation.
  1444. EDA_ANGLE pad_rot = aMasterPad.GetOrientation();
  1445. if( aMasterPad.GetParentFootprint() )
  1446. pad_rot -= aMasterPad.GetParentFootprint()->GetOrientation();
  1447. if( GetParentFootprint() )
  1448. pad_rot += GetParentFootprint()->GetOrientation();
  1449. SetOrientation( pad_rot );
  1450. Padstack().ForEachUniqueLayer(
  1451. [&]( PCB_LAYER_ID aLayer )
  1452. {
  1453. if( aMasterPad.GetShape( aLayer ) == PAD_SHAPE::CIRCLE )
  1454. SetSize( F_Cu, VECTOR2I( GetSize( PADSTACK::ALL_LAYERS ).x,
  1455. GetSize( PADSTACK::ALL_LAYERS ).x ) );
  1456. } );
  1457. switch( aMasterPad.GetAttribute() )
  1458. {
  1459. case PAD_ATTRIB::SMD:
  1460. case PAD_ATTRIB::CONN:
  1461. // These pads do not have a hole (they are expected to be on one external copper layer)
  1462. SetDrillSize( VECTOR2I( 0, 0 ) );
  1463. break;
  1464. default:
  1465. ;
  1466. }
  1467. // copy also local settings:
  1468. SetLocalClearance( aMasterPad.GetLocalClearance() );
  1469. SetLocalSolderMaskMargin( aMasterPad.GetLocalSolderMaskMargin() );
  1470. SetLocalSolderPasteMargin( aMasterPad.GetLocalSolderPasteMargin() );
  1471. SetLocalSolderPasteMarginRatio( aMasterPad.GetLocalSolderPasteMarginRatio() );
  1472. SetLocalZoneConnection( aMasterPad.GetLocalZoneConnection() );
  1473. SetLocalThermalSpokeWidthOverride( aMasterPad.GetLocalThermalSpokeWidthOverride() );
  1474. SetThermalSpokeAngle( aMasterPad.GetThermalSpokeAngle() );
  1475. SetLocalThermalGapOverride( aMasterPad.GetLocalThermalGapOverride() );
  1476. SetCustomShapeInZoneOpt( aMasterPad.GetCustomShapeInZoneOpt() );
  1477. m_teardropParams = aMasterPad.m_teardropParams;
  1478. SetDirty();
  1479. }
  1480. void PAD::swapData( BOARD_ITEM* aImage )
  1481. {
  1482. assert( aImage->Type() == PCB_PAD_T );
  1483. std::swap( *this, *static_cast<PAD*>( aImage ) );
  1484. }
  1485. bool PAD::TransformHoleToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
  1486. ERROR_LOC aErrorLoc ) const
  1487. {
  1488. VECTOR2I drillsize = GetDrillSize();
  1489. if( !drillsize.x || !drillsize.y )
  1490. return false;
  1491. std::shared_ptr<SHAPE_SEGMENT> slot = GetEffectiveHoleShape();
  1492. TransformOvalToPolygon( aBuffer, slot->GetSeg().A, slot->GetSeg().B,
  1493. slot->GetWidth() + aClearance * 2, aError, aErrorLoc );
  1494. return true;
  1495. }
  1496. void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance,
  1497. int aMaxError, ERROR_LOC aErrorLoc, bool ignoreLineWidth ) const
  1498. {
  1499. wxASSERT_MSG( !ignoreLineWidth, wxT( "IgnoreLineWidth has no meaning for pads." ) );
  1500. wxASSERT_MSG( aLayer != UNDEFINED_LAYER,
  1501. wxT( "UNDEFINED_LAYER is no longer allowed for PAD::TransformShapeToPolygon" ) );
  1502. // minimal segment count to approximate a circle to create the polygonal pad shape
  1503. // This minimal value is mainly for very small pads, like SM0402.
  1504. // Most of time pads are using the segment count given by aError value.
  1505. const int pad_min_seg_per_circle_count = 16;
  1506. int dx = m_padStack.Size( aLayer ).x / 2;
  1507. int dy = m_padStack.Size( aLayer ).y / 2;
  1508. VECTOR2I padShapePos = ShapePos( aLayer ); // Note: for pad having a shape offset, the pad
  1509. // position is NOT the shape position
  1510. switch( PAD_SHAPE shape = GetShape( aLayer ) )
  1511. {
  1512. case PAD_SHAPE::CIRCLE:
  1513. case PAD_SHAPE::OVAL:
  1514. // Note: dx == dy is not guaranteed for circle pads in legacy boards
  1515. if( dx == dy || ( shape == PAD_SHAPE::CIRCLE ) )
  1516. {
  1517. TransformCircleToPolygon( aBuffer, padShapePos, dx + aClearance, aMaxError, aErrorLoc,
  1518. pad_min_seg_per_circle_count );
  1519. }
  1520. else
  1521. {
  1522. int half_width = std::min( dx, dy );
  1523. VECTOR2I delta( dx - half_width, dy - half_width );
  1524. RotatePoint( delta, GetOrientation() );
  1525. TransformOvalToPolygon( aBuffer, padShapePos - delta, padShapePos + delta,
  1526. ( half_width + aClearance ) * 2, aMaxError, aErrorLoc,
  1527. pad_min_seg_per_circle_count );
  1528. }
  1529. break;
  1530. case PAD_SHAPE::TRAPEZOID:
  1531. case PAD_SHAPE::RECTANGLE:
  1532. {
  1533. const VECTOR2I& trapDelta = m_padStack.TrapezoidDeltaSize( aLayer );
  1534. int ddx = shape == PAD_SHAPE::TRAPEZOID ? trapDelta.x / 2 : 0;
  1535. int ddy = shape == PAD_SHAPE::TRAPEZOID ? trapDelta.y / 2 : 0;
  1536. SHAPE_POLY_SET outline;
  1537. TransformTrapezoidToPolygon( outline, padShapePos, m_padStack.Size( aLayer ),
  1538. GetOrientation(), ddx, ddy, aClearance, aMaxError, aErrorLoc );
  1539. aBuffer.Append( outline );
  1540. break;
  1541. }
  1542. case PAD_SHAPE::CHAMFERED_RECT:
  1543. case PAD_SHAPE::ROUNDRECT:
  1544. {
  1545. bool doChamfer = shape == PAD_SHAPE::CHAMFERED_RECT;
  1546. SHAPE_POLY_SET outline;
  1547. TransformRoundChamferedRectToPolygon( outline, padShapePos, m_padStack.Size( aLayer ),
  1548. GetOrientation(), GetRoundRectCornerRadius( aLayer ),
  1549. doChamfer ? GetChamferRectRatio( aLayer ) : 0,
  1550. doChamfer ? GetChamferPositions( aLayer ) : 0,
  1551. aClearance, aMaxError, aErrorLoc );
  1552. aBuffer.Append( outline );
  1553. break;
  1554. }
  1555. case PAD_SHAPE::CUSTOM:
  1556. {
  1557. SHAPE_POLY_SET outline;
  1558. MergePrimitivesAsPolygon( aLayer, &outline, aErrorLoc );
  1559. outline.Rotate( GetOrientation() );
  1560. outline.Move( VECTOR2I( padShapePos ) );
  1561. if( aClearance > 0 || aErrorLoc == ERROR_OUTSIDE )
  1562. {
  1563. if( aErrorLoc == ERROR_OUTSIDE )
  1564. aClearance += aMaxError;
  1565. outline.Inflate( aClearance, CORNER_STRATEGY::ROUND_ALL_CORNERS, aMaxError );
  1566. outline.Fracture();
  1567. }
  1568. else if( aClearance < 0 )
  1569. {
  1570. // Negative clearances are primarily for drawing solder paste layer, so we don't
  1571. // worry ourselves overly about which side the error is on.
  1572. // aClearance is negative so this is actually a deflate
  1573. outline.Inflate( aClearance, CORNER_STRATEGY::ALLOW_ACUTE_CORNERS, aMaxError );
  1574. outline.Fracture();
  1575. }
  1576. aBuffer.Append( outline );
  1577. break;
  1578. }
  1579. default:
  1580. wxFAIL_MSG( wxT( "PAD::TransformShapeToPolygon no implementation for " )
  1581. + wxString( std::string( magic_enum::enum_name( shape ) ) ) );
  1582. break;
  1583. }
  1584. }
  1585. std::vector<PCB_SHAPE*> PAD::Recombine( bool aIsDryRun, int maxError )
  1586. {
  1587. FOOTPRINT* footprint = GetParentFootprint();
  1588. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  1589. item->ClearFlags( SKIP_STRUCT );
  1590. auto findNext =
  1591. [&]( PCB_LAYER_ID aLayer ) -> PCB_SHAPE*
  1592. {
  1593. SHAPE_POLY_SET padPoly;
  1594. TransformShapeToPolygon( padPoly, aLayer, 0, maxError, ERROR_INSIDE );
  1595. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  1596. {
  1597. PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
  1598. if( !shape || ( shape->GetFlags() & SKIP_STRUCT ) )
  1599. continue;
  1600. if( shape->GetLayer() != aLayer )
  1601. continue;
  1602. if( shape->IsProxyItem() ) // Pad number (and net name) box
  1603. return shape;
  1604. SHAPE_POLY_SET drawPoly;
  1605. shape->TransformShapeToPolygon( drawPoly, aLayer, 0, maxError, ERROR_INSIDE );
  1606. drawPoly.BooleanIntersection( padPoly );
  1607. if( !drawPoly.IsEmpty() )
  1608. return shape;
  1609. }
  1610. return nullptr;
  1611. };
  1612. auto findMatching =
  1613. [&]( PCB_SHAPE* aShape ) -> std::vector<PCB_SHAPE*>
  1614. {
  1615. std::vector<PCB_SHAPE*> matching;
  1616. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  1617. {
  1618. PCB_SHAPE* other = dynamic_cast<PCB_SHAPE*>( item );
  1619. if( !other || ( other->GetFlags() & SKIP_STRUCT ) )
  1620. continue;
  1621. if( GetLayerSet().test( other->GetLayer() )
  1622. && aShape->Compare( other ) == 0 )
  1623. {
  1624. matching.push_back( other );
  1625. }
  1626. }
  1627. return matching;
  1628. };
  1629. PCB_LAYER_ID layer;
  1630. std::vector<PCB_SHAPE*> mergedShapes;
  1631. if( IsOnLayer( F_Cu ) )
  1632. layer = F_Cu;
  1633. else if( IsOnLayer( B_Cu ) )
  1634. layer = B_Cu;
  1635. else
  1636. layer = GetLayerSet().UIOrder().front();
  1637. PAD_SHAPE origShape = GetShape( layer );
  1638. // If there are intersecting items to combine, we need to first make sure the pad is a
  1639. // custom-shape pad.
  1640. if( !aIsDryRun && findNext( layer ) && origShape != PAD_SHAPE::CUSTOM )
  1641. {
  1642. if( origShape == PAD_SHAPE::CIRCLE || origShape == PAD_SHAPE::RECTANGLE )
  1643. {
  1644. // Use the existing pad as an anchor
  1645. SetAnchorPadShape( layer, origShape );
  1646. SetShape( layer, PAD_SHAPE::CUSTOM );
  1647. }
  1648. else
  1649. {
  1650. // Create a new circular anchor and convert existing pad to a polygon primitive
  1651. SHAPE_POLY_SET existingOutline;
  1652. TransformShapeToPolygon( existingOutline, layer, 0, maxError, ERROR_INSIDE );
  1653. int minExtent = std::min( GetSize( layer ).x, GetSize( layer ).y );
  1654. SetAnchorPadShape( layer, PAD_SHAPE::CIRCLE );
  1655. SetSize( layer, VECTOR2I( minExtent, minExtent ) );
  1656. SetShape( layer, PAD_SHAPE::CUSTOM );
  1657. PCB_SHAPE* shape = new PCB_SHAPE( nullptr, SHAPE_T::POLY );
  1658. shape->SetFilled( true );
  1659. shape->SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
  1660. shape->SetPolyShape( existingOutline );
  1661. shape->Move( - ShapePos( layer ) );
  1662. shape->Rotate( VECTOR2I( 0, 0 ), - GetOrientation() );
  1663. AddPrimitive( layer, shape );
  1664. }
  1665. }
  1666. while( PCB_SHAPE* fpShape = findNext( layer ) )
  1667. {
  1668. fpShape->SetFlags( SKIP_STRUCT );
  1669. mergedShapes.push_back( fpShape );
  1670. if( !aIsDryRun )
  1671. {
  1672. PCB_SHAPE* primitive = static_cast<PCB_SHAPE*>( fpShape->Duplicate() );
  1673. primitive->SetParent( nullptr );
  1674. primitive->Move( - ShapePos( layer ) );
  1675. primitive->Rotate( VECTOR2I( 0, 0 ), - GetOrientation() );
  1676. AddPrimitive( layer, primitive );
  1677. }
  1678. // See if there are other shapes that match and mark them for delete. (KiCad won't
  1679. // produce these, but old footprints from other vendors have them.)
  1680. for( PCB_SHAPE* other : findMatching( fpShape ) )
  1681. {
  1682. other->SetFlags( SKIP_STRUCT );
  1683. mergedShapes.push_back( other );
  1684. }
  1685. }
  1686. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  1687. item->ClearFlags( SKIP_STRUCT );
  1688. if( !aIsDryRun )
  1689. ClearFlags( ENTERED );
  1690. return mergedShapes;
  1691. }
  1692. void PAD::CheckPad( UNITS_PROVIDER* aUnitsProvider, bool aForPadProperties,
  1693. const std::function<void( int aErrorCode,
  1694. const wxString& aMsg )>& aErrorHandler ) const
  1695. {
  1696. Padstack().ForEachUniqueLayer(
  1697. [&]( PCB_LAYER_ID aLayer )
  1698. {
  1699. doCheckPad( aLayer, aUnitsProvider, aForPadProperties, aErrorHandler );
  1700. } );
  1701. LSET padlayers_mask = GetLayerSet();
  1702. VECTOR2I drill_size = GetDrillSize();
  1703. if( padlayers_mask.none() )
  1704. aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Pad has no layer)" ) );
  1705. if( GetAttribute() == PAD_ATTRIB::PTH && !IsOnCopperLayer() )
  1706. aErrorHandler( DRCE_PADSTACK, _( "(PTH pad has no copper layers)" ) );
  1707. if( !padlayers_mask[F_Cu] && !padlayers_mask[B_Cu] )
  1708. {
  1709. if( ( drill_size.x || drill_size.y ) && GetAttribute() != PAD_ATTRIB::NPTH )
  1710. {
  1711. aErrorHandler( DRCE_PADSTACK, _( "(plated through holes normally have a copper pad on "
  1712. "at least one layer)" ) );
  1713. }
  1714. }
  1715. if( ( GetProperty() == PAD_PROP::FIDUCIAL_GLBL || GetProperty() == PAD_PROP::FIDUCIAL_LOCAL )
  1716. && GetAttribute() == PAD_ATTRIB::NPTH )
  1717. {
  1718. aErrorHandler( DRCE_PADSTACK, _( "('fiducial' property makes no sense on NPTH pads)" ) );
  1719. }
  1720. if( GetProperty() == PAD_PROP::TESTPOINT && GetAttribute() == PAD_ATTRIB::NPTH )
  1721. aErrorHandler( DRCE_PADSTACK, _( "('testpoint' property makes no sense on NPTH pads)" ) );
  1722. if( GetProperty() == PAD_PROP::HEATSINK && GetAttribute() == PAD_ATTRIB::NPTH )
  1723. aErrorHandler( DRCE_PADSTACK, _( "('heatsink' property makes no sense of NPTH pads)" ) );
  1724. if( GetProperty() == PAD_PROP::CASTELLATED && GetAttribute() != PAD_ATTRIB::PTH )
  1725. aErrorHandler( DRCE_PADSTACK, _( "('castellated' property is for PTH pads)" ) );
  1726. if( GetProperty() == PAD_PROP::BGA && GetAttribute() != PAD_ATTRIB::SMD )
  1727. aErrorHandler( DRCE_PADSTACK, _( "('BGA' property is for SMD pads)" ) );
  1728. if( GetProperty() == PAD_PROP::MECHANICAL && GetAttribute() != PAD_ATTRIB::PTH )
  1729. aErrorHandler( DRCE_PADSTACK, _( "('mechanical' property is for PTH pads)" ) );
  1730. switch( GetAttribute() )
  1731. {
  1732. case PAD_ATTRIB::NPTH: // Not plated, but through hole, a hole is expected
  1733. case PAD_ATTRIB::PTH: // Pad through hole, a hole is also expected
  1734. if( drill_size.x <= 0
  1735. || ( drill_size.y <= 0 && GetDrillShape() == PAD_DRILL_SHAPE::OBLONG ) )
  1736. {
  1737. aErrorHandler( DRCE_PAD_TH_WITH_NO_HOLE, wxEmptyString );
  1738. }
  1739. break;
  1740. case PAD_ATTRIB::CONN: // Connector pads are smd pads, just they do not have solder paste.
  1741. if( padlayers_mask[B_Paste] || padlayers_mask[F_Paste] )
  1742. {
  1743. aErrorHandler( DRCE_PADSTACK, _( "(connector pads normally have no solder paste; use a "
  1744. "SMD pad instead)" ) );
  1745. }
  1746. KI_FALLTHROUGH;
  1747. case PAD_ATTRIB::SMD: // SMD and Connector pads (One external copper layer only)
  1748. {
  1749. if( drill_size.x > 0 || drill_size.y > 0 )
  1750. aErrorHandler( DRCE_PADSTACK_INVALID, _( "(SMD pad has a hole)" ) );
  1751. LSET innerlayers_mask = padlayers_mask & LSET::InternalCuMask();
  1752. if( IsOnLayer( F_Cu ) && IsOnLayer( B_Cu ) )
  1753. {
  1754. aErrorHandler( DRCE_PADSTACK, _( "(SMD pad has copper on both sides of the board)" ) );
  1755. }
  1756. else if( IsOnLayer( F_Cu ) )
  1757. {
  1758. if( IsOnLayer( B_Mask ) )
  1759. {
  1760. aErrorHandler( DRCE_PADSTACK, _( "(SMD pad has copper and mask layers on different "
  1761. "sides of the board)" ) );
  1762. }
  1763. else if( IsOnLayer( B_Paste ) )
  1764. {
  1765. aErrorHandler( DRCE_PADSTACK, _( "(SMD pad has copper and paste layers on different "
  1766. "sides of the board)" ) );
  1767. }
  1768. }
  1769. else if( IsOnLayer( B_Cu ) )
  1770. {
  1771. if( IsOnLayer( F_Mask ) )
  1772. {
  1773. aErrorHandler( DRCE_PADSTACK, _( "(SMD pad has copper and mask layers on different "
  1774. "sides of the board)" ) );
  1775. }
  1776. else if( IsOnLayer( F_Paste ) )
  1777. {
  1778. aErrorHandler( DRCE_PADSTACK, _( "(SMD pad has copper and paste layers on different "
  1779. "sides of the board)" ) );
  1780. }
  1781. }
  1782. else if( innerlayers_mask.count() != 0 )
  1783. {
  1784. aErrorHandler( DRCE_PADSTACK, _( "(SMD pad has no outer layers)" ) );
  1785. }
  1786. break;
  1787. }
  1788. }
  1789. }
  1790. void PAD::doCheckPad( PCB_LAYER_ID aLayer, UNITS_PROVIDER* aUnitsProvider, bool aForPadProperties,
  1791. const std::function<void( int aErrorCode,
  1792. const wxString& aMsg )>& aErrorHandler ) const
  1793. {
  1794. wxString msg;
  1795. VECTOR2I pad_size = GetSize( aLayer );
  1796. if( GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
  1797. pad_size = GetBoundingBox().GetSize();
  1798. else if( pad_size.x <= 0 || ( pad_size.y <= 0 && GetShape( aLayer ) != PAD_SHAPE::CIRCLE ) )
  1799. aErrorHandler( DRCE_PADSTACK_INVALID, _( "(Pad must have a positive size)" ) );
  1800. // Test hole against pad shape
  1801. if( IsOnCopperLayer() && GetDrillSize().x > 0 )
  1802. {
  1803. // Ensure the drill size can be handled in next calculations.
  1804. // Use min size = 4 IU to be able to build a polygon from a hole shape
  1805. const int min_drill_size = 4;
  1806. if( GetDrillSizeX() <= min_drill_size || GetDrillSizeY() <= min_drill_size )
  1807. {
  1808. msg.Printf( _( "(PTH pad hole size must be larger than %s)" ),
  1809. aUnitsProvider->StringFromValue( min_drill_size, true ) );
  1810. aErrorHandler( DRCE_PADSTACK_INVALID, msg );
  1811. }
  1812. int maxError = GetBoard()->GetDesignSettings().m_MaxError;
  1813. SHAPE_POLY_SET padOutline;
  1814. TransformShapeToPolygon( padOutline, aLayer, 0, maxError, ERROR_INSIDE );
  1815. if( GetAttribute() == PAD_ATTRIB::PTH )
  1816. {
  1817. // Test if there is copper area outside hole
  1818. std::shared_ptr<SHAPE_SEGMENT> hole = GetEffectiveHoleShape();
  1819. SHAPE_POLY_SET holeOutline;
  1820. TransformOvalToPolygon( holeOutline, hole->GetSeg().A, hole->GetSeg().B,
  1821. hole->GetWidth(), ARC_HIGH_DEF, ERROR_OUTSIDE );
  1822. SHAPE_POLY_SET copper = padOutline;
  1823. copper.BooleanSubtract( holeOutline );
  1824. if( copper.IsEmpty() )
  1825. {
  1826. aErrorHandler( DRCE_PADSTACK, _( "(PTH pad hole leaves no copper)" ) );
  1827. }
  1828. else if( aForPadProperties )
  1829. {
  1830. // Test if the pad hole is fully inside the copper area. Note that we only run
  1831. // this check for pad properties because we run the more complete annular ring
  1832. // checker on the board (which handles multiple pads with the same name).
  1833. holeOutline.BooleanSubtract( padOutline );
  1834. if( !holeOutline.IsEmpty() )
  1835. aErrorHandler( DRCE_PADSTACK, _( "(PTH pad hole not fully inside copper)" ) );
  1836. }
  1837. }
  1838. else
  1839. {
  1840. // Test only if the pad hole's centre is inside the copper area
  1841. if( !padOutline.Collide( GetPosition() ) )
  1842. aErrorHandler( DRCE_PADSTACK, _( "(pad hole not inside pad shape)" ) );
  1843. }
  1844. }
  1845. if( GetLocalClearance().value_or( 0 ) < 0 )
  1846. aErrorHandler( DRCE_PADSTACK, _( "(negative local clearance values have no effect)" ) );
  1847. // Some pads need a negative solder mask clearance (mainly for BGA with small pads)
  1848. // However the negative solder mask clearance must not create negative mask size
  1849. // Therefore test for minimal acceptable negative value
  1850. std::optional<int> solderMaskMargin = GetLocalSolderMaskMargin();
  1851. if( solderMaskMargin.has_value() && solderMaskMargin.value() < 0 )
  1852. {
  1853. int absMargin = abs( solderMaskMargin.value() );
  1854. if( GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
  1855. {
  1856. for( const std::shared_ptr<PCB_SHAPE>& shape : GetPrimitives( aLayer ) )
  1857. {
  1858. BOX2I shapeBBox = shape->GetBoundingBox();
  1859. if( absMargin > shapeBBox.GetWidth() || absMargin > shapeBBox.GetHeight() )
  1860. {
  1861. aErrorHandler( DRCE_PADSTACK, _( "(negative solder mask clearance is larger "
  1862. "than some shape primitives; results may be "
  1863. "surprising)" ) );
  1864. break;
  1865. }
  1866. }
  1867. }
  1868. else if( absMargin > pad_size.x || absMargin > pad_size.y )
  1869. {
  1870. aErrorHandler( DRCE_PADSTACK, _( "(negative solder mask clearance is larger than pad; "
  1871. "no solder mask will be generated)" ) );
  1872. }
  1873. }
  1874. // Some pads need a positive solder paste clearance (mainly for BGA with small pads)
  1875. // However, a positive value can create issues if the resulting shape is too big.
  1876. // (like a solder paste creating a solder paste area on a neighbor pad or on the solder mask)
  1877. // So we could ask for user to confirm the choice
  1878. // For now we just check for disappearing paste
  1879. wxSize paste_size;
  1880. int paste_margin = GetLocalSolderPasteMargin().value_or( 0 );
  1881. double paste_ratio = GetLocalSolderPasteMarginRatio().value_or( 0 );
  1882. paste_size.x = pad_size.x + paste_margin + KiROUND( pad_size.x * paste_ratio );
  1883. paste_size.y = pad_size.y + paste_margin + KiROUND( pad_size.y * paste_ratio );
  1884. if( paste_size.x <= 0 || paste_size.y <= 0 )
  1885. {
  1886. aErrorHandler( DRCE_PADSTACK, _( "(negative solder paste margin is larger than pad; "
  1887. "no solder paste mask will be generated)" ) );
  1888. }
  1889. if( GetShape( aLayer ) == PAD_SHAPE::ROUNDRECT )
  1890. {
  1891. if( GetRoundRectRadiusRatio( aLayer ) < 0.0 )
  1892. aErrorHandler( DRCE_PADSTACK_INVALID, _( "(negative corner radius is not allowed)" ) );
  1893. else if( GetRoundRectRadiusRatio( aLayer ) > 50.0 )
  1894. aErrorHandler( DRCE_PADSTACK, _( "(corner size will make pad circular)" ) );
  1895. }
  1896. else if( GetShape( aLayer ) == PAD_SHAPE::CHAMFERED_RECT )
  1897. {
  1898. if( GetChamferRectRatio( aLayer ) < 0.0 )
  1899. aErrorHandler( DRCE_PADSTACK_INVALID, _( "(negative corner chamfer is not allowed)" ) );
  1900. else if( GetChamferRectRatio( aLayer ) > 50.0 )
  1901. aErrorHandler( DRCE_PADSTACK_INVALID, _( "(corner chamfer is too large)" ) );
  1902. }
  1903. else if( GetShape( aLayer ) == PAD_SHAPE::TRAPEZOID )
  1904. {
  1905. if( ( GetDelta( aLayer ).x < 0 && GetDelta( aLayer ).x < -GetSize( aLayer ).y )
  1906. || ( GetDelta( aLayer ).x > 0 && GetDelta( aLayer ).x > GetSize( aLayer ).y )
  1907. || ( GetDelta( aLayer ).y < 0 && GetDelta( aLayer ).y < -GetSize( aLayer ).x )
  1908. || ( GetDelta( aLayer ).y > 0 && GetDelta( aLayer ).y > GetSize( aLayer ).x ) )
  1909. {
  1910. aErrorHandler( DRCE_PADSTACK_INVALID, _( "(trapezoid delta is too large)" ) );
  1911. }
  1912. }
  1913. if( GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
  1914. {
  1915. SHAPE_POLY_SET mergedPolygon;
  1916. MergePrimitivesAsPolygon( aLayer, &mergedPolygon );
  1917. if( mergedPolygon.OutlineCount() > 1 )
  1918. aErrorHandler( DRCE_PADSTACK_INVALID, _( "(custom pad shape must resolve to a single polygon)" ) );
  1919. }
  1920. }
  1921. bool PAD::operator==( const BOARD_ITEM& aBoardItem ) const
  1922. {
  1923. if( Type() != aBoardItem.Type() )
  1924. return false;
  1925. if( m_parent && aBoardItem.GetParent() && m_parent->m_Uuid != aBoardItem.GetParent()->m_Uuid )
  1926. return false;
  1927. const PAD& other = static_cast<const PAD&>( aBoardItem );
  1928. return *this == other;
  1929. }
  1930. bool PAD::operator==( const PAD& aOther ) const
  1931. {
  1932. if( Padstack() != aOther.Padstack() )
  1933. return false;
  1934. if( GetPosition() != aOther.GetPosition() )
  1935. return false;
  1936. if( GetAttribute() != aOther.GetAttribute() )
  1937. return false;
  1938. return true;
  1939. }
  1940. double PAD::Similarity( const BOARD_ITEM& aOther ) const
  1941. {
  1942. if( aOther.Type() != Type() )
  1943. return 0.0;
  1944. if( m_parent->m_Uuid != aOther.GetParent()->m_Uuid )
  1945. return 0.0;
  1946. const PAD& other = static_cast<const PAD&>( aOther );
  1947. double similarity = 1.0;
  1948. if( GetPosition() != other.GetPosition() )
  1949. similarity *= 0.9;
  1950. if( GetAttribute() != other.GetAttribute() )
  1951. similarity *= 0.9;
  1952. similarity *= Padstack().Similarity( other.Padstack() );
  1953. return similarity;
  1954. }
  1955. static struct PAD_DESC
  1956. {
  1957. PAD_DESC()
  1958. {
  1959. ENUM_MAP<PAD_ATTRIB>::Instance()
  1960. .Map( PAD_ATTRIB::PTH, _HKI( "Through-hole" ) )
  1961. .Map( PAD_ATTRIB::SMD, _HKI( "SMD" ) )
  1962. .Map( PAD_ATTRIB::CONN, _HKI( "Edge connector" ) )
  1963. .Map( PAD_ATTRIB::NPTH, _HKI( "NPTH, mechanical" ) );
  1964. ENUM_MAP<PAD_SHAPE>::Instance()
  1965. .Map( PAD_SHAPE::CIRCLE, _HKI( "Circle" ) )
  1966. .Map( PAD_SHAPE::RECTANGLE, _HKI( "Rectangle" ) )
  1967. .Map( PAD_SHAPE::OVAL, _HKI( "Oval" ) )
  1968. .Map( PAD_SHAPE::TRAPEZOID, _HKI( "Trapezoid" ) )
  1969. .Map( PAD_SHAPE::ROUNDRECT, _HKI( "Rounded rectangle" ) )
  1970. .Map( PAD_SHAPE::CHAMFERED_RECT, _HKI( "Chamfered rectangle" ) )
  1971. .Map( PAD_SHAPE::CUSTOM, _HKI( "Custom" ) );
  1972. ENUM_MAP<PAD_PROP>::Instance()
  1973. .Map( PAD_PROP::NONE, _HKI( "None" ) )
  1974. .Map( PAD_PROP::BGA, _HKI( "BGA pad" ) )
  1975. .Map( PAD_PROP::FIDUCIAL_GLBL, _HKI( "Fiducial, global to board" ) )
  1976. .Map( PAD_PROP::FIDUCIAL_LOCAL, _HKI( "Fiducial, local to footprint" ) )
  1977. .Map( PAD_PROP::TESTPOINT, _HKI( "Test point pad" ) )
  1978. .Map( PAD_PROP::HEATSINK, _HKI( "Heatsink pad" ) )
  1979. .Map( PAD_PROP::CASTELLATED, _HKI( "Castellated pad" ) )
  1980. .Map( PAD_PROP::MECHANICAL, _HKI( "Mechanical pad" ) );
  1981. ENUM_MAP<PAD_DRILL_SHAPE>::Instance()
  1982. .Map( PAD_DRILL_SHAPE::CIRCLE, _HKI( "Round" ) )
  1983. .Map( PAD_DRILL_SHAPE::OBLONG, _HKI( "Oblong" ) );
  1984. ENUM_MAP<ZONE_CONNECTION>& zcMap = ENUM_MAP<ZONE_CONNECTION>::Instance();
  1985. if( zcMap.Choices().GetCount() == 0 )
  1986. {
  1987. zcMap.Undefined( ZONE_CONNECTION::INHERITED );
  1988. zcMap.Map( ZONE_CONNECTION::INHERITED, _HKI( "Inherited" ) )
  1989. .Map( ZONE_CONNECTION::NONE, _HKI( "None" ) )
  1990. .Map( ZONE_CONNECTION::THERMAL, _HKI( "Thermal reliefs" ) )
  1991. .Map( ZONE_CONNECTION::FULL, _HKI( "Solid" ) )
  1992. .Map( ZONE_CONNECTION::THT_THERMAL, _HKI( "Thermal reliefs for PTH" ) );
  1993. }
  1994. ENUM_MAP<PADSTACK::UNCONNECTED_LAYER_MODE>::Instance()
  1995. .Map( PADSTACK::UNCONNECTED_LAYER_MODE::KEEP_ALL, _HKI( "All copper layers" ) )
  1996. .Map( PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_ALL, _HKI( "Connected layers only" ) )
  1997. .Map( PADSTACK::UNCONNECTED_LAYER_MODE::REMOVE_EXCEPT_START_AND_END,
  1998. _HKI( "Front, back and connected layers" ) );
  1999. PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
  2000. REGISTER_TYPE( PAD );
  2001. propMgr.InheritsAfter( TYPE_HASH( PAD ), TYPE_HASH( BOARD_CONNECTED_ITEM ) );
  2002. propMgr.Mask( TYPE_HASH( PAD ), TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Layer" ) );
  2003. propMgr.Mask( TYPE_HASH( PAD ), TYPE_HASH( BOARD_ITEM ), _HKI( "Locked" ) );
  2004. propMgr.AddProperty( new PROPERTY<PAD, double>( _HKI( "Orientation" ),
  2005. &PAD::SetOrientationDegrees, &PAD::GetOrientationDegrees,
  2006. PROPERTY_DISPLAY::PT_DEGREE ) );
  2007. auto isCopperPad =
  2008. []( INSPECTABLE* aItem ) -> bool
  2009. {
  2010. if( PAD* pad = dynamic_cast<PAD*>( aItem ) )
  2011. return pad->GetAttribute() != PAD_ATTRIB::NPTH;
  2012. return false;
  2013. };
  2014. auto padCanHaveHole =
  2015. []( INSPECTABLE* aItem ) -> bool
  2016. {
  2017. if( PAD* pad = dynamic_cast<PAD*>( aItem ) )
  2018. {
  2019. return pad->GetAttribute() == PAD_ATTRIB::PTH
  2020. || pad->GetAttribute() == PAD_ATTRIB::NPTH;
  2021. }
  2022. return false;
  2023. };
  2024. auto hasNormalPadstack =
  2025. []( INSPECTABLE* aItem ) -> bool
  2026. {
  2027. if( PAD* pad = dynamic_cast<PAD*>( aItem ) )
  2028. return pad->Padstack().Mode() == PADSTACK::MODE::NORMAL;
  2029. return true;
  2030. };
  2031. propMgr.OverrideAvailability( TYPE_HASH( PAD ), TYPE_HASH( BOARD_CONNECTED_ITEM ),
  2032. _HKI( "Net" ), isCopperPad );
  2033. propMgr.OverrideAvailability( TYPE_HASH( PAD ), TYPE_HASH( BOARD_CONNECTED_ITEM ),
  2034. _HKI( "Net Class" ), isCopperPad );
  2035. const wxString groupPad = _HKI( "Pad Properties" );
  2036. auto padType = new PROPERTY_ENUM<PAD, PAD_ATTRIB>( _HKI( "Pad Type" ),
  2037. &PAD::SetAttribute, &PAD::GetAttribute );
  2038. propMgr.AddProperty( padType, groupPad );
  2039. auto shape = new PROPERTY_ENUM<PAD, PAD_SHAPE>( _HKI( "Pad Shape" ),
  2040. &PAD::SetFrontShape, &PAD::GetFrontShape );
  2041. propMgr.AddProperty( shape, groupPad )
  2042. .SetAvailableFunc( hasNormalPadstack );
  2043. auto padNumber = new PROPERTY<PAD, wxString>( _HKI( "Pad Number" ),
  2044. &PAD::SetNumber, &PAD::GetNumber );
  2045. padNumber->SetAvailableFunc( isCopperPad );
  2046. propMgr.AddProperty( padNumber, groupPad );
  2047. propMgr.AddProperty( new PROPERTY<PAD, wxString>( _HKI( "Pin Name" ),
  2048. NO_SETTER( PAD, wxString ), &PAD::GetPinFunction ), groupPad )
  2049. .SetIsHiddenFromLibraryEditors();
  2050. propMgr.AddProperty( new PROPERTY<PAD, wxString>( _HKI( "Pin Type" ),
  2051. NO_SETTER( PAD, wxString ), &PAD::GetPinType ), groupPad )
  2052. .SetIsHiddenFromLibraryEditors();
  2053. propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Size X" ),
  2054. &PAD::SetSizeX, &PAD::GetSizeX,
  2055. PROPERTY_DISPLAY::PT_SIZE ), groupPad )
  2056. .SetAvailableFunc( hasNormalPadstack );
  2057. propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Size Y" ),
  2058. &PAD::SetSizeY, &PAD::GetSizeY,
  2059. PROPERTY_DISPLAY::PT_SIZE ), groupPad )
  2060. .SetAvailableFunc(
  2061. [=]( INSPECTABLE* aItem ) -> bool
  2062. {
  2063. if( PAD* pad = dynamic_cast<PAD*>( aItem ) )
  2064. {
  2065. // Custom padstacks can't have size modified through panel
  2066. if( pad->Padstack().Mode() != PADSTACK::MODE::NORMAL )
  2067. return false;
  2068. // Circle pads have no usable y-size
  2069. return pad->GetShape( PADSTACK::ALL_LAYERS ) != PAD_SHAPE::CIRCLE;
  2070. }
  2071. return true;
  2072. } );
  2073. const auto hasRoundRadius = [=]( INSPECTABLE* aItem ) -> bool
  2074. {
  2075. if( PAD* pad = dynamic_cast<PAD*>( aItem ) )
  2076. {
  2077. // Custom padstacks can't have this property modified through panel
  2078. if( pad->Padstack().Mode() != PADSTACK::MODE::NORMAL )
  2079. return false;
  2080. return PAD_UTILS::PadHasMeaningfulRoundingRadius( *pad, F_Cu );
  2081. }
  2082. return false;
  2083. };
  2084. auto roundRadiusRatio = new PROPERTY<PAD, double>( _HKI( "Corner Radius Ratio" ),
  2085. &PAD::SetFrontRoundRectRadiusRatio, &PAD::GetFrontRoundRectRadiusRatio );
  2086. roundRadiusRatio->SetAvailableFunc( hasRoundRadius );
  2087. propMgr.AddProperty( roundRadiusRatio, groupPad );
  2088. auto roundRadiusSize = new PROPERTY<PAD, int>( _HKI( "Corner Radius Size" ),
  2089. &PAD::SetFrontRoundRectRadiusSize, &PAD::GetFrontRoundRectRadiusSize,
  2090. PROPERTY_DISPLAY::PT_SIZE );
  2091. roundRadiusSize->SetAvailableFunc( hasRoundRadius );
  2092. propMgr.AddProperty( roundRadiusSize, groupPad );
  2093. propMgr.AddProperty( new PROPERTY_ENUM<PAD, PAD_DRILL_SHAPE>( _HKI( "Hole Shape" ),
  2094. &PAD::SetDrillShape, &PAD::GetDrillShape ), groupPad )
  2095. .SetWriteableFunc( padCanHaveHole );
  2096. propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Hole Size X" ),
  2097. &PAD::SetDrillSizeX, &PAD::GetDrillSizeX,
  2098. PROPERTY_DISPLAY::PT_SIZE ), groupPad )
  2099. .SetWriteableFunc( padCanHaveHole )
  2100. .SetValidator( PROPERTY_VALIDATORS::PositiveIntValidator );
  2101. propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Hole Size Y" ),
  2102. &PAD::SetDrillSizeY, &PAD::GetDrillSizeY,
  2103. PROPERTY_DISPLAY::PT_SIZE ), groupPad )
  2104. .SetWriteableFunc( padCanHaveHole )
  2105. .SetValidator( PROPERTY_VALIDATORS::PositiveIntValidator )
  2106. .SetAvailableFunc(
  2107. [=]( INSPECTABLE* aItem ) -> bool
  2108. {
  2109. // Circle holes have no usable y-size
  2110. if( PAD* pad = dynamic_cast<PAD*>( aItem ) )
  2111. return pad->GetDrillShape() != PAD_DRILL_SHAPE::CIRCLE;
  2112. return true;
  2113. } );
  2114. propMgr.AddProperty( new PROPERTY_ENUM<PAD, PAD_PROP>( _HKI( "Fabrication Property" ),
  2115. &PAD::SetProperty, &PAD::GetProperty ), groupPad );
  2116. auto layerMode = new PROPERTY_ENUM<PAD, PADSTACK::UNCONNECTED_LAYER_MODE>(
  2117. _HKI( "Copper Layers" ),
  2118. &PAD::SetUnconnectedLayerMode, &PAD::GetUnconnectedLayerMode );
  2119. propMgr.AddProperty( layerMode, groupPad );
  2120. auto padToDie = new PROPERTY<PAD, int>( _HKI( "Pad To Die Length" ),
  2121. &PAD::SetPadToDieLength, &PAD::GetPadToDieLength,
  2122. PROPERTY_DISPLAY::PT_SIZE );
  2123. padToDie->SetAvailableFunc( isCopperPad );
  2124. propMgr.AddProperty( padToDie, groupPad );
  2125. const wxString groupOverrides = _HKI( "Overrides" );
  2126. propMgr.AddProperty( new PROPERTY<PAD, std::optional<int>>(
  2127. _HKI( "Clearance Override" ),
  2128. &PAD::SetLocalClearance, &PAD::GetLocalClearance,
  2129. PROPERTY_DISPLAY::PT_SIZE ), groupOverrides );
  2130. propMgr.AddProperty( new PROPERTY<PAD, std::optional<int>>(
  2131. _HKI( "Soldermask Margin Override" ),
  2132. &PAD::SetLocalSolderMaskMargin, &PAD::GetLocalSolderMaskMargin,
  2133. PROPERTY_DISPLAY::PT_SIZE ), groupOverrides );
  2134. propMgr.AddProperty( new PROPERTY<PAD, std::optional<int>>(
  2135. _HKI( "Solderpaste Margin Override" ),
  2136. &PAD::SetLocalSolderPasteMargin, &PAD::GetLocalSolderPasteMargin,
  2137. PROPERTY_DISPLAY::PT_SIZE ), groupOverrides );
  2138. propMgr.AddProperty( new PROPERTY<PAD, std::optional<double>>(
  2139. _HKI( "Solderpaste Margin Ratio Override" ),
  2140. &PAD::SetLocalSolderPasteMarginRatio, &PAD::GetLocalSolderPasteMarginRatio,
  2141. PROPERTY_DISPLAY::PT_RATIO ),
  2142. groupOverrides );
  2143. propMgr.AddProperty( new PROPERTY_ENUM<PAD, ZONE_CONNECTION>(
  2144. _HKI( "Zone Connection Style" ),
  2145. &PAD::SetLocalZoneConnection, &PAD::GetLocalZoneConnection ), groupOverrides );
  2146. constexpr int minZoneWidth = pcbIUScale.mmToIU( ZONE_THICKNESS_MIN_VALUE_MM );
  2147. propMgr.AddProperty( new PROPERTY<PAD, std::optional<int>>(
  2148. _HKI( "Thermal Relief Spoke Width" ),
  2149. &PAD::SetLocalThermalSpokeWidthOverride, &PAD::GetLocalThermalSpokeWidthOverride,
  2150. PROPERTY_DISPLAY::PT_SIZE ), groupOverrides )
  2151. .SetValidator( PROPERTY_VALIDATORS::RangeIntValidator<minZoneWidth, INT_MAX> );
  2152. propMgr.AddProperty( new PROPERTY<PAD, double>(
  2153. _HKI( "Thermal Relief Spoke Angle" ),
  2154. &PAD::SetThermalSpokeAngleDegrees, &PAD::GetThermalSpokeAngleDegrees,
  2155. PROPERTY_DISPLAY::PT_DEGREE ), groupOverrides );
  2156. propMgr.AddProperty( new PROPERTY<PAD, std::optional<int>>(
  2157. _HKI( "Thermal Relief Gap" ),
  2158. &PAD::SetLocalThermalGapOverride, &PAD::GetLocalThermalGapOverride,
  2159. PROPERTY_DISPLAY::PT_SIZE ), groupOverrides )
  2160. .SetValidator( PROPERTY_VALIDATORS::PositiveIntValidator );
  2161. // TODO delta, drill shape offset, layer set
  2162. }
  2163. } _PAD_DESC;
  2164. ENUM_TO_WXANY( PAD_ATTRIB );
  2165. ENUM_TO_WXANY( PAD_SHAPE );
  2166. ENUM_TO_WXANY( PAD_PROP );
  2167. ENUM_TO_WXANY( PAD_DRILL_SHAPE );