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.

1382 lines
43 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2024 Jon Evans <jon@craftyjon.com>
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include <convert_basic_shapes_to_polygon.h> // RECT_CHAMFER_POSITIONS
  21. #include "padstack.h"
  22. #include <api/api_enums.h>
  23. #include <api/api_utils.h>
  24. #include <api/api_pcb_utils.h>
  25. #include <api/board/board_types.pb.h>
  26. #include <layer_range.h>
  27. #include <macros.h>
  28. #include <magic_enum.hpp>
  29. #include <pad.h>
  30. #include <pcb_shape.h>
  31. PADSTACK::PADSTACK( BOARD_ITEM* aParent ) :
  32. m_parent( aParent ),
  33. m_mode( MODE::NORMAL ),
  34. m_orientation( ANGLE_0 ),
  35. m_unconnectedLayerMode( UNCONNECTED_LAYER_MODE::KEEP_ALL ),
  36. m_customShapeInZoneMode( CUSTOM_SHAPE_ZONE_MODE::OUTLINE )
  37. {
  38. m_copperProps[PADSTACK::ALL_LAYERS].shape = SHAPE_PROPS();
  39. m_copperProps[PADSTACK::ALL_LAYERS].zone_connection = ZONE_CONNECTION::INHERITED;
  40. m_copperProps[PADSTACK::ALL_LAYERS].thermal_spoke_width = std::nullopt;
  41. m_copperProps[PADSTACK::ALL_LAYERS].thermal_spoke_angle = ANGLE_45;
  42. m_copperProps[PADSTACK::ALL_LAYERS].thermal_gap = std::nullopt;
  43. m_drill.shape = PAD_DRILL_SHAPE::CIRCLE;
  44. m_drill.start = F_Cu;
  45. m_drill.end = B_Cu;
  46. m_secondaryDrill.shape = PAD_DRILL_SHAPE::UNDEFINED;
  47. m_secondaryDrill.start = UNDEFINED_LAYER;
  48. m_secondaryDrill.end = UNDEFINED_LAYER;
  49. }
  50. PADSTACK::PADSTACK( const PADSTACK& aOther )
  51. {
  52. m_parent = aOther.m_parent;
  53. *this = aOther;
  54. ForEachUniqueLayer(
  55. [&]( PCB_LAYER_ID aLayer )
  56. {
  57. for( std::shared_ptr<PCB_SHAPE>& shape : CopperLayer( aLayer ).custom_shapes )
  58. shape->SetParent( m_parent );
  59. } );
  60. }
  61. PADSTACK& PADSTACK::operator=( const PADSTACK &aOther )
  62. {
  63. // NOTE: m_parent is not copied from operator=, because this operator is commonly used to
  64. // update the padstack properties, and such an update must not change the parent PAD to point to
  65. // the parent of some different padstack.
  66. m_mode = aOther.m_mode;
  67. m_layerSet = aOther.m_layerSet;
  68. m_customName = aOther.m_customName;
  69. m_orientation = aOther.m_orientation;
  70. m_copperProps = aOther.m_copperProps;
  71. m_frontMaskProps = aOther.m_frontMaskProps;
  72. m_backMaskProps = aOther.m_backMaskProps;
  73. m_unconnectedLayerMode = aOther.m_unconnectedLayerMode;
  74. m_customShapeInZoneMode = aOther.m_customShapeInZoneMode;
  75. m_drill = aOther.m_drill;
  76. m_secondaryDrill = aOther.m_secondaryDrill;
  77. // Data consistency enforcement logic that used to live in the pad properties dialog
  78. // TODO(JE) Should these move to individual property setters, so that they are always
  79. // enforced even through the properties panel and API?
  80. ForEachUniqueLayer(
  81. [&]( PCB_LAYER_ID aLayer )
  82. {
  83. PAD_SHAPE shape = Shape( aLayer );
  84. // Make sure leftover primitives don't stick around
  85. ClearPrimitives( aLayer );
  86. // For custom pad shape, duplicate primitives of the pad to copy
  87. if( shape == PAD_SHAPE::CUSTOM )
  88. ReplacePrimitives( aOther.Primitives( aLayer ), aLayer );
  89. // rounded rect pads with radius ratio = 0 are in fact rect pads.
  90. // So set the right shape (and perhaps issues with a radius = 0)
  91. if( shape == PAD_SHAPE::ROUNDRECT && RoundRectRadiusRatio( aLayer ) == 0.0 )
  92. SetShape( PAD_SHAPE::RECTANGLE, aLayer );
  93. } );
  94. return *this;
  95. }
  96. bool PADSTACK::operator==( const PADSTACK& aOther ) const
  97. {
  98. if( m_mode != aOther.m_mode )
  99. return false;
  100. if( m_layerSet != aOther.m_layerSet )
  101. return false;
  102. if( m_customName != aOther.m_customName )
  103. return false;
  104. if( m_orientation != aOther.m_orientation )
  105. return false;
  106. if( m_frontMaskProps != aOther.m_frontMaskProps )
  107. return false;
  108. if( m_backMaskProps != aOther.m_backMaskProps )
  109. return false;
  110. if( m_unconnectedLayerMode != aOther.m_unconnectedLayerMode )
  111. return false;
  112. if( m_customShapeInZoneMode != aOther.m_customShapeInZoneMode )
  113. return false;
  114. if( m_drill != aOther.m_drill )
  115. return false;
  116. if( m_secondaryDrill != aOther.m_secondaryDrill )
  117. return false;
  118. bool copperMatches = true;
  119. ForEachUniqueLayer(
  120. [&]( PCB_LAYER_ID aLayer )
  121. {
  122. if( CopperLayer( aLayer ) != aOther.CopperLayer( aLayer ) )
  123. copperMatches = false;
  124. } );
  125. return copperMatches;
  126. }
  127. bool PADSTACK::unpackCopperLayer( const kiapi::board::types::PadStackLayer& aProto )
  128. {
  129. using namespace kiapi::board::types;
  130. PCB_LAYER_ID layer = FromProtoEnum<PCB_LAYER_ID, BoardLayer>( aProto.layer() );
  131. if( m_mode == MODE::NORMAL && layer != ALL_LAYERS )
  132. return false;
  133. if( m_mode == MODE::FRONT_INNER_BACK && layer != F_Cu && layer != INNER_LAYERS && layer != B_Cu )
  134. return false;
  135. SetSize( kiapi::common::UnpackVector2( aProto.size() ), layer );
  136. SetShape( FromProtoEnum<PAD_SHAPE>( aProto.shape() ), layer );
  137. Offset( layer ) = kiapi::common::UnpackVector2( aProto.offset() );
  138. SetAnchorShape( FromProtoEnum<PAD_SHAPE>( aProto.custom_anchor_shape() ), layer );
  139. SHAPE_PROPS& props = CopperLayer( layer ).shape;
  140. props.chamfered_rect_ratio = aProto.chamfer_ratio();
  141. props.round_rect_radius_ratio = aProto.corner_rounding_ratio();
  142. if( Shape( layer ) == PAD_SHAPE::TRAPEZOID && aProto.has_trapezoid_delta() )
  143. TrapezoidDeltaSize( layer ) = kiapi::common::UnpackVector2( aProto.trapezoid_delta() );
  144. if( aProto.chamfered_corners().top_left() )
  145. props.chamfered_rect_positions |= RECT_CHAMFER_TOP_LEFT;
  146. if( aProto.chamfered_corners().top_right() )
  147. props.chamfered_rect_positions |= RECT_CHAMFER_TOP_RIGHT;
  148. if( aProto.chamfered_corners().bottom_left() )
  149. props.chamfered_rect_positions |= RECT_CHAMFER_BOTTOM_LEFT;
  150. if( aProto.chamfered_corners().bottom_right() )
  151. props.chamfered_rect_positions |= RECT_CHAMFER_BOTTOM_RIGHT;
  152. ClearPrimitives( layer );
  153. google::protobuf::Any a;
  154. for( const BoardGraphicShape& shapeProto : aProto.custom_shapes() )
  155. {
  156. a.PackFrom( shapeProto );
  157. std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_parent );
  158. if( shape->Deserialize( a ) )
  159. AddPrimitive( shape.release(), layer );
  160. }
  161. return true;
  162. }
  163. bool PADSTACK::Deserialize( const google::protobuf::Any& aContainer )
  164. {
  165. using namespace kiapi::board::types;
  166. PadStack padstack;
  167. auto unpackOptional = []<typename ProtoEnum>( const ProtoEnum& aProto,
  168. std::optional<bool>& aDest, ProtoEnum aTrueValue,
  169. ProtoEnum aFalseValue )
  170. {
  171. if( aProto == aTrueValue )
  172. aDest = true;
  173. else if( aProto == aFalseValue )
  174. aDest = false;
  175. else
  176. aDest = std::nullopt;
  177. };
  178. if( !aContainer.UnpackTo( &padstack ) )
  179. return false;
  180. m_mode = FromProtoEnum<MODE>( padstack.type() );
  181. SetLayerSet( kiapi::board::UnpackLayerSet( padstack.layers() ) );
  182. m_orientation = EDA_ANGLE( padstack.angle().value_degrees(), DEGREES_T );
  183. Drill().size = kiapi::common::UnpackVector2( padstack.drill().diameter() );
  184. Drill().start = FromProtoEnum<PCB_LAYER_ID>( padstack.drill().start_layer() );
  185. Drill().end = FromProtoEnum<PCB_LAYER_ID>( padstack.drill().end_layer() );
  186. unpackOptional( padstack.drill().capped(), Drill().is_capped, VDCM_CAPPED, VDCM_UNCAPPED );
  187. unpackOptional( padstack.drill().filled(), Drill().is_filled, VDFM_FILLED, VDFM_UNFILLED );
  188. for( const PadStackLayer& layer : padstack.copper_layers() )
  189. {
  190. if( !unpackCopperLayer( layer ) )
  191. return false;
  192. }
  193. CopperLayer( ALL_LAYERS ).thermal_gap = std::nullopt;
  194. CopperLayer( ALL_LAYERS ).thermal_spoke_width = std::nullopt;
  195. if( padstack.has_zone_settings() )
  196. {
  197. CopperLayer( ALL_LAYERS ).zone_connection =
  198. FromProtoEnum<ZONE_CONNECTION>( padstack.zone_settings().zone_connection() );
  199. if( padstack.zone_settings().has_thermal_spokes() )
  200. {
  201. const ThermalSpokeSettings& thermals = padstack.zone_settings().thermal_spokes();
  202. if( thermals.has_gap() )
  203. CopperLayer( ALL_LAYERS ).thermal_gap = thermals.gap().value_nm();
  204. if( thermals.has_width() )
  205. CopperLayer( ALL_LAYERS ).thermal_spoke_width = thermals.width().value_nm();
  206. SetThermalSpokeAngle( thermals.angle().value_degrees(), F_Cu );
  207. }
  208. }
  209. else
  210. {
  211. CopperLayer( ALL_LAYERS ).zone_connection = ZONE_CONNECTION::INHERITED;
  212. CopperLayer( ALL_LAYERS ).thermal_spoke_angle = DefaultThermalSpokeAngleForShape( F_Cu );
  213. }
  214. SetUnconnectedLayerMode(
  215. FromProtoEnum<UNCONNECTED_LAYER_MODE>( padstack.unconnected_layer_removal() ) );
  216. unpackOptional( padstack.front_outer_layers().solder_mask_mode(),
  217. FrontOuterLayers().has_solder_mask, SMM_MASKED, SMM_UNMASKED );
  218. unpackOptional( padstack.back_outer_layers().solder_mask_mode(),
  219. BackOuterLayers().has_solder_mask, SMM_MASKED, SMM_UNMASKED );
  220. unpackOptional( padstack.front_outer_layers().covering_mode(), FrontOuterLayers().has_covering,
  221. VCM_COVERED, VCM_UNCOVERED );
  222. unpackOptional( padstack.back_outer_layers().covering_mode(), BackOuterLayers().has_covering,
  223. VCM_COVERED, VCM_UNCOVERED );
  224. unpackOptional( padstack.front_outer_layers().plugging_mode(), FrontOuterLayers().has_plugging,
  225. VPM_PLUGGED, VPM_UNPLUGGED );
  226. unpackOptional( padstack.back_outer_layers().plugging_mode(), BackOuterLayers().has_plugging,
  227. VPM_PLUGGED, VPM_UNPLUGGED );
  228. unpackOptional( padstack.front_outer_layers().solder_paste_mode(),
  229. FrontOuterLayers().has_solder_paste, SPM_PASTE, SPM_NO_PASTE );
  230. unpackOptional( padstack.back_outer_layers().solder_paste_mode(),
  231. BackOuterLayers().has_solder_paste, SPM_PASTE, SPM_NO_PASTE );
  232. if( padstack.front_outer_layers().has_solder_mask_settings()
  233. && padstack.front_outer_layers().solder_mask_settings().has_solder_mask_margin() )
  234. {
  235. FrontOuterLayers().solder_mask_margin =
  236. padstack.front_outer_layers().solder_mask_settings().solder_mask_margin().value_nm();
  237. }
  238. else
  239. {
  240. FrontOuterLayers().solder_mask_margin = std::nullopt;
  241. }
  242. if( padstack.back_outer_layers().has_solder_mask_settings()
  243. && padstack.back_outer_layers().solder_mask_settings().has_solder_mask_margin() )
  244. {
  245. BackOuterLayers().solder_mask_margin =
  246. padstack.back_outer_layers().solder_mask_settings().solder_mask_margin().value_nm();
  247. }
  248. else
  249. {
  250. BackOuterLayers().solder_mask_margin = std::nullopt;
  251. }
  252. if( padstack.front_outer_layers().has_solder_paste_settings()
  253. && padstack.front_outer_layers().solder_paste_settings().has_solder_paste_margin() )
  254. {
  255. FrontOuterLayers().solder_paste_margin =
  256. padstack.front_outer_layers().solder_paste_settings().solder_paste_margin().value_nm();
  257. }
  258. else
  259. {
  260. FrontOuterLayers().solder_paste_margin = std::nullopt;
  261. }
  262. if( padstack.back_outer_layers().has_solder_paste_settings()
  263. && padstack.back_outer_layers().solder_paste_settings().has_solder_paste_margin() )
  264. {
  265. BackOuterLayers().solder_paste_margin =
  266. padstack.back_outer_layers().solder_paste_settings().solder_paste_margin().value_nm();
  267. }
  268. else
  269. {
  270. BackOuterLayers().solder_paste_margin = std::nullopt;
  271. }
  272. if( padstack.front_outer_layers().has_solder_paste_settings()
  273. && padstack.front_outer_layers().solder_paste_settings().has_solder_paste_margin_ratio() )
  274. {
  275. FrontOuterLayers().solder_paste_margin_ratio =
  276. padstack.front_outer_layers().solder_paste_settings().solder_paste_margin_ratio().value();
  277. }
  278. else
  279. {
  280. FrontOuterLayers().solder_paste_margin_ratio = std::nullopt;
  281. }
  282. if( padstack.back_outer_layers().has_solder_paste_settings()
  283. && padstack.back_outer_layers().solder_paste_settings().has_solder_paste_margin_ratio() )
  284. {
  285. BackOuterLayers().solder_paste_margin_ratio =
  286. padstack.back_outer_layers().solder_paste_settings().solder_paste_margin_ratio().value();
  287. }
  288. else
  289. {
  290. BackOuterLayers().solder_paste_margin_ratio = std::nullopt;
  291. }
  292. return true;
  293. }
  294. void PADSTACK::packCopperLayer( PCB_LAYER_ID aLayer, kiapi::board::types::PadStack& aProto ) const
  295. {
  296. using namespace kiapi::board::types;
  297. PadStackLayer* stackLayer = aProto.add_copper_layers();
  298. stackLayer->set_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( aLayer ) );
  299. kiapi::common::PackVector2( *stackLayer->mutable_size(), Size( aLayer ) );
  300. kiapi::common::PackVector2( *stackLayer->mutable_offset(), Offset( aLayer ) );
  301. stackLayer->set_shape( ToProtoEnum<PAD_SHAPE, PadStackShape>( Shape( aLayer ) ) );
  302. stackLayer->set_custom_anchor_shape(
  303. ToProtoEnum<PAD_SHAPE, PadStackShape>( AnchorShape( aLayer ) ) );
  304. stackLayer->set_chamfer_ratio( CopperLayer( aLayer ).shape.chamfered_rect_ratio );
  305. stackLayer->set_corner_rounding_ratio( CopperLayer( aLayer ).shape.round_rect_radius_ratio );
  306. if( Shape( aLayer ) == PAD_SHAPE::TRAPEZOID )
  307. {
  308. kiapi::common::PackVector2( *stackLayer->mutable_trapezoid_delta(),
  309. TrapezoidDeltaSize( aLayer ) );
  310. }
  311. google::protobuf::Any a;
  312. for( const std::shared_ptr<PCB_SHAPE>& shape : Primitives( aLayer ) )
  313. {
  314. shape->Serialize( a );
  315. BoardGraphicShape* s = stackLayer->add_custom_shapes();
  316. a.UnpackTo( s );
  317. }
  318. const int& corners = CopperLayer( aLayer ).shape.chamfered_rect_positions;
  319. stackLayer->mutable_chamfered_corners()->set_top_left( corners & RECT_CHAMFER_TOP_LEFT );
  320. stackLayer->mutable_chamfered_corners()->set_top_right( corners & RECT_CHAMFER_TOP_RIGHT );
  321. stackLayer->mutable_chamfered_corners()->set_bottom_left( corners & RECT_CHAMFER_BOTTOM_LEFT );
  322. stackLayer->mutable_chamfered_corners()->set_bottom_right( corners & RECT_CHAMFER_BOTTOM_RIGHT );
  323. }
  324. void PADSTACK::Serialize( google::protobuf::Any& aContainer ) const
  325. {
  326. using namespace kiapi::board::types;
  327. PadStack padstack;
  328. auto packOptional = []<typename ProtoEnum>( const std::optional<bool>& aVal, ProtoEnum aTrueVal,
  329. ProtoEnum aFalseVal,
  330. ProtoEnum aNullVal ) -> ProtoEnum
  331. {
  332. if( aVal.has_value() )
  333. return *aVal ? aTrueVal : aFalseVal;
  334. return aNullVal;
  335. };
  336. padstack.set_type( ToProtoEnum<MODE, PadStackType>( m_mode ) );
  337. kiapi::board::PackLayerSet( *padstack.mutable_layers(), m_layerSet );
  338. padstack.mutable_angle()->set_value_degrees( m_orientation.AsDegrees() );
  339. DrillProperties* drill = padstack.mutable_drill();
  340. drill->set_start_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( StartLayer() ) );
  341. drill->set_end_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( EndLayer() ) );
  342. drill->set_filled(
  343. packOptional( Drill().is_filled, VDFM_FILLED, VDFM_UNFILLED, VDFM_FROM_DESIGN_RULES ) );
  344. drill->set_capped(
  345. packOptional( Drill().is_capped, VDCM_CAPPED, VDCM_UNCAPPED, VDCM_FROM_DESIGN_RULES ) );
  346. kiapi::common::PackVector2( *drill->mutable_diameter(), Drill().size );
  347. ForEachUniqueLayer( [&]( PCB_LAYER_ID aLayer )
  348. {
  349. packCopperLayer( aLayer, padstack );
  350. } );
  351. ZoneConnectionSettings* zoneSettings = padstack.mutable_zone_settings();
  352. ThermalSpokeSettings* thermalSettings = zoneSettings->mutable_thermal_spokes();
  353. if( CopperLayer( ALL_LAYERS ).zone_connection.has_value() )
  354. {
  355. zoneSettings->set_zone_connection( ToProtoEnum<ZONE_CONNECTION, ZoneConnectionStyle>(
  356. *CopperLayer( ALL_LAYERS ).zone_connection ) );
  357. }
  358. if( std::optional<int> width = CopperLayer( ALL_LAYERS ).thermal_spoke_width )
  359. thermalSettings->mutable_width()->set_value_nm( *width );
  360. if( std::optional<int> gap = CopperLayer( ALL_LAYERS ).thermal_gap )
  361. thermalSettings->mutable_gap()->set_value_nm( *gap );
  362. thermalSettings->mutable_angle()->set_value_degrees( ThermalSpokeAngle( F_Cu ).AsDegrees() );
  363. padstack.set_unconnected_layer_removal(
  364. ToProtoEnum<UNCONNECTED_LAYER_MODE, UnconnectedLayerRemoval>( m_unconnectedLayerMode ) );
  365. PadStackOuterLayer* frontOuter = padstack.mutable_front_outer_layers();
  366. PadStackOuterLayer* backOuter = padstack.mutable_back_outer_layers();
  367. frontOuter->set_solder_mask_mode( packOptional( FrontOuterLayers().has_solder_mask, SMM_MASKED,
  368. SMM_UNMASKED, SMM_FROM_DESIGN_RULES ) );
  369. backOuter->set_solder_mask_mode( packOptional( BackOuterLayers().has_solder_mask, SMM_MASKED,
  370. SMM_UNMASKED, SMM_FROM_DESIGN_RULES ) );
  371. frontOuter->set_plugging_mode( packOptional( FrontOuterLayers().has_plugging, VPM_PLUGGED,
  372. VPM_UNPLUGGED, VPM_FROM_DESIGN_RULES ) );
  373. backOuter->set_plugging_mode( packOptional( BackOuterLayers().has_plugging, VPM_PLUGGED,
  374. VPM_UNPLUGGED, VPM_FROM_DESIGN_RULES ) );
  375. frontOuter->set_covering_mode( packOptional( FrontOuterLayers().has_covering, VCM_COVERED,
  376. VCM_UNCOVERED, VCM_FROM_DESIGN_RULES ) );
  377. backOuter->set_covering_mode( packOptional( BackOuterLayers().has_covering, VCM_COVERED,
  378. VCM_UNCOVERED, VCM_FROM_DESIGN_RULES ) );
  379. frontOuter->set_solder_paste_mode( packOptional( FrontOuterLayers().has_solder_paste, SPM_PASTE,
  380. SPM_NO_PASTE, SPM_FROM_DESIGN_RULES ) );
  381. backOuter->set_solder_paste_mode( packOptional( BackOuterLayers().has_solder_paste, SPM_PASTE,
  382. SPM_NO_PASTE, SPM_FROM_DESIGN_RULES ) );
  383. if( FrontOuterLayers().solder_mask_margin.has_value() )
  384. {
  385. frontOuter->mutable_solder_mask_settings()->mutable_solder_mask_margin()->set_value_nm(
  386. *FrontOuterLayers().solder_mask_margin );
  387. }
  388. if( BackOuterLayers().solder_mask_margin.has_value() )
  389. {
  390. backOuter->mutable_solder_mask_settings()->mutable_solder_mask_margin()->set_value_nm(
  391. *BackOuterLayers().solder_mask_margin );
  392. }
  393. if( FrontOuterLayers().solder_paste_margin.has_value() )
  394. {
  395. frontOuter->mutable_solder_paste_settings()->mutable_solder_paste_margin()->set_value_nm(
  396. *FrontOuterLayers().solder_paste_margin );
  397. }
  398. if( BackOuterLayers().solder_paste_margin.has_value() )
  399. {
  400. backOuter->mutable_solder_paste_settings()->mutable_solder_paste_margin()->set_value_nm(
  401. *BackOuterLayers().solder_paste_margin );
  402. }
  403. if( FrontOuterLayers().solder_paste_margin_ratio.has_value() )
  404. {
  405. frontOuter->mutable_solder_paste_settings()->mutable_solder_paste_margin_ratio()->set_value(
  406. *FrontOuterLayers().solder_paste_margin_ratio );
  407. }
  408. if( BackOuterLayers().solder_paste_margin_ratio.has_value() )
  409. {
  410. backOuter->mutable_solder_paste_settings()->mutable_solder_paste_margin_ratio()->set_value(
  411. *BackOuterLayers().solder_paste_margin_ratio );
  412. }
  413. aContainer.PackFrom( padstack );
  414. }
  415. int PADSTACK::Compare( const PADSTACK* aPadstackRef, const PADSTACK* aPadstackCmp )
  416. {
  417. int diff;
  418. auto compareCopperProps =
  419. [&]( PCB_LAYER_ID aLayer )
  420. {
  421. if( ( diff = static_cast<int>( aPadstackRef->Shape( aLayer ) ) -
  422. static_cast<int>( aPadstackCmp->Shape( aLayer ) ) ) != 0 )
  423. return diff;
  424. if( ( diff = aPadstackRef->Size( aLayer ).x - aPadstackCmp->Size( aLayer ).x ) != 0 )
  425. return diff;
  426. if( ( diff = aPadstackRef->Size( aLayer ).y - aPadstackCmp->Size( aLayer ).y ) != 0 )
  427. return diff;
  428. if( ( diff = aPadstackRef->Offset( aLayer ).x
  429. - aPadstackCmp->Offset( aLayer ).x ) != 0 )
  430. return diff;
  431. if( ( diff = aPadstackRef->Offset( aLayer ).y
  432. - aPadstackCmp->Offset( aLayer ).y ) != 0 )
  433. return diff;
  434. if( ( diff = aPadstackRef->TrapezoidDeltaSize( aLayer ).x
  435. - aPadstackCmp->TrapezoidDeltaSize( aLayer ).x )
  436. != 0 )
  437. {
  438. return diff;
  439. }
  440. if( ( diff = aPadstackRef->TrapezoidDeltaSize( aLayer ).y
  441. - aPadstackCmp->TrapezoidDeltaSize( aLayer ).y )
  442. != 0 )
  443. {
  444. return diff;
  445. }
  446. if( ( diff = aPadstackRef->RoundRectRadiusRatio( aLayer )
  447. - aPadstackCmp->RoundRectRadiusRatio( aLayer ) )
  448. != 0 )
  449. {
  450. return diff;
  451. }
  452. if( ( diff = aPadstackRef->ChamferPositions( aLayer )
  453. - aPadstackCmp->ChamferPositions( aLayer ) ) != 0 )
  454. return diff;
  455. if( ( diff = aPadstackRef->ChamferRatio( aLayer )
  456. - aPadstackCmp->ChamferRatio( aLayer ) ) != 0 )
  457. return diff;
  458. if( ( diff = static_cast<int>( aPadstackRef->Primitives( aLayer ).size() ) -
  459. static_cast<int>( aPadstackCmp->Primitives( aLayer ).size() ) ) != 0 )
  460. return diff;
  461. // @todo: Compare custom pad primitives for pads that have the same number of primitives
  462. // here. Currently there is no compare function for PCB_SHAPE objects.
  463. return 0;
  464. };
  465. aPadstackRef->ForEachUniqueLayer( compareCopperProps );
  466. if( ( diff = static_cast<int>( aPadstackRef->DrillShape() ) -
  467. static_cast<int>( aPadstackCmp->DrillShape() ) ) != 0 )
  468. return diff;
  469. if( ( diff = aPadstackRef->Drill().size.x - aPadstackCmp->Drill().size.x ) != 0 )
  470. return diff;
  471. if( ( diff = aPadstackRef->Drill().size.y - aPadstackCmp->Drill().size.y ) != 0 )
  472. return diff;
  473. return aPadstackRef->LayerSet().compare( aPadstackCmp->LayerSet() );
  474. }
  475. double PADSTACK::Similarity( const PADSTACK& aOther ) const
  476. {
  477. double similarity = 1.0;
  478. ForEachUniqueLayer(
  479. [&]( PCB_LAYER_ID aLayer )
  480. {
  481. if( Shape( aLayer ) != aOther.Shape( aLayer ) )
  482. similarity *= 0.9;
  483. if( Size( aLayer ) != aOther.Size( aLayer ) )
  484. similarity *= 0.9;
  485. if( Offset( aLayer ) != aOther.Offset( aLayer ) )
  486. similarity *= 0.9;
  487. if( RoundRectRadiusRatio( aLayer ) != aOther.RoundRectRadiusRatio( aLayer ) )
  488. similarity *= 0.9;
  489. if( ChamferRatio( aLayer ) != aOther.ChamferRatio( aLayer ) )
  490. similarity *= 0.9;
  491. if( ChamferPositions( aLayer ) != aOther.ChamferPositions( aLayer ) )
  492. similarity *= 0.9;
  493. if( Primitives( aLayer ).size() != aOther.Primitives( aLayer ).size() )
  494. similarity *= 0.9;
  495. if( AnchorShape( aLayer ) != aOther.AnchorShape( aLayer ) )
  496. similarity *= 0.9;
  497. } );
  498. if( Drill() != aOther.Drill() )
  499. similarity *= 0.9;
  500. if( DrillShape() != aOther.DrillShape() )
  501. similarity *= 0.9;
  502. if( GetOrientation() != aOther.GetOrientation() )
  503. similarity *= 0.9;
  504. if( ZoneConnection() != aOther.ZoneConnection() )
  505. similarity *= 0.9;
  506. if( ThermalSpokeWidth() != aOther.ThermalSpokeWidth() )
  507. similarity *= 0.9;
  508. if( ThermalSpokeAngle() != aOther.ThermalSpokeAngle() )
  509. similarity *= 0.9;
  510. if( ThermalGap() != aOther.ThermalGap() )
  511. similarity *= 0.9;
  512. if( CustomShapeInZoneMode() != aOther.CustomShapeInZoneMode() )
  513. similarity *= 0.9;
  514. if( Clearance() != aOther.Clearance() )
  515. similarity *= 0.9;
  516. if( SolderMaskMargin() != aOther.SolderMaskMargin() )
  517. similarity *= 0.9;
  518. if( SolderPasteMargin() != aOther.SolderPasteMargin() )
  519. similarity *= 0.9;
  520. if( SolderPasteMarginRatio() != aOther.SolderPasteMarginRatio() )
  521. similarity *= 0.9;
  522. if( ThermalGap() != aOther.ThermalGap() )
  523. similarity *= 0.9;
  524. if( ThermalSpokeWidth() != aOther.ThermalSpokeWidth() )
  525. similarity *= 0.9;
  526. if( ThermalSpokeAngle() != aOther.ThermalSpokeAngle() )
  527. similarity *= 0.9;
  528. if( LayerSet() != aOther.LayerSet() )
  529. similarity *= 0.9;
  530. return similarity;
  531. }
  532. wxString PADSTACK::Name() const
  533. {
  534. // TODO
  535. return wxEmptyString;
  536. }
  537. PCB_LAYER_ID PADSTACK::StartLayer() const
  538. {
  539. return m_drill.start;
  540. }
  541. PCB_LAYER_ID PADSTACK::EndLayer() const
  542. {
  543. return m_drill.end;
  544. }
  545. void PADSTACK::FlipLayers( int aCopperLayerCount )
  546. {
  547. switch( m_mode )
  548. {
  549. case MODE::NORMAL:
  550. // Same shape on all layers; nothing to do
  551. break;
  552. case MODE::CUSTOM:
  553. {
  554. if( aCopperLayerCount > 2 )
  555. {
  556. int innerCount = ( aCopperLayerCount - 2 );
  557. int halfInnerLayerCount = innerCount / 2;
  558. PCB_LAYER_ID lastInner
  559. = static_cast<PCB_LAYER_ID>( In1_Cu + ( innerCount - 1 ) * 2 );
  560. PCB_LAYER_ID midpointInner
  561. = static_cast<PCB_LAYER_ID>( In1_Cu + ( halfInnerLayerCount - 1 ) * 2 );
  562. for( PCB_LAYER_ID layer : LAYER_RANGE( In1_Cu, midpointInner, MAX_CU_LAYERS ) )
  563. {
  564. auto conjugate =
  565. magic_enum::enum_cast<PCB_LAYER_ID>( lastInner - ( layer - In1_Cu ) );
  566. wxCHECK2_MSG( conjugate.has_value() && m_copperProps.contains( conjugate.value() ),
  567. continue, "Invalid inner layer conjugate!" );
  568. std::swap( m_copperProps[layer], m_copperProps[conjugate.value()] );
  569. }
  570. }
  571. KI_FALLTHROUGH;
  572. }
  573. case MODE::FRONT_INNER_BACK:
  574. std::swap( m_copperProps[F_Cu], m_copperProps[B_Cu] );
  575. std::swap( m_frontMaskProps, m_backMaskProps );
  576. break;
  577. }
  578. }
  579. PADSTACK::SHAPE_PROPS::SHAPE_PROPS() :
  580. shape( PAD_SHAPE::CIRCLE ),
  581. anchor_shape( PAD_SHAPE::CIRCLE ),
  582. round_rect_corner_radius( 0 ),
  583. round_rect_radius_ratio( 0.25 ),
  584. chamfered_rect_ratio( 0.2 ),
  585. chamfered_rect_positions( RECT_NO_CHAMFER )
  586. {
  587. }
  588. bool PADSTACK::SHAPE_PROPS::operator==( const SHAPE_PROPS& aOther ) const
  589. {
  590. return shape == aOther.shape && offset == aOther.offset
  591. && round_rect_corner_radius == aOther.round_rect_corner_radius
  592. && round_rect_radius_ratio == aOther.round_rect_radius_ratio
  593. && chamfered_rect_ratio == aOther.chamfered_rect_ratio
  594. && chamfered_rect_positions == aOther.chamfered_rect_positions;
  595. }
  596. bool PADSTACK::COPPER_LAYER_PROPS::operator==( const COPPER_LAYER_PROPS& aOther ) const
  597. {
  598. if( shape != aOther.shape )
  599. return false;
  600. if( zone_connection != aOther.zone_connection )
  601. return false;
  602. if( thermal_spoke_width != aOther.thermal_spoke_width )
  603. return false;
  604. if( thermal_spoke_angle != aOther.thermal_spoke_angle )
  605. return false;
  606. if( thermal_gap != aOther.thermal_gap )
  607. return false;
  608. if( custom_shapes.size() != aOther.custom_shapes.size() )
  609. return false;
  610. if( !std::equal( custom_shapes.begin(), custom_shapes.end(),
  611. aOther.custom_shapes.begin(), aOther.custom_shapes.end(),
  612. []( const std::shared_ptr<PCB_SHAPE>& aFirst,
  613. const std::shared_ptr<PCB_SHAPE>& aSecond )
  614. {
  615. return *aFirst == *aSecond;
  616. } ) )
  617. {
  618. return false;
  619. }
  620. return true;
  621. }
  622. bool PADSTACK::MASK_LAYER_PROPS::operator==( const MASK_LAYER_PROPS& aOther ) const
  623. {
  624. return solder_mask_margin == aOther.solder_mask_margin
  625. && solder_paste_margin == aOther.solder_paste_margin
  626. && solder_paste_margin_ratio == aOther.solder_paste_margin_ratio
  627. && has_solder_mask == aOther.has_solder_mask
  628. && has_solder_paste == aOther.has_solder_paste;
  629. }
  630. bool PADSTACK::DRILL_PROPS::operator==( const DRILL_PROPS& aOther ) const
  631. {
  632. return size == aOther.size && shape == aOther.shape
  633. && start == aOther.start && end == aOther.end;
  634. }
  635. void PADSTACK::SetMode( MODE aMode )
  636. {
  637. if( m_mode == aMode )
  638. return;
  639. switch( aMode )
  640. {
  641. case MODE::NORMAL:
  642. std::erase_if( m_copperProps,
  643. []( const auto& aEntry )
  644. {
  645. const auto& [key, value] = aEntry;
  646. return key != ALL_LAYERS;
  647. } );
  648. break;
  649. case MODE::FRONT_INNER_BACK:
  650. // When coming from normal, these layers may be missing or have junk values
  651. if( m_mode == MODE::NORMAL )
  652. {
  653. m_copperProps[INNER_LAYERS] = m_copperProps[ALL_LAYERS];
  654. m_copperProps[B_Cu] = m_copperProps[ALL_LAYERS];
  655. }
  656. break;
  657. case MODE::CUSTOM:
  658. {
  659. PCB_LAYER_ID innerLayerTemplate = ( m_mode == MODE::NORMAL ) ? ALL_LAYERS : INNER_LAYERS;
  660. for( PCB_LAYER_ID layer : LAYER_RANGE( In1_Cu, In30_Cu, MAX_CU_LAYERS ) )
  661. m_copperProps[layer] = m_copperProps[innerLayerTemplate];
  662. if( m_mode == MODE::NORMAL )
  663. m_copperProps[B_Cu] = m_copperProps[ALL_LAYERS];
  664. break;
  665. }
  666. }
  667. m_mode = aMode;
  668. // Changing mode invalidates cached shapes
  669. // TODO(JE) clean this up -- maybe PADSTACK should own shape caches
  670. if( PAD* parentPad = dynamic_cast<PAD*>( m_parent ) )
  671. parentPad->SetDirty();
  672. }
  673. void PADSTACK::ForEachUniqueLayer( const std::function<void( PCB_LAYER_ID )>& aMethod ) const
  674. {
  675. switch( Mode() )
  676. {
  677. case MODE::NORMAL:
  678. aMethod( F_Cu );
  679. break;
  680. case MODE::FRONT_INNER_BACK:
  681. aMethod( F_Cu );
  682. aMethod( INNER_LAYERS );
  683. aMethod( B_Cu );
  684. break;
  685. case MODE::CUSTOM:
  686. {
  687. int layerCount = m_parent ? m_parent->BoardCopperLayerCount() : MAX_CU_LAYERS;
  688. for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, layerCount ) )
  689. aMethod( layer );
  690. break;
  691. }
  692. }
  693. }
  694. std::vector<PCB_LAYER_ID> PADSTACK::UniqueLayers() const
  695. {
  696. switch( Mode() )
  697. {
  698. default:
  699. case MODE::NORMAL:
  700. return { F_Cu };
  701. case MODE::FRONT_INNER_BACK:
  702. return { F_Cu, INNER_LAYERS, B_Cu };
  703. case MODE::CUSTOM:
  704. {
  705. std::vector<PCB_LAYER_ID> layers;
  706. int layerCount = m_parent ? m_parent->BoardCopperLayerCount() : MAX_CU_LAYERS;
  707. for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, layerCount ) )
  708. layers.push_back( layer );
  709. return layers;
  710. }
  711. }
  712. }
  713. PCB_LAYER_ID PADSTACK::EffectiveLayerFor( PCB_LAYER_ID aLayer ) const
  714. {
  715. switch( static_cast<int>( aLayer ) )
  716. {
  717. case LAYER_PAD_FR_NETNAMES:
  718. return F_Cu;
  719. case LAYER_PAD_BK_NETNAMES:
  720. return Mode() == MODE::NORMAL ? F_Cu : B_Cu;
  721. // For these, just give the front copper geometry, it doesn't matter.
  722. case LAYER_PAD_NETNAMES:
  723. case LAYER_VIA_NETNAMES:
  724. case LAYER_PADS:
  725. case LAYER_PAD_PLATEDHOLES:
  726. case LAYER_VIA_HOLES:
  727. case LAYER_PAD_HOLEWALLS:
  728. case LAYER_VIA_HOLEWALLS:
  729. return ALL_LAYERS;
  730. default:
  731. break;
  732. }
  733. switch( Mode() )
  734. {
  735. case MODE::CUSTOM:
  736. case MODE::FRONT_INNER_BACK:
  737. {
  738. PCB_LAYER_ID boardCuLayer = aLayer;
  739. if( IsViaCopperLayer( aLayer ) )
  740. boardCuLayer = ToLAYER_ID( static_cast<int>( aLayer ) - LAYER_VIA_COPPER_START );
  741. else if( IsPadCopperLayer( aLayer ) )
  742. boardCuLayer = ToLAYER_ID( static_cast<int>( aLayer ) - LAYER_PAD_COPPER_START );
  743. else if( IsClearanceLayer( aLayer ) )
  744. boardCuLayer = ToLAYER_ID( static_cast<int>( aLayer ) - LAYER_CLEARANCE_START );
  745. if( IsFrontLayer( boardCuLayer ) )
  746. return F_Cu;
  747. if( IsBackLayer( boardCuLayer ) )
  748. return B_Cu;
  749. wxASSERT_MSG( IsCopperLayer( boardCuLayer ),
  750. wxString::Format( wxT( "Unhandled layer %d in PADSTACK::EffectiveLayerFor" ),
  751. aLayer ) );
  752. if( Mode() == MODE::FRONT_INNER_BACK )
  753. return INNER_LAYERS;
  754. // Custom padstack: Clamp to parent board's stackup if present
  755. if( m_parent )
  756. {
  757. LSET boardCopper = m_parent->BoardLayerSet() & LSET::AllCuMask();
  758. if( boardCopper.Contains( boardCuLayer ) )
  759. return boardCuLayer;
  760. // We're asked for an inner copper layer not present in the board. There is no right
  761. // answer here, so fall back on the front shape
  762. return ALL_LAYERS;
  763. }
  764. // No parent, just pass through
  765. return boardCuLayer;
  766. }
  767. case MODE::NORMAL:
  768. break;
  769. }
  770. return F_Cu;
  771. }
  772. LSET PADSTACK::RelevantShapeLayers( const PADSTACK& aOther ) const
  773. {
  774. LSET ret;
  775. #ifdef DEBUG
  776. if( m_parent && aOther.m_parent
  777. && ( m_mode == MODE::CUSTOM || aOther.m_mode == MODE::CUSTOM ) )
  778. {
  779. wxASSERT_MSG( m_parent->BoardCopperLayerCount() == aOther.m_parent->BoardCopperLayerCount(),
  780. wxT( "Expected both padstacks to have the same board copper layer count" ) );
  781. }
  782. #endif
  783. ForEachUniqueLayer( [&]( PCB_LAYER_ID aLayer ) { ret.set( aLayer ); } );
  784. aOther.ForEachUniqueLayer( [&]( PCB_LAYER_ID aLayer ) { ret.set( aLayer ); } );
  785. return ret;
  786. }
  787. PADSTACK::COPPER_LAYER_PROPS& PADSTACK::CopperLayer( PCB_LAYER_ID aLayer )
  788. {
  789. PCB_LAYER_ID layer = EffectiveLayerFor( aLayer );
  790. // Create on-demand
  791. return m_copperProps[layer];
  792. }
  793. const PADSTACK::COPPER_LAYER_PROPS& PADSTACK::CopperLayer( PCB_LAYER_ID aLayer ) const
  794. {
  795. PCB_LAYER_ID layer = EffectiveLayerFor( aLayer );
  796. wxCHECK_MSG( m_copperProps.contains( layer ), m_copperProps.at( ALL_LAYERS ),
  797. "Attempt to retrieve layer " + std::string( magic_enum::enum_name( layer ) ) + " from a "
  798. "padstack that does not contain it" );
  799. return m_copperProps.at( layer );
  800. }
  801. PAD_SHAPE PADSTACK::Shape( PCB_LAYER_ID aLayer ) const
  802. {
  803. return CopperLayer( aLayer ).shape.shape;
  804. }
  805. void PADSTACK::SetShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer )
  806. {
  807. CopperLayer( aLayer ).shape.shape = aShape;
  808. }
  809. void PADSTACK::SetSize( const VECTOR2I& aSize, PCB_LAYER_ID aLayer )
  810. {
  811. // File formats do not enforce that sizes are always positive, but KiCad requires it
  812. VECTOR2I& size = CopperLayer( aLayer ).shape.size;
  813. size.x = std::abs( aSize.x );
  814. size.y = std::abs( aSize.y );
  815. }
  816. const VECTOR2I& PADSTACK::Size( PCB_LAYER_ID aLayer ) const
  817. {
  818. return CopperLayer( aLayer ).shape.size;
  819. }
  820. PAD_DRILL_SHAPE PADSTACK::DrillShape() const
  821. {
  822. return m_drill.shape;
  823. }
  824. void PADSTACK::SetDrillShape( PAD_DRILL_SHAPE aShape )
  825. {
  826. m_drill.shape = aShape;
  827. }
  828. VECTOR2I& PADSTACK::Offset( PCB_LAYER_ID aLayer )
  829. {
  830. return CopperLayer( aLayer ).shape.offset;
  831. }
  832. const VECTOR2I& PADSTACK::Offset( PCB_LAYER_ID aLayer ) const
  833. {
  834. return CopperLayer( aLayer ).shape.offset;
  835. }
  836. PAD_SHAPE PADSTACK::AnchorShape( PCB_LAYER_ID aLayer ) const
  837. {
  838. return CopperLayer( aLayer ).shape.anchor_shape;
  839. }
  840. void PADSTACK::SetAnchorShape( PAD_SHAPE aShape, PCB_LAYER_ID aLayer )
  841. {
  842. CopperLayer( aLayer ).shape.anchor_shape = aShape;
  843. }
  844. VECTOR2I& PADSTACK::TrapezoidDeltaSize( PCB_LAYER_ID aLayer )
  845. {
  846. return CopperLayer( aLayer ).shape.trapezoid_delta_size;
  847. }
  848. const VECTOR2I& PADSTACK::TrapezoidDeltaSize( PCB_LAYER_ID aLayer ) const
  849. {
  850. return CopperLayer( aLayer ).shape.trapezoid_delta_size;
  851. }
  852. double PADSTACK::RoundRectRadiusRatio( PCB_LAYER_ID aLayer ) const
  853. {
  854. return CopperLayer( aLayer ).shape.round_rect_radius_ratio;
  855. }
  856. void PADSTACK::SetRoundRectRadiusRatio( double aRatio, PCB_LAYER_ID aLayer )
  857. {
  858. CopperLayer( aLayer ).shape.round_rect_radius_ratio = aRatio;
  859. }
  860. int PADSTACK::RoundRectRadius( PCB_LAYER_ID aLayer ) const
  861. {
  862. const VECTOR2I& size = Size( aLayer );
  863. return KiROUND( std::min( size.x, size.y ) * RoundRectRadiusRatio( aLayer ) );
  864. }
  865. void PADSTACK::SetRoundRectRadius( double aRadius, PCB_LAYER_ID aLayer )
  866. {
  867. const VECTOR2I& size = Size( aLayer );
  868. int min_r = std::min( size.x, size.y );
  869. if( min_r > 0 )
  870. SetRoundRectRadiusRatio( aRadius / min_r, aLayer );
  871. }
  872. double PADSTACK::ChamferRatio( PCB_LAYER_ID aLayer ) const
  873. {
  874. return CopperLayer( aLayer ).shape.chamfered_rect_ratio;
  875. }
  876. void PADSTACK::SetChamferRatio( double aRatio, PCB_LAYER_ID aLayer )
  877. {
  878. CopperLayer( aLayer ).shape.chamfered_rect_ratio = aRatio;
  879. }
  880. int& PADSTACK::ChamferPositions( PCB_LAYER_ID aLayer )
  881. {
  882. return CopperLayer( aLayer ).shape.chamfered_rect_positions;
  883. }
  884. const int& PADSTACK::ChamferPositions( PCB_LAYER_ID aLayer ) const
  885. {
  886. return CopperLayer( aLayer ).shape.chamfered_rect_positions;
  887. }
  888. void PADSTACK::SetChamferPositions( int aPositions, PCB_LAYER_ID aLayer )
  889. {
  890. CopperLayer( aLayer ).shape.chamfered_rect_positions = aPositions;
  891. }
  892. std::optional<int>& PADSTACK::Clearance( PCB_LAYER_ID aLayer )
  893. {
  894. return CopperLayer( aLayer ).clearance;
  895. }
  896. const std::optional<int>& PADSTACK::Clearance( PCB_LAYER_ID aLayer ) const
  897. {
  898. return CopperLayer( aLayer ).clearance;
  899. }
  900. std::optional<int>& PADSTACK::SolderMaskMargin( PCB_LAYER_ID aLayer )
  901. {
  902. return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_mask_margin
  903. : m_backMaskProps.solder_mask_margin;
  904. }
  905. const std::optional<int>& PADSTACK::SolderMaskMargin( PCB_LAYER_ID aLayer ) const
  906. {
  907. return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_mask_margin
  908. : m_backMaskProps.solder_mask_margin;
  909. }
  910. std::optional<int>& PADSTACK::SolderPasteMargin( PCB_LAYER_ID aLayer )
  911. {
  912. return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_paste_margin
  913. : m_backMaskProps.solder_paste_margin;
  914. }
  915. const std::optional<int>& PADSTACK::SolderPasteMargin( PCB_LAYER_ID aLayer ) const
  916. {
  917. return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_paste_margin
  918. : m_backMaskProps.solder_paste_margin;}
  919. std::optional<double>& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer )
  920. {
  921. return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_paste_margin_ratio
  922. : m_backMaskProps.solder_paste_margin_ratio;
  923. }
  924. const std::optional<double>& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer ) const
  925. {
  926. return IsFrontLayer( aLayer ) ? m_frontMaskProps.solder_paste_margin_ratio
  927. : m_backMaskProps.solder_paste_margin_ratio;
  928. }
  929. std::optional<ZONE_CONNECTION>& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer )
  930. {
  931. return CopperLayer( aLayer ).zone_connection;
  932. }
  933. const std::optional<ZONE_CONNECTION>& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer ) const
  934. {
  935. return CopperLayer( aLayer ).zone_connection;
  936. }
  937. std::optional<int>& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer )
  938. {
  939. return CopperLayer( aLayer ).thermal_spoke_width;
  940. }
  941. const std::optional<int>& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer ) const
  942. {
  943. return CopperLayer( aLayer ).thermal_spoke_width;
  944. }
  945. std::optional<int>& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer )
  946. {
  947. return CopperLayer( aLayer ).thermal_gap;
  948. }
  949. const std::optional<int>& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer ) const
  950. {
  951. return CopperLayer( aLayer ).thermal_gap;
  952. }
  953. EDA_ANGLE PADSTACK::DefaultThermalSpokeAngleForShape( PCB_LAYER_ID aLayer ) const
  954. {
  955. const COPPER_LAYER_PROPS& defaults = CopperLayer( aLayer );
  956. return ( defaults.shape.shape == PAD_SHAPE::CIRCLE
  957. || ( defaults.shape.shape == PAD_SHAPE::CUSTOM
  958. && defaults.shape.anchor_shape == PAD_SHAPE::CIRCLE ) )
  959. ? ANGLE_45 : ANGLE_90;
  960. }
  961. EDA_ANGLE PADSTACK::ThermalSpokeAngle( PCB_LAYER_ID aLayer ) const
  962. {
  963. const COPPER_LAYER_PROPS& defaults = CopperLayer( aLayer );
  964. return defaults.thermal_spoke_angle.value_or( DefaultThermalSpokeAngleForShape( aLayer ) );
  965. }
  966. void PADSTACK::SetThermalSpokeAngle( EDA_ANGLE aAngle, PCB_LAYER_ID aLayer )
  967. {
  968. CopperLayer( aLayer ).thermal_spoke_angle = aAngle;
  969. }
  970. std::vector<std::shared_ptr<PCB_SHAPE>>& PADSTACK::Primitives( PCB_LAYER_ID aLayer )
  971. {
  972. return CopperLayer( aLayer ).custom_shapes;
  973. }
  974. const std::vector<std::shared_ptr<PCB_SHAPE>>& PADSTACK::Primitives( PCB_LAYER_ID aLayer ) const
  975. {
  976. return CopperLayer( aLayer ).custom_shapes;
  977. }
  978. void PADSTACK::AddPrimitive( PCB_SHAPE* aShape, PCB_LAYER_ID aLayer )
  979. {
  980. aShape->SetParent( m_parent );
  981. CopperLayer( aLayer ).custom_shapes.emplace_back( aShape );
  982. }
  983. void PADSTACK::AppendPrimitives( const std::vector<std::shared_ptr<PCB_SHAPE>>& aPrimitivesList,
  984. PCB_LAYER_ID aLayer )
  985. {
  986. for( const std::shared_ptr<PCB_SHAPE>& prim : aPrimitivesList )
  987. AddPrimitive( new PCB_SHAPE( *prim ), aLayer );
  988. }
  989. void PADSTACK::ReplacePrimitives( const std::vector<std::shared_ptr<PCB_SHAPE>>& aPrimitivesList,
  990. PCB_LAYER_ID aLayer )
  991. {
  992. ClearPrimitives( aLayer );
  993. if( aPrimitivesList.size() )
  994. AppendPrimitives( aPrimitivesList, aLayer );
  995. }
  996. void PADSTACK::ClearPrimitives( PCB_LAYER_ID aLayer )
  997. {
  998. CopperLayer( aLayer ).custom_shapes.clear();
  999. }
  1000. std::optional<bool> PADSTACK::IsTented( PCB_LAYER_ID aSide ) const
  1001. {
  1002. if( IsFrontLayer( aSide ) )
  1003. return m_frontMaskProps.has_solder_mask;
  1004. if( IsBackLayer( aSide ) )
  1005. return m_backMaskProps.has_solder_mask;
  1006. wxCHECK_MSG( false, std::nullopt, "IsTented expects a front or back layer" );
  1007. }
  1008. std::optional<bool> PADSTACK::IsCovered( PCB_LAYER_ID aSide ) const
  1009. {
  1010. if( IsFrontLayer( aSide ) )
  1011. return m_frontMaskProps.has_covering;
  1012. if( IsBackLayer( aSide ) )
  1013. return m_backMaskProps.has_covering;
  1014. wxCHECK_MSG( false, std::nullopt, "IsCovered expects a front or back layer" );
  1015. }
  1016. std::optional<bool> PADSTACK::IsPlugged( PCB_LAYER_ID aSide ) const
  1017. {
  1018. if( IsFrontLayer( aSide ) )
  1019. return m_frontMaskProps.has_plugging;
  1020. if( IsBackLayer( aSide ) )
  1021. return m_backMaskProps.has_plugging;
  1022. wxCHECK_MSG( false, std::nullopt, "IsPlugged expects a front or back layer" );
  1023. }
  1024. std::optional<bool> PADSTACK::IsCapped() const
  1025. {
  1026. return m_drill.is_capped;
  1027. }
  1028. std::optional<bool> PADSTACK::IsFilled() const
  1029. {
  1030. return m_drill.is_filled;
  1031. }
  1032. IMPLEMENT_ENUM_TO_WXANY( PADSTACK::UNCONNECTED_LAYER_MODE )