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 google::protobuf::Any& aContainer )
239{
240 using namespace kiapi::board::types;
241 PadStack padstack;
242
243 auto unpackOptional = []<typename ProtoEnum>( const ProtoEnum& aProto,
244 std::optional<bool>& aDest, ProtoEnum aTrueValue,
245 ProtoEnum aFalseValue )
246 {
247 if( aProto == aTrueValue )
248 aDest = true;
249 else if( aProto == aFalseValue )
250 aDest = false;
251 else
252 aDest = std::nullopt;
253 };
254
255 auto unpackPostMachining = []( const PostMachiningProperties& aProto,
257 {
258 switch( aProto.mode() )
259 {
260 case VDPM_NOT_POST_MACHINED: aDest.mode = PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED; break;
261 case VDPM_COUNTERBORE: aDest.mode = PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE; break;
262 case VDPM_COUNTERSINK: aDest.mode = PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK; break;
263 default: aDest.mode = std::nullopt; break;
264 }
265
266 aDest.size = aProto.size();
267 aDest.depth = aProto.depth();
268 aDest.angle = aProto.angle();
269 };
270
271 if( !aContainer.UnpackTo( &padstack ) )
272 return false;
273
274 m_mode = FromProtoEnum<MODE>( padstack.type() );
275 SetLayerSet( kiapi::board::UnpackLayerSet( padstack.layers() ) );
276 m_orientation = EDA_ANGLE( padstack.angle().value_degrees(), DEGREES_T );
277
278 Drill().size = kiapi::common::UnpackVector2( padstack.drill().diameter() );
279 Drill().start = FromProtoEnum<PCB_LAYER_ID>( padstack.drill().start_layer() );
280 Drill().end = FromProtoEnum<PCB_LAYER_ID>( padstack.drill().end_layer() );
281 unpackOptional( padstack.drill().capped(), Drill().is_capped, VDCM_CAPPED, VDCM_UNCAPPED );
282 unpackOptional( padstack.drill().filled(), Drill().is_filled, VDFM_FILLED, VDFM_UNFILLED );
283
284 if( padstack.has_front_post_machining() )
285 unpackPostMachining( padstack.front_post_machining(), FrontPostMachining() );
286
287 if( padstack.has_back_post_machining() )
288 unpackPostMachining( padstack.back_post_machining(), BackPostMachining() );
289
290 Drill().shape = FromProtoEnum<PAD_DRILL_SHAPE>( padstack.drill().shape() );
291
292 if( padstack.has_secondary_drill() )
293 {
294 const DrillProperties& secondary = padstack.secondary_drill();
295
296 SecondaryDrill().size = kiapi::common::UnpackVector2( secondary.diameter() );
297 SecondaryDrill().start = FromProtoEnum<PCB_LAYER_ID>( secondary.start_layer() );
298 SecondaryDrill().end = FromProtoEnum<PCB_LAYER_ID>( secondary.end_layer() );
299 SecondaryDrill().shape = FromProtoEnum<PAD_DRILL_SHAPE>( secondary.shape() );
300
301 unpackOptional( secondary.capped(), SecondaryDrill().is_capped, VDCM_CAPPED, VDCM_UNCAPPED );
302 unpackOptional( secondary.filled(), SecondaryDrill().is_filled, VDFM_FILLED, VDFM_UNFILLED );
303 }
304 else
305 {
306 SecondaryDrill().size = { 0, 0 };
310 SecondaryDrill().is_capped = std::nullopt;
311 SecondaryDrill().is_filled = std::nullopt;
312 }
313
314 if( padstack.has_tertiary_drill() )
315 {
316 const DrillProperties& tertiary = padstack.tertiary_drill();
317
318 TertiaryDrill().size = kiapi::common::UnpackVector2( tertiary.diameter() );
319 TertiaryDrill().start = FromProtoEnum<PCB_LAYER_ID>( tertiary.start_layer() );
320 TertiaryDrill().end = FromProtoEnum<PCB_LAYER_ID>( tertiary.end_layer() );
321 TertiaryDrill().shape = FromProtoEnum<PAD_DRILL_SHAPE>( tertiary.shape() );
322
323 unpackOptional( tertiary.capped(), TertiaryDrill().is_capped, VDCM_CAPPED, VDCM_UNCAPPED );
324 unpackOptional( tertiary.filled(), TertiaryDrill().is_filled, VDFM_FILLED, VDFM_UNFILLED );
325 }
326 else
327 {
328 TertiaryDrill().size = { 0, 0 };
332 TertiaryDrill().is_capped = std::nullopt;
333 TertiaryDrill().is_filled = std::nullopt;
334 }
335
336 for( const PadStackLayer& layer : padstack.copper_layers() )
337 {
338 if( !unpackCopperLayer( layer ) )
339 return false;
340 }
341
342 CopperLayer( ALL_LAYERS ).thermal_gap = std::nullopt;
344
345 if( padstack.has_zone_settings() )
346 {
348 FromProtoEnum<ZONE_CONNECTION>( padstack.zone_settings().zone_connection() );
349
350 if( padstack.zone_settings().has_thermal_spokes() )
351 {
352 const ThermalSpokeSettings& thermals = padstack.zone_settings().thermal_spokes();
353
354 if( thermals.has_gap() )
355 CopperLayer( ALL_LAYERS ).thermal_gap = thermals.gap().value_nm();
356
357 if( thermals.has_width() )
358 CopperLayer( ALL_LAYERS ).thermal_spoke_width = thermals.width().value_nm();
359
360 SetThermalSpokeAngle( EDA_ANGLE( thermals.angle().value_degrees(), DEGREES_T ), F_Cu );
361 }
362 }
363 else
364 {
367 }
368
370 FromProtoEnum<UNCONNECTED_LAYER_MODE>( padstack.unconnected_layer_removal() ) );
371
372 unpackOptional( padstack.front_outer_layers().solder_mask_mode(),
373 FrontOuterLayers().has_solder_mask, SMM_MASKED, SMM_UNMASKED );
374
375 unpackOptional( padstack.back_outer_layers().solder_mask_mode(),
376 BackOuterLayers().has_solder_mask, SMM_MASKED, SMM_UNMASKED );
377
378 unpackOptional( padstack.front_outer_layers().covering_mode(), FrontOuterLayers().has_covering,
379 VCM_COVERED, VCM_UNCOVERED );
380
381 unpackOptional( padstack.back_outer_layers().covering_mode(), BackOuterLayers().has_covering,
382 VCM_COVERED, VCM_UNCOVERED );
383
384 unpackOptional( padstack.front_outer_layers().plugging_mode(), FrontOuterLayers().has_plugging,
385 VPM_PLUGGED, VPM_UNPLUGGED );
386
387 unpackOptional( padstack.back_outer_layers().plugging_mode(), BackOuterLayers().has_plugging,
388 VPM_PLUGGED, VPM_UNPLUGGED );
389
390 unpackOptional( padstack.front_outer_layers().solder_paste_mode(),
391 FrontOuterLayers().has_solder_paste, SPM_PASTE, SPM_NO_PASTE );
392
393 unpackOptional( padstack.back_outer_layers().solder_paste_mode(),
394 BackOuterLayers().has_solder_paste, SPM_PASTE, SPM_NO_PASTE );
395
396 if( padstack.front_outer_layers().has_solder_mask_settings()
397 && padstack.front_outer_layers().solder_mask_settings().has_solder_mask_margin() )
398 {
400 padstack.front_outer_layers().solder_mask_settings().solder_mask_margin().value_nm();
401 }
402 else
403 {
404 FrontOuterLayers().solder_mask_margin = std::nullopt;
405 }
406
407 if( padstack.back_outer_layers().has_solder_mask_settings()
408 && padstack.back_outer_layers().solder_mask_settings().has_solder_mask_margin() )
409 {
411 padstack.back_outer_layers().solder_mask_settings().solder_mask_margin().value_nm();
412 }
413 else
414 {
415 BackOuterLayers().solder_mask_margin = std::nullopt;
416 }
417
418 if( padstack.front_outer_layers().has_solder_paste_settings()
419 && padstack.front_outer_layers().solder_paste_settings().has_solder_paste_margin() )
420 {
422 padstack.front_outer_layers().solder_paste_settings().solder_paste_margin().value_nm();
423 }
424 else
425 {
426 FrontOuterLayers().solder_paste_margin = std::nullopt;
427 }
428
429 if( padstack.back_outer_layers().has_solder_paste_settings()
430 && padstack.back_outer_layers().solder_paste_settings().has_solder_paste_margin() )
431 {
433 padstack.back_outer_layers().solder_paste_settings().solder_paste_margin().value_nm();
434 }
435 else
436 {
437 BackOuterLayers().solder_paste_margin = std::nullopt;
438 }
439
440 if( padstack.front_outer_layers().has_solder_paste_settings()
441 && padstack.front_outer_layers().solder_paste_settings().has_solder_paste_margin_ratio() )
442 {
444 padstack.front_outer_layers().solder_paste_settings().solder_paste_margin_ratio().value();
445 }
446 else
447 {
449 }
450
451 if( padstack.back_outer_layers().has_solder_paste_settings()
452 && padstack.back_outer_layers().solder_paste_settings().has_solder_paste_margin_ratio() )
453 {
455 padstack.back_outer_layers().solder_paste_settings().solder_paste_margin_ratio().value();
456 }
457 else
458 {
460 }
461
462
463 return true;
464}
465
466
467// A backdrill's side is identified by its start layer (F_Cu = top, B_Cu = bottom), not by which
468// drill slot it occupies. Reads scan both slots so a board written by KiCad 10.0 - which stored the
469// top backdrill in the tertiary slot - is understood, and writes always stamp the start layer so a
470// backdrill saved here is read back correctly by older KiCad. Presence is keyed on the drill size.
472{
473 PCB_LAYER_ID side = aTop ? F_Cu : B_Cu;
474
475 if( m_secondaryDrill.size.x > 0 && m_secondaryDrill.start == side )
476 return &m_secondaryDrill;
477
478 if( m_tertiaryDrill.size.x > 0 && m_tertiaryDrill.start == side )
479 return &m_tertiaryDrill;
480
481 return nullptr;
482}
483
484
486{
487 return const_cast<DRILL_PROPS*>( std::as_const( *this ).findBackdrillDrill( aTop ) );
488}
489
490
492{
493 if( DRILL_PROPS* existing = findBackdrillDrill( aTop ) )
494 return *existing;
495
498 PCB_LAYER_ID otherSide = aTop ? B_Cu : F_Cu;
499
500 // Displace to the other slot only when the canonical home is already taken by the opposite
501 // side, which happens when extending a backdrill on a board written by KiCad 10.0.
502 if( home.size.x > 0 && home.start == otherSide )
503 return other;
504
505 return home;
506}
507
508
510{
511 PCB_LAYER_ID side = aTop ? F_Cu : B_Cu;
512
513 if( m_secondaryDrill.start == side )
514 m_secondaryDrill.size = { 0, 0 };
515
516 if( m_tertiaryDrill.start == side )
517 m_tertiaryDrill.size = { 0, 0 };
518}
519
520
522{
523 bool hasTop = findBackdrillDrill( true ) != nullptr;
524 bool hasBottom = findBackdrillDrill( false ) != nullptr;
525
526 if( hasTop && hasBottom )
528
529 if( hasTop )
531
532 if( hasBottom )
534
536}
537
538
540{
541 auto apply = [this]( bool aTop, bool aWant )
542 {
543 if( aWant )
544 {
545 DRILL_PROPS& drill = backdrillWriteSlot( aTop );
546 drill.start = aTop ? F_Cu : B_Cu;
548
549 if( drill.size.x <= 0 )
550 drill.size = m_drill.size * 1.1; // Backdrill slightly larger than main drill
551 }
552 else
553 {
554 clearBackdrillSide( aTop );
555 }
556 };
557
558 apply( true, aMode == BACKDRILL_MODE::BACKDRILL_TOP || aMode == BACKDRILL_MODE::BACKDRILL_BOTH );
559 apply( false, aMode == BACKDRILL_MODE::BACKDRILL_BOTTOM || aMode == BACKDRILL_MODE::BACKDRILL_BOTH );
560}
561
562
563std::optional<int> PADSTACK::GetBackdrillSize( bool aTop ) const
564{
565 if( const DRILL_PROPS* drill = findBackdrillDrill( aTop ) )
566 return drill->size.x;
567
568 return std::nullopt;
569}
570
571
572void PADSTACK::SetBackdrillSize( bool aTop, std::optional<int> aSize )
573{
574 if( aSize.has_value() )
575 {
576 DRILL_PROPS& target = backdrillWriteSlot( aTop );
577 target.size = { *aSize, *aSize };
579 target.start = aTop ? F_Cu : B_Cu;
580 }
581 else
582 {
583 clearBackdrillSide( aTop );
584 }
585}
586
587
589{
590 if( const DRILL_PROPS* drill = findBackdrillDrill( aTop ) )
591 return drill->end;
592
593 return UNDEFINED_LAYER;
594}
595
596
598{
599 // A backdrill with no must-cut layer does not exist (matching the via layer sanitizer), so
600 // clearing the must-cut clears the side rather than leaving a sizeful drill with no end.
601 if( aLayer == UNDEFINED_LAYER )
602 {
603 clearBackdrillSide( aTop );
604 return;
605 }
606
607 DRILL_PROPS& target = backdrillWriteSlot( aTop );
608
609 target.end = aLayer;
610 target.start = aTop ? F_Cu : B_Cu;
611}
612
613void PADSTACK::Serialize( google::protobuf::Any& aContainer ) const
614{
615 using namespace kiapi::board::types;
616 PadStack padstack;
617
618 padstack.set_type( ToProtoEnum<MODE, PadStackType>( m_mode ) );
619 kiapi::board::PackLayerSet( *padstack.mutable_layers(), m_layerSet );
620 padstack.mutable_angle()->set_value_degrees( m_orientation.AsDegrees() );
621
622 kiapi::common::PackVector2( *padstack.mutable_drill()->mutable_diameter(), m_drill.size );
623 padstack.mutable_drill()->set_start_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_drill.start ) );
624 padstack.mutable_drill()->set_end_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_drill.end ) );
625 padstack.mutable_drill()->set_shape( ToProtoEnum<PAD_DRILL_SHAPE, kiapi::board::types::DrillShape>( m_drill.shape ) );
626
627 if( m_drill.is_capped.has_value() )
628 padstack.mutable_drill()->set_capped( m_drill.is_capped.value() ? VDCM_CAPPED : VDCM_UNCAPPED );
629
630 if( m_drill.is_filled.has_value() )
631 padstack.mutable_drill()->set_filled( m_drill.is_filled.value() ? VDFM_FILLED : VDFM_UNFILLED );
632
633 if( m_secondaryDrill.size.x > 0 )
634 {
635 DrillProperties* secondary = padstack.mutable_secondary_drill();
636 kiapi::common::PackVector2( *secondary->mutable_diameter(), m_secondaryDrill.size );
637 secondary->set_start_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_secondaryDrill.start ) );
638 secondary->set_end_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_secondaryDrill.end ) );
640
641 if( m_secondaryDrill.is_capped.has_value() )
642 secondary->set_capped( m_secondaryDrill.is_capped.value() ? VDCM_CAPPED : VDCM_UNCAPPED );
643
644 if( m_secondaryDrill.is_filled.has_value() )
645 secondary->set_filled( m_secondaryDrill.is_filled.value() ? VDFM_FILLED : VDFM_UNFILLED );
646 }
647
648 if( m_tertiaryDrill.size.x > 0 )
649 {
650 DrillProperties* tertiary = padstack.mutable_tertiary_drill();
651 kiapi::common::PackVector2( *tertiary->mutable_diameter(), m_tertiaryDrill.size );
652 tertiary->set_start_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_tertiaryDrill.start ) );
653 tertiary->set_end_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_tertiaryDrill.end ) );
655
656 if( m_tertiaryDrill.is_capped.has_value() )
657 tertiary->set_capped( m_tertiaryDrill.is_capped.value() ? VDCM_CAPPED : VDCM_UNCAPPED );
658
659 if( m_tertiaryDrill.is_filled.has_value() )
660 tertiary->set_filled( m_tertiaryDrill.is_filled.value() ? VDFM_FILLED : VDFM_UNFILLED );
661 }
662
663 auto packPostMachining = []( const PADSTACK::POST_MACHINING_PROPS& aProps,
664 PostMachiningProperties* aProto )
665 {
666 if( aProps.mode.has_value() )
667 {
668 switch( aProps.mode.value() )
669 {
670 case PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED: aProto->set_mode( VDPM_NOT_POST_MACHINED ); break;
671 case PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE: aProto->set_mode( VDPM_COUNTERBORE ); break;
672 case PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK: aProto->set_mode( VDPM_COUNTERSINK ); break;
673 default: break;
674 }
675 }
676
677 aProto->set_size( aProps.size );
678 aProto->set_depth( aProps.depth );
679 aProto->set_angle( aProps.angle );
680 };
681
682 if( m_frontPostMachining.mode.has_value() || m_frontPostMachining.size > 0 )
683 packPostMachining( m_frontPostMachining, padstack.mutable_front_post_machining() );
684
685 if( m_backPostMachining.mode.has_value() || m_backPostMachining.size > 0 )
686 packPostMachining( m_backPostMachining, padstack.mutable_back_post_machining() );
687
689 [&]( PCB_LAYER_ID aLayer )
690 {
691 PadStackLayer* layer = padstack.add_copper_layers();
692 const COPPER_LAYER_PROPS& props = CopperLayer( aLayer );
693
694 layer->set_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( aLayer ) );
695 kiapi::common::PackVector2( *layer->mutable_size(), props.shape.size );
697 kiapi::common::PackVector2( *layer->mutable_offset(), props.shape.offset );
698 layer->set_custom_anchor_shape( ToProtoEnum<PAD_SHAPE, kiapi::board::types::PadStackShape>( props.shape.anchor_shape ) );
699 layer->set_chamfer_ratio( props.shape.chamfered_rect_ratio );
700 layer->set_corner_rounding_ratio( props.shape.round_rect_radius_ratio );
701
702 if( props.shape.shape == PAD_SHAPE::TRAPEZOID )
703 kiapi::common::PackVector2( *layer->mutable_trapezoid_delta(), props.shape.trapezoid_delta_size );
704
706 layer->mutable_chamfered_corners()->set_top_left( true );
707
709 layer->mutable_chamfered_corners()->set_top_right( true );
710
712 layer->mutable_chamfered_corners()->set_bottom_left( true );
713
715 layer->mutable_chamfered_corners()->set_bottom_right( true );
716
717 for( const std::shared_ptr<PCB_SHAPE>& shape : props.custom_shapes )
718 {
719 google::protobuf::Any a;
720 shape->Serialize( a );
721 a.UnpackTo( layer->add_custom_shapes() );
722 }
723 } );
724
725 if( CopperLayer( ALL_LAYERS ).zone_connection.has_value() )
726 {
727 padstack.mutable_zone_settings()->set_zone_connection(
729 }
730
731 if( CopperLayer( ALL_LAYERS ).thermal_gap.has_value() )
732 {
733 padstack.mutable_zone_settings()->mutable_thermal_spokes()->mutable_gap()->set_value_nm(
734 CopperLayer( ALL_LAYERS ).thermal_gap.value() );
735 }
736
737 if( CopperLayer( ALL_LAYERS ).thermal_spoke_width.has_value() )
738 {
739 padstack.mutable_zone_settings()->mutable_thermal_spokes()->mutable_width()->set_value_nm(
740 CopperLayer( ALL_LAYERS ).thermal_spoke_width.value() );
741 }
742
743 if( CopperLayer( ALL_LAYERS ).thermal_spoke_angle.has_value() )
744 {
745 padstack.mutable_zone_settings()->mutable_thermal_spokes()->mutable_angle()->set_value_degrees(
746 CopperLayer( ALL_LAYERS ).thermal_spoke_angle.value().AsDegrees() );
747 }
748
749 padstack.set_unconnected_layer_removal( ToProtoEnum<UNCONNECTED_LAYER_MODE,
750 kiapi::board::types::UnconnectedLayerRemoval>( m_unconnectedLayerMode ) );
751
752 if( FrontOuterLayers().has_solder_mask.has_value() )
753 {
754 padstack.mutable_front_outer_layers()->set_solder_mask_mode(
755 FrontOuterLayers().has_solder_mask.value() ? SMM_MASKED : SMM_UNMASKED );
756 }
757
758 if( BackOuterLayers().has_solder_mask.has_value() )
759 {
760 padstack.mutable_back_outer_layers()->set_solder_mask_mode(
761 BackOuterLayers().has_solder_mask.value() ? SMM_MASKED : SMM_UNMASKED );
762 }
763
764 if( FrontOuterLayers().has_covering.has_value() )
765 {
766 padstack.mutable_front_outer_layers()->set_covering_mode(
767 FrontOuterLayers().has_covering.value() ? VCM_COVERED : VCM_UNCOVERED );
768 }
769
770 if( BackOuterLayers().has_covering.has_value() )
771 {
772 padstack.mutable_back_outer_layers()->set_covering_mode(
773 BackOuterLayers().has_covering.value() ? VCM_COVERED : VCM_UNCOVERED );
774 }
775
776 if( FrontOuterLayers().has_plugging.has_value() )
777 {
778 padstack.mutable_front_outer_layers()->set_plugging_mode(
779 FrontOuterLayers().has_plugging.value() ? VPM_PLUGGED : VPM_UNPLUGGED );
780 }
781
782 if( BackOuterLayers().has_plugging.has_value() )
783 {
784 padstack.mutable_back_outer_layers()->set_plugging_mode(
785 BackOuterLayers().has_plugging.value() ? VPM_PLUGGED : VPM_UNPLUGGED );
786 }
787
788 if( FrontOuterLayers().has_solder_paste.has_value() )
789 {
790 padstack.mutable_front_outer_layers()->set_solder_paste_mode(
791 FrontOuterLayers().has_solder_paste.value() ? SPM_PASTE : SPM_NO_PASTE );
792 }
793
794 if( BackOuterLayers().has_solder_paste.has_value() )
795 {
796 padstack.mutable_back_outer_layers()->set_solder_paste_mode(
797 BackOuterLayers().has_solder_paste.value() ? SPM_PASTE : SPM_NO_PASTE );
798 }
799
800 if( FrontOuterLayers().solder_mask_margin.has_value() )
801 {
802 padstack.mutable_front_outer_layers()->mutable_solder_mask_settings()->mutable_solder_mask_margin()->set_value_nm(
803 FrontOuterLayers().solder_mask_margin.value() );
804 }
805
806 if( BackOuterLayers().solder_mask_margin.has_value() )
807 {
808 padstack.mutable_back_outer_layers()->mutable_solder_mask_settings()->mutable_solder_mask_margin()->set_value_nm(
809 BackOuterLayers().solder_mask_margin.value() );
810 }
811
812 if( FrontOuterLayers().solder_paste_margin.has_value() )
813 {
814 padstack.mutable_front_outer_layers()->mutable_solder_paste_settings()->mutable_solder_paste_margin()->set_value_nm(
815 FrontOuterLayers().solder_paste_margin.value() );
816 }
817
818 if( BackOuterLayers().solder_paste_margin.has_value() )
819 {
820 padstack.mutable_back_outer_layers()->mutable_solder_paste_settings()->mutable_solder_paste_margin()->set_value_nm(
821 BackOuterLayers().solder_paste_margin.value() );
822 }
823
824 if( FrontOuterLayers().solder_paste_margin_ratio.has_value() )
825 {
826 padstack.mutable_front_outer_layers()->mutable_solder_paste_settings()->mutable_solder_paste_margin_ratio()->set_value(
827 FrontOuterLayers().solder_paste_margin_ratio.value() );
828 }
829
830 if( BackOuterLayers().solder_paste_margin_ratio.has_value() )
831 {
832 padstack.mutable_back_outer_layers()->mutable_solder_paste_settings()->mutable_solder_paste_margin_ratio()->set_value(
833 BackOuterLayers().solder_paste_margin_ratio.value() );
834 }
835
836 aContainer.PackFrom( padstack );
837}
838
839
840void PADSTACK::SetSize( const VECTOR2I& aSize, PCB_LAYER_ID aLayer )
841{
842 VECTOR2I size = aSize;
843
844 if( size.x < 0 )
845 size.x = 0;
846
847 if( size.y < 0 )
848 size.y = 0;
849
850 CopperLayer( aLayer ).shape.size = size;
851}
852
853
855{
856 return CopperLayer( aLayer ).shape.size;
857}
858
859
861{
862 return CopperLayer( aLayer ).shape.shape;
863}
864
865
867{
868 CopperLayer( aLayer ).shape.shape = aShape;
869}
870
871
873{
874 return m_drill.shape;
875}
876
877
879{
880 m_drill.shape = aShape;
881}
882
883
885{
886 return CopperLayer( aLayer ).shape.offset;
887}
888
889
891{
892 return CopperLayer( aLayer ).shape.offset;
893}
894
895
897{
898 return CopperLayer( aLayer ).shape.anchor_shape;
899}
900
901
903{
904 CopperLayer( aLayer ).shape.anchor_shape = aShape;
905}
906
907
912
913
915{
916 return CopperLayer( aLayer ).shape.trapezoid_delta_size;
917}
918
919
921{
923}
924
925
927{
928 CopperLayer( aLayer ).shape.round_rect_radius_ratio = aRatio;
929}
930
931
933{
934 const VECTOR2I& size = Size( aLayer );
935 return KiROUND( std::min( size.x, size.y ) * RoundRectRadiusRatio( aLayer ) );
936}
937
938
939void PADSTACK::SetRoundRectRadius( double aRadius, PCB_LAYER_ID aLayer )
940{
941 const VECTOR2I& size = Size( aLayer );
942 int min_r = std::min( size.x, size.y );
943
944 if( min_r > 0 )
945 SetRoundRectRadiusRatio( aRadius / min_r, aLayer );
946}
947
948
950{
951 return CopperLayer( aLayer ).shape.chamfered_rect_ratio;
952}
953
954
955void PADSTACK::SetChamferRatio( double aRatio, PCB_LAYER_ID aLayer )
956{
957 CopperLayer( aLayer ).shape.chamfered_rect_ratio = aRatio;
958}
959
960
965
966
967const int& PADSTACK::ChamferPositions( PCB_LAYER_ID aLayer ) const
968{
970}
971
972
973void PADSTACK::SetChamferPositions( int aPositions, PCB_LAYER_ID aLayer )
974{
975 CopperLayer( aLayer ).shape.chamfered_rect_positions = aPositions;
976}
977
978
979std::optional<int>& PADSTACK::Clearance( PCB_LAYER_ID aLayer )
980{
981 return CopperLayer( aLayer ).clearance;
982}
983
984
985const std::optional<int>& PADSTACK::Clearance( PCB_LAYER_ID aLayer ) const
986{
987 return CopperLayer( aLayer ).clearance;
988}
989
990
991std::optional<int>& PADSTACK::SolderMaskMargin( PCB_LAYER_ID aLayer )
992{
993 if( IsFrontLayer( aLayer ) )
995 else if( IsBackLayer( aLayer ) )
997 else
998 return FrontOuterLayers().solder_mask_margin; // Should not happen
999}
1000
1001
1002const std::optional<int>& PADSTACK::SolderMaskMargin( PCB_LAYER_ID aLayer ) const
1003{
1004 if( IsFrontLayer( aLayer ) )
1006 else if( IsBackLayer( aLayer ) )
1008 else
1009 return FrontOuterLayers().solder_mask_margin; // Should not happen
1010}
1011
1012
1013std::optional<int>& PADSTACK::SolderPasteMargin( PCB_LAYER_ID aLayer )
1014{
1015 if( IsFrontLayer( aLayer ) )
1017 else if( IsBackLayer( aLayer ) )
1019 else
1020 return FrontOuterLayers().solder_paste_margin; // Should not happen
1021}
1022
1023
1024const std::optional<int>& PADSTACK::SolderPasteMargin( PCB_LAYER_ID aLayer ) const
1025{
1026 if( IsFrontLayer( aLayer ) )
1028 else if( IsBackLayer( aLayer ) )
1030 else
1031 return FrontOuterLayers().solder_paste_margin; // Should not happen
1032}
1033
1034
1035std::optional<double>& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer )
1036{
1037 if( IsFrontLayer( aLayer ) )
1039 else if( IsBackLayer( aLayer ) )
1041 else
1042 return FrontOuterLayers().solder_paste_margin_ratio; // Should not happen
1043}
1044
1045
1046const std::optional<double>& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer ) const
1047{
1048 if( IsFrontLayer( aLayer ) )
1050 else if( IsBackLayer( aLayer ) )
1052 else
1053 return FrontOuterLayers().solder_paste_margin_ratio; // Should not happen
1054}
1055
1056
1057std::optional<ZONE_CONNECTION>& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer )
1058{
1059 return CopperLayer( aLayer ).zone_connection;
1060}
1061
1062
1063const std::optional<ZONE_CONNECTION>& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer ) const
1064{
1065 return CopperLayer( aLayer ).zone_connection;
1066}
1067
1068
1069std::optional<int>& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer )
1070{
1071 return CopperLayer( aLayer ).thermal_spoke_width;
1072}
1073
1074
1075const std::optional<int>& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer ) const
1076{
1077 return CopperLayer( aLayer ).thermal_spoke_width;
1078}
1079
1080
1081std::optional<int>& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer )
1082{
1083 return CopperLayer( aLayer ).thermal_gap;
1084}
1085
1086
1087const std::optional<int>& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer ) const
1088{
1089 return CopperLayer( aLayer ).thermal_gap;
1090}
1091
1092
1094{
1095 if( Shape( aLayer ) == PAD_SHAPE::OVAL || Shape( aLayer ) == PAD_SHAPE::RECTANGLE
1096 || Shape( aLayer ) == PAD_SHAPE::ROUNDRECT || Shape( aLayer ) == PAD_SHAPE::CHAMFERED_RECT )
1097 {
1098 return ANGLE_90;
1099 }
1100
1101 return ANGLE_45;
1102}
1103
1104
1106{
1107 if( CopperLayer( aLayer ).thermal_spoke_angle.has_value() )
1108 return CopperLayer( aLayer ).thermal_spoke_angle.value();
1109
1110 return DefaultThermalSpokeAngleForShape( aLayer );
1111}
1112
1113
1115{
1116 CopperLayer( aLayer ).thermal_spoke_angle = aAngle;
1117}
1118
1119
1120std::vector<std::shared_ptr<PCB_SHAPE>>& PADSTACK::Primitives( PCB_LAYER_ID aLayer )
1121{
1122 return CopperLayer( aLayer ).custom_shapes;
1123}
1124
1125
1126const std::vector<std::shared_ptr<PCB_SHAPE>>& PADSTACK::Primitives( PCB_LAYER_ID aLayer ) const
1127{
1128 return CopperLayer( aLayer ).custom_shapes;
1129}
1130
1131
1133{
1134 CopperLayer( aLayer ).custom_shapes.emplace_back( aShape );
1135}
1136
1137
1138void PADSTACK::AppendPrimitives( const std::vector<std::shared_ptr<PCB_SHAPE>>& aList,
1139 PCB_LAYER_ID aLayer )
1140{
1141 std::vector<std::shared_ptr<PCB_SHAPE>>& list = CopperLayer( aLayer ).custom_shapes;
1142
1143 for( const std::shared_ptr<PCB_SHAPE>& item : aList )
1144 {
1145 PCB_SHAPE* new_shape = static_cast<PCB_SHAPE*>( item->Clone() );
1146 new_shape->SetParent( m_parent );
1147 list.emplace_back( new_shape );
1148 }
1149}
1150
1151
1152void PADSTACK::ReplacePrimitives( const std::vector<std::shared_ptr<PCB_SHAPE>>& aList,
1153 PCB_LAYER_ID aLayer )
1154{
1155 ClearPrimitives( aLayer );
1156 AppendPrimitives( aList, aLayer );
1157}
1158
1159
1161{
1162 CopperLayer( aLayer ).custom_shapes.clear();
1163}
1164
1165
1167{
1168 if( m_mode == MODE::NORMAL )
1169 return m_copperProps[ALL_LAYERS];
1170
1172 {
1173 if( IsFrontLayer( aLayer ) )
1174 return m_copperProps[F_Cu];
1175 else if( IsBackLayer( aLayer ) )
1176 return m_copperProps[B_Cu];
1177 else
1179 }
1180
1181 return m_copperProps[aLayer];
1182}
1183
1184
1186{
1188 {
1189 if( IsFrontLayer( aLayer ) && m_copperProps.contains( F_Cu ) )
1190 return m_copperProps.at( F_Cu );
1191 else if( IsBackLayer( aLayer ) && m_copperProps.contains( B_Cu ) )
1192 return m_copperProps.at( B_Cu );
1193 else if( m_copperProps.contains( INNER_LAYERS ) )
1194 return m_copperProps.at( INNER_LAYERS );
1195 }
1196 else if( m_mode == MODE::CUSTOM )
1197 {
1198 if( IsFrontLayer( aLayer ) && m_copperProps.contains( F_Cu ) )
1199 return m_copperProps.at( F_Cu );
1200 else if( IsBackLayer( aLayer ) && m_copperProps.contains( B_Cu ) )
1201 return m_copperProps.at( B_Cu );
1202
1203 if( m_copperProps.count( aLayer ) )
1204 return m_copperProps.at( aLayer );
1205
1206 // For CUSTOM mode, fall back to ALL_LAYERS if available (e.g. for layers not yet
1207 // explicitly defined). If ALL_LAYERS is also absent (e.g. after a FlipLayers()
1208 // that renamed the only entry from F_Cu to B_Cu), return whatever entry is first.
1209 if( m_copperProps.count( ALL_LAYERS ) )
1210 return m_copperProps.at( ALL_LAYERS );
1211
1212 wxASSERT( !m_copperProps.empty() );
1213 return m_copperProps.begin()->second;
1214 }
1215
1216 return m_copperProps.at( ALL_LAYERS );
1217}
1218
1219
1220void PADSTACK::ForEachUniqueLayer( const std::function<void( PCB_LAYER_ID )>& aMethod ) const
1221{
1222 if( m_mode == MODE::NORMAL )
1223 {
1224 aMethod( ALL_LAYERS );
1225 }
1226 else if( m_mode == MODE::FRONT_INNER_BACK )
1227 {
1228 aMethod( F_Cu );
1229 aMethod( INNER_LAYERS );
1230 aMethod( B_Cu );
1231 }
1232 else
1233 {
1234 for( const auto& [layer, props] : m_copperProps )
1235 aMethod( layer );
1236 }
1237}
1238
1239
1240std::vector<PCB_LAYER_ID> PADSTACK::UniqueLayers() const
1241{
1242 std::vector<PCB_LAYER_ID> layers;
1243
1245 [&]( PCB_LAYER_ID layer )
1246 {
1247 layers.push_back( layer );
1248 } );
1249
1250 return layers;
1251}
1252
1253
1255{
1256 if( m_mode == MODE::NORMAL )
1257 return ALL_LAYERS;
1258
1259 if( m_mode == MODE::FRONT_INNER_BACK || IsNonCopperLayer( aLayer ) )
1260 {
1261 PCB_LAYER_ID candidate;
1262
1263 if( IsFrontLayer( aLayer ) )
1264 candidate = F_Cu;
1265 else if( IsBackLayer( aLayer ) )
1266 candidate = B_Cu;
1267 else
1268 candidate = INNER_LAYERS;
1269
1270 // FRONT_INNER_BACK always has all three sides.
1271 // In CUSTOM mode only return the side if the pad actually defines it.
1272 if( m_mode == MODE::FRONT_INNER_BACK || m_copperProps.count( candidate ) )
1273 return candidate;
1274 }
1275
1276 if( m_copperProps.count( aLayer ) )
1277 return aLayer;
1278
1279 // For CUSTOM mode, if ALL_LAYERS is present use it as the default; otherwise return the
1280 // first available layer (e.g. after FlipLayers renamed ALL_LAYERS from F_Cu to B_Cu).
1281 if( m_copperProps.count( ALL_LAYERS ) )
1282 return ALL_LAYERS;
1283
1284 wxASSERT( !m_copperProps.empty() );
1285 return m_copperProps.begin()->first;
1286}
1287
1288
1290{
1291 LSET layers;
1292
1293 if( m_mode == MODE::NORMAL && aOther.m_mode == MODE::NORMAL )
1294 {
1295 layers.set( ALL_LAYERS );
1296 }
1297 else
1298 {
1300 [&]( PCB_LAYER_ID layer )
1301 {
1302 layers.set( layer );
1303 } );
1304 aOther.ForEachUniqueLayer(
1305 [&]( PCB_LAYER_ID layer )
1306 {
1307 layers.set( layer );
1308 } );
1309 }
1310
1311 return layers;
1312}
1313
1314
1315std::optional<bool> PADSTACK::IsTented( PCB_LAYER_ID aSide ) const
1316{
1317 if( IsFrontLayer( aSide ) )
1319 else if( IsBackLayer( aSide ) )
1321 else
1322 return std::nullopt;
1323}
1324
1325
1326std::optional<bool> PADSTACK::IsCovered( PCB_LAYER_ID aSide ) const
1327{
1328 if( IsFrontLayer( aSide ) )
1330 else if( IsBackLayer( aSide ) )
1332 else
1333 return std::nullopt;
1334}
1335
1336
1337std::optional<bool> PADSTACK::IsPlugged( PCB_LAYER_ID aSide ) const
1338{
1339 if( IsFrontLayer( aSide ) )
1341 else if( IsBackLayer( aSide ) )
1343 else
1344 return std::nullopt;
1345}
1346
1347
1348std::optional<bool> PADSTACK::IsCapped() const
1349{
1350 return m_drill.is_capped;
1351}
1352
1353
1354std::optional<bool> PADSTACK::IsFilled() const
1355{
1356 return m_drill.is_filled;
1357}
1358
1359
1360#define TEST( a, b ) { if( a != b ) return a - b; }
1361
1362
1363int PADSTACK::Compare( const PADSTACK* aLeft, const PADSTACK* aRight )
1364{
1365 int diff;
1366
1367 TEST( (int) aLeft->m_mode, (int) aRight->m_mode );
1368
1369 if( aLeft->m_layerSet != aRight->m_layerSet )
1370 return aLeft->m_layerSet.Seq() < aRight->m_layerSet.Seq();
1371
1372 if( ( diff = wxString( aLeft->CustomName() ).Cmp( aRight->CustomName() ) ) != 0 )
1373 return diff;
1374
1376
1377 if( ( diff = aLeft->m_frontMaskProps.Compare( aRight->m_frontMaskProps ) ) != 0 )
1378 return diff;
1379
1380 if( ( diff = aLeft->m_backMaskProps.Compare( aRight->m_backMaskProps ) ) != 0 )
1381 return diff;
1382
1383 TEST( (int) aLeft->m_unconnectedLayerMode, (int) aRight->m_unconnectedLayerMode );
1384 TEST( (int) aLeft->m_customShapeInZoneMode, (int) aRight->m_customShapeInZoneMode );
1385
1386 if( ( diff = aLeft->m_drill.Compare( aRight->m_drill ) ) != 0 )
1387 return diff;
1388
1389 if( ( diff = aLeft->m_secondaryDrill.Compare( aRight->m_secondaryDrill ) ) != 0 )
1390 return diff;
1391
1392 if( ( diff = aLeft->m_tertiaryDrill.Compare( aRight->m_tertiaryDrill ) ) != 0 )
1393 return diff;
1394
1395 if( ( diff = aLeft->m_frontPostMachining.Compare( aRight->m_frontPostMachining ) ) != 0 )
1396 return diff;
1397
1398 if( ( diff = aLeft->m_backPostMachining.Compare( aRight->m_backPostMachining ) ) != 0 )
1399 return diff;
1400
1401 aLeft->ForEachUniqueLayer(
1402 [&]( PCB_LAYER_ID aLayer )
1403 {
1404 if( diff != 0 ) // we want to return the first non-matching layer
1405 return;
1406
1407 diff = aLeft->CopperLayer( aLayer ).Compare( aRight->CopperLayer( aLayer ) );
1408 } );
1409
1410 if( diff != 0 )
1411 return diff;
1412
1413 return 0;
1414}
1415
1416
1418{
1419 return m_copperProps.count( aLayer ) > 0;
1420}
1421
1422
1423double PADSTACK::Similarity( const PADSTACK& aOther ) const
1424{
1425 double similarity = 1.0;
1426
1427 if( m_mode != aOther.m_mode )
1428 similarity *= 0.9;
1429
1430 if( m_layerSet != aOther.m_layerSet )
1431 similarity *= 0.9;
1432
1433 if( CustomName() != aOther.CustomName() )
1434 similarity *= 0.9;
1435
1436 if( m_orientation != aOther.m_orientation )
1437 similarity *= 0.9;
1438
1439 if( m_frontMaskProps != aOther.m_frontMaskProps )
1440 similarity *= 0.9;
1441
1442 if( m_backMaskProps != aOther.m_backMaskProps )
1443 similarity *= 0.9;
1444
1446 similarity *= 0.9;
1447
1449 similarity *= 0.9;
1450
1451 if( m_drill != aOther.m_drill )
1452 similarity *= 0.9;
1453
1454 if( m_secondaryDrill != aOther.m_secondaryDrill )
1455 similarity *= 0.9;
1456
1457 if( m_tertiaryDrill != aOther.m_tertiaryDrill )
1458 similarity *= 0.9;
1459
1461 similarity *= 0.9;
1462
1464 similarity *= 0.9;
1465
1467 [&]( PCB_LAYER_ID aLayer )
1468 {
1469 similarity *= CopperLayer( aLayer ).Similarity( aOther.CopperLayer( aLayer ) );
1470 } );
1471
1472 return similarity;
1473}
1474
1475
1477{
1479 {
1480 std::unordered_map<PCB_LAYER_ID, COPPER_LAYER_PROPS> oldCopperProps = m_copperProps;
1481 m_copperProps.clear();
1482
1483 m_copperProps[aBoard->FlipLayer( F_Cu )] = oldCopperProps[F_Cu];
1484 m_copperProps[INNER_LAYERS] = oldCopperProps[INNER_LAYERS];
1485 m_copperProps[aBoard->FlipLayer( B_Cu )] = oldCopperProps[B_Cu];
1486 }
1487 else if( m_mode == MODE::CUSTOM )
1488 {
1489 std::unordered_map<PCB_LAYER_ID, COPPER_LAYER_PROPS> oldCopperProps = m_copperProps;
1490 m_copperProps.clear();
1491
1492 for( const auto& [layer, props] : oldCopperProps )
1493 m_copperProps[aBoard->FlipLayer( layer )] = props;
1494 }
1495
1496 std::swap( m_frontMaskProps, m_backMaskProps );
1497
1498 m_drill.start = aBoard->FlipLayer( m_drill.start );
1499 m_drill.end = aBoard->FlipLayer( m_drill.end );
1500
1501 m_secondaryDrill.start = aBoard->FlipLayer( m_secondaryDrill.start );
1502 m_secondaryDrill.end = aBoard->FlipLayer( m_secondaryDrill.end );
1503
1504 m_tertiaryDrill.start = aBoard->FlipLayer( m_tertiaryDrill.start );
1505 m_tertiaryDrill.end = aBoard->FlipLayer( m_tertiaryDrill.end );
1506
1508}
1509
1510
1512{
1513 return m_drill.start;
1514}
1515
1516
1518{
1519 return m_drill.end;
1520}
1521
1522
1523wxString PADSTACK::Name() const
1524{
1525 return CustomName();
1526}
1527
1528
1529const wxChar* PADSTACK::CustomName() const
1530{
1531 if( m_customName )
1532 return m_customName->wx_str();
1533
1534 return wxEmptyString;
1535}
1536
1537
1538void PADSTACK::SetCustomName( const wxString& aCustomName )
1539{
1540 if( aCustomName.IsEmpty() )
1541 {
1542 m_customName.reset();
1543 }
1544 else if( m_customName )
1545 {
1546 *m_customName = aCustomName;
1547 }
1548 else
1549 {
1550 m_customName = std::make_unique<wxString>( aCustomName );
1551 }
1552}
1553
1554
1558 size( 0, 0 ),
1559 offset( 0, 0 ),
1561 chamfered_rect_ratio( 0.0 ),
1563 trapezoid_delta_size( 0, 0 )
1564{
1565}
1566
1567
1569{
1570 return shape == aOther.shape &&
1571 anchor_shape == aOther.anchor_shape &&
1572 size == aOther.size &&
1573 offset == aOther.offset &&
1578}
1579
1580
1582{
1583 TEST( (int) shape, (int) aOther.shape );
1584 TEST( (int) anchor_shape, (int) aOther.anchor_shape );
1585 TEST( size.x, aOther.size.x );
1586
1588 TEST( size.y, aOther.size.y );
1589
1590 TEST( offset.x, aOther.offset.x );
1591 TEST( offset.y, aOther.offset.y );
1592
1593 if( abs( round_rect_radius_ratio - aOther.round_rect_radius_ratio ) > 0.0001 )
1594 return round_rect_radius_ratio > aOther.round_rect_radius_ratio ? 1 : -1;
1595
1596 if( abs( chamfered_rect_ratio - aOther.chamfered_rect_ratio ) > 0.0001 )
1597 return chamfered_rect_ratio > aOther.chamfered_rect_ratio ? 1 : -1;
1598
1600
1601 return 0;
1602}
1603
1604
1606{
1607 if( !( shape == aOther.shape ) ) return false;
1608 if( zone_connection != aOther.zone_connection ) return false;
1609 if( thermal_spoke_width != aOther.thermal_spoke_width ) return false;
1610 if( thermal_spoke_angle != aOther.thermal_spoke_angle ) return false;
1611 if( thermal_gap != aOther.thermal_gap ) return false;
1612 if( clearance != aOther.clearance ) return false;
1613
1614 if( custom_shapes.size() != aOther.custom_shapes.size() ) return false;
1615
1616 // Deep compare of shapes?
1617 // For now, just check pointers or size
1618 return true;
1619}
1620
1621
1623{
1624 double similarity = 1.0;
1625
1626 if( shape != aOther.shape )
1627 similarity *= 0.5;
1628
1629 if( zone_connection != aOther.zone_connection )
1630 similarity *= 0.9;
1631
1633 similarity *= 0.9;
1634
1636 similarity *= 0.9;
1637
1638 if( thermal_gap != aOther.thermal_gap )
1639 similarity *= 0.9;
1640
1641 if( clearance != aOther.clearance )
1642 similarity *= 0.9;
1643
1644 if( custom_shapes != aOther.custom_shapes )
1645 similarity *= 0.5;
1646
1647 return similarity;
1648}
1649
1650
1651#define TEST_OPT( a, b, v ) \
1652 { \
1653 if( a.has_value() != b.has_value() ) \
1654 return a.has_value() - b.has_value(); \
1655 if( (int) a.value_or( v ) - (int) b.value_or( v ) != 0 ) \
1656 return (int) a.value_or( v ) - (int) b.value_or( v ); \
1657 }
1658
1659#define TEST_OPT_ANGLE( a, b, v ) \
1660 { \
1661 if( a.has_value() != b.has_value() ) \
1662 return a.has_value() - b.has_value(); \
1663 if( abs( a.value_or( v ).AsDegrees() - b.value_or( v ).AsDegrees() ) > 0.001 ) \
1664 return a.value_or( v ).AsDegrees() > b.value_or( v ).AsDegrees() ? 1 : -1; \
1665 }
1666
1667
1669{
1670 int diff;
1671
1672 if( ( diff = shape.Compare( aOther.shape ) ) != 0 )
1673 return diff;
1674
1678 TEST_OPT( thermal_gap, aOther.thermal_gap, 0 );
1679 TEST_OPT( clearance, aOther.clearance, 0 );
1680
1681 if( ( diff = (int) custom_shapes.size() - (int) aOther.custom_shapes.size() ) != 0 )
1682 return diff;
1683
1684 for( int ii = 0; ii < (int) custom_shapes.size(); ++ii )
1685 {
1686 if( ( diff = custom_shapes[ii]->Compare( aOther.custom_shapes[ii].get() ) ) != 0 )
1687 return diff;
1688 }
1689
1690 return 0;
1691}
1692
1693
1704
1705
1707{
1710
1711 if( solder_paste_margin_ratio.has_value() != aOther.solder_paste_margin_ratio.has_value() )
1712 return solder_paste_margin_ratio.has_value() - aOther.solder_paste_margin_ratio.has_value();
1713 if( abs( solder_paste_margin_ratio.value_or( 0.0 ) - aOther.solder_paste_margin_ratio.value_or( 0.0 ) ) > 0.0001 )
1714 return solder_paste_margin_ratio.value_or( 0.0 ) > aOther.solder_paste_margin_ratio.value_or( 0.0 ) ? 1 : -1;
1715
1716 TEST_OPT( has_solder_mask, aOther.has_solder_mask, false );
1717 TEST_OPT( has_solder_paste, aOther.has_solder_paste, false );
1718 TEST_OPT( has_covering, aOther.has_covering, false );
1719 TEST_OPT( has_plugging, aOther.has_plugging, false );
1720
1721 return 0;
1722}
1723
1724
1726{
1727 return size == aOther.size &&
1728 shape == aOther.shape &&
1729 start == aOther.start &&
1730 end == aOther.end &&
1731 is_filled == aOther.is_filled &&
1732 is_capped == aOther.is_capped;
1733}
1734
1735
1737{
1738 TEST( (int) shape, (int) aOther.shape );
1739
1740 TEST( size.x, aOther.size.x );
1741
1743 TEST( size.y, aOther.size.y );
1744
1745 TEST( (int) start, (int) aOther.start );
1746 TEST( (int) end, (int) aOther.end );
1747
1748 TEST_OPT( is_filled, aOther.is_filled, false );
1749 TEST_OPT( is_capped, aOther.is_capped, false );
1750
1751 return 0;
1752}
1753
1754
1756{
1757 return mode == aOther.mode &&
1758 size == aOther.size &&
1759 depth == aOther.depth &&
1760 angle == aOther.angle;
1761}
1762
1763
1765{
1767 TEST( size, aOther.size );
1768 TEST( depth, aOther.depth );
1769 TEST( angle, aOther.angle );
1770
1771 return 0;
1772}
1773
1774
1776{
1777 m_mode = aMode;
1778}
types::KiCadObjectType ToProtoEnum(KICAD_T aValue)
KICAD_T FromProtoEnum(types::KiCadObjectType aValue)
Definition api_enums.cpp:47
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
BASE_SET & set(size_t pos)
Definition base_set.h:116
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayer) const
Definition board.cpp:978
int AsTenthsOfADegree() const
Definition eda_angle.h:118
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:89
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)
Definition padstack.cpp:979
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:539
bool Deserialize(const google::protobuf::Any &aContainer) override
Deserializes the given protobuf message into this object.
Definition padstack.cpp:238
MASK_LAYER_PROPS & FrontOuterLayers()
Definition padstack.h:372
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:932
void SetDrillShape(PAD_DRILL_SHAPE aShape)
Definition padstack.cpp:878
std::optional< double > & SolderPasteMarginRatio(PCB_LAYER_ID aLayer=F_Cu)
void SetBackdrillSize(bool aTop, std::optional< int > aSize)
Definition padstack.cpp:572
DRILL_PROPS m_drill
!
Definition padstack.h:580
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:926
UNCONNECTED_LAYER_MODE m_unconnectedLayerMode
Definition padstack.h:569
void ClearPrimitives(PCB_LAYER_ID aLayer)
void SetUnconnectedLayerMode(UNCONNECTED_LAYER_MODE aMode)
Definition padstack.h:367
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:589
std::optional< int > & SolderMaskMargin(PCB_LAYER_ID aLayer=F_Cu)
Definition padstack.cpp:991
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:471
void SetChamferRatio(double aRatio, PCB_LAYER_ID aLayer)
Definition padstack.cpp:955
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 SetShape(PAD_SHAPE aShape, PCB_LAYER_ID aLayer)
Definition padstack.cpp:866
EDA_ANGLE DefaultThermalSpokeAngleForShape(PCB_LAYER_ID aLayer=F_Cu) const
VECTOR2I & TrapezoidDeltaSize(PCB_LAYER_ID aLayer)
Definition padstack.cpp:908
DRILL_PROPS m_secondaryDrill
! Secondary drill, used to define back-drilling starting from the bottom side
Definition padstack.h:583
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:509
VECTOR2I & Offset(PCB_LAYER_ID aLayer)
Definition padstack.cpp:884
MASK_LAYER_PROPS m_backMaskProps
! The overrides applied to back outer technical layers
Definition padstack.h:567
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:576
bool unpackCopperLayer(const kiapi::board::types::PadStackLayer &aProto)
Definition padstack.cpp:187
PAD_DRILL_SHAPE DrillShape() const
Definition padstack.cpp:872
void SetChamferPositions(int aPositions, PCB_LAYER_ID aLayer)
Definition padstack.cpp:973
POST_MACHINING_PROPS & FrontPostMachining()
Definition padstack.h:360
DRILL_PROPS m_tertiaryDrill
! Tertiary drill, used to define back-drilling starting from the top side
Definition padstack.h:586
void SetRoundRectRadius(double aRadius, PCB_LAYER_ID aLayer)
Definition padstack.cpp:939
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:357
PAD_SHAPE Shape(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:860
DRILL_PROPS & Drill()
Definition padstack.h:351
void SetAnchorShape(PAD_SHAPE aShape, PCB_LAYER_ID aLayer)
Definition padstack.cpp:902
BOARD_ITEM * m_parent
! The BOARD_ITEM this PADSTACK belongs to; will be used as the parent for owned shapes
Definition padstack.h:545
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:597
LSET m_layerSet
! The board layers that this padstack is active on
Definition padstack.h:551
std::optional< int > GetBackdrillSize(bool aTop) const
Definition padstack.cpp:563
std::unordered_map< PCB_LAYER_ID, COPPER_LAYER_PROPS > m_copperProps
! The properties applied to copper layers if they aren't overridden
Definition padstack.h:561
BACKDRILL_MODE GetBackdrillMode() const
Definition padstack.cpp:521
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:491
int & ChamferPositions(PCB_LAYER_ID aLayer)
Definition padstack.cpp:961
MASK_LAYER_PROPS m_frontMaskProps
! The overrides applied to front outer technical layers
Definition padstack.h:564
const VECTOR2I & Size(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:854
MODE
! Copper geometry mode: controls how many unique copper layer shapes this padstack has
Definition padstack.h:170
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:171
@ CUSTOM
Shapes can be defined on arbitrary layers.
Definition padstack.h:173
@ FRONT_INNER_BACK
Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu)
Definition padstack.h:172
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:354
double RoundRectRadiusRatio(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:920
PCB_LAYER_ID GetBackdrillEndLayer(bool aTop) const
Definition padstack.cpp:588
PCB_LAYER_ID StartLayer() const
POST_MACHINING_PROPS & BackPostMachining()
Definition padstack.h:363
POST_MACHINING_PROPS m_frontPostMachining
Definition padstack.h:588
std::unique_ptr< wxString > m_customName
! An override for the IPC-7351 padstack name
Definition padstack.h:554
PAD_SHAPE AnchorShape(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:896
MASK_LAYER_PROPS & BackOuterLayers()
Definition padstack.h:375
void Serialize(google::protobuf::Any &aContainer) const override
Serializes this object to the given Any message.
Definition padstack.cpp:613
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:840
double ChamferRatio(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:949
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
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:180
void SetLayerSet(const LSET &aSet)
Definition padstack.h:324
std::optional< ZONE_CONNECTION > & ZoneConnection(PCB_LAYER_ID aLayer=F_Cu)
@ NORMAL
Padstack for a footprint pad.
Definition padstack.h:163
MODE m_mode
! The copper layer variation mode this padstack is in
Definition padstack.h:548
EDA_ANGLE m_orientation
! The rotation of the pad relative to an outer reference frame
Definition padstack.h:557
std::vector< std::shared_ptr< PCB_SHAPE > > & Primitives(PCB_LAYER_ID aLayer)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:412
#define TEST(a, b)
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:778
bool IsBackLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a back layer.
Definition layer_ids.h:801
bool IsNonCopperLayer(int aLayerId)
Test whether a layer is a non copper layer.
Definition layer_ids.h:708
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_ANGLE(a, b, v)
#define TEST_OPT(a, b, v)
PAD_DRILL_POST_MACHINING_MODE
Definition padstack.h:76
PAD_DRILL_SHAPE
The set of pad drill shapes, used with PAD::{Set,Get}DrillShape()
Definition padstack.h:69
CUSTOM_SHAPE_ZONE_MODE
Definition padstack.h:137
BACKDRILL_MODE
Definition padstack.h:84
PAD_SHAPE
The set of pad shapes, used with PAD::{Set,Get}Shape()
Definition padstack.h:52
@ CHAMFERED_RECT
Definition padstack.h:60
@ ROUNDRECT
Definition padstack.h:57
@ TRAPEZOID
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:54
UNCONNECTED_LAYER_MODE
Definition padstack.h:128
#define IMPLEMENT_ENUM_TO_WXANY(type)
Definition property.h:826
The features of a padstack that can vary between copper layers All parameters are optional; leaving t...
Definition padstack.h:227
double Similarity(const COPPER_LAYER_PROPS &aOther) const
std::optional< ZONE_CONNECTION > zone_connection
Definition padstack.h:229
int Compare(const COPPER_LAYER_PROPS &aOther) const
std::optional< int > thermal_spoke_width
Definition padstack.h:230
std::vector< std::shared_ptr< PCB_SHAPE > > custom_shapes
Definition padstack.h:239
bool operator==(const COPPER_LAYER_PROPS &aOther) const
std::optional< EDA_ANGLE > thermal_spoke_angle
Definition padstack.h:231
std::optional< int > clearance
Definition padstack.h:233
std::optional< int > thermal_gap
Definition padstack.h:232
! The properties of a padstack drill. Drill position is always the pad position (origin).
Definition padstack.h:266
PCB_LAYER_ID start
Definition padstack.h:269
PCB_LAYER_ID end
Definition padstack.h:270
bool operator==(const DRILL_PROPS &aOther) const
VECTOR2I size
Drill diameter (x == y) or slot dimensions (x != y)
Definition padstack.h:267
std::optional< bool > is_capped
True if the drill hole should be capped.
Definition padstack.h:273
std::optional< bool > is_filled
True if the drill hole should be filled completely.
Definition padstack.h:272
PAD_DRILL_SHAPE shape
Definition padstack.h:268
int Compare(const DRILL_PROPS &aOther) const
bool operator==(const MASK_LAYER_PROPS &aOther) const
std::optional< int > solder_mask_margin
Definition padstack.h:251
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:257
std::optional< int > solder_paste_margin
Definition padstack.h:252
std::optional< double > solder_paste_margin_ratio
Definition padstack.h:253
std::optional< bool > has_solder_mask
True if this outer layer has mask (is not tented)
Definition padstack.h:255
std::optional< bool > has_solder_paste
True if this outer layer has solder paste.
Definition padstack.h:256
std::optional< bool > has_plugging
True if the drill hole should be plugged on this side.
Definition padstack.h:258
std::optional< PAD_DRILL_POST_MACHINING_MODE > mode
Definition padstack.h:281
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:207
int Compare(const SHAPE_PROPS &aOther) const
VECTOR2I offset
Offset of the shape center from the pad center.
Definition padstack.h:197
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:188
double chamfered_rect_ratio
Size of chamfer: ratio of smallest of X,Y size.
Definition padstack.h:200
double round_rect_radius_ratio
Definition padstack.h:199
PAD_SHAPE shape
Shape of the pad.
Definition padstack.h:186
PAD_SHAPE anchor_shape
Shape of the anchor when shape == PAD_SHAPE::CUSTOM.
Definition padstack.h:187
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
@ NONE
Pads are not covered.
Definition zones.h:45