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.

2981 lines
100 KiB

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