KiCad PCB EDA Suite
Loading...
Searching...
No Matches
padstack.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2024 Jon Evans <[email protected]>
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
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <utility> // std::as_const
22
23#include <convert_basic_shapes_to_polygon.h> // RECT_CHAMFER_POSITIONS
24#include "padstack.h"
25#include <api/api_enums.h>
26#include <api/api_utils.h>
27#include <api/api_pcb_utils.h>
28#include <api/board/board_types.pb.h>
29#include <layer_range.h>
30#include <macros.h>
31#include <magic_enum.hpp>
32#include <pad.h>
33#include <board.h>
34#include <pcb_shape.h>
35#include <properties/property.h>
37
38
42
43
69
70
72{
73 m_parent = aOther.m_parent;
74 *this = aOther;
75
77 [&]( PCB_LAYER_ID aLayer )
78 {
79 for( std::shared_ptr<PCB_SHAPE>& shape : CopperLayer( aLayer ).custom_shapes )
80 shape->SetParent( m_parent );
81 } );
82}
83
84
86{
87 // NOTE: m_parent is not copied from operator=, because this operator is commonly used to
88 // update the padstack properties, and such an update must not change the parent PAD to point to
89 // the parent of some different padstack.
90
91 m_mode = aOther.m_mode;
92 m_layerSet = aOther.m_layerSet;
93 SetCustomName( aOther.CustomName() );
100 m_drill = aOther.m_drill;
105
106 // Data consistency enforcement logic that used to live in the pad properties dialog.
107 // While it might be tempting to put these in the individual property setters, there's no
108 // well-defined order in which they're called, and many of the consistency checks are
109 // between multiple properties.
110
112 [&]( PCB_LAYER_ID aLayer )
113 {
114 PAD_SHAPE shape = Shape( aLayer );
115
116 // Make sure leftover primitives don't stick around
117 ClearPrimitives( aLayer );
118
119 // For custom pad shape, duplicate primitives of the pad to copy
120 if( shape == PAD_SHAPE::CUSTOM )
121 ReplacePrimitives( aOther.Primitives( aLayer ), aLayer );
122
123 // rounded rect pads with radius ratio = 0 are in fact rect pads.
124 // So set the right shape (and perhaps issues with a radius = 0)
125 if( shape == PAD_SHAPE::ROUNDRECT && RoundRectRadiusRatio( aLayer ) == 0.0 )
127 } );
128
129 return *this;
130}
131
132
133bool PADSTACK::operator==( const PADSTACK& aOther ) const
134{
135 if( m_mode != aOther.m_mode )
136 return false;
137
138 if( m_layerSet != aOther.m_layerSet )
139 return false;
140
141 if( CustomName() != aOther.CustomName() )
142 return false;
143
144 if( m_orientation != aOther.m_orientation )
145 return false;
146
147 if( m_frontMaskProps != aOther.m_frontMaskProps )
148 return false;
149
150 if( m_backMaskProps != aOther.m_backMaskProps )
151 return false;
152
154 return false;
155
157 return false;
158
159 if( m_drill != aOther.m_drill )
160 return false;
161
162 if( m_secondaryDrill != aOther.m_secondaryDrill )
163 return false;
164
165 if( m_tertiaryDrill != aOther.m_tertiaryDrill )
166 return false;
167
169 return false;
170
172 return false;
173
174 bool copperMatches = true;
175
177 [&]( PCB_LAYER_ID aLayer )
178 {
179 if( CopperLayer( aLayer ) != aOther.CopperLayer( aLayer ) )
180 copperMatches = false;
181 } );
182
183 return copperMatches;
184}
185
186
187bool PADSTACK::unpackCopperLayer( const kiapi::board::types::PadStackLayer& aProto )
188{
189 using namespace kiapi::board::types;
191
192 if( m_mode == MODE::NORMAL && layer != ALL_LAYERS )
193 return false;
194
195 if( m_mode == MODE::FRONT_INNER_BACK && layer != F_Cu && layer != INNER_LAYERS && layer != B_Cu )
196 return false;
197
198 SetSize( kiapi::common::UnpackVector2( aProto.size() ), layer );
199 SetShape( FromProtoEnum<PAD_SHAPE>( aProto.shape() ), layer );
200 Offset( layer ) = kiapi::common::UnpackVector2( aProto.offset() );
201 SetAnchorShape( FromProtoEnum<PAD_SHAPE>( aProto.custom_anchor_shape() ), layer );
202
203 SHAPE_PROPS& props = CopperLayer( layer ).shape;
204 props.chamfered_rect_ratio = aProto.chamfer_ratio();
205 props.round_rect_radius_ratio = aProto.corner_rounding_ratio();
206
207 if( Shape( layer ) == PAD_SHAPE::TRAPEZOID && aProto.has_trapezoid_delta() )
208 TrapezoidDeltaSize( layer ) = kiapi::common::UnpackVector2( aProto.trapezoid_delta() );
209
210 if( aProto.chamfered_corners().top_left() )
212
213 if( aProto.chamfered_corners().top_right() )
215
216 if( aProto.chamfered_corners().bottom_left() )
218
219 if( aProto.chamfered_corners().bottom_right() )
221
222 ClearPrimitives( layer );
223 google::protobuf::Any a;
224
225 for( const BoardGraphicShape& shapeProto : aProto.custom_shapes() )
226 {
227 a.PackFrom( shapeProto );
228 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_parent );
229
230 if( shape->Deserialize( a ) )
231 AddPrimitive( shape.release(), layer );
232 }
233
234 return true;
235}
236
237
238bool PADSTACK::Deserialize( const kiapi::board::types::PadStack& padstack )
239{
240 using namespace kiapi::board::types;
241
242 auto unpackOptional = []<typename ProtoEnum>( const ProtoEnum& aProto,
243 std::optional<bool>& aDest, ProtoEnum aTrueValue,
244 ProtoEnum aFalseValue )
245 {
246 if( aProto == aTrueValue )
247 aDest = true;
248 else if( aProto == aFalseValue )
249 aDest = false;
250 else
251 aDest = std::nullopt;
252 };
253
254 auto unpackPostMachining = []( const PostMachiningProperties& aProto,
256 {
257 switch( aProto.mode() )
258 {
259 case VDPM_NOT_POST_MACHINED: aDest.mode = PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED; break;
260 case VDPM_COUNTERBORE: aDest.mode = PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE; break;
261 case VDPM_COUNTERSINK: aDest.mode = PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK; break;
262 default: aDest.mode = std::nullopt; break;
263 }
264
265 aDest.size = aProto.size();
266 aDest.depth = aProto.depth();
267 aDest.angle = aProto.angle();
268 };
269
270
271 m_mode = FromProtoEnum<MODE>( padstack.type() );
272 SetLayerSet( kiapi::board::UnpackLayerSet( padstack.layers() ) );
273 SetOrientation( EDA_ANGLE( padstack.angle().value_degrees(), DEGREES_T ) );
274
275 Drill().size = kiapi::common::UnpackVector2( padstack.drill().diameter() );
276 Drill().start = FromProtoEnum<PCB_LAYER_ID>( padstack.drill().start_layer() );
277 Drill().end = FromProtoEnum<PCB_LAYER_ID>( padstack.drill().end_layer() );
278 unpackOptional( padstack.drill().capped(), Drill().is_capped, VDCM_CAPPED, VDCM_UNCAPPED );
279 unpackOptional( padstack.drill().filled(), Drill().is_filled, VDFM_FILLED, VDFM_UNFILLED );
280
281 if( padstack.has_front_post_machining() )
282 unpackPostMachining( padstack.front_post_machining(), FrontPostMachining() );
283
284 if( padstack.has_back_post_machining() )
285 unpackPostMachining( padstack.back_post_machining(), BackPostMachining() );
286
287 Drill().shape = FromProtoEnum<PAD_DRILL_SHAPE>( padstack.drill().shape() );
288
289 if( padstack.has_secondary_drill() )
290 {
291 const DrillProperties& secondary = padstack.secondary_drill();
292
293 SecondaryDrill().size = kiapi::common::UnpackVector2( secondary.diameter() );
294 SecondaryDrill().start = FromProtoEnum<PCB_LAYER_ID>( secondary.start_layer() );
295 SecondaryDrill().end = FromProtoEnum<PCB_LAYER_ID>( secondary.end_layer() );
296 SecondaryDrill().shape = FromProtoEnum<PAD_DRILL_SHAPE>( secondary.shape() );
297
298 unpackOptional( secondary.capped(), SecondaryDrill().is_capped, VDCM_CAPPED, VDCM_UNCAPPED );
299 unpackOptional( secondary.filled(), SecondaryDrill().is_filled, VDFM_FILLED, VDFM_UNFILLED );
300 }
301 else
302 {
303 SecondaryDrill().size = { 0, 0 };
307 SecondaryDrill().is_capped = std::nullopt;
308 SecondaryDrill().is_filled = std::nullopt;
309 }
310
311 if( padstack.has_tertiary_drill() )
312 {
313 const DrillProperties& tertiary = padstack.tertiary_drill();
314
315 TertiaryDrill().size = kiapi::common::UnpackVector2( tertiary.diameter() );
316 TertiaryDrill().start = FromProtoEnum<PCB_LAYER_ID>( tertiary.start_layer() );
317 TertiaryDrill().end = FromProtoEnum<PCB_LAYER_ID>( tertiary.end_layer() );
318 TertiaryDrill().shape = FromProtoEnum<PAD_DRILL_SHAPE>( tertiary.shape() );
319
320 unpackOptional( tertiary.capped(), TertiaryDrill().is_capped, VDCM_CAPPED, VDCM_UNCAPPED );
321 unpackOptional( tertiary.filled(), TertiaryDrill().is_filled, VDFM_FILLED, VDFM_UNFILLED );
322 }
323 else
324 {
325 TertiaryDrill().size = { 0, 0 };
329 TertiaryDrill().is_capped = std::nullopt;
330 TertiaryDrill().is_filled = std::nullopt;
331 }
332
333 for( const PadStackLayer& layer : padstack.copper_layers() )
334 {
335 if( !unpackCopperLayer( layer ) )
336 return false;
337 }
338
339 CopperLayer( ALL_LAYERS ).thermal_gap = std::nullopt;
341
342 if( padstack.has_zone_settings() )
343 {
345 FromProtoEnum<ZONE_CONNECTION>( padstack.zone_settings().zone_connection() );
346
347 if( padstack.zone_settings().has_thermal_spokes() )
348 {
349 const ThermalSpokeSettings& thermals = padstack.zone_settings().thermal_spokes();
350
351 if( thermals.has_gap() )
352 CopperLayer( ALL_LAYERS ).thermal_gap = thermals.gap().value_nm();
353
354 if( thermals.has_width() )
355 CopperLayer( ALL_LAYERS ).thermal_spoke_width = thermals.width().value_nm();
356
357 SetThermalSpokeAngle( EDA_ANGLE( thermals.angle().value_degrees(), DEGREES_T ), F_Cu );
358 }
359 }
360 else
361 {
364 }
365
367 FromProtoEnum<UNCONNECTED_LAYER_MODE>( padstack.unconnected_layer_removal() ) );
368
369 unpackOptional( padstack.front_outer_layers().solder_mask_mode(),
370 FrontOuterLayers().has_solder_mask, SMM_MASKED, SMM_UNMASKED );
371
372 unpackOptional( padstack.back_outer_layers().solder_mask_mode(),
373 BackOuterLayers().has_solder_mask, SMM_MASKED, SMM_UNMASKED );
374
375 unpackOptional( padstack.front_outer_layers().covering_mode(), FrontOuterLayers().has_covering,
376 VCM_COVERED, VCM_UNCOVERED );
377
378 unpackOptional( padstack.back_outer_layers().covering_mode(), BackOuterLayers().has_covering,
379 VCM_COVERED, VCM_UNCOVERED );
380
381 unpackOptional( padstack.front_outer_layers().plugging_mode(), FrontOuterLayers().has_plugging,
382 VPM_PLUGGED, VPM_UNPLUGGED );
383
384 unpackOptional( padstack.back_outer_layers().plugging_mode(), BackOuterLayers().has_plugging,
385 VPM_PLUGGED, VPM_UNPLUGGED );
386
387 unpackOptional( padstack.front_outer_layers().solder_paste_mode(),
388 FrontOuterLayers().has_solder_paste, SPM_PASTE, SPM_NO_PASTE );
389
390 unpackOptional( padstack.back_outer_layers().solder_paste_mode(),
391 BackOuterLayers().has_solder_paste, SPM_PASTE, SPM_NO_PASTE );
392
393 if( padstack.front_outer_layers().has_solder_mask_settings()
394 && padstack.front_outer_layers().solder_mask_settings().has_solder_mask_margin() )
395 {
397 padstack.front_outer_layers().solder_mask_settings().solder_mask_margin().value_nm();
398 }
399 else
400 {
401 FrontOuterLayers().solder_mask_margin = std::nullopt;
402 }
403
404 if( padstack.back_outer_layers().has_solder_mask_settings()
405 && padstack.back_outer_layers().solder_mask_settings().has_solder_mask_margin() )
406 {
408 padstack.back_outer_layers().solder_mask_settings().solder_mask_margin().value_nm();
409 }
410 else
411 {
412 BackOuterLayers().solder_mask_margin = std::nullopt;
413 }
414
415 if( padstack.front_outer_layers().has_solder_paste_settings()
416 && padstack.front_outer_layers().solder_paste_settings().has_solder_paste_margin() )
417 {
419 padstack.front_outer_layers().solder_paste_settings().solder_paste_margin().value_nm();
420 }
421 else
422 {
423 FrontOuterLayers().solder_paste_margin = std::nullopt;
424 }
425
426 if( padstack.back_outer_layers().has_solder_paste_settings()
427 && padstack.back_outer_layers().solder_paste_settings().has_solder_paste_margin() )
428 {
430 padstack.back_outer_layers().solder_paste_settings().solder_paste_margin().value_nm();
431 }
432 else
433 {
434 BackOuterLayers().solder_paste_margin = std::nullopt;
435 }
436
437 if( padstack.front_outer_layers().has_solder_paste_settings()
438 && padstack.front_outer_layers().solder_paste_settings().has_solder_paste_margin_ratio() )
439 {
441 padstack.front_outer_layers().solder_paste_settings().solder_paste_margin_ratio().value();
442 }
443 else
444 {
446 }
447
448 if( padstack.back_outer_layers().has_solder_paste_settings()
449 && padstack.back_outer_layers().solder_paste_settings().has_solder_paste_margin_ratio() )
450 {
452 padstack.back_outer_layers().solder_paste_settings().solder_paste_margin_ratio().value();
453 }
454 else
455 {
457 }
458
459
460 return true;
461}
462
463
464bool PADSTACK::Deserialize( const google::protobuf::Any& aContainer )
465{
466 kiapi::board::types::PadStack padstack;
467
468 if( !aContainer.UnpackTo( &padstack ) )
469 return false;
470
471 return Deserialize( padstack );
472}
473
474
475// A backdrill's side is identified by its start layer (F_Cu = top, B_Cu = bottom), not by which
476// drill slot it occupies. Reads scan both slots so a board written by KiCad 10.0 - which stored the
477// top backdrill in the tertiary slot - is understood, and writes always stamp the start layer so a
478// backdrill saved here is read back correctly by older KiCad. Presence is keyed on the drill size.
480{
481 PCB_LAYER_ID side = aTop ? F_Cu : B_Cu;
482
483 if( m_secondaryDrill.size.x > 0 && m_secondaryDrill.start == side )
484 return &m_secondaryDrill;
485
486 if( m_tertiaryDrill.size.x > 0 && m_tertiaryDrill.start == side )
487 return &m_tertiaryDrill;
488
489 return nullptr;
490}
491
492
494{
495 return const_cast<DRILL_PROPS*>( std::as_const( *this ).findBackdrillDrill( aTop ) );
496}
497
498
500{
501 if( DRILL_PROPS* existing = findBackdrillDrill( aTop ) )
502 return *existing;
503
506 PCB_LAYER_ID otherSide = aTop ? B_Cu : F_Cu;
507
508 // Displace to the other slot only when the canonical home is already taken by the opposite
509 // side, which happens when extending a backdrill on a board written by KiCad 10.0.
510 if( home.size.x > 0 && home.start == otherSide )
511 return other;
512
513 return home;
514}
515
516
518{
519 PCB_LAYER_ID side = aTop ? F_Cu : B_Cu;
520
521 if( m_secondaryDrill.start == side )
522 m_secondaryDrill.size = { 0, 0 };
523
524 if( m_tertiaryDrill.start == side )
525 m_tertiaryDrill.size = { 0, 0 };
526}
527
528
530{
531 bool hasTop = findBackdrillDrill( true ) != nullptr;
532 bool hasBottom = findBackdrillDrill( false ) != nullptr;
533
534 if( hasTop && hasBottom )
536
537 if( hasTop )
539
540 if( hasBottom )
542
544}
545
546
548{
549 auto apply =
550 [this]( bool aTop, bool aWant )
551 {
552 if( aWant )
553 {
554 DRILL_PROPS& drill = backdrillWriteSlot( aTop );
555 drill.start = aTop ? F_Cu : B_Cu;
556 drill.end = drill.start; // Must-cut a single copper layer
558
559 if( drill.size.x <= 0 )
560 drill.size = m_drill.size * 1.1; // Backdrill slightly larger than main drill
561 }
562 else
563 {
564 clearBackdrillSide( aTop );
565 }
566 };
567
568 apply( true, aMode == BACKDRILL_MODE::BACKDRILL_TOP || aMode == BACKDRILL_MODE::BACKDRILL_BOTH );
569 apply( false, aMode == BACKDRILL_MODE::BACKDRILL_BOTTOM || aMode == BACKDRILL_MODE::BACKDRILL_BOTH );
570}
571
572
573std::optional<int> PADSTACK::GetBackdrillSize( bool aTop ) const
574{
575 if( const DRILL_PROPS* drill = findBackdrillDrill( aTop ) )
576 return drill->size.x;
577
578 return std::nullopt;
579}
580
581
582void PADSTACK::SetBackdrillSize( bool aTop, std::optional<int> aSize )
583{
584 if( aSize.has_value() )
585 {
586 DRILL_PROPS& target = backdrillWriteSlot( aTop );
587 target.size = { *aSize, *aSize };
589 target.start = aTop ? F_Cu : B_Cu;
590 }
591 else
592 {
593 clearBackdrillSide( aTop );
594 }
595}
596
597
599{
600 if( const DRILL_PROPS* drill = findBackdrillDrill( aTop ) )
601 return drill->end;
602
603 return UNDEFINED_LAYER;
604}
605
606
608{
609 // A backdrill with no must-cut layer does not exist (matching the via layer sanitizer), so
610 // clearing the must-cut clears the side rather than leaving a sizeful drill with no end.
611 if( aLayer == UNDEFINED_LAYER )
612 {
613 clearBackdrillSide( aTop );
614 return;
615 }
616
617 DRILL_PROPS& target = backdrillWriteSlot( aTop );
618
619 target.end = aLayer;
620 target.start = aTop ? F_Cu : B_Cu;
621}
622
623void PADSTACK::Serialize( kiapi::board::types::PadStack& padstack ) const
624{
625 using namespace kiapi::board::types;
626
627 padstack.set_type( ToProtoEnum<MODE, PadStackType>( m_mode ) );
628 kiapi::board::PackLayerSet( *padstack.mutable_layers(), m_layerSet );
629 padstack.mutable_angle()->set_value_degrees( m_orientation.GetAngle().AsDegrees() );
630
631 kiapi::common::PackVector2( *padstack.mutable_drill()->mutable_diameter(), m_drill.size );
632 padstack.mutable_drill()->set_start_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_drill.start ) );
633 padstack.mutable_drill()->set_end_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_drill.end ) );
634 padstack.mutable_drill()->set_shape( ToProtoEnum<PAD_DRILL_SHAPE, kiapi::board::types::DrillShape>( m_drill.shape ) );
635
636 if( m_drill.is_capped.has_value() )
637 padstack.mutable_drill()->set_capped( m_drill.is_capped.value() ? VDCM_CAPPED : VDCM_UNCAPPED );
638
639 if( m_drill.is_filled.has_value() )
640 padstack.mutable_drill()->set_filled( m_drill.is_filled.value() ? VDFM_FILLED : VDFM_UNFILLED );
641
642 if( m_secondaryDrill.size.x > 0 )
643 {
644 DrillProperties* secondary = padstack.mutable_secondary_drill();
645 kiapi::common::PackVector2( *secondary->mutable_diameter(), m_secondaryDrill.size );
646 secondary->set_start_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_secondaryDrill.start ) );
647 secondary->set_end_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_secondaryDrill.end ) );
649
650 if( m_secondaryDrill.is_capped.has_value() )
651 secondary->set_capped( m_secondaryDrill.is_capped.value() ? VDCM_CAPPED : VDCM_UNCAPPED );
652
653 if( m_secondaryDrill.is_filled.has_value() )
654 secondary->set_filled( m_secondaryDrill.is_filled.value() ? VDFM_FILLED : VDFM_UNFILLED );
655 }
656
657 if( m_tertiaryDrill.size.x > 0 )
658 {
659 DrillProperties* tertiary = padstack.mutable_tertiary_drill();
660 kiapi::common::PackVector2( *tertiary->mutable_diameter(), m_tertiaryDrill.size );
661 tertiary->set_start_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_tertiaryDrill.start ) );
662 tertiary->set_end_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_tertiaryDrill.end ) );
664
665 if( m_tertiaryDrill.is_capped.has_value() )
666 tertiary->set_capped( m_tertiaryDrill.is_capped.value() ? VDCM_CAPPED : VDCM_UNCAPPED );
667
668 if( m_tertiaryDrill.is_filled.has_value() )
669 tertiary->set_filled( m_tertiaryDrill.is_filled.value() ? VDFM_FILLED : VDFM_UNFILLED );
670 }
671
672 auto packPostMachining = []( const PADSTACK::POST_MACHINING_PROPS& aProps,
673 PostMachiningProperties* aProto )
674 {
675 if( aProps.mode.has_value() )
676 {
677 switch( aProps.mode.value() )
678 {
679 case PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED: aProto->set_mode( VDPM_NOT_POST_MACHINED ); break;
680 case PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE: aProto->set_mode( VDPM_COUNTERBORE ); break;
681 case PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK: aProto->set_mode( VDPM_COUNTERSINK ); break;
682 default: break;
683 }
684 }
685
686 aProto->set_size( aProps.size );
687 aProto->set_depth( aProps.depth );
688 aProto->set_angle( aProps.angle );
689 };
690
691 if( m_frontPostMachining.mode.has_value() || m_frontPostMachining.size > 0 )
692 packPostMachining( m_frontPostMachining, padstack.mutable_front_post_machining() );
693
694 if( m_backPostMachining.mode.has_value() || m_backPostMachining.size > 0 )
695 packPostMachining( m_backPostMachining, padstack.mutable_back_post_machining() );
696
698 [&]( PCB_LAYER_ID aLayer )
699 {
700 PadStackLayer* layer = padstack.add_copper_layers();
701 const COPPER_LAYER_PROPS& props = CopperLayer( aLayer );
702
703 layer->set_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( aLayer ) );
704 kiapi::common::PackVector2( *layer->mutable_size(), props.shape.size );
706 kiapi::common::PackVector2( *layer->mutable_offset(), props.shape.offset );
707 layer->set_custom_anchor_shape( ToProtoEnum<PAD_SHAPE, kiapi::board::types::PadStackShape>( props.shape.anchor_shape ) );
708 layer->set_chamfer_ratio( props.shape.chamfered_rect_ratio );
709 layer->set_corner_rounding_ratio( props.shape.round_rect_radius_ratio );
710
711 if( props.shape.shape == PAD_SHAPE::TRAPEZOID )
712 kiapi::common::PackVector2( *layer->mutable_trapezoid_delta(), props.shape.trapezoid_delta_size );
713
715 layer->mutable_chamfered_corners()->set_top_left( true );
716
718 layer->mutable_chamfered_corners()->set_top_right( true );
719
721 layer->mutable_chamfered_corners()->set_bottom_left( true );
722
724 layer->mutable_chamfered_corners()->set_bottom_right( true );
725
726 for( const std::shared_ptr<PCB_SHAPE>& shape : props.custom_shapes )
727 {
728 google::protobuf::Any a;
729 shape->Serialize( a );
730 a.UnpackTo( layer->add_custom_shapes() );
731 }
732 } );
733
734 if( CopperLayer( ALL_LAYERS ).zone_connection.has_value() )
735 {
736 padstack.mutable_zone_settings()->set_zone_connection(
738 }
739
740 if( CopperLayer( ALL_LAYERS ).thermal_gap.has_value() )
741 {
742 padstack.mutable_zone_settings()->mutable_thermal_spokes()->mutable_gap()->set_value_nm(
743 CopperLayer( ALL_LAYERS ).thermal_gap.value() );
744 }
745
746 if( CopperLayer( ALL_LAYERS ).thermal_spoke_width.has_value() )
747 {
748 padstack.mutable_zone_settings()->mutable_thermal_spokes()->mutable_width()->set_value_nm(
749 CopperLayer( ALL_LAYERS ).thermal_spoke_width.value() );
750 }
751
752 if( CopperLayer( ALL_LAYERS ).thermal_spoke_angle.has_value() )
753 {
754 padstack.mutable_zone_settings()->mutable_thermal_spokes()->mutable_angle()->set_value_degrees(
755 CopperLayer( ALL_LAYERS ).thermal_spoke_angle.value().GetAngle().AsDegrees() );
756 }
757
758 padstack.set_unconnected_layer_removal( ToProtoEnum<UNCONNECTED_LAYER_MODE,
759 kiapi::board::types::UnconnectedLayerRemoval>( m_unconnectedLayerMode ) );
760
761 if( FrontOuterLayers().has_solder_mask.has_value() )
762 {
763 padstack.mutable_front_outer_layers()->set_solder_mask_mode(
764 FrontOuterLayers().has_solder_mask.value() ? SMM_MASKED : SMM_UNMASKED );
765 }
766
767 if( BackOuterLayers().has_solder_mask.has_value() )
768 {
769 padstack.mutable_back_outer_layers()->set_solder_mask_mode(
770 BackOuterLayers().has_solder_mask.value() ? SMM_MASKED : SMM_UNMASKED );
771 }
772
773 if( FrontOuterLayers().has_covering.has_value() )
774 {
775 padstack.mutable_front_outer_layers()->set_covering_mode(
776 FrontOuterLayers().has_covering.value() ? VCM_COVERED : VCM_UNCOVERED );
777 }
778
779 if( BackOuterLayers().has_covering.has_value() )
780 {
781 padstack.mutable_back_outer_layers()->set_covering_mode(
782 BackOuterLayers().has_covering.value() ? VCM_COVERED : VCM_UNCOVERED );
783 }
784
785 if( FrontOuterLayers().has_plugging.has_value() )
786 {
787 padstack.mutable_front_outer_layers()->set_plugging_mode(
788 FrontOuterLayers().has_plugging.value() ? VPM_PLUGGED : VPM_UNPLUGGED );
789 }
790
791 if( BackOuterLayers().has_plugging.has_value() )
792 {
793 padstack.mutable_back_outer_layers()->set_plugging_mode(
794 BackOuterLayers().has_plugging.value() ? VPM_PLUGGED : VPM_UNPLUGGED );
795 }
796
797 if( FrontOuterLayers().has_solder_paste.has_value() )
798 {
799 padstack.mutable_front_outer_layers()->set_solder_paste_mode(
800 FrontOuterLayers().has_solder_paste.value() ? SPM_PASTE : SPM_NO_PASTE );
801 }
802
803 if( BackOuterLayers().has_solder_paste.has_value() )
804 {
805 padstack.mutable_back_outer_layers()->set_solder_paste_mode(
806 BackOuterLayers().has_solder_paste.value() ? SPM_PASTE : SPM_NO_PASTE );
807 }
808
809 if( FrontOuterLayers().solder_mask_margin.has_value() )
810 {
811 padstack.mutable_front_outer_layers()->mutable_solder_mask_settings()->mutable_solder_mask_margin()->set_value_nm(
812 FrontOuterLayers().solder_mask_margin.value() );
813 }
814
815 if( BackOuterLayers().solder_mask_margin.has_value() )
816 {
817 padstack.mutable_back_outer_layers()->mutable_solder_mask_settings()->mutable_solder_mask_margin()->set_value_nm(
818 BackOuterLayers().solder_mask_margin.value() );
819 }
820
821 if( FrontOuterLayers().solder_paste_margin.has_value() )
822 {
823 padstack.mutable_front_outer_layers()->mutable_solder_paste_settings()->mutable_solder_paste_margin()->set_value_nm(
824 FrontOuterLayers().solder_paste_margin.value() );
825 }
826
827 if( BackOuterLayers().solder_paste_margin.has_value() )
828 {
829 padstack.mutable_back_outer_layers()->mutable_solder_paste_settings()->mutable_solder_paste_margin()->set_value_nm(
830 BackOuterLayers().solder_paste_margin.value() );
831 }
832
833 if( FrontOuterLayers().solder_paste_margin_ratio.has_value() )
834 {
835 padstack.mutable_front_outer_layers()->mutable_solder_paste_settings()->mutable_solder_paste_margin_ratio()->set_value(
836 FrontOuterLayers().solder_paste_margin_ratio.value() );
837 }
838
839 if( BackOuterLayers().solder_paste_margin_ratio.has_value() )
840 {
841 padstack.mutable_back_outer_layers()->mutable_solder_paste_settings()->mutable_solder_paste_margin_ratio()->set_value(
842 BackOuterLayers().solder_paste_margin_ratio.value() );
843 }
844
845}
846
847
848void PADSTACK::Serialize( google::protobuf::Any& aContainer ) const
849{
850 kiapi::board::types::PadStack padstack;
851 Serialize( padstack );
852 aContainer.PackFrom( padstack );
853}
854
855
856void PADSTACK::SetSize( const VECTOR2I& aSize, PCB_LAYER_ID aLayer )
857{
858 VECTOR2I size = aSize;
859
860 if( size.x < 0 )
861 size.x = 0;
862
863 if( size.y < 0 )
864 size.y = 0;
865
866 CopperLayer( aLayer ).shape.size = size;
867}
868
869
871{
872 return CopperLayer( aLayer ).shape.size;
873}
874
875
877{
878 return CopperLayer( aLayer ).shape.shape;
879}
880
881
883{
884 CopperLayer( aLayer ).shape.shape = aShape;
885}
886
887
889{
890 return m_drill.shape;
891}
892
893
895{
896 m_drill.shape = aShape;
897}
898
899
901{
902 return CopperLayer( aLayer ).shape.offset;
903}
904
905
907{
908 return CopperLayer( aLayer ).shape.offset;
909}
910
911
912void PADSTACK::SetOffset( const VECTOR2I& aOffset, PCB_LAYER_ID aLayer )
913{
914 CopperLayer( aLayer ).shape.offset = aOffset;
915}
916
917
919{
920 return CopperLayer( aLayer ).shape.anchor_shape;
921}
922
923
925{
926 CopperLayer( aLayer ).shape.anchor_shape = aShape;
927}
928
929
934
935
937{
938 return CopperLayer( aLayer ).shape.trapezoid_delta_size;
939}
940
941
943{
945}
946
947
949{
950 CopperLayer( aLayer ).shape.round_rect_radius_ratio = aRatio;
951}
952
953
955{
956 const VECTOR2I& size = Size( aLayer );
957 return KiROUND( std::min( size.x, size.y ) * RoundRectRadiusRatio( aLayer ) );
958}
959
960
961void PADSTACK::SetRoundRectRadius( double aRadius, PCB_LAYER_ID aLayer )
962{
963 const VECTOR2I& size = Size( aLayer );
964 int min_r = std::min( size.x, size.y );
965
966 if( min_r > 0 )
967 SetRoundRectRadiusRatio( aRadius / min_r, aLayer );
968}
969
970
972{
973 return CopperLayer( aLayer ).shape.chamfered_rect_ratio;
974}
975
976
977void PADSTACK::SetChamferRatio( double aRatio, PCB_LAYER_ID aLayer )
978{
979 CopperLayer( aLayer ).shape.chamfered_rect_ratio = aRatio;
980}
981
982
987
988
989const int& PADSTACK::ChamferPositions( PCB_LAYER_ID aLayer ) const
990{
992}
993
994
995void PADSTACK::SetChamferPositions( int aPositions, PCB_LAYER_ID aLayer )
996{
997 CopperLayer( aLayer ).shape.chamfered_rect_positions = aPositions;
998}
999
1000
1001std::optional<int>& PADSTACK::Clearance( PCB_LAYER_ID aLayer )
1002{
1003 return CopperLayer( aLayer ).clearance;
1004}
1005
1006
1007const std::optional<int>& PADSTACK::Clearance( PCB_LAYER_ID aLayer ) const
1008{
1009 return CopperLayer( aLayer ).clearance;
1010}
1011
1012
1013std::optional<int>& PADSTACK::SolderMaskMargin( PCB_LAYER_ID aLayer )
1014{
1015 if( IsFrontLayer( aLayer ) )
1017 else if( IsBackLayer( aLayer ) )
1019 else
1020 return FrontOuterLayers().solder_mask_margin; // Should not happen
1021}
1022
1023
1024const std::optional<int>& PADSTACK::SolderMaskMargin( PCB_LAYER_ID aLayer ) const
1025{
1026 if( IsFrontLayer( aLayer ) )
1028 else if( IsBackLayer( aLayer ) )
1030 else
1031 return FrontOuterLayers().solder_mask_margin; // Should not happen
1032}
1033
1034
1035std::optional<int>& PADSTACK::SolderPasteMargin( PCB_LAYER_ID aLayer )
1036{
1037 if( IsFrontLayer( aLayer ) )
1039 else if( IsBackLayer( aLayer ) )
1041 else
1042 return FrontOuterLayers().solder_paste_margin; // Should not happen
1043}
1044
1045
1046const std::optional<int>& PADSTACK::SolderPasteMargin( PCB_LAYER_ID aLayer ) const
1047{
1048 if( IsFrontLayer( aLayer ) )
1050 else if( IsBackLayer( aLayer ) )
1052 else
1053 return FrontOuterLayers().solder_paste_margin; // Should not happen
1054}
1055
1056
1057std::optional<double>& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer )
1058{
1059 if( IsFrontLayer( aLayer ) )
1061 else if( IsBackLayer( aLayer ) )
1063 else
1064 return FrontOuterLayers().solder_paste_margin_ratio; // Should not happen
1065}
1066
1067
1068const std::optional<double>& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer ) const
1069{
1070 if( IsFrontLayer( aLayer ) )
1072 else if( IsBackLayer( aLayer ) )
1074 else
1075 return FrontOuterLayers().solder_paste_margin_ratio; // Should not happen
1076}
1077
1078
1079std::optional<ZONE_CONNECTION>& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer )
1080{
1081 return CopperLayer( aLayer ).zone_connection;
1082}
1083
1084
1085const std::optional<ZONE_CONNECTION>& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer ) const
1086{
1087 return CopperLayer( aLayer ).zone_connection;
1088}
1089
1090
1091std::optional<int>& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer )
1092{
1093 return CopperLayer( aLayer ).thermal_spoke_width;
1094}
1095
1096
1097const std::optional<int>& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer ) const
1098{
1099 return CopperLayer( aLayer ).thermal_spoke_width;
1100}
1101
1102
1103std::optional<int>& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer )
1104{
1105 return CopperLayer( aLayer ).thermal_gap;
1106}
1107
1108
1109const std::optional<int>& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer ) const
1110{
1111 return CopperLayer( aLayer ).thermal_gap;
1112}
1113
1114
1116{
1117 PAD_SHAPE shape = Shape( aLayer );
1118
1119 if( shape == PAD_SHAPE::CUSTOM )
1120 shape = AnchorShape( aLayer );
1121
1122 return shape == PAD_SHAPE::CIRCLE ? ANGLE_45 : ANGLE_90;
1123}
1124
1125
1127{
1128 if( CopperLayer( aLayer ).thermal_spoke_angle.has_value() )
1129 return CopperLayer( aLayer ).thermal_spoke_angle.value().GetAngle();
1130
1131 return DefaultThermalSpokeAngleForShape( aLayer );
1132}
1133
1134
1136{
1137 CopperLayer( aLayer ).thermal_spoke_angle = aAngle;
1138}
1139
1140
1141std::vector<std::shared_ptr<PCB_SHAPE>>& PADSTACK::Primitives( PCB_LAYER_ID aLayer )
1142{
1143 return CopperLayer( aLayer ).custom_shapes;
1144}
1145
1146
1147const std::vector<std::shared_ptr<PCB_SHAPE>>& PADSTACK::Primitives( PCB_LAYER_ID aLayer ) const
1148{
1149 return CopperLayer( aLayer ).custom_shapes;
1150}
1151
1152
1154{
1155 CopperLayer( aLayer ).custom_shapes.emplace_back( aShape );
1156}
1157
1158
1159void PADSTACK::AppendPrimitives( const std::vector<std::shared_ptr<PCB_SHAPE>>& aList,
1160 PCB_LAYER_ID aLayer )
1161{
1162 std::vector<std::shared_ptr<PCB_SHAPE>>& list = CopperLayer( aLayer ).custom_shapes;
1163
1164 for( const std::shared_ptr<PCB_SHAPE>& item : aList )
1165 {
1166 PCB_SHAPE* new_shape = static_cast<PCB_SHAPE*>( item->Clone() );
1167 new_shape->SetParent( m_parent );
1168 list.emplace_back( new_shape );
1169 }
1170}
1171
1172
1173void PADSTACK::ReplacePrimitives( const std::vector<std::shared_ptr<PCB_SHAPE>>& aList,
1174 PCB_LAYER_ID aLayer )
1175{
1176 ClearPrimitives( aLayer );
1177 AppendPrimitives( aList, aLayer );
1178}
1179
1180
1182{
1183 CopperLayer( aLayer ).custom_shapes.clear();
1184}
1185
1186
1188{
1189 if( m_mode == MODE::NORMAL )
1190 return m_copperProps[ALL_LAYERS];
1191
1192 PCB_LAYER_ID key = aLayer;
1193
1195 {
1196 if( IsFrontLayer( aLayer ) )
1197 key = F_Cu;
1198 else if( IsBackLayer( aLayer ) )
1199 key = B_Cu;
1200 else
1201 key = INNER_LAYERS;
1202 }
1203
1204 // A layer without an entry reads through to another one, so a new entry starts from what was read
1205 const bool readsThrough = m_copperProps.contains( ALL_LAYERS )
1206 || ( m_mode == MODE::CUSTOM && !m_copperProps.empty() );
1207
1208 if( !m_copperProps.contains( key ) && readsThrough )
1209 {
1210 COPPER_LAYER_PROPS inherited = std::as_const( *this ).CopperLayer( aLayer );
1211
1212 // the new layer owns its primitives, as flipping or editing one layer must not change the other
1213 for( std::shared_ptr<PCB_SHAPE>& shape : inherited.custom_shapes )
1214 {
1215 shape.reset( static_cast<PCB_SHAPE*>( shape->Clone() ) );
1216 shape->SetParent( m_parent );
1217 }
1218
1219 return m_copperProps.emplace( key, std::move( inherited ) ).first->second;
1220 }
1221
1222 return m_copperProps[key];
1223}
1224
1225
1227{
1229 {
1230 if( IsFrontLayer( aLayer ) && m_copperProps.contains( F_Cu ) )
1231 return m_copperProps.at( F_Cu );
1232 else if( IsBackLayer( aLayer ) && m_copperProps.contains( B_Cu ) )
1233 return m_copperProps.at( B_Cu );
1234 else if( m_copperProps.contains( INNER_LAYERS ) )
1235 return m_copperProps.at( INNER_LAYERS );
1236 }
1237 else if( m_mode == MODE::CUSTOM )
1238 {
1239 if( IsFrontLayer( aLayer ) && m_copperProps.contains( F_Cu ) )
1240 return m_copperProps.at( F_Cu );
1241 else if( IsBackLayer( aLayer ) && m_copperProps.contains( B_Cu ) )
1242 return m_copperProps.at( B_Cu );
1243
1244 if( m_copperProps.count( aLayer ) )
1245 return m_copperProps.at( aLayer );
1246
1247 // For CUSTOM mode, fall back to ALL_LAYERS if available (e.g. for layers not yet
1248 // explicitly defined). If ALL_LAYERS is also absent (e.g. after a FlipLayers()
1249 // that renamed the only entry from F_Cu to B_Cu), return whatever entry is first.
1250 if( m_copperProps.count( ALL_LAYERS ) )
1251 return m_copperProps.at( ALL_LAYERS );
1252
1253 wxASSERT( !m_copperProps.empty() );
1254 return m_copperProps.begin()->second;
1255 }
1256
1257 return m_copperProps.at( ALL_LAYERS );
1258}
1259
1260
1261void PADSTACK::ForEachUniqueLayer( const std::function<void( PCB_LAYER_ID )>& aMethod ) const
1262{
1263 if( m_mode == MODE::NORMAL )
1264 {
1265 aMethod( ALL_LAYERS );
1266 }
1267 else if( m_mode == MODE::FRONT_INNER_BACK )
1268 {
1269 aMethod( F_Cu );
1270 aMethod( INNER_LAYERS );
1271 aMethod( B_Cu );
1272 }
1273 else
1274 {
1275 for( const auto& [layer, props] : m_copperProps )
1276 aMethod( layer );
1277 }
1278}
1279
1280
1281std::vector<PCB_LAYER_ID> PADSTACK::UniqueLayers() const
1282{
1283 std::vector<PCB_LAYER_ID> layers;
1284
1286 [&]( PCB_LAYER_ID layer )
1287 {
1288 layers.push_back( layer );
1289 } );
1290
1291 return layers;
1292}
1293
1294
1296{
1297 if( m_mode == MODE::NORMAL )
1298 return ALL_LAYERS;
1299
1300 if( m_mode == MODE::FRONT_INNER_BACK || IsNonCopperLayer( aLayer ) )
1301 {
1302 PCB_LAYER_ID candidate;
1303
1304 if( IsFrontLayer( aLayer ) )
1305 candidate = F_Cu;
1306 else if( IsBackLayer( aLayer ) )
1307 candidate = B_Cu;
1308 else
1309 candidate = INNER_LAYERS;
1310
1311 // FRONT_INNER_BACK always has all three sides.
1312 // In CUSTOM mode only return the side if the pad actually defines it.
1313 if( m_mode == MODE::FRONT_INNER_BACK || m_copperProps.count( candidate ) )
1314 return candidate;
1315 }
1316
1317 if( m_copperProps.count( aLayer ) )
1318 return aLayer;
1319
1320 // For CUSTOM mode, if ALL_LAYERS is present use it as the default; otherwise return the
1321 // first available layer (e.g. after FlipLayers renamed ALL_LAYERS from F_Cu to B_Cu).
1322 if( m_copperProps.count( ALL_LAYERS ) )
1323 return ALL_LAYERS;
1324
1325 wxASSERT( !m_copperProps.empty() );
1326 return m_copperProps.begin()->first;
1327}
1328
1329
1331{
1332 LSET layers;
1333
1334 if( m_mode == MODE::NORMAL && aOther.m_mode == MODE::NORMAL )
1335 {
1336 layers.set( ALL_LAYERS );
1337 }
1338 else
1339 {
1341 [&]( PCB_LAYER_ID layer )
1342 {
1343 layers.set( layer );
1344 } );
1345 aOther.ForEachUniqueLayer(
1346 [&]( PCB_LAYER_ID layer )
1347 {
1348 layers.set( layer );
1349 } );
1350 }
1351
1352 return layers;
1353}
1354
1355
1357{
1358 int maxHoleSize = Drill().size.x;
1359
1362
1365 {
1366 maxHoleSize = std::max( maxHoleSize, frontPM.size );
1367 }
1368
1371 {
1372 maxHoleSize = std::max( maxHoleSize, backPM.size );
1373 }
1374
1375 const PADSTACK::DRILL_PROPS& secondaryDrill = SecondaryDrill();
1376 const PADSTACK::DRILL_PROPS& tertiaryDrill = TertiaryDrill();
1377
1378 if( secondaryDrill.start != UNDEFINED_LAYER && secondaryDrill.end != UNDEFINED_LAYER )
1379 maxHoleSize = std::max( maxHoleSize, secondaryDrill.size.x );
1380
1381 if( tertiaryDrill.start != UNDEFINED_LAYER && tertiaryDrill.end != UNDEFINED_LAYER )
1382 maxHoleSize = std::max( maxHoleSize, tertiaryDrill.size.x );
1383
1384 return maxHoleSize;
1385}
1386
1387
1388std::optional<bool> PADSTACK::IsTented( PCB_LAYER_ID aSide ) const
1389{
1390 if( IsFrontLayer( aSide ) )
1392 else if( IsBackLayer( aSide ) )
1394 else
1395 return std::nullopt;
1396}
1397
1398
1399std::optional<bool> PADSTACK::IsCovered( PCB_LAYER_ID aSide ) const
1400{
1401 if( IsFrontLayer( aSide ) )
1403 else if( IsBackLayer( aSide ) )
1405 else
1406 return std::nullopt;
1407}
1408
1409
1410std::optional<bool> PADSTACK::IsPlugged( PCB_LAYER_ID aSide ) const
1411{
1412 if( IsFrontLayer( aSide ) )
1414 else if( IsBackLayer( aSide ) )
1416 else
1417 return std::nullopt;
1418}
1419
1420
1421std::optional<bool> PADSTACK::IsCapped() const
1422{
1423 return m_drill.is_capped;
1424}
1425
1426
1427std::optional<bool> PADSTACK::IsFilled() const
1428{
1429 return m_drill.is_filled;
1430}
1431
1432
1433#define TEST( a, b ) { if( a != b ) return a - b; }
1434
1435
1436int PADSTACK::Compare( const PADSTACK* aLeft, const PADSTACK* aRight )
1437{
1438 int diff;
1439
1440 TEST( (int) aLeft->m_mode, (int) aRight->m_mode );
1441
1442 if( aLeft->m_layerSet != aRight->m_layerSet )
1443 return aLeft->m_layerSet.Seq() < aRight->m_layerSet.Seq();
1444
1445 if( ( diff = wxString( aLeft->CustomName() ).Cmp( aRight->CustomName() ) ) != 0 )
1446 return diff;
1447
1450
1451 if( ( diff = aLeft->m_frontMaskProps.Compare( aRight->m_frontMaskProps ) ) != 0 )
1452 return diff;
1453
1454 if( ( diff = aLeft->m_backMaskProps.Compare( aRight->m_backMaskProps ) ) != 0 )
1455 return diff;
1456
1457 TEST( (int) aLeft->m_unconnectedLayerMode, (int) aRight->m_unconnectedLayerMode );
1458 TEST( (int) aLeft->m_customShapeInZoneMode, (int) aRight->m_customShapeInZoneMode );
1459
1460 if( ( diff = aLeft->m_drill.Compare( aRight->m_drill ) ) != 0 )
1461 return diff;
1462
1463 if( ( diff = aLeft->m_secondaryDrill.Compare( aRight->m_secondaryDrill ) ) != 0 )
1464 return diff;
1465
1466 if( ( diff = aLeft->m_tertiaryDrill.Compare( aRight->m_tertiaryDrill ) ) != 0 )
1467 return diff;
1468
1469 if( ( diff = aLeft->m_frontPostMachining.Compare( aRight->m_frontPostMachining ) ) != 0 )
1470 return diff;
1471
1472 if( ( diff = aLeft->m_backPostMachining.Compare( aRight->m_backPostMachining ) ) != 0 )
1473 return diff;
1474
1475 aLeft->ForEachUniqueLayer(
1476 [&]( PCB_LAYER_ID aLayer )
1477 {
1478 if( diff != 0 ) // we want to return the first non-matching layer
1479 return;
1480
1481 diff = aLeft->CopperLayer( aLayer ).Compare( aRight->CopperLayer( aLayer ) );
1482 } );
1483
1484 if( diff != 0 )
1485 return diff;
1486
1487 return 0;
1488}
1489
1490
1492{
1493 return m_copperProps.count( aLayer ) > 0;
1494}
1495
1496
1497double PADSTACK::Similarity( const PADSTACK& aOther ) const
1498{
1499 double similarity = 1.0;
1500
1501 if( m_mode != aOther.m_mode )
1502 similarity *= 0.9;
1503
1504 if( m_layerSet != aOther.m_layerSet )
1505 similarity *= 0.9;
1506
1507 if( CustomName() != aOther.CustomName() )
1508 similarity *= 0.9;
1509
1510 if( m_orientation != aOther.m_orientation )
1511 similarity *= 0.9;
1512
1513 if( m_frontMaskProps != aOther.m_frontMaskProps )
1514 similarity *= 0.9;
1515
1516 if( m_backMaskProps != aOther.m_backMaskProps )
1517 similarity *= 0.9;
1518
1520 similarity *= 0.9;
1521
1523 similarity *= 0.9;
1524
1525 if( m_drill != aOther.m_drill )
1526 similarity *= 0.9;
1527
1528 if( m_secondaryDrill != aOther.m_secondaryDrill )
1529 similarity *= 0.9;
1530
1531 if( m_tertiaryDrill != aOther.m_tertiaryDrill )
1532 similarity *= 0.9;
1533
1535 similarity *= 0.9;
1536
1538 similarity *= 0.9;
1539
1541 [&]( PCB_LAYER_ID aLayer )
1542 {
1543 similarity *= CopperLayer( aLayer ).Similarity( aOther.CopperLayer( aLayer ) );
1544 } );
1545
1546 return similarity;
1547}
1548
1549
1551{
1552 auto flipLayer = [aBoard]( PCB_LAYER_ID aLayer )
1553 {
1554 return aBoard ? aBoard->FlipLayer( aLayer ) : ::FlipLayer( aLayer );
1555 };
1556
1558 {
1559 std::unordered_map<PCB_LAYER_ID, COPPER_LAYER_PROPS> oldCopperProps = m_copperProps;
1560 m_copperProps.clear();
1561
1562 m_copperProps[flipLayer( F_Cu )] = oldCopperProps[F_Cu];
1563 m_copperProps[INNER_LAYERS] = oldCopperProps[INNER_LAYERS];
1564 m_copperProps[flipLayer( B_Cu )] = oldCopperProps[B_Cu];
1565 }
1566 else if( m_mode == MODE::CUSTOM )
1567 {
1568 std::unordered_map<PCB_LAYER_ID, COPPER_LAYER_PROPS> oldCopperProps = m_copperProps;
1569 m_copperProps.clear();
1570
1571 for( const auto& [layer, props] : oldCopperProps )
1572 m_copperProps[flipLayer( layer )] = props;
1573 }
1574
1575 std::swap( m_frontMaskProps, m_backMaskProps );
1576
1577 m_drill.start = flipLayer( m_drill.start );
1578 m_drill.end = flipLayer( m_drill.end );
1579
1580 m_secondaryDrill.start = flipLayer( m_secondaryDrill.start );
1581 m_secondaryDrill.end = flipLayer( m_secondaryDrill.end );
1582
1583 m_tertiaryDrill.start = flipLayer( m_tertiaryDrill.start );
1584 m_tertiaryDrill.end = flipLayer( m_tertiaryDrill.end );
1585
1587}
1588
1589
1591{
1592 return m_drill.start;
1593}
1594
1595
1597{
1598 return m_drill.end;
1599}
1600
1601
1602wxString PADSTACK::Name() const
1603{
1604 return CustomName();
1605}
1606
1607
1608const wxChar* PADSTACK::CustomName() const
1609{
1610 if( m_customName )
1611 return m_customName->wx_str();
1612
1613 return wxEmptyString;
1614}
1615
1616
1617void PADSTACK::SetCustomName( const wxString& aCustomName )
1618{
1619 if( aCustomName.IsEmpty() )
1620 {
1621 m_customName.reset();
1622 }
1623 else if( m_customName )
1624 {
1625 *m_customName = aCustomName;
1626 }
1627 else
1628 {
1629 m_customName = std::make_unique<wxString>( aCustomName );
1630 }
1631}
1632
1633
1637 size( 0, 0 ),
1638 offset( 0, 0 ),
1640 chamfered_rect_ratio( 0.0 ),
1642 trapezoid_delta_size( 0, 0 )
1643{
1644}
1645
1646
1648{
1649 return shape == aOther.shape &&
1650 anchor_shape == aOther.anchor_shape &&
1651 size == aOther.size &&
1652 offset == aOther.offset &&
1657}
1658
1659
1661{
1662 TEST( (int) shape, (int) aOther.shape );
1663 TEST( (int) anchor_shape, (int) aOther.anchor_shape );
1664 TEST( size.x, aOther.size.x );
1665
1667 TEST( size.y, aOther.size.y );
1668
1669 TEST( offset.x, aOther.offset.x );
1670 TEST( offset.y, aOther.offset.y );
1671
1672 if( abs( round_rect_radius_ratio - aOther.round_rect_radius_ratio ) > 0.0001 )
1673 return round_rect_radius_ratio > aOther.round_rect_radius_ratio ? 1 : -1;
1674
1675 if( abs( chamfered_rect_ratio - aOther.chamfered_rect_ratio ) > 0.0001 )
1676 return chamfered_rect_ratio > aOther.chamfered_rect_ratio ? 1 : -1;
1677
1679
1680 return 0;
1681}
1682
1683
1685{
1686 if( !( shape == aOther.shape ) ) return false;
1687 if( zone_connection != aOther.zone_connection ) return false;
1688 if( thermal_spoke_width != aOther.thermal_spoke_width ) return false;
1689 if( thermal_spoke_angle != aOther.thermal_spoke_angle ) return false;
1690 if( thermal_gap != aOther.thermal_gap ) return false;
1691 if( clearance != aOther.clearance ) return false;
1692
1693 if( custom_shapes.size() != aOther.custom_shapes.size() ) return false;
1694
1695 // Deep compare of shapes?
1696 // For now, just check pointers or size
1697 return true;
1698}
1699
1700
1702{
1703 double similarity = 1.0;
1704
1705 if( shape != aOther.shape )
1706 similarity *= 0.5;
1707
1708 if( zone_connection != aOther.zone_connection )
1709 similarity *= 0.9;
1710
1712 similarity *= 0.9;
1713
1715 similarity *= 0.9;
1716
1717 if( thermal_gap != aOther.thermal_gap )
1718 similarity *= 0.9;
1719
1720 if( clearance != aOther.clearance )
1721 similarity *= 0.9;
1722
1723 if( custom_shapes != aOther.custom_shapes )
1724 similarity *= 0.5;
1725
1726 return similarity;
1727}
1728
1729
1730#define TEST_OPT( a, b, v ) \
1731 { \
1732 if( a.has_value() != b.has_value() ) \
1733 return a.has_value() - b.has_value(); \
1734 if( (int) a.value_or( v ) - (int) b.value_or( v ) != 0 ) \
1735 return (int) a.value_or( v ) - (int) b.value_or( v ); \
1736 }
1737
1738#define TEST_OPT_ORIENTATION( a, b, v ) \
1739 { \
1740 if( a.has_value() != b.has_value() ) \
1741 return a.has_value() - b.has_value(); \
1742 const EDA_ANGLE aVal = a.value_or( EDA_ORIENTATION( v ) ).GetAngle(); \
1743 const EDA_ANGLE bVal = b.value_or( EDA_ORIENTATION( v ) ).GetAngle(); \
1744 if( abs( aVal.AsDegrees() - bVal.AsDegrees() ) > 0.001 ) \
1745 return aVal.AsDegrees() > bVal.AsDegrees() ? 1 : -1; \
1746 }
1747
1748
1750{
1751 int diff;
1752
1753 if( ( diff = shape.Compare( aOther.shape ) ) != 0 )
1754 return diff;
1755
1759 TEST_OPT( thermal_gap, aOther.thermal_gap, 0 );
1760 TEST_OPT( clearance, aOther.clearance, 0 );
1761
1762 if( ( diff = (int) custom_shapes.size() - (int) aOther.custom_shapes.size() ) != 0 )
1763 return diff;
1764
1765 for( int ii = 0; ii < (int) custom_shapes.size(); ++ii )
1766 {
1767 if( ( diff = custom_shapes[ii]->Compare( aOther.custom_shapes[ii].get() ) ) != 0 )
1768 return diff;
1769 }
1770
1771 return 0;
1772}
1773
1774
1785
1786
1788{
1791
1792 if( solder_paste_margin_ratio.has_value() != aOther.solder_paste_margin_ratio.has_value() )
1793 return solder_paste_margin_ratio.has_value() - aOther.solder_paste_margin_ratio.has_value();
1794 if( abs( solder_paste_margin_ratio.value_or( 0.0 ) - aOther.solder_paste_margin_ratio.value_or( 0.0 ) ) > 0.0001 )
1795 return solder_paste_margin_ratio.value_or( 0.0 ) > aOther.solder_paste_margin_ratio.value_or( 0.0 ) ? 1 : -1;
1796
1797 TEST_OPT( has_solder_mask, aOther.has_solder_mask, false );
1798 TEST_OPT( has_solder_paste, aOther.has_solder_paste, false );
1799 TEST_OPT( has_covering, aOther.has_covering, false );
1800 TEST_OPT( has_plugging, aOther.has_plugging, false );
1801
1802 return 0;
1803}
1804
1805
1807{
1808 return size == aOther.size &&
1809 shape == aOther.shape &&
1810 start == aOther.start &&
1811 end == aOther.end &&
1812 is_filled == aOther.is_filled &&
1813 is_capped == aOther.is_capped;
1814}
1815
1816
1818{
1819 TEST( (int) shape, (int) aOther.shape );
1820
1821 TEST( size.x, aOther.size.x );
1822
1824 TEST( size.y, aOther.size.y );
1825
1826 TEST( (int) start, (int) aOther.start );
1827 TEST( (int) end, (int) aOther.end );
1828
1829 TEST_OPT( is_filled, aOther.is_filled, false );
1830 TEST_OPT( is_capped, aOther.is_capped, false );
1831
1832 return 0;
1833}
1834
1835
1837{
1838 return mode == aOther.mode &&
1839 size == aOther.size &&
1840 depth == aOther.depth &&
1841 angle == aOther.angle;
1842}
1843
1844
1846{
1848 TEST( size, aOther.size );
1849 TEST( depth, aOther.depth );
1850 TEST( angle, aOther.angle );
1851
1852 return 0;
1853}
1854
1855
1857{
1858 m_mode = aMode;
1859}
KICOMMON_API types::KiCadObjectType ToProtoEnum(KICAD_T aValue)
KICOMMON_API KICAD_T FromProtoEnum(types::KiCadObjectType aValue)
Definition api_enums.cpp:55
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
BASE_SET & set(size_t pos)
Definition base_set.h:126
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayer) const
Definition board.cpp:1120
int AsTenthsOfADegree() const
Definition eda_angle.h:117
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:153
EDA_ANGLE GetAngle() const
Definition eda_angle.h:533
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition lset.cpp:309
std::optional< bool > IsFilled() const
bool operator==(const PADSTACK &aOther) const
Definition padstack.cpp:133
std::optional< int > & Clearance(PCB_LAYER_ID aLayer=F_Cu)
void AddPrimitive(PCB_SHAPE *aShape, PCB_LAYER_ID aLayer)
Adds a custom shape primitive to the padstack.
void ReplacePrimitives(const std::vector< std::shared_ptr< PCB_SHAPE > > &aPrimitivesList, PCB_LAYER_ID aLayer)
Clears the existing primitive list (freeing the owned shapes) and adds copies of the given shapes to ...
void SetBackdrillMode(BACKDRILL_MODE aMode)
Definition padstack.cpp:547
bool Deserialize(const google::protobuf::Any &aContainer) override
Deserializes the given protobuf message into this object.
Definition padstack.cpp:464
MASK_LAYER_PROPS & FrontOuterLayers()
Definition padstack.h:381
double Similarity(const PADSTACK &aOther) const
Return a measure of how likely the other object is to represent the same object.
int RoundRectRadius(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:954
void SetDrillShape(PAD_DRILL_SHAPE aShape)
Definition padstack.cpp:894
int GetMaxHoleSize() const
std::optional< double > & SolderPasteMarginRatio(PCB_LAYER_ID aLayer=F_Cu)
void SetBackdrillSize(bool aTop, std::optional< int > aSize)
Definition padstack.cpp:582
DRILL_PROPS m_drill
!
Definition padstack.h:585
void ForEachUniqueLayer(const std::function< void(PCB_LAYER_ID)> &aMethod) const
Runs the given callable for each active unique copper layer in this padstack, meaning F_Cu for MODE::...
void SetThermalSpokeAngle(EDA_ANGLE aAngle, PCB_LAYER_ID aLayer=F_Cu)
void SetRoundRectRadiusRatio(double aRatio, PCB_LAYER_ID aLayer)
Definition padstack.cpp:948
UNCONNECTED_LAYER_MODE m_unconnectedLayerMode
Definition padstack.h:574
void ClearPrimitives(PCB_LAYER_ID aLayer)
void SetUnconnectedLayerMode(UNCONNECTED_LAYER_MODE aMode)
Definition padstack.h:376
void SetCustomName(const wxString &aCustomName)
bool HasExplicitDefinitionForLayer(PCB_LAYER_ID aLayer) const
Check if the padstack has an explicit definition for the given layer.
std::optional< bool > IsTented(PCB_LAYER_ID aSide) const
Checks if this padstack is tented (covered in soldermask) on the given side.
void SetMode(MODE aMode)
std::optional< int > & ThermalSpokeWidth(PCB_LAYER_ID aLayer=F_Cu)
std::optional< int > & SolderPasteMargin(PCB_LAYER_ID aLayer=F_Cu)
void FlipLayers(BOARD *aBoard)
Flips the padstack layers in the case that the pad's parent footprint is flipped to the other side of...
POST_MACHINING_PROPS m_backPostMachining
Definition padstack.h:594
std::optional< int > & SolderMaskMargin(PCB_LAYER_ID aLayer=F_Cu)
const DRILL_PROPS * findBackdrillDrill(bool aTop) const
Return the drill slot holding the top (aTop true) or bottom backdrill, identified by its start layer ...
Definition padstack.cpp:479
void SetChamferRatio(double aRatio, PCB_LAYER_ID aLayer)
Definition padstack.cpp:977
PCB_LAYER_ID EffectiveLayerFor(PCB_LAYER_ID aLayer) const
Determines which geometry layer should be used for the given input layer.
PADSTACK & operator=(const PADSTACK &aOther)
Definition padstack.cpp:85
void SetOffset(const VECTOR2I &aOffset, PCB_LAYER_ID aLayer)
Definition padstack.cpp:912
void SetShape(PAD_SHAPE aShape, PCB_LAYER_ID aLayer)
Definition padstack.cpp:882
EDA_ANGLE DefaultThermalSpokeAngleForShape(PCB_LAYER_ID aLayer=F_Cu) const
VECTOR2I & TrapezoidDeltaSize(PCB_LAYER_ID aLayer)
Definition padstack.cpp:930
DRILL_PROPS m_secondaryDrill
! Secondary drill, used to define back-drilling starting from the bottom side
Definition padstack.h:588
void clearBackdrillSide(bool aTop)
Clear every slot whose start layer marks it as the given backdrill side, so a malformed padstack with...
Definition padstack.cpp:517
VECTOR2I & Offset(PCB_LAYER_ID aLayer)
Definition padstack.cpp:900
MASK_LAYER_PROPS m_backMaskProps
! The overrides applied to back outer technical layers
Definition padstack.h:572
COPPER_LAYER_PROPS & CopperLayer(PCB_LAYER_ID aLayer)
EDA_ANGLE ThermalSpokeAngle(PCB_LAYER_ID aLayer=F_Cu) const
CUSTOM_SHAPE_ZONE_MODE m_customShapeInZoneMode
How to build the custom shape in zone, to create the clearance area: CUSTOM_SHAPE_ZONE_MODE::OUTLINE ...
Definition padstack.h:581
bool unpackCopperLayer(const kiapi::board::types::PadStackLayer &aProto)
Definition padstack.cpp:187
PAD_DRILL_SHAPE DrillShape() const
Definition padstack.cpp:888
void SetChamferPositions(int aPositions, PCB_LAYER_ID aLayer)
Definition padstack.cpp:995
POST_MACHINING_PROPS & FrontPostMachining()
Definition padstack.h:367
DRILL_PROPS m_tertiaryDrill
! Tertiary drill, used to define back-drilling starting from the top side
Definition padstack.h:591
void SetRoundRectRadius(double aRadius, PCB_LAYER_ID aLayer)
Definition padstack.cpp:961
std::optional< bool > IsPlugged(PCB_LAYER_ID aSide) const
LSET RelevantShapeLayers(const PADSTACK &aOther) const
Returns the set of layers that must be considered if checking one padstack against another.
std::optional< int > & ThermalGap(PCB_LAYER_ID aLayer=F_Cu)
DRILL_PROPS & TertiaryDrill()
Definition padstack.h:364
PAD_SHAPE Shape(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:876
DRILL_PROPS & Drill()
Definition padstack.h:358
void SetAnchorShape(PAD_SHAPE aShape, PCB_LAYER_ID aLayer)
Definition padstack.cpp:924
EDA_ORIENTATION m_orientation
! The rotation of the pad relative to an outer reference frame
Definition padstack.h:564
BOARD_ITEM * m_parent
! The BOARD_ITEM this PADSTACK belongs to; will be used as the parent for owned shapes
Definition padstack.h:552
PADSTACK(BOARD_ITEM *aParent)
Definition padstack.cpp:44
std::optional< bool > IsCapped() const
std::vector< PCB_LAYER_ID > UniqueLayers() const
void SetBackdrillEndLayer(bool aTop, PCB_LAYER_ID aLayer)
Definition padstack.cpp:607
LSET m_layerSet
! The board layers that this padstack is active on
Definition padstack.h:558
std::optional< int > GetBackdrillSize(bool aTop) const
Definition padstack.cpp:573
std::unordered_map< PCB_LAYER_ID, COPPER_LAYER_PROPS > m_copperProps
Definition padstack.h:566
BACKDRILL_MODE GetBackdrillMode() const
Definition padstack.cpp:529
static int Compare(const PADSTACK *aPadstackRef, const PADSTACK *aPadstackCmp)
Compare two padstacks and return 0 if they are equal.
PCB_LAYER_ID EndLayer() const
std::optional< bool > IsCovered(PCB_LAYER_ID aSide) const
DRILL_PROPS & backdrillWriteSlot(bool aTop)
Return the slot to write a backdrill side into, reusing the slot already holding that side or otherwi...
Definition padstack.cpp:499
int & ChamferPositions(PCB_LAYER_ID aLayer)
Definition padstack.cpp:983
MASK_LAYER_PROPS m_frontMaskProps
! The overrides applied to front outer technical layers
Definition padstack.h:569
const VECTOR2I & Size(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:870
MODE
! Copper geometry mode: controls how many unique copper layer shapes this padstack has
Definition padstack.h:169
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:170
@ CUSTOM
Shapes can be defined on arbitrary layers.
Definition padstack.h:172
@ FRONT_INNER_BACK
Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu)
Definition padstack.h:171
void AppendPrimitives(const std::vector< std::shared_ptr< PCB_SHAPE > > &aPrimitivesList, PCB_LAYER_ID aLayer)
Appends a copy of each shape in the given list to this padstack's custom shape list.
DRILL_PROPS & SecondaryDrill()
Definition padstack.h:361
double RoundRectRadiusRatio(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:942
PCB_LAYER_ID GetBackdrillEndLayer(bool aTop) const
Definition padstack.cpp:598
PCB_LAYER_ID StartLayer() const
POST_MACHINING_PROPS & BackPostMachining()
Definition padstack.h:370
POST_MACHINING_PROPS m_frontPostMachining
Definition padstack.h:593
std::unique_ptr< wxString > m_customName
! An override for the IPC-7351 padstack name
Definition padstack.h:561
PAD_SHAPE AnchorShape(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:918
MASK_LAYER_PROPS & BackOuterLayers()
Definition padstack.h:384
void Serialize(google::protobuf::Any &aContainer) const override
Serializes this object to the given Any message.
Definition padstack.cpp:848
wxString Name() const
! Returns the name of this padstack in IPC-7351 format
void SetSize(const VECTOR2I &aSize, PCB_LAYER_ID aLayer)
Definition padstack.cpp:856
void SetOrientation(const EDA_ANGLE &aAngle)
Definition padstack.h:356
double ChamferRatio(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:971
static constexpr PCB_LAYER_ID ALL_LAYERS
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
const wxChar * CustomName() const
static constexpr PCB_LAYER_ID INNER_LAYERS
! The layer identifier to use for "inner layers" on top/inner/bottom padstacks
Definition padstack.h:182
void SetLayerSet(const LSET &aSet)
Definition padstack.h:333
std::optional< ZONE_CONNECTION > & ZoneConnection(PCB_LAYER_ID aLayer=F_Cu)
@ NORMAL
Padstack for a footprint pad.
Definition padstack.h:162
MODE m_mode
! The copper layer variation mode this padstack is in
Definition padstack.h:555
std::vector< std::shared_ptr< PCB_SHAPE > > & Primitives(PCB_LAYER_ID aLayer)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:450
@ DEGREES_T
Definition eda_angle.h:30
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:449
#define TEST(a, b)
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayerId, int aCopperLayersCount)
Definition layer_id.cpp:181
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:844
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:867
bool IsNonCopperLayer(int aLayerId)
Test whether a layer is a non copper layer.
Definition layer_ids.h:774
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ B_Cu
Definition layer_ids.h:61
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ F_Cu
Definition layer_ids.h:60
This file contains miscellaneous commonly used macros and functions.
void PackLayerSet(google::protobuf::RepeatedField< int > &aOutput, const LSET &aLayerSet)
LSET UnpackLayerSet(const google::protobuf::RepeatedField< int > &aProtoLayerSet)
KICOMMON_API VECTOR2I UnpackVector2(const types::Vector2 &aInput, const EDA_IU_SCALE &aScale)
KICOMMON_API void PackVector2(types::Vector2 &aOutput, const VECTOR2I &aInput, const EDA_IU_SCALE &aScale)
#define TEST_OPT_ORIENTATION(a, b, v)
#define TEST_OPT(a, b, v)
PAD_DRILL_POST_MACHINING_MODE
Definition padstack.h:75
PAD_DRILL_SHAPE
The set of pad drill shapes, used with PAD::{Set,Get}DrillShape()
Definition padstack.h:68
CUSTOM_SHAPE_ZONE_MODE
Definition padstack.h:136
BACKDRILL_MODE
Definition padstack.h:83
PAD_SHAPE
The set of pad shapes, used with PAD::{Set,Get}Shape()
Definition padstack.h:51
@ ROUNDRECT
Definition padstack.h:56
@ TRAPEZOID
Definition padstack.h:55
@ RECTANGLE
Definition padstack.h:53
UNCONNECTED_LAYER_MODE
Definition padstack.h:127
#define IMPLEMENT_ENUM_TO_WXANY(type)
Definition property.h:897
The features of a padstack that can vary between copper layers All parameters are optional; leaving t...
Definition padstack.h:229
double Similarity(const COPPER_LAYER_PROPS &aOther) const
std::optional< ZONE_CONNECTION > zone_connection
Definition padstack.h:231
int Compare(const COPPER_LAYER_PROPS &aOther) const
std::optional< int > thermal_spoke_width
Definition padstack.h:232
std::vector< std::shared_ptr< PCB_SHAPE > > custom_shapes
Definition padstack.h:241
bool operator==(const COPPER_LAYER_PROPS &aOther) const
std::optional< EDA_ORIENTATION > thermal_spoke_angle
Definition padstack.h:233
std::optional< int > clearance
Definition padstack.h:235
std::optional< int > thermal_gap
Definition padstack.h:234
The properties of a padstack drill.
Definition padstack.h:272
PCB_LAYER_ID start
Definition padstack.h:275
PCB_LAYER_ID end
Definition padstack.h:276
bool operator==(const DRILL_PROPS &aOther) const
VECTOR2I size
Drill diameter (x == y) or slot dimensions (x != y)
Definition padstack.h:273
std::optional< bool > is_capped
True if the drill hole should be capped.
Definition padstack.h:279
std::optional< bool > is_filled
True if the drill hole should be filled completely.
Definition padstack.h:278
PAD_DRILL_SHAPE shape
Definition padstack.h:274
int Compare(const DRILL_PROPS &aOther) const
The features of a padstack that can vary on outer layers.
Definition padstack.h:254
bool operator==(const MASK_LAYER_PROPS &aOther) const
std::optional< int > solder_mask_margin
Definition padstack.h:255
int Compare(const MASK_LAYER_PROPS &aOther) const
std::optional< bool > has_covering
True if the pad on this side should have covering.
Definition padstack.h:261
std::optional< int > solder_paste_margin
Definition padstack.h:256
std::optional< double > solder_paste_margin_ratio
Definition padstack.h:257
std::optional< bool > has_solder_mask
True if this outer layer has mask (is not tented)
Definition padstack.h:259
std::optional< bool > has_solder_paste
True if this outer layer has solder paste.
Definition padstack.h:260
std::optional< bool > has_plugging
True if the drill hole should be plugged on this side.
Definition padstack.h:262
std::optional< PAD_DRILL_POST_MACHINING_MODE > mode
Definition padstack.h:287
bool operator==(const POST_MACHINING_PROPS &aOther) const
int Compare(const POST_MACHINING_PROPS &aOther) const
VECTOR2I trapezoid_delta_size
Delta for PAD_SHAPE::TRAPEZOID; half the delta squeezes one end and half expands the other.
Definition padstack.h:209
int Compare(const SHAPE_PROPS &aOther) const
VECTOR2I offset
Offset of the shape center from the pad center.
Definition padstack.h:199
bool operator==(const SHAPE_PROPS &aOther) const
VECTOR2I size
Size of the shape, or of the anchor pad for custom shape pads.
Definition padstack.h:190
double chamfered_rect_ratio
Size of chamfer: ratio of smallest of X,Y size.
Definition padstack.h:202
double round_rect_radius_ratio
Definition padstack.h:201
PAD_SHAPE shape
Shape of the pad.
Definition padstack.h:188
PAD_SHAPE anchor_shape
Shape of the anchor when shape == PAD_SHAPE::CUSTOM.
Definition padstack.h:189
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
@ NONE
Pads are not covered.
Definition zones.h:45