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.

2278 lines
72 KiB

14 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
5 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
18 years ago
18 years ago
18 years ago
18 years ago
5 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
5 years ago
5 years ago
Introduction of Graphics Abstraction Layer based rendering for pcbnew. New classes: - VIEW - represents view that is seen by user, takes care of layer ordering & visibility and how it is displayed (which location, how much zoomed, etc.) - VIEW_ITEM - Base class for every item that can be displayed on VIEW (the biggest change is that now it may be necessary to override ViewBBox & ViewGetLayers method for derived classes). - EDA_DRAW_PANEL_GAL - Inherits after EDA_DRAW_PANEL, displays VIEW output, right now it is not editable (in opposite to usual EDA_DRAW_PANEL). - GAL/OPENGL_GAL/CAIRO_GAL - Base Graphics Abstraction Layer class + two different flavours (Cairo is not fully supported yet), that offers methods to draw primitives using different libraries. - WX_VIEW_CONTROLS - Controller for VIEW, handles user events, allows zooming, panning, etc. - PAINTER/PCB_PAINTER - Classes that uses GAL interface to draw items (as you may have already guessed - PCB_PAINTER is a class for drawing PCB specific object, PAINTER is an abstract class). Its methods are invoked by VIEW, when an item has to be drawn. To display a new type of item - you need to implement draw(ITEM_TYPE*) method that draws it using GAL methods. - STROKE_FONT - Implements stroke font drawing using GAL methods. Most important changes to Kicad original code: * EDA_ITEM now inherits from VIEW_ITEM, which is a base class for all drawable objects. * EDA_DRAW_FRAME contains both usual EDA_DRAW_PANEL and new EDA_DRAW_PANEL_GAL, that can be switched anytime. * There are some new layers for displaying multilayer pads, vias & pads holes (these are not shown yet on the right sidebar in pcbnew) * Display order of layers is different than in previous versions (if you are curious - you may check m_galLayerOrder@pcbnew/basepcbframe.cpp). Preserving usual order would result in not very natural display, such as showing silkscreen texts on the bottom. * Introduced new hotkey (Alt+F12) and new menu option (View->Switch canvas) for switching canvas during runtime. * Some of classes (mostly derived from BOARD_ITEM) now includes ViewBBox & ViewGetLayers methods. * Removed tools/class_painter.h, as now it is extended and included in source code. Build changes: * GAL-based rendering option is turned on by a new compilation CMake option KICAD_GAL. * When compiling with CMake option KICAD_GAL=ON, GLEW and Cairo libraries are required. * GAL-related code is compiled into a static library (common/libgal). * Build with KICAD_GAL=OFF should not need any new libraries and should come out as a standard version of Kicad Currently most of items in pcbnew can be displayed using OpenGL (to be done are DIMENSIONS and MARKERS). More details about GAL can be found in: http://www.ohwr.org/attachments/1884/view-spec.pdf
13 years ago
Introduction of Graphics Abstraction Layer based rendering for pcbnew. New classes: - VIEW - represents view that is seen by user, takes care of layer ordering & visibility and how it is displayed (which location, how much zoomed, etc.) - VIEW_ITEM - Base class for every item that can be displayed on VIEW (the biggest change is that now it may be necessary to override ViewBBox & ViewGetLayers method for derived classes). - EDA_DRAW_PANEL_GAL - Inherits after EDA_DRAW_PANEL, displays VIEW output, right now it is not editable (in opposite to usual EDA_DRAW_PANEL). - GAL/OPENGL_GAL/CAIRO_GAL - Base Graphics Abstraction Layer class + two different flavours (Cairo is not fully supported yet), that offers methods to draw primitives using different libraries. - WX_VIEW_CONTROLS - Controller for VIEW, handles user events, allows zooming, panning, etc. - PAINTER/PCB_PAINTER - Classes that uses GAL interface to draw items (as you may have already guessed - PCB_PAINTER is a class for drawing PCB specific object, PAINTER is an abstract class). Its methods are invoked by VIEW, when an item has to be drawn. To display a new type of item - you need to implement draw(ITEM_TYPE*) method that draws it using GAL methods. - STROKE_FONT - Implements stroke font drawing using GAL methods. Most important changes to Kicad original code: * EDA_ITEM now inherits from VIEW_ITEM, which is a base class for all drawable objects. * EDA_DRAW_FRAME contains both usual EDA_DRAW_PANEL and new EDA_DRAW_PANEL_GAL, that can be switched anytime. * There are some new layers for displaying multilayer pads, vias & pads holes (these are not shown yet on the right sidebar in pcbnew) * Display order of layers is different than in previous versions (if you are curious - you may check m_galLayerOrder@pcbnew/basepcbframe.cpp). Preserving usual order would result in not very natural display, such as showing silkscreen texts on the bottom. * Introduced new hotkey (Alt+F12) and new menu option (View->Switch canvas) for switching canvas during runtime. * Some of classes (mostly derived from BOARD_ITEM) now includes ViewBBox & ViewGetLayers methods. * Removed tools/class_painter.h, as now it is extended and included in source code. Build changes: * GAL-based rendering option is turned on by a new compilation CMake option KICAD_GAL. * When compiling with CMake option KICAD_GAL=ON, GLEW and Cairo libraries are required. * GAL-related code is compiled into a static library (common/libgal). * Build with KICAD_GAL=OFF should not need any new libraries and should come out as a standard version of Kicad Currently most of items in pcbnew can be displayed using OpenGL (to be done are DIMENSIONS and MARKERS). More details about GAL can be found in: http://www.ohwr.org/attachments/1884/view-spec.pdf
13 years ago
Introduction of Graphics Abstraction Layer based rendering for pcbnew. New classes: - VIEW - represents view that is seen by user, takes care of layer ordering & visibility and how it is displayed (which location, how much zoomed, etc.) - VIEW_ITEM - Base class for every item that can be displayed on VIEW (the biggest change is that now it may be necessary to override ViewBBox & ViewGetLayers method for derived classes). - EDA_DRAW_PANEL_GAL - Inherits after EDA_DRAW_PANEL, displays VIEW output, right now it is not editable (in opposite to usual EDA_DRAW_PANEL). - GAL/OPENGL_GAL/CAIRO_GAL - Base Graphics Abstraction Layer class + two different flavours (Cairo is not fully supported yet), that offers methods to draw primitives using different libraries. - WX_VIEW_CONTROLS - Controller for VIEW, handles user events, allows zooming, panning, etc. - PAINTER/PCB_PAINTER - Classes that uses GAL interface to draw items (as you may have already guessed - PCB_PAINTER is a class for drawing PCB specific object, PAINTER is an abstract class). Its methods are invoked by VIEW, when an item has to be drawn. To display a new type of item - you need to implement draw(ITEM_TYPE*) method that draws it using GAL methods. - STROKE_FONT - Implements stroke font drawing using GAL methods. Most important changes to Kicad original code: * EDA_ITEM now inherits from VIEW_ITEM, which is a base class for all drawable objects. * EDA_DRAW_FRAME contains both usual EDA_DRAW_PANEL and new EDA_DRAW_PANEL_GAL, that can be switched anytime. * There are some new layers for displaying multilayer pads, vias & pads holes (these are not shown yet on the right sidebar in pcbnew) * Display order of layers is different than in previous versions (if you are curious - you may check m_galLayerOrder@pcbnew/basepcbframe.cpp). Preserving usual order would result in not very natural display, such as showing silkscreen texts on the bottom. * Introduced new hotkey (Alt+F12) and new menu option (View->Switch canvas) for switching canvas during runtime. * Some of classes (mostly derived from BOARD_ITEM) now includes ViewBBox & ViewGetLayers methods. * Removed tools/class_painter.h, as now it is extended and included in source code. Build changes: * GAL-based rendering option is turned on by a new compilation CMake option KICAD_GAL. * When compiling with CMake option KICAD_GAL=ON, GLEW and Cairo libraries are required. * GAL-related code is compiled into a static library (common/libgal). * Build with KICAD_GAL=OFF should not need any new libraries and should come out as a standard version of Kicad Currently most of items in pcbnew can be displayed using OpenGL (to be done are DIMENSIONS and MARKERS). More details about GAL can be found in: http://www.ohwr.org/attachments/1884/view-spec.pdf
13 years ago
Introduction of Graphics Abstraction Layer based rendering for pcbnew. New classes: - VIEW - represents view that is seen by user, takes care of layer ordering & visibility and how it is displayed (which location, how much zoomed, etc.) - VIEW_ITEM - Base class for every item that can be displayed on VIEW (the biggest change is that now it may be necessary to override ViewBBox & ViewGetLayers method for derived classes). - EDA_DRAW_PANEL_GAL - Inherits after EDA_DRAW_PANEL, displays VIEW output, right now it is not editable (in opposite to usual EDA_DRAW_PANEL). - GAL/OPENGL_GAL/CAIRO_GAL - Base Graphics Abstraction Layer class + two different flavours (Cairo is not fully supported yet), that offers methods to draw primitives using different libraries. - WX_VIEW_CONTROLS - Controller for VIEW, handles user events, allows zooming, panning, etc. - PAINTER/PCB_PAINTER - Classes that uses GAL interface to draw items (as you may have already guessed - PCB_PAINTER is a class for drawing PCB specific object, PAINTER is an abstract class). Its methods are invoked by VIEW, when an item has to be drawn. To display a new type of item - you need to implement draw(ITEM_TYPE*) method that draws it using GAL methods. - STROKE_FONT - Implements stroke font drawing using GAL methods. Most important changes to Kicad original code: * EDA_ITEM now inherits from VIEW_ITEM, which is a base class for all drawable objects. * EDA_DRAW_FRAME contains both usual EDA_DRAW_PANEL and new EDA_DRAW_PANEL_GAL, that can be switched anytime. * There are some new layers for displaying multilayer pads, vias & pads holes (these are not shown yet on the right sidebar in pcbnew) * Display order of layers is different than in previous versions (if you are curious - you may check m_galLayerOrder@pcbnew/basepcbframe.cpp). Preserving usual order would result in not very natural display, such as showing silkscreen texts on the bottom. * Introduced new hotkey (Alt+F12) and new menu option (View->Switch canvas) for switching canvas during runtime. * Some of classes (mostly derived from BOARD_ITEM) now includes ViewBBox & ViewGetLayers methods. * Removed tools/class_painter.h, as now it is extended and included in source code. Build changes: * GAL-based rendering option is turned on by a new compilation CMake option KICAD_GAL. * When compiling with CMake option KICAD_GAL=ON, GLEW and Cairo libraries are required. * GAL-related code is compiled into a static library (common/libgal). * Build with KICAD_GAL=OFF should not need any new libraries and should come out as a standard version of Kicad Currently most of items in pcbnew can be displayed using OpenGL (to be done are DIMENSIONS and MARKERS). More details about GAL can be found in: http://www.ohwr.org/attachments/1884/view-spec.pdf
13 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
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 (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors.
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. #include <base_units.h>
  26. #include <bitmaps.h>
  27. #include <core/mirror.h>
  28. #include <math/util.h> // for KiROUND
  29. #include <eda_draw_frame.h>
  30. #include <geometry/shape_circle.h>
  31. #include <geometry/shape_segment.h>
  32. #include <geometry/shape_simple.h>
  33. #include <geometry/shape_rect.h>
  34. #include <geometry/shape_compound.h>
  35. #include <geometry/shape_null.h>
  36. #include <string_utils.h>
  37. #include <i18n_utility.h>
  38. #include <view/view.h>
  39. #include <board.h>
  40. #include <board_connected_item.h>
  41. #include <board_design_settings.h>
  42. #include <footprint.h>
  43. #include <pad.h>
  44. #include <pcb_shape.h>
  45. #include <connectivity/connectivity_data.h>
  46. #include <eda_units.h>
  47. #include <convert_basic_shapes_to_polygon.h>
  48. #include <widgets/msgpanel.h>
  49. #include <pcb_painter.h>
  50. #include <properties/property_validators.h>
  51. #include <wx/log.h>
  52. #include <api/api_enums.h>
  53. #include <api/api_utils.h>
  54. #include <api/api_pcb_utils.h>
  55. #include <api/board/board_types.pb.h>
  56. #include <memory>
  57. #include <macros.h>
  58. #include <magic_enum.hpp>
  59. #include "kiface_base.h"
  60. #include "pcbnew_settings.h"
  61. using KIGFX::PCB_PAINTER;
  62. using KIGFX::PCB_RENDER_SETTINGS;
  63. PAD::PAD( FOOTPRINT* parent ) :
  64. BOARD_CONNECTED_ITEM( parent, PCB_PAD_T )
  65. {
  66. m_size.x = m_size.y = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 60 ); // Default pad size 60 mils.
  67. m_drill.x = m_drill.y = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 30 ); // Default drill size 30 mils.
  68. m_orient = ANGLE_0;
  69. m_lengthPadToDie = 0;
  70. if( m_parent && m_parent->Type() == PCB_FOOTPRINT_T )
  71. m_pos = GetParent()->GetPosition();
  72. SetShape( PAD_SHAPE::CIRCLE ); // Default pad shape is PAD_CIRCLE.
  73. SetAnchorPadShape( PAD_SHAPE::CIRCLE ); // Default shape for custom shaped pads
  74. // is PAD_CIRCLE.
  75. SetDrillShape( PAD_DRILL_SHAPE_CIRCLE ); // Default pad drill shape is a circle.
  76. m_attribute = PAD_ATTRIB::PTH; // Default pad type is plated through hole
  77. SetProperty( PAD_PROP::NONE ); // no special fabrication property
  78. // Parameters for round rect only:
  79. m_roundedCornerScale = 0.25; // from IPC-7351C standard
  80. // Parameters for chamfered rect only:
  81. m_chamferScale = 0.2; // Size of chamfer: ratio of smallest of X,Y size
  82. m_chamferPositions = RECT_NO_CHAMFER; // No chamfered corner
  83. m_zoneConnection = ZONE_CONNECTION::INHERITED; // Use parent setting by default
  84. m_thermalSpokeWidth = 0; // Use parent setting by default
  85. m_thermalSpokeAngle = ANGLE_45; // Default for circular pads
  86. m_thermalGap = 0; // Use parent setting by default
  87. m_customShapeClearanceArea = CUST_PAD_SHAPE_IN_ZONE_OUTLINE;
  88. // Set layers mask to default for a standard thru hole pad.
  89. m_layerMask = PTHMask();
  90. SetSubRatsnest( 0 ); // used in ratsnest calculations
  91. SetDirty();
  92. m_effectiveBoundingRadius = 0;
  93. m_removeUnconnectedLayer = false;
  94. m_keepTopBottomLayer = true;
  95. m_zoneLayerOverrides.fill( ZLO_NONE );
  96. }
  97. PAD::PAD( const PAD& aOther ) :
  98. BOARD_CONNECTED_ITEM( aOther.GetParent(), PCB_PAD_T )
  99. {
  100. PAD::operator=( aOther );
  101. const_cast<KIID&>( m_Uuid ) = aOther.m_Uuid;
  102. }
  103. PAD& PAD::operator=( const PAD &aOther )
  104. {
  105. BOARD_CONNECTED_ITEM::operator=( aOther );
  106. ImportSettingsFrom( aOther );
  107. SetPadToDieLength( aOther.GetPadToDieLength() );
  108. SetPosition( aOther.GetPosition() );
  109. SetNumber( aOther.GetNumber() );
  110. SetPinType( aOther.GetPinType() );
  111. SetPinFunction( aOther.GetPinFunction() );
  112. SetSubRatsnest( aOther.GetSubRatsnest() );
  113. m_effectiveBoundingRadius = aOther.m_effectiveBoundingRadius;
  114. m_removeUnconnectedLayer = aOther.m_removeUnconnectedLayer;
  115. m_keepTopBottomLayer = aOther.m_keepTopBottomLayer;
  116. return *this;
  117. }
  118. void PAD::Serialize( google::protobuf::Any &aContainer ) const
  119. {
  120. kiapi::board::types::Pad pad;
  121. pad.mutable_id()->set_value( m_Uuid.AsStdString() );
  122. kiapi::common::PackVector2( *pad.mutable_position(), GetPosition() );
  123. pad.set_locked( IsLocked() ? kiapi::common::types::LockedState::LS_LOCKED
  124. : kiapi::common::types::LockedState::LS_UNLOCKED );
  125. pad.mutable_net()->mutable_code()->set_value( GetNetCode() );
  126. pad.mutable_net()->set_name( GetNetname() );
  127. kiapi::board::types::PadStack* padstack = pad.mutable_pad_stack();
  128. padstack->set_type( kiapi::board::types::PadStackType::PST_THROUGH );
  129. padstack->set_start_layer(
  130. ToProtoEnum<PCB_LAYER_ID, kiapi::board::types::BoardLayer>( m_layer ) );
  131. padstack->set_end_layer(
  132. ToProtoEnum<PCB_LAYER_ID, kiapi::board::types::BoardLayer>( FlipLayer( m_layer ) ) );
  133. kiapi::common::PackVector2( *padstack->mutable_drill_diameter(),
  134. { GetDrillSizeX(), GetDrillSizeY() } );
  135. padstack->mutable_angle()->set_value_degrees( GetOrientationDegrees() );
  136. kiapi::board::types::PadStackLayer* stackLayer = padstack->add_layers();
  137. kiapi::board::PackLayerSet( *stackLayer->mutable_layers(), GetLayerSet() );
  138. kiapi::common::PackVector2( *stackLayer->mutable_size(),
  139. { GetSizeX(), GetSizeY() } );
  140. stackLayer->set_shape(
  141. ToProtoEnum<PAD_SHAPE, kiapi::board::types::PadStackShape>( GetShape() ) );
  142. kiapi::board::types::UnconnectedLayerRemoval ulr;
  143. if( m_removeUnconnectedLayer )
  144. {
  145. if( m_keepTopBottomLayer )
  146. ulr = kiapi::board::types::UnconnectedLayerRemoval::ULR_REMOVE_EXCEPT_START_AND_END;
  147. else
  148. ulr = kiapi::board::types::UnconnectedLayerRemoval::ULR_REMOVE;
  149. }
  150. else
  151. {
  152. ulr = kiapi::board::types::UnconnectedLayerRemoval::ULR_KEEP;
  153. }
  154. padstack->set_unconnected_layer_removal( ulr );
  155. kiapi::board::types::DesignRuleOverrides* overrides = pad.mutable_overrides();
  156. if( GetLocalClearance().has_value() )
  157. overrides->mutable_clearance()->set_value_nm( *GetLocalClearance() );
  158. if( GetLocalSolderMaskMargin().has_value() )
  159. overrides->mutable_solder_mask_margin()->set_value_nm( *GetLocalSolderMaskMargin() );
  160. if( GetLocalSolderPasteMargin().has_value() )
  161. overrides->mutable_solder_paste_margin()->set_value_nm( *GetLocalSolderPasteMargin() );
  162. if( GetLocalSolderPasteMarginRatio().has_value() )
  163. overrides->mutable_solder_paste_margin_ratio()->set_value( *GetLocalSolderPasteMarginRatio() );
  164. overrides->set_zone_connection(
  165. ToProtoEnum<ZONE_CONNECTION,
  166. kiapi::board::types::ZoneConnectionStyle>( GetLocalZoneConnection() ) );
  167. kiapi::board::types::ThermalSpokeSettings* thermals = pad.mutable_thermal_spokes();
  168. thermals->set_width( GetThermalSpokeWidth() );
  169. thermals->set_gap( GetThermalGap() );
  170. thermals->mutable_angle()->set_value_degrees( GetThermalSpokeAngleDegrees() );
  171. aContainer.PackFrom( pad );
  172. }
  173. bool PAD::Deserialize( const google::protobuf::Any &aContainer )
  174. {
  175. kiapi::board::types::Pad pad;
  176. if( !aContainer.UnpackTo( &pad ) )
  177. return false;
  178. const_cast<KIID&>( m_Uuid ) = KIID( pad.id().value() );
  179. SetPosition( kiapi::common::UnpackVector2( pad.position() ) );
  180. SetNetCode( pad.net().code().value() );
  181. SetLocked( pad.locked() == kiapi::common::types::LockedState::LS_LOCKED );
  182. const kiapi::board::types::PadStack& padstack = pad.pad_stack();
  183. SetLayer( FromProtoEnum<PCB_LAYER_ID, kiapi::board::types::BoardLayer>(
  184. padstack.start_layer() ) );
  185. SetDrillSize( kiapi::common::UnpackVector2( padstack.drill_diameter() ) );
  186. SetOrientationDegrees( padstack.angle().value_degrees() );
  187. // We don't yet support complex padstacks
  188. if( padstack.layers_size() == 1 )
  189. {
  190. const kiapi::board::types::PadStackLayer& layer = padstack.layers( 0 );
  191. SetSize( kiapi::common::UnpackVector2( layer.size() ) );
  192. SetLayerSet( kiapi::board::UnpackLayerSet( layer.layers() ) );
  193. SetShape( FromProtoEnum<PAD_SHAPE>( layer.shape() ) );
  194. }
  195. switch( padstack.unconnected_layer_removal() )
  196. {
  197. case kiapi::board::types::UnconnectedLayerRemoval::ULR_REMOVE:
  198. m_removeUnconnectedLayer = true;
  199. m_keepTopBottomLayer = false;
  200. break;
  201. case kiapi::board::types::UnconnectedLayerRemoval::ULR_REMOVE_EXCEPT_START_AND_END:
  202. m_removeUnconnectedLayer = true;
  203. m_keepTopBottomLayer = true;
  204. break;
  205. default:
  206. case kiapi::board::types::UnconnectedLayerRemoval::ULR_KEEP:
  207. m_removeUnconnectedLayer = false;
  208. m_keepTopBottomLayer = false;
  209. break;
  210. }
  211. const kiapi::board::types::DesignRuleOverrides& overrides = pad.overrides();
  212. if( overrides.has_clearance() )
  213. SetLocalClearance( overrides.clearance().value_nm() );
  214. else
  215. SetLocalClearance( std::nullopt );
  216. if( overrides.has_solder_mask_margin() )
  217. SetLocalSolderMaskMargin( overrides.solder_mask_margin().value_nm() );
  218. else
  219. SetLocalSolderMaskMargin( std::nullopt );
  220. if( overrides.has_solder_paste_margin() )
  221. SetLocalSolderPasteMargin( overrides.solder_paste_margin().value_nm() );
  222. else
  223. SetLocalSolderPasteMargin( std::nullopt );
  224. if( overrides.has_solder_paste_margin_ratio() )
  225. SetLocalSolderPasteMarginRatio( overrides.solder_paste_margin_ratio().value() );
  226. else
  227. SetLocalSolderPasteMarginRatio( std::nullopt );
  228. SetLocalZoneConnection( FromProtoEnum<ZONE_CONNECTION>( overrides.zone_connection() ) );
  229. const kiapi::board::types::ThermalSpokeSettings& thermals = pad.thermal_spokes();
  230. SetThermalGap( thermals.gap() );
  231. SetThermalSpokeWidth( thermals.width() );
  232. SetThermalSpokeAngleDegrees( thermals.angle().value_degrees() );
  233. return true;
  234. }
  235. bool PAD::CanHaveNumber() const
  236. {
  237. // Aperture pads don't get a number
  238. if( IsAperturePad() )
  239. return false;
  240. // NPTH pads don't get numbers
  241. if( GetAttribute() == PAD_ATTRIB::NPTH )
  242. return false;
  243. return true;
  244. }
  245. bool PAD::IsLocked() const
  246. {
  247. if( GetParent() && GetParent()->IsLocked() )
  248. return true;
  249. return BOARD_ITEM::IsLocked();
  250. };
  251. bool PAD::SharesNetTieGroup( const PAD* aOther ) const
  252. {
  253. FOOTPRINT* parentFp = GetParentFootprint();
  254. if( parentFp && parentFp->IsNetTie() && aOther->GetParentFootprint() == parentFp )
  255. {
  256. std::map<wxString, int> padToNetTieGroupMap = parentFp->MapPadNumbersToNetTieGroups();
  257. int thisNetTieGroup = padToNetTieGroupMap[ GetNumber() ];
  258. int otherNetTieGroup = padToNetTieGroupMap[ aOther->GetNumber() ];
  259. return thisNetTieGroup >= 0 && thisNetTieGroup == otherNetTieGroup;
  260. }
  261. return false;
  262. }
  263. bool PAD::IsNoConnectPad() const
  264. {
  265. return m_pinType.Contains( wxT( "no_connect" ) );
  266. }
  267. bool PAD::IsFreePad() const
  268. {
  269. return GetShortNetname().StartsWith( wxT( "unconnected-(" ) )
  270. && m_pinType == wxT( "free" );
  271. }
  272. LSET PAD::PTHMask()
  273. {
  274. static LSET saved = LSET::AllCuMask() | LSET( 2, F_Mask, B_Mask );
  275. return saved;
  276. }
  277. LSET PAD::SMDMask()
  278. {
  279. static LSET saved( 3, F_Cu, F_Paste, F_Mask );
  280. return saved;
  281. }
  282. LSET PAD::ConnSMDMask()
  283. {
  284. static LSET saved( 2, F_Cu, F_Mask );
  285. return saved;
  286. }
  287. LSET PAD::UnplatedHoleMask()
  288. {
  289. static LSET saved = LSET( 4, F_Cu, B_Cu, F_Mask, B_Mask );
  290. return saved;
  291. }
  292. LSET PAD::ApertureMask()
  293. {
  294. static LSET saved( 1, F_Paste );
  295. return saved;
  296. }
  297. bool PAD::IsFlipped() const
  298. {
  299. FOOTPRINT* parent = GetParentFootprint();
  300. return ( parent && parent->GetLayer() == B_Cu );
  301. }
  302. PCB_LAYER_ID PAD::GetLayer() const
  303. {
  304. return BOARD_ITEM::GetLayer();
  305. }
  306. PCB_LAYER_ID PAD::GetPrincipalLayer() const
  307. {
  308. if( m_attribute == PAD_ATTRIB::SMD || m_attribute == PAD_ATTRIB::CONN || GetLayerSet().none() )
  309. return m_layer;
  310. else
  311. return GetLayerSet().Seq().front();
  312. }
  313. bool PAD::FlashLayer( LSET aLayers ) const
  314. {
  315. for( PCB_LAYER_ID layer : aLayers.Seq() )
  316. {
  317. if( FlashLayer( layer ) )
  318. return true;
  319. }
  320. return false;
  321. }
  322. bool PAD::FlashLayer( int aLayer, bool aOnlyCheckIfPermitted ) const
  323. {
  324. if( aLayer == UNDEFINED_LAYER )
  325. return true;
  326. if( !IsOnLayer( static_cast<PCB_LAYER_ID>( aLayer ) ) )
  327. return false;
  328. if( GetAttribute() == PAD_ATTRIB::NPTH && IsCopperLayer( aLayer ) )
  329. {
  330. if( GetShape() == PAD_SHAPE::CIRCLE && GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE )
  331. {
  332. if( GetOffset() == VECTOR2I( 0, 0 ) && GetDrillSize().x >= GetSize().x )
  333. return false;
  334. }
  335. else if( GetShape() == PAD_SHAPE::OVAL && GetDrillShape() == PAD_DRILL_SHAPE_OBLONG )
  336. {
  337. if( GetOffset() == VECTOR2I( 0, 0 )
  338. && GetDrillSize().x >= GetSize().x && GetDrillSize().y >= GetSize().y )
  339. {
  340. return false;
  341. }
  342. }
  343. }
  344. if( LSET::FrontBoardTechMask().test( aLayer ) )
  345. aLayer = F_Cu;
  346. else if( LSET::BackBoardTechMask().test( aLayer ) )
  347. aLayer = B_Cu;
  348. if( GetAttribute() == PAD_ATTRIB::PTH && IsCopperLayer( aLayer ) )
  349. {
  350. /// Heat sink pads always get copper
  351. if( GetProperty() == PAD_PROP::HEATSINK )
  352. return true;
  353. if( !m_removeUnconnectedLayer )
  354. return true;
  355. // Plated through hole pads need copper on the top/bottom layers for proper soldering
  356. // Unless the user has removed them in the pad dialog
  357. if( m_keepTopBottomLayer && ( aLayer == F_Cu || aLayer == B_Cu ) )
  358. return true;
  359. if( const BOARD* board = GetBoard() )
  360. {
  361. // Must be static to keep from raising its ugly head in performance profiles
  362. static std::initializer_list<KICAD_T> types = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T,
  363. PCB_PAD_T };
  364. if( m_zoneLayerOverrides[ aLayer ] == ZLO_FORCE_FLASHED )
  365. return true;
  366. else if( aOnlyCheckIfPermitted )
  367. return true;
  368. else
  369. return board->GetConnectivity()->IsConnectedOnLayer( this, aLayer, types );
  370. }
  371. }
  372. return true;
  373. }
  374. int PAD::GetRoundRectCornerRadius() const
  375. {
  376. return KiROUND( std::min( m_size.x, m_size.y ) * m_roundedCornerScale );
  377. }
  378. void PAD::SetRoundRectCornerRadius( double aRadius )
  379. {
  380. int min_r = std::min( m_size.x, m_size.y );
  381. if( min_r > 0 )
  382. SetRoundRectRadiusRatio( aRadius / min_r );
  383. }
  384. void PAD::SetRoundRectRadiusRatio( double aRadiusScale )
  385. {
  386. m_roundedCornerScale = alg::clamp( 0.0, aRadiusScale, 0.5 );
  387. SetDirty();
  388. }
  389. void PAD::SetChamferRectRatio( double aChamferScale )
  390. {
  391. m_chamferScale = alg::clamp( 0.0, aChamferScale, 0.5 );
  392. SetDirty();
  393. }
  394. const std::shared_ptr<SHAPE_POLY_SET>& PAD::GetEffectivePolygon( ERROR_LOC aErrorLoc ) const
  395. {
  396. if( m_polyDirty[ aErrorLoc ] )
  397. BuildEffectivePolygon( aErrorLoc );
  398. return m_effectivePolygon[ aErrorLoc ];
  399. }
  400. std::shared_ptr<SHAPE> PAD::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING flashPTHPads ) const
  401. {
  402. if( aLayer == Edge_Cuts )
  403. {
  404. if( GetAttribute() == PAD_ATTRIB::PTH || GetAttribute() == PAD_ATTRIB::NPTH )
  405. return GetEffectiveHoleShape();
  406. else
  407. return std::make_shared<SHAPE_NULL>();
  408. }
  409. if( GetAttribute() == PAD_ATTRIB::PTH )
  410. {
  411. bool flash;
  412. if( flashPTHPads == FLASHING::NEVER_FLASHED )
  413. flash = false;
  414. else if( flashPTHPads == FLASHING::ALWAYS_FLASHED )
  415. flash = true;
  416. else
  417. flash = FlashLayer( aLayer );
  418. if( !flash )
  419. {
  420. if( GetAttribute() == PAD_ATTRIB::PTH )
  421. return GetEffectiveHoleShape();
  422. else
  423. return std::make_shared<SHAPE_NULL>();
  424. }
  425. }
  426. if( m_shapesDirty )
  427. BuildEffectiveShapes( aLayer );
  428. return m_effectiveShape;
  429. }
  430. std::shared_ptr<SHAPE_SEGMENT> PAD::GetEffectiveHoleShape() const
  431. {
  432. if( m_shapesDirty )
  433. BuildEffectiveShapes( UNDEFINED_LAYER );
  434. return m_effectiveHoleShape;
  435. }
  436. int PAD::GetBoundingRadius() const
  437. {
  438. if( m_polyDirty[ ERROR_OUTSIDE ] )
  439. BuildEffectivePolygon( ERROR_OUTSIDE );
  440. return m_effectiveBoundingRadius;
  441. }
  442. void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const
  443. {
  444. std::lock_guard<std::mutex> RAII_lock( m_shapesBuildingLock );
  445. // If we had to wait for the lock then we were probably waiting for someone else to
  446. // finish rebuilding the shapes. So check to see if they're clean now.
  447. if( !m_shapesDirty )
  448. return;
  449. const BOARD* board = GetBoard();
  450. int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF;
  451. m_effectiveShape = std::make_shared<SHAPE_COMPOUND>();
  452. m_effectiveHoleShape = nullptr;
  453. auto add = [this]( SHAPE* aShape )
  454. {
  455. m_effectiveShape->AddShape( aShape );
  456. };
  457. VECTOR2I shapePos = ShapePos(); // Fetch only once; rotation involves trig
  458. PAD_SHAPE effectiveShape = GetShape();
  459. if( GetShape() == PAD_SHAPE::CUSTOM )
  460. effectiveShape = GetAnchorPadShape();
  461. switch( effectiveShape )
  462. {
  463. case PAD_SHAPE::CIRCLE:
  464. add( new SHAPE_CIRCLE( shapePos, m_size.x / 2 ) );
  465. break;
  466. case PAD_SHAPE::OVAL:
  467. if( m_size.x == m_size.y ) // the oval pad is in fact a circle
  468. {
  469. add( new SHAPE_CIRCLE( shapePos, m_size.x / 2 ) );
  470. }
  471. else
  472. {
  473. VECTOR2I half_size = m_size / 2;
  474. int half_width = std::min( half_size.x, half_size.y );
  475. VECTOR2I half_len( half_size.x - half_width, half_size.y - half_width );
  476. RotatePoint( half_len, m_orient );
  477. add( new SHAPE_SEGMENT( shapePos - half_len, shapePos + half_len, half_width * 2 ) );
  478. }
  479. break;
  480. case PAD_SHAPE::RECTANGLE:
  481. case PAD_SHAPE::TRAPEZOID:
  482. case PAD_SHAPE::ROUNDRECT:
  483. {
  484. int r = ( effectiveShape == PAD_SHAPE::ROUNDRECT ) ? GetRoundRectCornerRadius() : 0;
  485. VECTOR2I half_size( m_size.x / 2, m_size.y / 2 );
  486. VECTOR2I trap_delta( 0, 0 );
  487. if( r )
  488. {
  489. half_size -= VECTOR2I( r, r );
  490. // Avoid degenerated shapes (0 length segments) that always create issues
  491. // For roundrect pad very near a circle, use only a circle
  492. const int min_len = pcbIUScale.mmToIU( 0.0001);
  493. if( half_size.x < min_len && half_size.y < min_len )
  494. {
  495. add( new SHAPE_CIRCLE( shapePos, r ) );
  496. break;
  497. }
  498. }
  499. else if( effectiveShape == PAD_SHAPE::TRAPEZOID )
  500. {
  501. trap_delta = m_deltaSize / 2;
  502. }
  503. SHAPE_LINE_CHAIN corners;
  504. corners.Append( -half_size.x - trap_delta.y, half_size.y + trap_delta.x );
  505. corners.Append( half_size.x + trap_delta.y, half_size.y - trap_delta.x );
  506. corners.Append( half_size.x - trap_delta.y, -half_size.y + trap_delta.x );
  507. corners.Append( -half_size.x + trap_delta.y, -half_size.y - trap_delta.x );
  508. corners.Rotate( m_orient );
  509. corners.Move( shapePos );
  510. // GAL renders rectangles faster than 4-point polygons so it's worth checking if our
  511. // body shape is a rectangle.
  512. if( corners.PointCount() == 4
  513. &&
  514. ( ( corners.CPoint( 0 ).y == corners.CPoint( 1 ).y
  515. && corners.CPoint( 1 ).x == corners.CPoint( 2 ).x
  516. && corners.CPoint( 2 ).y == corners.CPoint( 3 ).y
  517. && corners.CPoint( 3 ).x == corners.CPoint( 0 ).x )
  518. ||
  519. ( corners.CPoint( 0 ).x == corners.CPoint( 1 ).x
  520. && corners.CPoint( 1 ).y == corners.CPoint( 2 ).y
  521. && corners.CPoint( 2 ).x == corners.CPoint( 3 ).x
  522. && corners.CPoint( 3 ).y == corners.CPoint( 0 ).y )
  523. )
  524. )
  525. {
  526. int width = std::abs( corners.CPoint( 2 ).x - corners.CPoint( 0 ).x );
  527. int height = std::abs( corners.CPoint( 2 ).y - corners.CPoint( 0 ).y );
  528. VECTOR2I pos( std::min( corners.CPoint( 2 ).x, corners.CPoint( 0 ).x ),
  529. std::min( corners.CPoint( 2 ).y, corners.CPoint( 0 ).y ) );
  530. add( new SHAPE_RECT( pos, width, height ) );
  531. }
  532. else
  533. {
  534. add( new SHAPE_SIMPLE( corners ) );
  535. }
  536. if( r )
  537. {
  538. add( new SHAPE_SEGMENT( corners.CPoint( 0 ), corners.CPoint( 1 ), r * 2 ) );
  539. add( new SHAPE_SEGMENT( corners.CPoint( 1 ), corners.CPoint( 2 ), r * 2 ) );
  540. add( new SHAPE_SEGMENT( corners.CPoint( 2 ), corners.CPoint( 3 ), r * 2 ) );
  541. add( new SHAPE_SEGMENT( corners.CPoint( 3 ), corners.CPoint( 0 ), r * 2 ) );
  542. }
  543. }
  544. break;
  545. case PAD_SHAPE::CHAMFERED_RECT:
  546. {
  547. SHAPE_POLY_SET outline;
  548. TransformRoundChamferedRectToPolygon( outline, shapePos, GetSize(), m_orient,
  549. GetRoundRectCornerRadius(), GetChamferRectRatio(),
  550. GetChamferPositions(), 0, maxError, ERROR_INSIDE );
  551. add( new SHAPE_SIMPLE( outline.COutline( 0 ) ) );
  552. }
  553. break;
  554. default:
  555. wxFAIL_MSG( wxT( "PAD::buildEffectiveShapes: Unsupported pad shape: PAD_SHAPE::" )
  556. + wxString( std::string( magic_enum::enum_name( effectiveShape ) ) ) );
  557. break;
  558. }
  559. if( GetShape() == PAD_SHAPE::CUSTOM )
  560. {
  561. for( const std::shared_ptr<PCB_SHAPE>& primitive : m_editPrimitives )
  562. {
  563. if( !primitive->IsProxyItem() )
  564. {
  565. for( SHAPE* shape : primitive->MakeEffectiveShapes() )
  566. {
  567. shape->Rotate( m_orient );
  568. shape->Move( shapePos );
  569. add( shape );
  570. }
  571. }
  572. }
  573. }
  574. m_effectiveBoundingBox = m_effectiveShape->BBox();
  575. // Hole shape
  576. VECTOR2I half_size = m_drill / 2;
  577. int half_width = std::min( half_size.x, half_size.y );
  578. VECTOR2I half_len( half_size.x - half_width, half_size.y - half_width );
  579. RotatePoint( half_len, m_orient );
  580. m_effectiveHoleShape = std::make_shared<SHAPE_SEGMENT>( m_pos - half_len, m_pos + half_len,
  581. half_width * 2 );
  582. m_effectiveBoundingBox.Merge( m_effectiveHoleShape->BBox() );
  583. // All done
  584. m_shapesDirty = false;
  585. }
  586. void PAD::BuildEffectivePolygon( ERROR_LOC aErrorLoc ) const
  587. {
  588. std::lock_guard<std::mutex> RAII_lock( m_polyBuildingLock );
  589. // If we had to wait for the lock then we were probably waiting for someone else to
  590. // finish rebuilding the shapes. So check to see if they're clean now.
  591. if( !m_polyDirty[ aErrorLoc ] )
  592. return;
  593. const BOARD* board = GetBoard();
  594. int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF;
  595. // Polygon
  596. std::shared_ptr<SHAPE_POLY_SET>& effectivePolygon = m_effectivePolygon[ aErrorLoc ];
  597. effectivePolygon = std::make_shared<SHAPE_POLY_SET>();
  598. TransformShapeToPolygon( *effectivePolygon, UNDEFINED_LAYER, 0, maxError, aErrorLoc );
  599. // Bounding radius
  600. //
  601. // PADSTACKS TODO: these will both need to cycle through all layers to get the largest
  602. // values....
  603. if( aErrorLoc == ERROR_OUTSIDE )
  604. {
  605. m_effectiveBoundingRadius = 0;
  606. for( int cnt = 0; cnt < effectivePolygon->OutlineCount(); ++cnt )
  607. {
  608. const SHAPE_LINE_CHAIN& poly = effectivePolygon->COutline( cnt );
  609. for( int ii = 0; ii < poly.PointCount(); ++ii )
  610. {
  611. int dist = KiROUND( ( poly.CPoint( ii ) - m_pos ).EuclideanNorm() );
  612. m_effectiveBoundingRadius = std::max( m_effectiveBoundingRadius, dist );
  613. }
  614. }
  615. }
  616. // All done
  617. m_polyDirty[ aErrorLoc ] = false;
  618. }
  619. const BOX2I PAD::GetBoundingBox() const
  620. {
  621. if( m_shapesDirty )
  622. BuildEffectiveShapes( UNDEFINED_LAYER );
  623. return m_effectiveBoundingBox;
  624. }
  625. void PAD::SetAttribute( PAD_ATTRIB aAttribute )
  626. {
  627. if( m_attribute != aAttribute )
  628. {
  629. m_attribute = aAttribute;
  630. switch( aAttribute )
  631. {
  632. case PAD_ATTRIB::PTH:
  633. m_layerMask |= LSET::AllCuMask();
  634. break;
  635. case PAD_ATTRIB::SMD:
  636. case PAD_ATTRIB::CONN:
  637. if( m_layerMask.test( F_Cu ) )
  638. {
  639. m_layerMask &= ~LSET::AllCuMask();
  640. m_layerMask.set( F_Cu );
  641. }
  642. else
  643. {
  644. m_layerMask &= ~LSET::AllCuMask();
  645. m_layerMask.set( B_Cu );
  646. }
  647. m_drill = VECTOR2I( 0, 0 );
  648. break;
  649. case PAD_ATTRIB::NPTH:
  650. m_number = wxEmptyString;
  651. SetNetCode( NETINFO_LIST::UNCONNECTED );
  652. break;
  653. }
  654. }
  655. SetDirty();
  656. }
  657. void PAD::SetProperty( PAD_PROP aProperty )
  658. {
  659. m_property = aProperty;
  660. SetDirty();
  661. }
  662. void PAD::SetOrientation( const EDA_ANGLE& aAngle )
  663. {
  664. m_orient = aAngle;
  665. m_orient.Normalize();
  666. SetDirty();
  667. }
  668. void PAD::SetFPRelativeOrientation( const EDA_ANGLE& aAngle )
  669. {
  670. if( FOOTPRINT* parentFP = GetParentFootprint() )
  671. SetOrientation( aAngle + parentFP->GetOrientation() );
  672. else
  673. SetOrientation( aAngle );
  674. }
  675. EDA_ANGLE PAD::GetFPRelativeOrientation()
  676. {
  677. if( FOOTPRINT* parentFP = GetParentFootprint() )
  678. return GetOrientation() - parentFP->GetOrientation();
  679. else
  680. return GetOrientation();
  681. }
  682. void PAD::Flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
  683. {
  684. if( aFlipLeftRight )
  685. {
  686. MIRROR( m_pos.x, aCentre.x );
  687. MIRROR( m_offset.x, 0 );
  688. MIRROR( m_deltaSize.x, 0 );
  689. }
  690. else
  691. {
  692. MIRROR( m_pos.y, aCentre.y );
  693. MIRROR( m_offset.y, 0 );
  694. MIRROR( m_deltaSize.y, 0 );
  695. }
  696. SetFPRelativeOrientation( -GetFPRelativeOrientation() );
  697. auto mirrorBitFlags = []( int& aBitfield, int a, int b )
  698. {
  699. bool temp = aBitfield & a;
  700. if( aBitfield & b )
  701. aBitfield |= a;
  702. else
  703. aBitfield &= ~a;
  704. if( temp )
  705. aBitfield |= b;
  706. else
  707. aBitfield &= ~b;
  708. };
  709. if( aFlipLeftRight )
  710. {
  711. mirrorBitFlags( m_chamferPositions, RECT_CHAMFER_TOP_LEFT, RECT_CHAMFER_TOP_RIGHT );
  712. mirrorBitFlags( m_chamferPositions, RECT_CHAMFER_BOTTOM_LEFT, RECT_CHAMFER_BOTTOM_RIGHT );
  713. }
  714. else
  715. {
  716. mirrorBitFlags( m_chamferPositions, RECT_CHAMFER_TOP_LEFT, RECT_CHAMFER_BOTTOM_LEFT );
  717. mirrorBitFlags( m_chamferPositions, RECT_CHAMFER_TOP_RIGHT, RECT_CHAMFER_BOTTOM_RIGHT );
  718. }
  719. // flip pads layers
  720. // PADS items are currently on all copper layers, or
  721. // currently, only on Front or Back layers.
  722. // So the copper layers count is not taken in account
  723. SetLayerSet( FlipLayerMask( m_layerMask ) );
  724. // Flip the basic shapes, in custom pads
  725. FlipPrimitives( aFlipLeftRight );
  726. SetDirty();
  727. }
  728. void PAD::FlipPrimitives( bool aFlipLeftRight )
  729. {
  730. for( std::shared_ptr<PCB_SHAPE>& primitive : m_editPrimitives )
  731. primitive->Flip( VECTOR2I( 0, 0 ), aFlipLeftRight );
  732. SetDirty();
  733. }
  734. VECTOR2I PAD::ShapePos() const
  735. {
  736. if( m_offset.x == 0 && m_offset.y == 0 )
  737. return m_pos;
  738. VECTOR2I loc_offset = m_offset;
  739. RotatePoint( loc_offset, m_orient );
  740. VECTOR2I shape_pos = m_pos + loc_offset;
  741. return shape_pos;
  742. }
  743. bool PAD::IsOnCopperLayer() const
  744. {
  745. if( GetAttribute() == PAD_ATTRIB::NPTH )
  746. {
  747. // NPTH pads have no plated hole cylinder. If their annular ring size is 0 or
  748. // negative, then they have no annular ring either.
  749. switch( GetShape() )
  750. {
  751. case PAD_SHAPE::CIRCLE:
  752. if( m_offset == VECTOR2I( 0, 0 ) && m_size.x <= m_drill.x )
  753. return false;
  754. break;
  755. case PAD_SHAPE::OVAL:
  756. if( m_offset == VECTOR2I( 0, 0 ) && m_size.x <= m_drill.x && m_size.y <= m_drill.y )
  757. return false;
  758. break;
  759. default:
  760. // We could subtract the hole polygon from the shape polygon for these, but it
  761. // would be expensive and we're probably well out of the common use cases....
  762. break;
  763. }
  764. }
  765. return ( GetLayerSet() & LSET::AllCuMask() ).any();
  766. }
  767. std::optional<int> PAD::GetLocalClearance( wxString* aSource ) const
  768. {
  769. if( m_clearance.has_value() && aSource )
  770. *aSource = _( "pad" );
  771. return m_clearance;
  772. }
  773. std::optional<int> PAD::GetClearanceOverrides( wxString* aSource ) const
  774. {
  775. if( m_clearance.has_value() )
  776. return GetLocalClearance( aSource );
  777. if( FOOTPRINT* parentFootprint = GetParentFootprint() )
  778. return parentFootprint->GetClearanceOverrides( aSource );
  779. return std::optional<int>();
  780. }
  781. int PAD::GetOwnClearance( PCB_LAYER_ID aLayer, wxString* aSource ) const
  782. {
  783. DRC_CONSTRAINT c;
  784. if( GetBoard() && GetBoard()->GetDesignSettings().m_DRCEngine )
  785. {
  786. BOARD_DESIGN_SETTINGS& bds = GetBoard()->GetDesignSettings();
  787. if( GetAttribute() == PAD_ATTRIB::NPTH )
  788. c = bds.m_DRCEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, this, nullptr, aLayer );
  789. else
  790. c = bds.m_DRCEngine->EvalRules( CLEARANCE_CONSTRAINT, this, nullptr, aLayer );
  791. }
  792. if( c.Value().HasMin() )
  793. {
  794. if( aSource )
  795. *aSource = c.GetName();
  796. return c.Value().Min();
  797. }
  798. return 0;
  799. }
  800. int PAD::GetSolderMaskExpansion() const
  801. {
  802. // Pads defined only on mask layers (and perhaps on other tech layers) use the shape
  803. // defined by the pad settings only. ALL other pads, even those that don't actually have
  804. // any copper (such as NPTH pads with holes the same size as the pad) get mask expansion.
  805. if( ( m_layerMask & LSET::AllCuMask() ).none() )
  806. return 0;
  807. std::optional<int> margin = m_solderMaskMargin;
  808. if( !margin.has_value() )
  809. {
  810. if( FOOTPRINT* parentFootprint = GetParentFootprint() )
  811. margin = parentFootprint->GetLocalSolderMaskMargin();
  812. }
  813. if( !margin.has_value() )
  814. {
  815. if( const BOARD* brd = GetBoard() )
  816. margin = brd->GetDesignSettings().m_SolderMaskExpansion;
  817. }
  818. int marginValue = margin.value_or( 0 );
  819. // ensure mask have a size always >= 0
  820. if( marginValue < 0 )
  821. {
  822. int minsize = -std::min( m_size.x, m_size.y ) / 2;
  823. if( marginValue < minsize )
  824. marginValue = minsize;
  825. }
  826. return marginValue;
  827. }
  828. VECTOR2I PAD::GetSolderPasteMargin() const
  829. {
  830. // Pads defined only on mask layers (and perhaps on other tech layers) use the shape
  831. // defined by the pad settings only. ALL other pads, even those that don't actually have
  832. // any copper (such as NPTH pads with holes the same size as the pad) get paste expansion.
  833. if( ( m_layerMask & LSET::AllCuMask() ).none() )
  834. return VECTOR2I( 0, 0 );
  835. std::optional<int> margin = m_solderPasteMargin;
  836. std::optional<double> mratio = m_solderPasteMarginRatio;
  837. if( !margin.has_value() )
  838. {
  839. if( FOOTPRINT* parentFootprint = GetParentFootprint() )
  840. margin = parentFootprint->GetLocalSolderPasteMargin();
  841. }
  842. if( !margin.has_value() )
  843. {
  844. if( const BOARD* board = GetBoard() )
  845. margin = board->GetDesignSettings().m_SolderPasteMargin;
  846. }
  847. if( !mratio.has_value() )
  848. {
  849. if( FOOTPRINT* parentFootprint = GetParentFootprint() )
  850. mratio = parentFootprint->GetLocalSolderPasteMarginRatio();
  851. }
  852. if( !mratio.has_value() )
  853. {
  854. if( const BOARD* board = GetBoard() )
  855. mratio = board->GetDesignSettings().m_SolderPasteMarginRatio;
  856. }
  857. VECTOR2I pad_margin;
  858. pad_margin.x = margin.value_or( 0 ) + KiROUND( m_size.x * mratio.value_or( 0 ) );
  859. pad_margin.y = margin.value_or( 0 ) + KiROUND( m_size.y * mratio.value_or( 0 ) );
  860. // ensure mask have a size always >= 0
  861. if( m_padShape != PAD_SHAPE::CUSTOM )
  862. {
  863. if( pad_margin.x < -m_size.x / 2 )
  864. pad_margin.x = -m_size.x / 2;
  865. if( pad_margin.y < -m_size.y / 2 )
  866. pad_margin.y = -m_size.y / 2;
  867. }
  868. return pad_margin;
  869. }
  870. ZONE_CONNECTION PAD::GetZoneConnectionOverrides( wxString* aSource ) const
  871. {
  872. ZONE_CONNECTION connection = m_zoneConnection;
  873. if( connection != ZONE_CONNECTION::INHERITED )
  874. {
  875. if( aSource )
  876. *aSource = _( "pad" );
  877. }
  878. if( connection == ZONE_CONNECTION::INHERITED )
  879. {
  880. if( FOOTPRINT* parentFootprint = GetParentFootprint() )
  881. connection = parentFootprint->GetZoneConnectionOverrides( aSource );
  882. }
  883. return connection;
  884. }
  885. int PAD::GetLocalSpokeWidthOverride( wxString* aSource ) const
  886. {
  887. if( m_thermalSpokeWidth > 0 && aSource )
  888. *aSource = _( "pad" );
  889. return m_thermalSpokeWidth;
  890. }
  891. int PAD::GetLocalThermalGapOverride( wxString* aSource ) const
  892. {
  893. if( m_thermalGap > 0 && aSource )
  894. *aSource = _( "pad" );
  895. return m_thermalGap;
  896. }
  897. void PAD::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  898. {
  899. wxString msg;
  900. FOOTPRINT* parentFootprint = static_cast<FOOTPRINT*>( m_parent );
  901. if( aFrame->GetName() == PCB_EDIT_FRAME_NAME )
  902. {
  903. if( parentFootprint )
  904. aList.emplace_back( _( "Footprint" ), parentFootprint->GetReference() );
  905. }
  906. aList.emplace_back( _( "Pad" ), m_number );
  907. if( !GetPinFunction().IsEmpty() )
  908. aList.emplace_back( _( "Pin Name" ), GetPinFunction() );
  909. if( !GetPinType().IsEmpty() )
  910. aList.emplace_back( _( "Pin Type" ), GetPinType() );
  911. if( aFrame->GetName() == PCB_EDIT_FRAME_NAME )
  912. {
  913. aList.emplace_back( _( "Net" ), UnescapeString( GetNetname() ) );
  914. aList.emplace_back( _( "Resolved Netclass" ),
  915. UnescapeString( GetEffectiveNetClass()->GetName() ) );
  916. if( IsLocked() )
  917. aList.emplace_back( _( "Status" ), _( "Locked" ) );
  918. }
  919. if( GetAttribute() == PAD_ATTRIB::SMD || GetAttribute() == PAD_ATTRIB::CONN )
  920. aList.emplace_back( _( "Layer" ), layerMaskDescribe() );
  921. if( aFrame->GetName() == FOOTPRINT_EDIT_FRAME_NAME )
  922. {
  923. if( GetAttribute() == PAD_ATTRIB::SMD )
  924. {
  925. const std::shared_ptr<SHAPE_POLY_SET>& poly = PAD::GetEffectivePolygon();
  926. double area = poly->Area();
  927. aList.emplace_back( _( "Area" ), aFrame->MessageTextFromValue( area, true, EDA_DATA_TYPE::AREA ) );
  928. }
  929. }
  930. // Show the pad shape, attribute and property
  931. wxString props = ShowPadAttr();
  932. if( GetProperty() != PAD_PROP::NONE )
  933. props += ',';
  934. switch( GetProperty() )
  935. {
  936. case PAD_PROP::NONE: break;
  937. case PAD_PROP::BGA: props += _( "BGA" ); break;
  938. case PAD_PROP::FIDUCIAL_GLBL: props += _( "Fiducial global" ); break;
  939. case PAD_PROP::FIDUCIAL_LOCAL: props += _( "Fiducial local" ); break;
  940. case PAD_PROP::TESTPOINT: props += _( "Test point" ); break;
  941. case PAD_PROP::HEATSINK: props += _( "Heat sink" ); break;
  942. case PAD_PROP::CASTELLATED: props += _( "Castellated" ); break;
  943. }
  944. aList.emplace_back( ShowPadShape(), props );
  945. if( ( GetShape() == PAD_SHAPE::CIRCLE || GetShape() == PAD_SHAPE::OVAL )
  946. && m_size.x == m_size.y )
  947. {
  948. aList.emplace_back( _( "Diameter" ), aFrame->MessageTextFromValue( m_size.x ) );
  949. }
  950. else
  951. {
  952. aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( m_size.x ) );
  953. aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( m_size.y ) );
  954. }
  955. EDA_ANGLE fp_orient = parentFootprint ? parentFootprint->GetOrientation() : ANGLE_0;
  956. EDA_ANGLE pad_orient = GetOrientation() - fp_orient;
  957. pad_orient.Normalize180();
  958. if( !fp_orient.IsZero() )
  959. msg.Printf( wxT( "%g(+ %g)" ), pad_orient.AsDegrees(), fp_orient.AsDegrees() );
  960. else
  961. msg.Printf( wxT( "%g" ), GetOrientation().AsDegrees() );
  962. aList.emplace_back( _( "Rotation" ), msg );
  963. if( GetPadToDieLength() )
  964. {
  965. aList.emplace_back( _( "Length in Package" ),
  966. aFrame->MessageTextFromValue( GetPadToDieLength() ) );
  967. }
  968. if( m_drill.x > 0 || m_drill.y > 0 )
  969. {
  970. if( GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE )
  971. {
  972. aList.emplace_back( _( "Hole" ),
  973. wxString::Format( wxT( "%s" ),
  974. aFrame->MessageTextFromValue( m_drill.x ) ) );
  975. }
  976. else
  977. {
  978. aList.emplace_back( _( "Hole X / Y" ),
  979. wxString::Format( wxT( "%s / %s" ),
  980. aFrame->MessageTextFromValue( m_drill.x ),
  981. aFrame->MessageTextFromValue( m_drill.y ) ) );
  982. }
  983. }
  984. wxString source;
  985. int clearance = GetOwnClearance( UNDEFINED_LAYER, &source );
  986. if( !source.IsEmpty() )
  987. {
  988. aList.emplace_back( wxString::Format( _( "Min Clearance: %s" ),
  989. aFrame->MessageTextFromValue( clearance ) ),
  990. wxString::Format( _( "(from %s)" ),
  991. source ) );
  992. }
  993. #if 0
  994. // useful for debug only
  995. aList.emplace_back( wxT( "UUID" ), m_Uuid.AsString() );
  996. #endif
  997. }
  998. bool PAD::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
  999. {
  1000. VECTOR2I delta = aPosition - GetPosition();
  1001. int boundingRadius = GetBoundingRadius() + aAccuracy;
  1002. if( delta.SquaredEuclideanNorm() > SEG::Square( boundingRadius ) )
  1003. return false;
  1004. return GetEffectivePolygon( ERROR_INSIDE )->Contains( aPosition, -1, aAccuracy );
  1005. }
  1006. bool PAD::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
  1007. {
  1008. BOX2I arect = aRect;
  1009. arect.Normalize();
  1010. arect.Inflate( aAccuracy );
  1011. BOX2I bbox = GetBoundingBox();
  1012. if( aContained )
  1013. {
  1014. return arect.Contains( bbox );
  1015. }
  1016. else
  1017. {
  1018. // Fast test: if aRect is outside the polygon bounding box,
  1019. // rectangles cannot intersect
  1020. if( !arect.Intersects( bbox ) )
  1021. return false;
  1022. const std::shared_ptr<SHAPE_POLY_SET>& poly = GetEffectivePolygon( ERROR_INSIDE );
  1023. int count = poly->TotalVertices();
  1024. for( int ii = 0; ii < count; ii++ )
  1025. {
  1026. VECTOR2I vertex = poly->CVertex( ii );
  1027. VECTOR2I vertexNext = poly->CVertex( ( ii + 1 ) % count );
  1028. // Test if the point is within aRect
  1029. if( arect.Contains( vertex ) )
  1030. return true;
  1031. // Test if this edge intersects aRect
  1032. if( arect.Intersects( vertex, vertexNext ) )
  1033. return true;
  1034. }
  1035. return false;
  1036. }
  1037. }
  1038. int PAD::Compare( const PAD* aPadRef, const PAD* aPadCmp )
  1039. {
  1040. int diff;
  1041. if( ( diff = static_cast<int>( aPadRef->GetShape() ) -
  1042. static_cast<int>( aPadCmp->GetShape() ) ) != 0 )
  1043. return diff;
  1044. if( ( diff = static_cast<int>( aPadRef->m_attribute ) -
  1045. static_cast<int>( aPadCmp->m_attribute ) ) != 0 )
  1046. return diff;
  1047. if( ( diff = aPadRef->m_drillShape - aPadCmp->m_drillShape ) != 0 )
  1048. return diff;
  1049. if( ( diff = aPadRef->m_drill.x - aPadCmp->m_drill.x ) != 0 )
  1050. return diff;
  1051. if( ( diff = aPadRef->m_drill.y - aPadCmp->m_drill.y ) != 0 )
  1052. return diff;
  1053. if( ( diff = aPadRef->m_size.x - aPadCmp->m_size.x ) != 0 )
  1054. return diff;
  1055. if( ( diff = aPadRef->m_size.y - aPadCmp->m_size.y ) != 0 )
  1056. return diff;
  1057. if( ( diff = aPadRef->m_offset.x - aPadCmp->m_offset.x ) != 0 )
  1058. return diff;
  1059. if( ( diff = aPadRef->m_offset.y - aPadCmp->m_offset.y ) != 0 )
  1060. return diff;
  1061. if( ( diff = aPadRef->m_deltaSize.x - aPadCmp->m_deltaSize.x ) != 0 )
  1062. return diff;
  1063. if( ( diff = aPadRef->m_deltaSize.y - aPadCmp->m_deltaSize.y ) != 0 )
  1064. return diff;
  1065. if( ( diff = aPadRef->m_roundedCornerScale - aPadCmp->m_roundedCornerScale ) != 0 )
  1066. return diff;
  1067. if( ( diff = aPadRef->m_chamferPositions - aPadCmp->m_chamferPositions ) != 0 )
  1068. return diff;
  1069. if( ( diff = aPadRef->m_chamferScale - aPadCmp->m_chamferScale ) != 0 )
  1070. return diff;
  1071. if( ( diff = static_cast<int>( aPadRef->m_editPrimitives.size() ) -
  1072. static_cast<int>( aPadCmp->m_editPrimitives.size() ) ) != 0 )
  1073. return diff;
  1074. // @todo: Compare custom pad primitives for pads that have the same number of primitives
  1075. // here. Currently there is no compare function for PCB_SHAPE objects.
  1076. // Dick: specctra_export needs this
  1077. // Lorenzo: gencad also needs it to implement padstacks!
  1078. #if __cplusplus >= 201103L
  1079. long long d = aPadRef->m_layerMask.to_ullong() - aPadCmp->m_layerMask.to_ullong();
  1080. if( d < 0 )
  1081. return -1;
  1082. else if( d > 0 )
  1083. return 1;
  1084. return 0;
  1085. #else
  1086. // these strings are not typically constructed, since we don't get here often.
  1087. std::string s1 = aPadRef->m_layerMask.to_string();
  1088. std::string s2 = aPadCmp->m_layerMask.to_string();
  1089. return s1.compare( s2 );
  1090. #endif
  1091. }
  1092. void PAD::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
  1093. {
  1094. RotatePoint( m_pos, aRotCentre, aAngle );
  1095. m_orient += aAngle;
  1096. m_orient.Normalize();
  1097. SetDirty();
  1098. }
  1099. wxString PAD::ShowPadShape() const
  1100. {
  1101. switch( GetShape() )
  1102. {
  1103. case PAD_SHAPE::CIRCLE: return _( "Circle" );
  1104. case PAD_SHAPE::OVAL: return _( "Oval" );
  1105. case PAD_SHAPE::RECTANGLE: return _( "Rect" );
  1106. case PAD_SHAPE::TRAPEZOID: return _( "Trap" );
  1107. case PAD_SHAPE::ROUNDRECT: return _( "Roundrect" );
  1108. case PAD_SHAPE::CHAMFERED_RECT: return _( "Chamferedrect" );
  1109. case PAD_SHAPE::CUSTOM: return _( "CustomShape" );
  1110. default: return wxT( "???" );
  1111. }
  1112. }
  1113. wxString PAD::ShowPadAttr() const
  1114. {
  1115. switch( GetAttribute() )
  1116. {
  1117. case PAD_ATTRIB::PTH: return _( "PTH" );
  1118. case PAD_ATTRIB::SMD: return _( "SMD" );
  1119. case PAD_ATTRIB::CONN: return _( "Conn" );
  1120. case PAD_ATTRIB::NPTH: return _( "NPTH" );
  1121. default: return wxT( "???" );
  1122. }
  1123. }
  1124. wxString PAD::GetItemDescription( UNITS_PROVIDER* aUnitsProvider ) const
  1125. {
  1126. if( GetNumber().IsEmpty() )
  1127. {
  1128. if( GetAttribute() == PAD_ATTRIB::SMD || GetAttribute() == PAD_ATTRIB::CONN )
  1129. {
  1130. return wxString::Format( _( "Pad %s of %s on %s" ),
  1131. GetNetnameMsg(),
  1132. GetParentFootprint()->GetReference(),
  1133. layerMaskDescribe() );
  1134. }
  1135. else if( GetAttribute() == PAD_ATTRIB::NPTH )
  1136. {
  1137. return wxString::Format( _( "NPTH pad of %s" ), GetParentFootprint()->GetReference() );
  1138. }
  1139. else
  1140. {
  1141. return wxString::Format( _( "PTH pad %s of %s" ),
  1142. GetNetnameMsg(),
  1143. GetParentFootprint()->GetReference() );
  1144. }
  1145. }
  1146. else
  1147. {
  1148. if( GetAttribute() == PAD_ATTRIB::SMD || GetAttribute() == PAD_ATTRIB::CONN )
  1149. {
  1150. return wxString::Format( _( "Pad %s %s of %s on %s" ),
  1151. GetNumber(),
  1152. GetNetnameMsg(),
  1153. GetParentFootprint()->GetReference(),
  1154. layerMaskDescribe() );
  1155. }
  1156. else if( GetAttribute() == PAD_ATTRIB::NPTH )
  1157. {
  1158. return wxString::Format( _( "NPTH of %s" ), GetParentFootprint()->GetReference() );
  1159. }
  1160. else
  1161. {
  1162. return wxString::Format( _( "PTH pad %s %s of %s" ),
  1163. GetNumber(),
  1164. GetNetnameMsg(),
  1165. GetParentFootprint()->GetReference() );
  1166. }
  1167. }
  1168. }
  1169. BITMAPS PAD::GetMenuImage() const
  1170. {
  1171. return BITMAPS::pad;
  1172. }
  1173. EDA_ITEM* PAD::Clone() const
  1174. {
  1175. return new PAD( *this );
  1176. }
  1177. void PAD::ViewGetLayers( int aLayers[], int& aCount ) const
  1178. {
  1179. aCount = 0;
  1180. // These 2 types of pads contain a hole
  1181. if( m_attribute == PAD_ATTRIB::PTH )
  1182. {
  1183. aLayers[aCount++] = LAYER_PAD_PLATEDHOLES;
  1184. aLayers[aCount++] = LAYER_PAD_HOLEWALLS;
  1185. }
  1186. if( m_attribute == PAD_ATTRIB::NPTH )
  1187. aLayers[aCount++] = LAYER_NON_PLATEDHOLES;
  1188. if( IsOnLayer( F_Cu ) && IsOnLayer( B_Cu ) )
  1189. {
  1190. // Multi layer pad
  1191. aLayers[aCount++] = LAYER_PADS_TH;
  1192. aLayers[aCount++] = LAYER_PAD_NETNAMES;
  1193. }
  1194. else if( IsOnLayer( F_Cu ) )
  1195. {
  1196. aLayers[aCount++] = LAYER_PADS_SMD_FR;
  1197. // Is this a PTH pad that has only front copper? If so, we need to also display the
  1198. // net name on the PTH netname layer so that it isn't blocked by the drill hole.
  1199. if( m_attribute == PAD_ATTRIB::PTH )
  1200. aLayers[aCount++] = LAYER_PAD_NETNAMES;
  1201. else
  1202. aLayers[aCount++] = LAYER_PAD_FR_NETNAMES;
  1203. }
  1204. else if( IsOnLayer( B_Cu ) )
  1205. {
  1206. aLayers[aCount++] = LAYER_PADS_SMD_BK;
  1207. // Is this a PTH pad that has only back copper? If so, we need to also display the
  1208. // net name on the PTH netname layer so that it isn't blocked by the drill hole.
  1209. if( m_attribute == PAD_ATTRIB::PTH )
  1210. aLayers[aCount++] = LAYER_PAD_NETNAMES;
  1211. else
  1212. aLayers[aCount++] = LAYER_PAD_BK_NETNAMES;
  1213. }
  1214. else
  1215. {
  1216. // Internal layers only. (Not yet supported in GUI, but is being used by Python
  1217. // footprint generators and will be needed anyway once pad stacks are supported.)
  1218. for ( int internal = In1_Cu; internal < In30_Cu; ++internal )
  1219. {
  1220. if( IsOnLayer( (PCB_LAYER_ID) internal ) )
  1221. aLayers[aCount++] = internal;
  1222. }
  1223. }
  1224. // Check non-copper layers. This list should include all the layers that the
  1225. // footprint editor allows a pad to be placed on.
  1226. static const PCB_LAYER_ID layers_mech[] = { F_Mask, B_Mask, F_Paste, B_Paste,
  1227. F_Adhes, B_Adhes, F_SilkS, B_SilkS, Dwgs_User, Eco1_User, Eco2_User };
  1228. for( PCB_LAYER_ID each_layer : layers_mech )
  1229. {
  1230. if( IsOnLayer( each_layer ) )
  1231. aLayers[aCount++] = each_layer;
  1232. }
  1233. }
  1234. double PAD::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
  1235. {
  1236. constexpr double HIDE = std::numeric_limits<double>::max();
  1237. PCB_PAINTER* painter = static_cast<PCB_PAINTER*>( aView->GetPainter() );
  1238. PCB_RENDER_SETTINGS* renderSettings = painter->GetSettings();
  1239. const BOARD* board = GetBoard();
  1240. // Meta control for hiding all pads
  1241. if( !aView->IsLayerVisible( LAYER_PADS ) )
  1242. return HIDE;
  1243. // Handle Render tab switches
  1244. if( ( GetAttribute() == PAD_ATTRIB::PTH || GetAttribute() == PAD_ATTRIB::NPTH )
  1245. && !aView->IsLayerVisible( LAYER_PADS_TH ) )
  1246. {
  1247. return HIDE;
  1248. }
  1249. if( !IsFlipped() && !aView->IsLayerVisible( LAYER_FOOTPRINTS_FR ) )
  1250. return HIDE;
  1251. if( IsFlipped() && !aView->IsLayerVisible( LAYER_FOOTPRINTS_BK ) )
  1252. return HIDE;
  1253. if( IsFrontLayer( (PCB_LAYER_ID) aLayer ) && !aView->IsLayerVisible( LAYER_PADS_SMD_FR ) )
  1254. return HIDE;
  1255. if( IsBackLayer( (PCB_LAYER_ID) aLayer ) && !aView->IsLayerVisible( LAYER_PADS_SMD_BK ) )
  1256. return HIDE;
  1257. LSET visible = board->GetVisibleLayers() & board->GetEnabledLayers();
  1258. if( IsHoleLayer( aLayer ) )
  1259. {
  1260. if( !( visible & LSET::PhysicalLayersMask() ).any() )
  1261. return HIDE;
  1262. }
  1263. else if( IsNetnameLayer( aLayer ) )
  1264. {
  1265. if( renderSettings->GetHighContrast() )
  1266. {
  1267. // Hide netnames unless pad is flashed to a high-contrast layer
  1268. if( !FlashLayer( renderSettings->GetPrimaryHighContrastLayer() ) )
  1269. return HIDE;
  1270. }
  1271. else
  1272. {
  1273. // Hide netnames unless pad is flashed to a visible layer
  1274. if( !FlashLayer( visible ) )
  1275. return HIDE;
  1276. }
  1277. // Netnames will be shown only if zoom is appropriate
  1278. int divisor = std::min( GetBoundingBox().GetWidth(), GetBoundingBox().GetHeight() );
  1279. // Pad sizes can be zero briefly when someone is typing a number like "0.5" in the pad
  1280. // properties dialog
  1281. if( divisor == 0 )
  1282. return HIDE;
  1283. return ( double ) pcbIUScale.mmToIU( 5 ) / divisor;
  1284. }
  1285. // Passed all tests; show.
  1286. return 0.0;
  1287. }
  1288. const BOX2I PAD::ViewBBox() const
  1289. {
  1290. // Bounding box includes soldermask too. Remember mask and/or paste margins can be < 0
  1291. int solderMaskMargin = std::max( GetSolderMaskExpansion(), 0 );
  1292. VECTOR2I solderPasteMargin = VECTOR2D( GetSolderPasteMargin() );
  1293. BOX2I bbox = GetBoundingBox();
  1294. int clearance = 0;
  1295. // If we're drawing clearance lines then get the biggest possible clearance
  1296. if( PCBNEW_SETTINGS* cfg = dynamic_cast<PCBNEW_SETTINGS*>( Kiface().KifaceSettings() ) )
  1297. {
  1298. if( cfg && cfg->m_Display.m_PadClearance && GetBoard() )
  1299. clearance = GetBoard()->GetMaxClearanceValue();
  1300. }
  1301. // Look for the biggest possible bounding box
  1302. int xMargin = std::max( solderMaskMargin, solderPasteMargin.x ) + clearance;
  1303. int yMargin = std::max( solderMaskMargin, solderPasteMargin.y ) + clearance;
  1304. return BOX2I( VECTOR2I( bbox.GetOrigin() ) - VECTOR2I( xMargin, yMargin ),
  1305. VECTOR2I( bbox.GetSize() ) + VECTOR2I( 2 * xMargin, 2 * yMargin ) );
  1306. }
  1307. void PAD::ImportSettingsFrom( const PAD& aMasterPad )
  1308. {
  1309. SetShape( aMasterPad.GetShape() );
  1310. // Layer Set should be updated before calling SetAttribute()
  1311. SetLayerSet( aMasterPad.GetLayerSet() );
  1312. SetAttribute( aMasterPad.GetAttribute() );
  1313. // Unfortunately, SetAttribute() can change m_layerMask.
  1314. // Be sure we keep the original mask by calling SetLayerSet() after SetAttribute()
  1315. SetLayerSet( aMasterPad.GetLayerSet() );
  1316. SetProperty( aMasterPad.GetProperty() );
  1317. // Must be after setting attribute and layerSet
  1318. if( !CanHaveNumber() )
  1319. SetNumber( wxEmptyString );
  1320. // I am not sure the m_LengthPadToDie should be imported, because this is a parameter
  1321. // really specific to a given pad (JPC).
  1322. #if 0
  1323. SetPadToDieLength( aMasterPad.GetPadToDieLength() );
  1324. #endif
  1325. // The pad orientation, for historical reasons is the pad rotation + parent rotation.
  1326. EDA_ANGLE pad_rot = aMasterPad.GetOrientation();
  1327. if( aMasterPad.GetParentFootprint() )
  1328. pad_rot -= aMasterPad.GetParentFootprint()->GetOrientation();
  1329. if( GetParentFootprint() )
  1330. pad_rot += GetParentFootprint()->GetOrientation();
  1331. SetOrientation( pad_rot );
  1332. SetRemoveUnconnected( aMasterPad.GetRemoveUnconnected() );
  1333. SetKeepTopBottom( aMasterPad.GetKeepTopBottom() );
  1334. SetSize( aMasterPad.GetSize() );
  1335. SetDelta( VECTOR2I( 0, 0 ) );
  1336. SetOffset( aMasterPad.GetOffset() );
  1337. SetDrillSize( aMasterPad.GetDrillSize() );
  1338. SetDrillShape( aMasterPad.GetDrillShape() );
  1339. SetRoundRectRadiusRatio( aMasterPad.GetRoundRectRadiusRatio() );
  1340. SetChamferRectRatio( aMasterPad.GetChamferRectRatio() );
  1341. SetChamferPositions( aMasterPad.GetChamferPositions() );
  1342. switch( aMasterPad.GetShape() )
  1343. {
  1344. case PAD_SHAPE::TRAPEZOID:
  1345. SetDelta( aMasterPad.GetDelta() );
  1346. break;
  1347. case PAD_SHAPE::CIRCLE:
  1348. // ensure size.y == size.x
  1349. SetSize( VECTOR2I( GetSize().x, GetSize().x ) );
  1350. break;
  1351. default:
  1352. ;
  1353. }
  1354. switch( aMasterPad.GetAttribute() )
  1355. {
  1356. case PAD_ATTRIB::SMD:
  1357. case PAD_ATTRIB::CONN:
  1358. // These pads do not have a hole (they are expected to be on one external copper layer)
  1359. SetDrillSize( VECTOR2I( 0, 0 ) );
  1360. break;
  1361. default:
  1362. ;
  1363. }
  1364. // copy also local settings:
  1365. SetLocalClearance( aMasterPad.GetLocalClearance() );
  1366. SetLocalSolderMaskMargin( aMasterPad.GetLocalSolderMaskMargin() );
  1367. SetLocalSolderPasteMargin( aMasterPad.GetLocalSolderPasteMargin() );
  1368. SetLocalSolderPasteMarginRatio( aMasterPad.GetLocalSolderPasteMarginRatio() );
  1369. SetLocalZoneConnection( aMasterPad.GetLocalZoneConnection() );
  1370. SetThermalSpokeWidth( aMasterPad.GetThermalSpokeWidth() );
  1371. SetThermalSpokeAngle( aMasterPad.GetThermalSpokeAngle() );
  1372. SetThermalGap( aMasterPad.GetThermalGap() );
  1373. SetCustomShapeInZoneOpt( aMasterPad.GetCustomShapeInZoneOpt() );
  1374. m_teardropParams = aMasterPad.m_teardropParams;
  1375. // Add or remove custom pad shapes:
  1376. ReplacePrimitives( aMasterPad.GetPrimitives() );
  1377. SetAnchorPadShape( aMasterPad.GetAnchorPadShape() );
  1378. SetDirty();
  1379. }
  1380. void PAD::swapData( BOARD_ITEM* aImage )
  1381. {
  1382. assert( aImage->Type() == PCB_PAD_T );
  1383. std::swap( *this, *static_cast<PAD*>( aImage ) );
  1384. }
  1385. bool PAD::TransformHoleToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
  1386. ERROR_LOC aErrorLoc ) const
  1387. {
  1388. VECTOR2I drillsize = GetDrillSize();
  1389. if( !drillsize.x || !drillsize.y )
  1390. return false;
  1391. std::shared_ptr<SHAPE_SEGMENT> slot = GetEffectiveHoleShape();
  1392. TransformOvalToPolygon( aBuffer, slot->GetSeg().A, slot->GetSeg().B,
  1393. slot->GetWidth() + aClearance * 2, aError, aErrorLoc );
  1394. return true;
  1395. }
  1396. void PAD::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance,
  1397. int aMaxError, ERROR_LOC aErrorLoc, bool ignoreLineWidth ) const
  1398. {
  1399. wxASSERT_MSG( !ignoreLineWidth, wxT( "IgnoreLineWidth has no meaning for pads." ) );
  1400. // minimal segment count to approximate a circle to create the polygonal pad shape
  1401. // This minimal value is mainly for very small pads, like SM0402.
  1402. // Most of time pads are using the segment count given by aError value.
  1403. const int pad_min_seg_per_circle_count = 16;
  1404. int dx = m_size.x / 2;
  1405. int dy = m_size.y / 2;
  1406. VECTOR2I padShapePos = ShapePos(); // Note: for pad having a shape offset, the pad
  1407. // position is NOT the shape position
  1408. switch( GetShape() )
  1409. {
  1410. case PAD_SHAPE::CIRCLE:
  1411. case PAD_SHAPE::OVAL:
  1412. // Note: dx == dy is not guaranteed for circle pads in legacy boards
  1413. if( dx == dy || ( GetShape() == PAD_SHAPE::CIRCLE ) )
  1414. {
  1415. TransformCircleToPolygon( aBuffer, padShapePos, dx + aClearance, aMaxError, aErrorLoc,
  1416. pad_min_seg_per_circle_count );
  1417. }
  1418. else
  1419. {
  1420. int half_width = std::min( dx, dy );
  1421. VECTOR2I delta( dx - half_width, dy - half_width );
  1422. RotatePoint( delta, m_orient );
  1423. TransformOvalToPolygon( aBuffer, padShapePos - delta, padShapePos + delta,
  1424. ( half_width + aClearance ) * 2, aMaxError, aErrorLoc,
  1425. pad_min_seg_per_circle_count );
  1426. }
  1427. break;
  1428. case PAD_SHAPE::TRAPEZOID:
  1429. case PAD_SHAPE::RECTANGLE:
  1430. {
  1431. int ddx = GetShape() == PAD_SHAPE::TRAPEZOID ? m_deltaSize.x / 2 : 0;
  1432. int ddy = GetShape() == PAD_SHAPE::TRAPEZOID ? m_deltaSize.y / 2 : 0;
  1433. SHAPE_POLY_SET outline;
  1434. TransformTrapezoidToPolygon( outline, padShapePos, m_size, m_orient, ddx, ddy, aClearance,
  1435. aMaxError, aErrorLoc );
  1436. aBuffer.Append( outline );
  1437. break;
  1438. }
  1439. case PAD_SHAPE::CHAMFERED_RECT:
  1440. case PAD_SHAPE::ROUNDRECT:
  1441. {
  1442. bool doChamfer = GetShape() == PAD_SHAPE::CHAMFERED_RECT;
  1443. SHAPE_POLY_SET outline;
  1444. TransformRoundChamferedRectToPolygon( outline, padShapePos, m_size, m_orient,
  1445. GetRoundRectCornerRadius(),
  1446. doChamfer ? GetChamferRectRatio() : 0,
  1447. doChamfer ? GetChamferPositions() : 0,
  1448. aClearance, aMaxError, aErrorLoc );
  1449. aBuffer.Append( outline );
  1450. break;
  1451. }
  1452. case PAD_SHAPE::CUSTOM:
  1453. {
  1454. SHAPE_POLY_SET outline;
  1455. MergePrimitivesAsPolygon( &outline, aErrorLoc );
  1456. outline.Rotate( m_orient );
  1457. outline.Move( VECTOR2I( padShapePos ) );
  1458. if( aClearance > 0 || aErrorLoc == ERROR_OUTSIDE )
  1459. {
  1460. if( aErrorLoc == ERROR_OUTSIDE )
  1461. aClearance += aMaxError;
  1462. outline.Inflate( aClearance, CORNER_STRATEGY::ROUND_ALL_CORNERS, aMaxError );
  1463. outline.Fracture( SHAPE_POLY_SET::PM_FAST );
  1464. }
  1465. else if( aClearance < 0 )
  1466. {
  1467. // Negative clearances are primarily for drawing solder paste layer, so we don't
  1468. // worry ourselves overly about which side the error is on.
  1469. // aClearance is negative so this is actually a deflate
  1470. outline.Inflate( aClearance, CORNER_STRATEGY::ALLOW_ACUTE_CORNERS, aMaxError );
  1471. outline.Fracture( SHAPE_POLY_SET::PM_FAST );
  1472. }
  1473. aBuffer.Append( outline );
  1474. break;
  1475. }
  1476. default:
  1477. wxFAIL_MSG( wxT( "PAD::TransformShapeToPolygon no implementation for " )
  1478. + wxString( std::string( magic_enum::enum_name( GetShape() ) ) ) );
  1479. break;
  1480. }
  1481. }
  1482. bool PAD::operator==( const BOARD_ITEM& aOther ) const
  1483. {
  1484. if( Type() != aOther.Type() )
  1485. return false;
  1486. if( m_parent && aOther.GetParent() && m_parent->m_Uuid != aOther.GetParent()->m_Uuid )
  1487. return false;
  1488. const PAD& other = static_cast<const PAD&>( aOther );
  1489. if( GetShape() != other.GetShape() )
  1490. return false;
  1491. if( GetPosition() != other.GetPosition() )
  1492. return false;
  1493. if( GetAttribute() != other.GetAttribute() )
  1494. return false;
  1495. if( GetSize() != other.GetSize() )
  1496. return false;
  1497. if( GetOffset() != other.GetOffset() )
  1498. return false;
  1499. if( GetDrillSize() != other.GetDrillSize() )
  1500. return false;
  1501. if( GetDrillShape() != other.GetDrillShape() )
  1502. return false;
  1503. if( GetRoundRectRadiusRatio() != other.GetRoundRectRadiusRatio() )
  1504. return false;
  1505. if( GetChamferRectRatio() != other.GetChamferRectRatio() )
  1506. return false;
  1507. if( GetChamferPositions() != other.GetChamferPositions() )
  1508. return false;
  1509. if( GetOrientation() != other.GetOrientation() )
  1510. return false;
  1511. if( GetLocalZoneConnection() != other.GetLocalZoneConnection() )
  1512. return false;
  1513. if( GetThermalSpokeWidth() != other.GetThermalSpokeWidth() )
  1514. return false;
  1515. if( GetThermalSpokeAngle() != other.GetThermalSpokeAngle() )
  1516. return false;
  1517. if( GetThermalGap() != other.GetThermalGap() )
  1518. return false;
  1519. if( GetCustomShapeInZoneOpt() != other.GetCustomShapeInZoneOpt() )
  1520. return false;
  1521. if( GetPrimitives().size() != other.GetPrimitives().size() )
  1522. return false;
  1523. for( size_t ii = 0; ii < GetPrimitives().size(); ii++ )
  1524. {
  1525. if( GetPrimitives()[ii] != other.GetPrimitives()[ii] )
  1526. return false;
  1527. }
  1528. if( GetAnchorPadShape() != other.GetAnchorPadShape() )
  1529. return false;
  1530. if( GetLocalClearance() != other.GetLocalClearance() )
  1531. return false;
  1532. if( GetLocalSolderMaskMargin() != other.GetLocalSolderMaskMargin() )
  1533. return false;
  1534. if( GetLocalSolderPasteMargin() != other.GetLocalSolderPasteMargin() )
  1535. return false;
  1536. if( GetLocalSolderPasteMarginRatio() != other.GetLocalSolderPasteMarginRatio() )
  1537. return false;
  1538. if( GetLocalSpokeWidthOverride() != other.GetLocalSpokeWidthOverride() )
  1539. return false;
  1540. if( GetLayerSet() != other.GetLayerSet() )
  1541. return false;
  1542. return true;
  1543. }
  1544. double PAD::Similarity( const BOARD_ITEM& aOther ) const
  1545. {
  1546. if( aOther.Type() != Type() )
  1547. return 0.0;
  1548. if( m_parent->m_Uuid != aOther.GetParent()->m_Uuid )
  1549. return 0.0;
  1550. const PAD& other = static_cast<const PAD&>( aOther );
  1551. double similarity = 1.0;
  1552. if( GetShape() != other.GetShape() )
  1553. similarity *= 0.9;
  1554. if( GetPosition() != other.GetPosition() )
  1555. similarity *= 0.9;
  1556. if( GetAttribute() != other.GetAttribute() )
  1557. similarity *= 0.9;
  1558. if( GetSize() != other.GetSize() )
  1559. similarity *= 0.9;
  1560. if( GetOffset() != other.GetOffset() )
  1561. similarity *= 0.9;
  1562. if( GetDrillSize() != other.GetDrillSize() )
  1563. similarity *= 0.9;
  1564. if( GetDrillShape() != other.GetDrillShape() )
  1565. similarity *= 0.9;
  1566. if( GetRoundRectRadiusRatio() != other.GetRoundRectRadiusRatio() )
  1567. similarity *= 0.9;
  1568. if( GetChamferRectRatio() != other.GetChamferRectRatio() )
  1569. similarity *= 0.9;
  1570. if( GetChamferPositions() != other.GetChamferPositions() )
  1571. similarity *= 0.9;
  1572. if( GetOrientation() != other.GetOrientation() )
  1573. similarity *= 0.9;
  1574. if( GetLocalZoneConnection() != other.GetLocalZoneConnection() )
  1575. similarity *= 0.9;
  1576. if( GetThermalSpokeWidth() != other.GetThermalSpokeWidth() )
  1577. similarity *= 0.9;
  1578. if( GetThermalSpokeAngle() != other.GetThermalSpokeAngle() )
  1579. similarity *= 0.9;
  1580. if( GetThermalGap() != other.GetThermalGap() )
  1581. similarity *= 0.9;
  1582. if( GetCustomShapeInZoneOpt() != other.GetCustomShapeInZoneOpt() )
  1583. similarity *= 0.9;
  1584. if( GetPrimitives().size() != other.GetPrimitives().size() )
  1585. similarity *= 0.9;
  1586. if( GetAnchorPadShape() != other.GetAnchorPadShape() )
  1587. similarity *= 0.9;
  1588. if( GetLocalClearance() != other.GetLocalClearance() )
  1589. similarity *= 0.9;
  1590. if( GetLocalSolderMaskMargin() != other.GetLocalSolderMaskMargin() )
  1591. similarity *= 0.9;
  1592. if( GetLocalSolderPasteMargin() != other.GetLocalSolderPasteMargin() )
  1593. similarity *= 0.9;
  1594. if( GetLocalSolderPasteMarginRatio() != other.GetLocalSolderPasteMarginRatio() )
  1595. similarity *= 0.9;
  1596. if( GetLocalSpokeWidthOverride() != other.GetLocalSpokeWidthOverride() )
  1597. similarity *= 0.9;
  1598. if( GetLayerSet() != other.GetLayerSet() )
  1599. similarity *= 0.9;
  1600. return similarity;
  1601. }
  1602. static struct PAD_DESC
  1603. {
  1604. PAD_DESC()
  1605. {
  1606. ENUM_MAP<PAD_ATTRIB>::Instance()
  1607. .Map( PAD_ATTRIB::PTH, _HKI( "Through-hole" ) )
  1608. .Map( PAD_ATTRIB::SMD, _HKI( "SMD" ) )
  1609. .Map( PAD_ATTRIB::CONN, _HKI( "Edge connector" ) )
  1610. .Map( PAD_ATTRIB::NPTH, _HKI( "NPTH, mechanical" ) );
  1611. ENUM_MAP<PAD_SHAPE>::Instance()
  1612. .Map( PAD_SHAPE::CIRCLE, _HKI( "Circle" ) )
  1613. .Map( PAD_SHAPE::RECTANGLE, _HKI( "Rectangle" ) )
  1614. .Map( PAD_SHAPE::OVAL, _HKI( "Oval" ) )
  1615. .Map( PAD_SHAPE::TRAPEZOID, _HKI( "Trapezoid" ) )
  1616. .Map( PAD_SHAPE::ROUNDRECT, _HKI( "Rounded rectangle" ) )
  1617. .Map( PAD_SHAPE::CHAMFERED_RECT, _HKI( "Chamfered rectangle" ) )
  1618. .Map( PAD_SHAPE::CUSTOM, _HKI( "Custom" ) );
  1619. ENUM_MAP<PAD_PROP>::Instance()
  1620. .Map( PAD_PROP::NONE, _HKI( "None" ) )
  1621. .Map( PAD_PROP::BGA, _HKI( "BGA pad" ) )
  1622. .Map( PAD_PROP::FIDUCIAL_GLBL, _HKI( "Fiducial, global to board" ) )
  1623. .Map( PAD_PROP::FIDUCIAL_LOCAL, _HKI( "Fiducial, local to footprint" ) )
  1624. .Map( PAD_PROP::TESTPOINT, _HKI( "Test point pad" ) )
  1625. .Map( PAD_PROP::HEATSINK, _HKI( "Heatsink pad" ) )
  1626. .Map( PAD_PROP::CASTELLATED, _HKI( "Castellated pad" ) );
  1627. ENUM_MAP<ZONE_CONNECTION>& zcMap = ENUM_MAP<ZONE_CONNECTION>::Instance();
  1628. if( zcMap.Choices().GetCount() == 0 )
  1629. {
  1630. zcMap.Undefined( ZONE_CONNECTION::INHERITED );
  1631. zcMap.Map( ZONE_CONNECTION::INHERITED, _HKI( "Inherited" ) )
  1632. .Map( ZONE_CONNECTION::NONE, _HKI( "None" ) )
  1633. .Map( ZONE_CONNECTION::THERMAL, _HKI( "Thermal reliefs" ) )
  1634. .Map( ZONE_CONNECTION::FULL, _HKI( "Solid" ) )
  1635. .Map( ZONE_CONNECTION::THT_THERMAL, _HKI( "Thermal reliefs for PTH" ) );
  1636. }
  1637. PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
  1638. REGISTER_TYPE( PAD );
  1639. propMgr.InheritsAfter( TYPE_HASH( PAD ), TYPE_HASH( BOARD_CONNECTED_ITEM ) );
  1640. propMgr.Mask( TYPE_HASH( PAD ), TYPE_HASH( BOARD_CONNECTED_ITEM ), _HKI( "Layer" ) );
  1641. propMgr.AddProperty( new PROPERTY<PAD, double>( _HKI( "Orientation" ),
  1642. &PAD::SetOrientationDegrees, &PAD::GetOrientationDegrees,
  1643. PROPERTY_DISPLAY::PT_DEGREE ) );
  1644. auto isCopperPad =
  1645. []( INSPECTABLE* aItem ) -> bool
  1646. {
  1647. if( PAD* pad = dynamic_cast<PAD*>( aItem ) )
  1648. return pad->GetAttribute() != PAD_ATTRIB::NPTH;
  1649. return false;
  1650. };
  1651. auto padCanHaveHole =
  1652. []( INSPECTABLE* aItem ) -> bool
  1653. {
  1654. if( PAD* pad = dynamic_cast<PAD*>( aItem ) )
  1655. {
  1656. return pad->GetAttribute() == PAD_ATTRIB::PTH
  1657. || pad->GetAttribute() == PAD_ATTRIB::NPTH;
  1658. }
  1659. return false;
  1660. };
  1661. propMgr.OverrideAvailability( TYPE_HASH( PAD ), TYPE_HASH( BOARD_CONNECTED_ITEM ),
  1662. _HKI( "Net" ), isCopperPad );
  1663. propMgr.OverrideAvailability( TYPE_HASH( PAD ), TYPE_HASH( BOARD_CONNECTED_ITEM ),
  1664. _HKI( "Net Class" ), isCopperPad );
  1665. const wxString groupPad = _HKI( "Pad Properties" );
  1666. auto padType = new PROPERTY_ENUM<PAD, PAD_ATTRIB>( _HKI( "Pad Type" ),
  1667. &PAD::SetAttribute, &PAD::GetAttribute );
  1668. propMgr.AddProperty( padType, groupPad );
  1669. auto shape = new PROPERTY_ENUM<PAD, PAD_SHAPE>( _HKI( "Pad Shape" ),
  1670. &PAD::SetShape, &PAD::GetShape );
  1671. propMgr.AddProperty( shape, groupPad );
  1672. auto padNumber = new PROPERTY<PAD, wxString>( _HKI( "Pad Number" ),
  1673. &PAD::SetNumber, &PAD::GetNumber );
  1674. padNumber->SetAvailableFunc( isCopperPad );
  1675. propMgr.AddProperty( padNumber, groupPad );
  1676. propMgr.AddProperty( new PROPERTY<PAD, wxString>( _HKI( "Pin Name" ),
  1677. NO_SETTER( PAD, wxString ), &PAD::GetPinFunction ), groupPad )
  1678. .SetIsHiddenFromLibraryEditors();
  1679. propMgr.AddProperty( new PROPERTY<PAD, wxString>( _HKI( "Pin Type" ),
  1680. NO_SETTER( PAD, wxString ), &PAD::GetPinType ), groupPad )
  1681. .SetIsHiddenFromLibraryEditors();
  1682. propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Size X" ),
  1683. &PAD::SetSizeX, &PAD::GetSizeX,
  1684. PROPERTY_DISPLAY::PT_SIZE ), groupPad );
  1685. propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Size Y" ),
  1686. &PAD::SetSizeY, &PAD::GetSizeY,
  1687. PROPERTY_DISPLAY::PT_SIZE ), groupPad )
  1688. .SetAvailableFunc(
  1689. [=]( INSPECTABLE* aItem ) -> bool
  1690. {
  1691. // Circle pads have no usable y-size
  1692. if( PAD* pad = dynamic_cast<PAD*>( aItem ) )
  1693. return pad->GetShape() != PAD_SHAPE::CIRCLE;
  1694. return true;
  1695. } );
  1696. auto roundRadiusRatio = new PROPERTY<PAD, double>( _HKI( "Corner Radius Ratio" ),
  1697. &PAD::SetRoundRectRadiusRatio, &PAD::GetRoundRectRadiusRatio );
  1698. roundRadiusRatio->SetAvailableFunc(
  1699. [=]( INSPECTABLE* aItem ) -> bool
  1700. {
  1701. if( PAD* pad = dynamic_cast<PAD*>( aItem ) )
  1702. return pad->GetShape() == PAD_SHAPE::ROUNDRECT;
  1703. return false;
  1704. } );
  1705. propMgr.AddProperty( roundRadiusRatio, groupPad );
  1706. propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Hole Size X" ),
  1707. &PAD::SetDrillSizeX, &PAD::GetDrillSizeX,
  1708. PROPERTY_DISPLAY::PT_SIZE ), groupPad )
  1709. .SetWriteableFunc( padCanHaveHole )
  1710. .SetValidator( PROPERTY_VALIDATORS::PositiveIntValidator );
  1711. propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Hole Size Y" ),
  1712. &PAD::SetDrillSizeY, &PAD::GetDrillSizeY,
  1713. PROPERTY_DISPLAY::PT_SIZE ), groupPad )
  1714. .SetWriteableFunc( padCanHaveHole )
  1715. .SetValidator( PROPERTY_VALIDATORS::PositiveIntValidator );
  1716. propMgr.AddProperty( new PROPERTY_ENUM<PAD, PAD_PROP>( _HKI( "Fabrication Property" ),
  1717. &PAD::SetProperty, &PAD::GetProperty ), groupPad );
  1718. auto padToDie = new PROPERTY<PAD, int>( _HKI( "Pad To Die Length" ),
  1719. &PAD::SetPadToDieLength, &PAD::GetPadToDieLength,
  1720. PROPERTY_DISPLAY::PT_SIZE );
  1721. padToDie->SetAvailableFunc( isCopperPad );
  1722. propMgr.AddProperty( padToDie, groupPad );
  1723. const wxString groupOverrides = _HKI( "Overrides" );
  1724. propMgr.AddProperty( new PROPERTY<PAD, std::optional<int>>(
  1725. _HKI( "Clearance Override" ),
  1726. &PAD::SetLocalClearance, &PAD::GetLocalClearance,
  1727. PROPERTY_DISPLAY::PT_SIZE ), groupOverrides );
  1728. propMgr.AddProperty( new PROPERTY<PAD, std::optional<int>>(
  1729. _HKI( "Soldermask Margin Override" ),
  1730. &PAD::SetLocalSolderMaskMargin, &PAD::GetLocalSolderMaskMargin,
  1731. PROPERTY_DISPLAY::PT_SIZE ), groupOverrides );
  1732. propMgr.AddProperty( new PROPERTY<PAD, std::optional<int>>(
  1733. _HKI( "Solderpaste Margin Override" ),
  1734. &PAD::SetLocalSolderPasteMargin, &PAD::GetLocalSolderPasteMargin,
  1735. PROPERTY_DISPLAY::PT_SIZE ), groupOverrides );
  1736. propMgr.AddProperty( new PROPERTY<PAD, std::optional<double>>(
  1737. _HKI( "Solderpaste Margin Ratio Override" ),
  1738. &PAD::SetLocalSolderPasteMarginRatio, &PAD::GetLocalSolderPasteMarginRatio,
  1739. PROPERTY_DISPLAY::PT_RATIO ),
  1740. groupOverrides );
  1741. propMgr.AddProperty( new PROPERTY_ENUM<PAD, ZONE_CONNECTION>(
  1742. _HKI( "Zone Connection Style" ),
  1743. &PAD::SetLocalZoneConnection, &PAD::GetLocalZoneConnection ), groupOverrides );
  1744. constexpr int minZoneWidth = pcbIUScale.mmToIU( ZONE_THICKNESS_MIN_VALUE_MM );
  1745. propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Thermal Relief Spoke Width" ),
  1746. &PAD::SetThermalSpokeWidth, &PAD::GetThermalSpokeWidth,
  1747. PROPERTY_DISPLAY::PT_SIZE ), groupOverrides )
  1748. .SetValidator( PROPERTY_VALIDATORS::RangeIntValidator<minZoneWidth, INT_MAX> );
  1749. propMgr.AddProperty( new PROPERTY<PAD, double>( _HKI( "Thermal Relief Spoke Angle" ),
  1750. &PAD::SetThermalSpokeAngleDegrees, &PAD::GetThermalSpokeAngleDegrees,
  1751. PROPERTY_DISPLAY::PT_DEGREE ), groupOverrides );
  1752. propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Thermal Relief Gap" ),
  1753. &PAD::SetThermalGap, &PAD::GetThermalGap,
  1754. PROPERTY_DISPLAY::PT_SIZE ), groupOverrides )
  1755. .SetValidator( PROPERTY_VALIDATORS::PositiveIntValidator );
  1756. // TODO delta, drill shape offset, layer set
  1757. }
  1758. } _PAD_DESC;
  1759. ENUM_TO_WXANY( PAD_ATTRIB );
  1760. ENUM_TO_WXANY( PAD_SHAPE );
  1761. ENUM_TO_WXANY( PAD_PROP );