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 <convert_basic_shapes_to_polygon.h> // RECT_CHAMFER_POSITIONS
22#include "padstack.h"
23#include <api/api_enums.h>
24#include <api/api_utils.h>
25#include <api/api_pcb_utils.h>
26#include <api/board/board_types.pb.h>
27#include <layer_range.h>
28#include <macros.h>
29#include <magic_enum.hpp>
30#include <pad.h>
31#include <board.h>
32#include <pcb_shape.h>
33#include <properties/property.h>
35
36
40
41
67
68
70{
71 m_parent = aOther.m_parent;
72 *this = aOther;
73
75 [&]( PCB_LAYER_ID aLayer )
76 {
77 for( std::shared_ptr<PCB_SHAPE>& shape : CopperLayer( aLayer ).custom_shapes )
78 shape->SetParent( m_parent );
79 } );
80}
81
82
84{
85 // NOTE: m_parent is not copied from operator=, because this operator is commonly used to
86 // update the padstack properties, and such an update must not change the parent PAD to point to
87 // the parent of some different padstack.
88
89 m_mode = aOther.m_mode;
90 m_layerSet = aOther.m_layerSet;
91 SetCustomName( aOther.CustomName() );
98 m_drill = aOther.m_drill;
103
104 // Data consistency enforcement logic that used to live in the pad properties dialog.
105 // While it might be tempting to put these in the individual property setters, there's no
106 // well-defined order in which they're called, and many of the consistency checks are
107 // between multiple properties.
108
110 [&]( PCB_LAYER_ID aLayer )
111 {
112 PAD_SHAPE shape = Shape( aLayer );
113
114 // Make sure leftover primitives don't stick around
115 ClearPrimitives( aLayer );
116
117 // For custom pad shape, duplicate primitives of the pad to copy
118 if( shape == PAD_SHAPE::CUSTOM )
119 ReplacePrimitives( aOther.Primitives( aLayer ), aLayer );
120
121 // rounded rect pads with radius ratio = 0 are in fact rect pads.
122 // So set the right shape (and perhaps issues with a radius = 0)
123 if( shape == PAD_SHAPE::ROUNDRECT && RoundRectRadiusRatio( aLayer ) == 0.0 )
125 } );
126
127 return *this;
128}
129
130
131bool PADSTACK::operator==( const PADSTACK& aOther ) const
132{
133 if( m_mode != aOther.m_mode )
134 return false;
135
136 if( m_layerSet != aOther.m_layerSet )
137 return false;
138
139 if( CustomName() != aOther.CustomName() )
140 return false;
141
142 if( m_orientation != aOther.m_orientation )
143 return false;
144
145 if( m_frontMaskProps != aOther.m_frontMaskProps )
146 return false;
147
148 if( m_backMaskProps != aOther.m_backMaskProps )
149 return false;
150
152 return false;
153
155 return false;
156
157 if( m_drill != aOther.m_drill )
158 return false;
159
160 if( m_secondaryDrill != aOther.m_secondaryDrill )
161 return false;
162
163 if( m_tertiaryDrill != aOther.m_tertiaryDrill )
164 return false;
165
167 return false;
168
170 return false;
171
172 bool copperMatches = true;
173
175 [&]( PCB_LAYER_ID aLayer )
176 {
177 if( CopperLayer( aLayer ) != aOther.CopperLayer( aLayer ) )
178 copperMatches = false;
179 } );
180
181 return copperMatches;
182}
183
184
185bool PADSTACK::unpackCopperLayer( const kiapi::board::types::PadStackLayer& aProto )
186{
187 using namespace kiapi::board::types;
189
190 if( m_mode == MODE::NORMAL && layer != ALL_LAYERS )
191 return false;
192
193 if( m_mode == MODE::FRONT_INNER_BACK && layer != F_Cu && layer != INNER_LAYERS && layer != B_Cu )
194 return false;
195
196 SetSize( kiapi::common::UnpackVector2( aProto.size() ), layer );
197 SetShape( FromProtoEnum<PAD_SHAPE>( aProto.shape() ), layer );
198 Offset( layer ) = kiapi::common::UnpackVector2( aProto.offset() );
199 SetAnchorShape( FromProtoEnum<PAD_SHAPE>( aProto.custom_anchor_shape() ), layer );
200
201 SHAPE_PROPS& props = CopperLayer( layer ).shape;
202 props.chamfered_rect_ratio = aProto.chamfer_ratio();
203 props.round_rect_radius_ratio = aProto.corner_rounding_ratio();
204
205 if( Shape( layer ) == PAD_SHAPE::TRAPEZOID && aProto.has_trapezoid_delta() )
206 TrapezoidDeltaSize( layer ) = kiapi::common::UnpackVector2( aProto.trapezoid_delta() );
207
208 if( aProto.chamfered_corners().top_left() )
210
211 if( aProto.chamfered_corners().top_right() )
213
214 if( aProto.chamfered_corners().bottom_left() )
216
217 if( aProto.chamfered_corners().bottom_right() )
219
220 ClearPrimitives( layer );
221 google::protobuf::Any a;
222
223 for( const BoardGraphicShape& shapeProto : aProto.custom_shapes() )
224 {
225 a.PackFrom( shapeProto );
226 std::unique_ptr<PCB_SHAPE> shape = std::make_unique<PCB_SHAPE>( m_parent );
227
228 if( shape->Deserialize( a ) )
229 AddPrimitive( shape.release(), layer );
230 }
231
232 return true;
233}
234
235
236bool PADSTACK::Deserialize( const google::protobuf::Any& aContainer )
237{
238 using namespace kiapi::board::types;
239 PadStack padstack;
240
241 auto unpackOptional = []<typename ProtoEnum>( const ProtoEnum& aProto,
242 std::optional<bool>& aDest, ProtoEnum aTrueValue,
243 ProtoEnum aFalseValue )
244 {
245 if( aProto == aTrueValue )
246 aDest = true;
247 else if( aProto == aFalseValue )
248 aDest = false;
249 else
250 aDest = std::nullopt;
251 };
252
253 auto unpackPostMachining = []( const PostMachiningProperties& aProto,
255 {
256 switch( aProto.mode() )
257 {
258 case VDPM_NOT_POST_MACHINED: aDest.mode = PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED; break;
259 case VDPM_COUNTERBORE: aDest.mode = PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE; break;
260 case VDPM_COUNTERSINK: aDest.mode = PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK; break;
261 default: aDest.mode = std::nullopt; break;
262 }
263
264 aDest.size = aProto.size();
265 aDest.depth = aProto.depth();
266 aDest.angle = aProto.angle();
267 };
268
269 if( !aContainer.UnpackTo( &padstack ) )
270 return false;
271
272 m_mode = FromProtoEnum<MODE>( padstack.type() );
273 SetLayerSet( kiapi::board::UnpackLayerSet( padstack.layers() ) );
274 m_orientation = EDA_ANGLE( padstack.angle().value_degrees(), DEGREES_T );
275
276 Drill().size = kiapi::common::UnpackVector2( padstack.drill().diameter() );
277 Drill().start = FromProtoEnum<PCB_LAYER_ID>( padstack.drill().start_layer() );
278 Drill().end = FromProtoEnum<PCB_LAYER_ID>( padstack.drill().end_layer() );
279 unpackOptional( padstack.drill().capped(), Drill().is_capped, VDCM_CAPPED, VDCM_UNCAPPED );
280 unpackOptional( padstack.drill().filled(), Drill().is_filled, VDFM_FILLED, VDFM_UNFILLED );
281
282 if( padstack.has_front_post_machining() )
283 unpackPostMachining( padstack.front_post_machining(), FrontPostMachining() );
284
285 if( padstack.has_back_post_machining() )
286 unpackPostMachining( padstack.back_post_machining(), BackPostMachining() );
287
288 Drill().shape = FromProtoEnum<PAD_DRILL_SHAPE>( padstack.drill().shape() );
289
290 if( padstack.has_secondary_drill() )
291 {
292 const DrillProperties& secondary = padstack.secondary_drill();
293
294 SecondaryDrill().size = kiapi::common::UnpackVector2( secondary.diameter() );
295 SecondaryDrill().start = FromProtoEnum<PCB_LAYER_ID>( secondary.start_layer() );
296 SecondaryDrill().end = FromProtoEnum<PCB_LAYER_ID>( secondary.end_layer() );
297 SecondaryDrill().shape = FromProtoEnum<PAD_DRILL_SHAPE>( secondary.shape() );
298
299 unpackOptional( secondary.capped(), SecondaryDrill().is_capped, VDCM_CAPPED, VDCM_UNCAPPED );
300 unpackOptional( secondary.filled(), SecondaryDrill().is_filled, VDFM_FILLED, VDFM_UNFILLED );
301 }
302 else
303 {
304 SecondaryDrill().size = { 0, 0 };
308 SecondaryDrill().is_capped = std::nullopt;
309 SecondaryDrill().is_filled = std::nullopt;
310 }
311
312 if( padstack.has_tertiary_drill() )
313 {
314 const DrillProperties& tertiary = padstack.tertiary_drill();
315
316 TertiaryDrill().size = kiapi::common::UnpackVector2( tertiary.diameter() );
317 TertiaryDrill().start = FromProtoEnum<PCB_LAYER_ID>( tertiary.start_layer() );
318 TertiaryDrill().end = FromProtoEnum<PCB_LAYER_ID>( tertiary.end_layer() );
319 TertiaryDrill().shape = FromProtoEnum<PAD_DRILL_SHAPE>( tertiary.shape() );
320
321 unpackOptional( tertiary.capped(), TertiaryDrill().is_capped, VDCM_CAPPED, VDCM_UNCAPPED );
322 unpackOptional( tertiary.filled(), TertiaryDrill().is_filled, VDFM_FILLED, VDFM_UNFILLED );
323 }
324 else
325 {
326 TertiaryDrill().size = { 0, 0 };
330 TertiaryDrill().is_capped = std::nullopt;
331 TertiaryDrill().is_filled = std::nullopt;
332 }
333
334 for( const PadStackLayer& layer : padstack.copper_layers() )
335 {
336 if( !unpackCopperLayer( layer ) )
337 return false;
338 }
339
340 CopperLayer( ALL_LAYERS ).thermal_gap = std::nullopt;
342
343 if( padstack.has_zone_settings() )
344 {
346 FromProtoEnum<ZONE_CONNECTION>( padstack.zone_settings().zone_connection() );
347
348 if( padstack.zone_settings().has_thermal_spokes() )
349 {
350 const ThermalSpokeSettings& thermals = padstack.zone_settings().thermal_spokes();
351
352 if( thermals.has_gap() )
353 CopperLayer( ALL_LAYERS ).thermal_gap = thermals.gap().value_nm();
354
355 if( thermals.has_width() )
356 CopperLayer( ALL_LAYERS ).thermal_spoke_width = thermals.width().value_nm();
357
358 SetThermalSpokeAngle( EDA_ANGLE( thermals.angle().value_degrees(), DEGREES_T ), F_Cu );
359 }
360 }
361 else
362 {
365 }
366
368 FromProtoEnum<UNCONNECTED_LAYER_MODE>( padstack.unconnected_layer_removal() ) );
369
370 unpackOptional( padstack.front_outer_layers().solder_mask_mode(),
371 FrontOuterLayers().has_solder_mask, SMM_MASKED, SMM_UNMASKED );
372
373 unpackOptional( padstack.back_outer_layers().solder_mask_mode(),
374 BackOuterLayers().has_solder_mask, SMM_MASKED, SMM_UNMASKED );
375
376 unpackOptional( padstack.front_outer_layers().covering_mode(), FrontOuterLayers().has_covering,
377 VCM_COVERED, VCM_UNCOVERED );
378
379 unpackOptional( padstack.back_outer_layers().covering_mode(), BackOuterLayers().has_covering,
380 VCM_COVERED, VCM_UNCOVERED );
381
382 unpackOptional( padstack.front_outer_layers().plugging_mode(), FrontOuterLayers().has_plugging,
383 VPM_PLUGGED, VPM_UNPLUGGED );
384
385 unpackOptional( padstack.back_outer_layers().plugging_mode(), BackOuterLayers().has_plugging,
386 VPM_PLUGGED, VPM_UNPLUGGED );
387
388 unpackOptional( padstack.front_outer_layers().solder_paste_mode(),
389 FrontOuterLayers().has_solder_paste, SPM_PASTE, SPM_NO_PASTE );
390
391 unpackOptional( padstack.back_outer_layers().solder_paste_mode(),
392 BackOuterLayers().has_solder_paste, SPM_PASTE, SPM_NO_PASTE );
393
394 if( padstack.front_outer_layers().has_solder_mask_settings()
395 && padstack.front_outer_layers().solder_mask_settings().has_solder_mask_margin() )
396 {
398 padstack.front_outer_layers().solder_mask_settings().solder_mask_margin().value_nm();
399 }
400 else
401 {
402 FrontOuterLayers().solder_mask_margin = std::nullopt;
403 }
404
405 if( padstack.back_outer_layers().has_solder_mask_settings()
406 && padstack.back_outer_layers().solder_mask_settings().has_solder_mask_margin() )
407 {
409 padstack.back_outer_layers().solder_mask_settings().solder_mask_margin().value_nm();
410 }
411 else
412 {
413 BackOuterLayers().solder_mask_margin = std::nullopt;
414 }
415
416 if( padstack.front_outer_layers().has_solder_paste_settings()
417 && padstack.front_outer_layers().solder_paste_settings().has_solder_paste_margin() )
418 {
420 padstack.front_outer_layers().solder_paste_settings().solder_paste_margin().value_nm();
421 }
422 else
423 {
424 FrontOuterLayers().solder_paste_margin = std::nullopt;
425 }
426
427 if( padstack.back_outer_layers().has_solder_paste_settings()
428 && padstack.back_outer_layers().solder_paste_settings().has_solder_paste_margin() )
429 {
431 padstack.back_outer_layers().solder_paste_settings().solder_paste_margin().value_nm();
432 }
433 else
434 {
435 BackOuterLayers().solder_paste_margin = std::nullopt;
436 }
437
438 if( padstack.front_outer_layers().has_solder_paste_settings()
439 && padstack.front_outer_layers().solder_paste_settings().has_solder_paste_margin_ratio() )
440 {
442 padstack.front_outer_layers().solder_paste_settings().solder_paste_margin_ratio().value();
443 }
444 else
445 {
447 }
448
449 if( padstack.back_outer_layers().has_solder_paste_settings()
450 && padstack.back_outer_layers().solder_paste_settings().has_solder_paste_margin_ratio() )
451 {
453 padstack.back_outer_layers().solder_paste_settings().solder_paste_margin_ratio().value();
454 }
455 else
456 {
458 }
459
460
461 return true;
462}
463
464
466{
467 bool hasSecondary = m_secondaryDrill.size.x > 0;
468 bool hasTertiary = m_tertiaryDrill.size.x > 0;
469
470 if( !hasSecondary && !hasTertiary )
472
473 if( hasSecondary && hasTertiary )
475
476 if( hasSecondary )
477 {
478 if( m_secondaryDrill.start == F_Cu )
480
482 }
483
484 if( hasTertiary )
485 {
486 if( m_tertiaryDrill.start == B_Cu )
488
490 }
491
493}
494
495
497{
498 auto initDrill = [this]( DRILL_PROPS& aDrill, PCB_LAYER_ID aStart )
499 {
500 if( aDrill.size.x <= 0 )
501 {
502 aDrill.size = m_drill.size * 1.1; // Backdrill slightly larger than main drill
504 aDrill.start = aStart;
505 aDrill.end = aStart;
506 }
507 else
508 {
509 aDrill.start = aStart;
510 }
511 };
513 {
514 m_tertiaryDrill.size = { 0, 0 };
515 }
516
518 {
519 m_secondaryDrill.size = { 0, 0 };
520 }
521
523 {
524 m_secondaryDrill.start = B_Cu;
525
526 if( m_secondaryDrill.size.x > 0 ) { /* ok */ }
527 else initDrill( m_secondaryDrill, B_Cu );
528 }
529
531 {
532 m_tertiaryDrill.start = F_Cu;
533
534 if( m_tertiaryDrill.size.x > 0 ) { /* ok */ }
535 else initDrill( m_tertiaryDrill, F_Cu );
536 }
537}
538
539
540std::optional<int> PADSTACK::GetBackdrillSize( bool aTop ) const
541{
542 if( m_secondaryDrill.size.x > 0 )
543 {
544 if( aTop && m_secondaryDrill.start == F_Cu ) return m_secondaryDrill.size.x;
545 if( !aTop && m_secondaryDrill.start == B_Cu ) return m_secondaryDrill.size.x;
546 }
547 if( m_tertiaryDrill.size.x > 0 )
548 {
549 if( aTop && m_tertiaryDrill.start == F_Cu ) return m_tertiaryDrill.size.x;
550 if( !aTop && m_tertiaryDrill.start == B_Cu ) return m_tertiaryDrill.size.x;
551 }
552 return std::nullopt;
553}
554
555
556void PADSTACK::SetBackdrillSize( bool aTop, std::optional<int> aSize )
557{
558 DRILL_PROPS* target = nullptr;
559
560 if( aTop )
561 {
562 if( m_secondaryDrill.start == F_Cu ) target = &m_secondaryDrill;
563 else if( m_tertiaryDrill.start == F_Cu ) target = &m_tertiaryDrill;
564 }
565 else
566 {
567 if( m_secondaryDrill.start == B_Cu ) target = &m_secondaryDrill;
568 else if( m_tertiaryDrill.start == B_Cu ) target = &m_tertiaryDrill;
569 }
570
571 if( !target )
572 {
573 if( m_secondaryDrill.size.x <= 0 ) target = &m_secondaryDrill;
574 else if( m_tertiaryDrill.size.x <= 0 ) target = &m_tertiaryDrill;
575 }
576
577 if( !target )
578 {
579 if( aTop ) target = &m_tertiaryDrill;
580 else target = &m_secondaryDrill;
581 }
582
583 if( aSize.has_value() )
584 {
585 target->size = { *aSize, *aSize };
587 target->start = aTop ? F_Cu : B_Cu;
588 if( target->end == UNDEFINED_LAYER ) target->end = UNDEFINED_LAYER;
589 }
590 else
591 {
592 target->size = { 0, 0 };
593 }
594}
595
596
598{
599 if( m_secondaryDrill.size.x > 0 )
600 {
601 if( aTop && m_secondaryDrill.start == F_Cu ) return m_secondaryDrill.end;
602 if( !aTop && m_secondaryDrill.start == B_Cu ) return m_secondaryDrill.end;
603 }
604 if( m_tertiaryDrill.size.x > 0 )
605 {
606 if( aTop && m_tertiaryDrill.start == F_Cu ) return m_tertiaryDrill.end;
607 if( !aTop && m_tertiaryDrill.start == B_Cu ) return m_tertiaryDrill.end;
608 }
609 return UNDEFINED_LAYER;
610}
611
612
614{
615 DRILL_PROPS* target = nullptr;
616
617 if( aTop )
618 {
619 if( m_secondaryDrill.start == F_Cu ) target = &m_secondaryDrill;
620 else if( m_tertiaryDrill.start == F_Cu ) target = &m_tertiaryDrill;
621 }
622 else
623 {
624 if( m_secondaryDrill.start == B_Cu ) target = &m_secondaryDrill;
625 else if( m_tertiaryDrill.start == B_Cu ) target = &m_tertiaryDrill;
626 }
627
628 if( target )
629 {
630 target->end = aLayer;
631
632 if( aLayer == UNDEFINED_LAYER )
633 target->size = { 0, 0 };
634 }
635}
636
637void PADSTACK::Serialize( google::protobuf::Any& aContainer ) const
638{
639 using namespace kiapi::board::types;
640 PadStack padstack;
641
642 padstack.set_type( ToProtoEnum<MODE, PadStackType>( m_mode ) );
643 kiapi::board::PackLayerSet( *padstack.mutable_layers(), m_layerSet );
644 padstack.mutable_angle()->set_value_degrees( m_orientation.AsDegrees() );
645
646 kiapi::common::PackVector2( *padstack.mutable_drill()->mutable_diameter(), m_drill.size );
647 padstack.mutable_drill()->set_start_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_drill.start ) );
648 padstack.mutable_drill()->set_end_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_drill.end ) );
649 padstack.mutable_drill()->set_shape( ToProtoEnum<PAD_DRILL_SHAPE, kiapi::board::types::DrillShape>( m_drill.shape ) );
650
651 if( m_drill.is_capped.has_value() )
652 padstack.mutable_drill()->set_capped( m_drill.is_capped.value() ? VDCM_CAPPED : VDCM_UNCAPPED );
653
654 if( m_drill.is_filled.has_value() )
655 padstack.mutable_drill()->set_filled( m_drill.is_filled.value() ? VDFM_FILLED : VDFM_UNFILLED );
656
657 if( m_secondaryDrill.size.x > 0 )
658 {
659 DrillProperties* secondary = padstack.mutable_secondary_drill();
660 kiapi::common::PackVector2( *secondary->mutable_diameter(), m_secondaryDrill.size );
661 secondary->set_start_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_secondaryDrill.start ) );
662 secondary->set_end_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_secondaryDrill.end ) );
664
665 if( m_secondaryDrill.is_capped.has_value() )
666 secondary->set_capped( m_secondaryDrill.is_capped.value() ? VDCM_CAPPED : VDCM_UNCAPPED );
667
668 if( m_secondaryDrill.is_filled.has_value() )
669 secondary->set_filled( m_secondaryDrill.is_filled.value() ? VDFM_FILLED : VDFM_UNFILLED );
670 }
671
672 if( m_tertiaryDrill.size.x > 0 )
673 {
674 DrillProperties* tertiary = padstack.mutable_tertiary_drill();
675 kiapi::common::PackVector2( *tertiary->mutable_diameter(), m_tertiaryDrill.size );
676 tertiary->set_start_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_tertiaryDrill.start ) );
677 tertiary->set_end_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( m_tertiaryDrill.end ) );
679
680 if( m_tertiaryDrill.is_capped.has_value() )
681 tertiary->set_capped( m_tertiaryDrill.is_capped.value() ? VDCM_CAPPED : VDCM_UNCAPPED );
682
683 if( m_tertiaryDrill.is_filled.has_value() )
684 tertiary->set_filled( m_tertiaryDrill.is_filled.value() ? VDFM_FILLED : VDFM_UNFILLED );
685 }
686
687 auto packPostMachining = []( const PADSTACK::POST_MACHINING_PROPS& aProps,
688 PostMachiningProperties* aProto )
689 {
690 if( aProps.mode.has_value() )
691 {
692 switch( aProps.mode.value() )
693 {
694 case PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED: aProto->set_mode( VDPM_NOT_POST_MACHINED ); break;
695 case PAD_DRILL_POST_MACHINING_MODE::COUNTERBORE: aProto->set_mode( VDPM_COUNTERBORE ); break;
696 case PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK: aProto->set_mode( VDPM_COUNTERSINK ); break;
697 default: break;
698 }
699 }
700
701 aProto->set_size( aProps.size );
702 aProto->set_depth( aProps.depth );
703 aProto->set_angle( aProps.angle );
704 };
705
706 if( m_frontPostMachining.mode.has_value() || m_frontPostMachining.size > 0 )
707 packPostMachining( m_frontPostMachining, padstack.mutable_front_post_machining() );
708
709 if( m_backPostMachining.mode.has_value() || m_backPostMachining.size > 0 )
710 packPostMachining( m_backPostMachining, padstack.mutable_back_post_machining() );
711
713 [&]( PCB_LAYER_ID aLayer )
714 {
715 PadStackLayer* layer = padstack.add_copper_layers();
716 const COPPER_LAYER_PROPS& props = CopperLayer( aLayer );
717
718 layer->set_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( aLayer ) );
719 kiapi::common::PackVector2( *layer->mutable_size(), props.shape.size );
721 kiapi::common::PackVector2( *layer->mutable_offset(), props.shape.offset );
722 layer->set_custom_anchor_shape( ToProtoEnum<PAD_SHAPE, kiapi::board::types::PadStackShape>( props.shape.anchor_shape ) );
723 layer->set_chamfer_ratio( props.shape.chamfered_rect_ratio );
724 layer->set_corner_rounding_ratio( props.shape.round_rect_radius_ratio );
725
726 if( props.shape.shape == PAD_SHAPE::TRAPEZOID )
727 kiapi::common::PackVector2( *layer->mutable_trapezoid_delta(), props.shape.trapezoid_delta_size );
728
730 layer->mutable_chamfered_corners()->set_top_left( true );
731
733 layer->mutable_chamfered_corners()->set_top_right( true );
734
736 layer->mutable_chamfered_corners()->set_bottom_left( true );
737
739 layer->mutable_chamfered_corners()->set_bottom_right( true );
740
741 for( const std::shared_ptr<PCB_SHAPE>& shape : props.custom_shapes )
742 {
743 google::protobuf::Any a;
744 shape->Serialize( a );
745 a.UnpackTo( layer->add_custom_shapes() );
746 }
747 } );
748
749 if( CopperLayer( ALL_LAYERS ).zone_connection.has_value() )
750 {
751 padstack.mutable_zone_settings()->set_zone_connection(
753 }
754
755 if( CopperLayer( ALL_LAYERS ).thermal_gap.has_value() )
756 {
757 padstack.mutable_zone_settings()->mutable_thermal_spokes()->mutable_gap()->set_value_nm(
758 CopperLayer( ALL_LAYERS ).thermal_gap.value() );
759 }
760
761 if( CopperLayer( ALL_LAYERS ).thermal_spoke_width.has_value() )
762 {
763 padstack.mutable_zone_settings()->mutable_thermal_spokes()->mutable_width()->set_value_nm(
764 CopperLayer( ALL_LAYERS ).thermal_spoke_width.value() );
765 }
766
767 if( CopperLayer( ALL_LAYERS ).thermal_spoke_angle.has_value() )
768 {
769 padstack.mutable_zone_settings()->mutable_thermal_spokes()->mutable_angle()->set_value_degrees(
770 CopperLayer( ALL_LAYERS ).thermal_spoke_angle.value().AsDegrees() );
771 }
772
773 padstack.set_unconnected_layer_removal( ToProtoEnum<UNCONNECTED_LAYER_MODE,
774 kiapi::board::types::UnconnectedLayerRemoval>( m_unconnectedLayerMode ) );
775
776 if( FrontOuterLayers().has_solder_mask.has_value() )
777 {
778 padstack.mutable_front_outer_layers()->set_solder_mask_mode(
779 FrontOuterLayers().has_solder_mask.value() ? SMM_MASKED : SMM_UNMASKED );
780 }
781
782 if( BackOuterLayers().has_solder_mask.has_value() )
783 {
784 padstack.mutable_back_outer_layers()->set_solder_mask_mode(
785 BackOuterLayers().has_solder_mask.value() ? SMM_MASKED : SMM_UNMASKED );
786 }
787
788 if( FrontOuterLayers().has_covering.has_value() )
789 {
790 padstack.mutable_front_outer_layers()->set_covering_mode(
791 FrontOuterLayers().has_covering.value() ? VCM_COVERED : VCM_UNCOVERED );
792 }
793
794 if( BackOuterLayers().has_covering.has_value() )
795 {
796 padstack.mutable_back_outer_layers()->set_covering_mode(
797 BackOuterLayers().has_covering.value() ? VCM_COVERED : VCM_UNCOVERED );
798 }
799
800 if( FrontOuterLayers().has_plugging.has_value() )
801 {
802 padstack.mutable_front_outer_layers()->set_plugging_mode(
803 FrontOuterLayers().has_plugging.value() ? VPM_PLUGGED : VPM_UNPLUGGED );
804 }
805
806 if( BackOuterLayers().has_plugging.has_value() )
807 {
808 padstack.mutable_back_outer_layers()->set_plugging_mode(
809 BackOuterLayers().has_plugging.value() ? VPM_PLUGGED : VPM_UNPLUGGED );
810 }
811
812 if( FrontOuterLayers().has_solder_paste.has_value() )
813 {
814 padstack.mutable_front_outer_layers()->set_solder_paste_mode(
815 FrontOuterLayers().has_solder_paste.value() ? SPM_PASTE : SPM_NO_PASTE );
816 }
817
818 if( BackOuterLayers().has_solder_paste.has_value() )
819 {
820 padstack.mutable_back_outer_layers()->set_solder_paste_mode(
821 BackOuterLayers().has_solder_paste.value() ? SPM_PASTE : SPM_NO_PASTE );
822 }
823
824 if( FrontOuterLayers().solder_mask_margin.has_value() )
825 {
826 padstack.mutable_front_outer_layers()->mutable_solder_mask_settings()->mutable_solder_mask_margin()->set_value_nm(
827 FrontOuterLayers().solder_mask_margin.value() );
828 }
829
830 if( BackOuterLayers().solder_mask_margin.has_value() )
831 {
832 padstack.mutable_back_outer_layers()->mutable_solder_mask_settings()->mutable_solder_mask_margin()->set_value_nm(
833 BackOuterLayers().solder_mask_margin.value() );
834 }
835
836 if( FrontOuterLayers().solder_paste_margin.has_value() )
837 {
838 padstack.mutable_front_outer_layers()->mutable_solder_paste_settings()->mutable_solder_paste_margin()->set_value_nm(
839 FrontOuterLayers().solder_paste_margin.value() );
840 }
841
842 if( BackOuterLayers().solder_paste_margin.has_value() )
843 {
844 padstack.mutable_back_outer_layers()->mutable_solder_paste_settings()->mutable_solder_paste_margin()->set_value_nm(
845 BackOuterLayers().solder_paste_margin.value() );
846 }
847
848 if( FrontOuterLayers().solder_paste_margin_ratio.has_value() )
849 {
850 padstack.mutable_front_outer_layers()->mutable_solder_paste_settings()->mutable_solder_paste_margin_ratio()->set_value(
851 FrontOuterLayers().solder_paste_margin_ratio.value() );
852 }
853
854 if( BackOuterLayers().solder_paste_margin_ratio.has_value() )
855 {
856 padstack.mutable_back_outer_layers()->mutable_solder_paste_settings()->mutable_solder_paste_margin_ratio()->set_value(
857 BackOuterLayers().solder_paste_margin_ratio.value() );
858 }
859
860 aContainer.PackFrom( padstack );
861}
862
863
864void PADSTACK::SetSize( const VECTOR2I& aSize, PCB_LAYER_ID aLayer )
865{
866 VECTOR2I size = aSize;
867
868 if( size.x < 0 )
869 size.x = 0;
870
871 if( size.y < 0 )
872 size.y = 0;
873
874 CopperLayer( aLayer ).shape.size = size;
875}
876
877
879{
880 return CopperLayer( aLayer ).shape.size;
881}
882
883
885{
886 return CopperLayer( aLayer ).shape.shape;
887}
888
889
891{
892 CopperLayer( aLayer ).shape.shape = aShape;
893}
894
895
897{
898 return m_drill.shape;
899}
900
901
903{
904 m_drill.shape = aShape;
905}
906
907
909{
910 return CopperLayer( aLayer ).shape.offset;
911}
912
913
915{
916 return CopperLayer( aLayer ).shape.offset;
917}
918
919
921{
922 return CopperLayer( aLayer ).shape.anchor_shape;
923}
924
925
927{
928 CopperLayer( aLayer ).shape.anchor_shape = aShape;
929}
930
931
936
937
939{
940 return CopperLayer( aLayer ).shape.trapezoid_delta_size;
941}
942
943
945{
947}
948
949
951{
952 CopperLayer( aLayer ).shape.round_rect_radius_ratio = aRatio;
953}
954
955
957{
958 const VECTOR2I& size = Size( aLayer );
959 return KiROUND( std::min( size.x, size.y ) * RoundRectRadiusRatio( aLayer ) );
960}
961
962
963void PADSTACK::SetRoundRectRadius( double aRadius, PCB_LAYER_ID aLayer )
964{
965 const VECTOR2I& size = Size( aLayer );
966 int min_r = std::min( size.x, size.y );
967
968 if( min_r > 0 )
969 SetRoundRectRadiusRatio( aRadius / min_r, aLayer );
970}
971
972
974{
975 return CopperLayer( aLayer ).shape.chamfered_rect_ratio;
976}
977
978
979void PADSTACK::SetChamferRatio( double aRatio, PCB_LAYER_ID aLayer )
980{
981 CopperLayer( aLayer ).shape.chamfered_rect_ratio = aRatio;
982}
983
984
989
990
991const int& PADSTACK::ChamferPositions( PCB_LAYER_ID aLayer ) const
992{
994}
995
996
997void PADSTACK::SetChamferPositions( int aPositions, PCB_LAYER_ID aLayer )
998{
999 CopperLayer( aLayer ).shape.chamfered_rect_positions = aPositions;
1000}
1001
1002
1003std::optional<int>& PADSTACK::Clearance( PCB_LAYER_ID aLayer )
1004{
1005 return CopperLayer( aLayer ).clearance;
1006}
1007
1008
1009const std::optional<int>& PADSTACK::Clearance( PCB_LAYER_ID aLayer ) const
1010{
1011 return CopperLayer( aLayer ).clearance;
1012}
1013
1014
1015std::optional<int>& PADSTACK::SolderMaskMargin( PCB_LAYER_ID aLayer )
1016{
1017 if( IsFrontLayer( aLayer ) )
1019 else if( IsBackLayer( aLayer ) )
1021 else
1022 return FrontOuterLayers().solder_mask_margin; // Should not happen
1023}
1024
1025
1026const std::optional<int>& PADSTACK::SolderMaskMargin( PCB_LAYER_ID aLayer ) const
1027{
1028 if( IsFrontLayer( aLayer ) )
1030 else if( IsBackLayer( aLayer ) )
1032 else
1033 return FrontOuterLayers().solder_mask_margin; // Should not happen
1034}
1035
1036
1037std::optional<int>& PADSTACK::SolderPasteMargin( PCB_LAYER_ID aLayer )
1038{
1039 if( IsFrontLayer( aLayer ) )
1041 else if( IsBackLayer( aLayer ) )
1043 else
1044 return FrontOuterLayers().solder_paste_margin; // Should not happen
1045}
1046
1047
1048const std::optional<int>& PADSTACK::SolderPasteMargin( PCB_LAYER_ID aLayer ) const
1049{
1050 if( IsFrontLayer( aLayer ) )
1052 else if( IsBackLayer( aLayer ) )
1054 else
1055 return FrontOuterLayers().solder_paste_margin; // Should not happen
1056}
1057
1058
1059std::optional<double>& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer )
1060{
1061 if( IsFrontLayer( aLayer ) )
1063 else if( IsBackLayer( aLayer ) )
1065 else
1066 return FrontOuterLayers().solder_paste_margin_ratio; // Should not happen
1067}
1068
1069
1070const std::optional<double>& PADSTACK::SolderPasteMarginRatio( PCB_LAYER_ID aLayer ) const
1071{
1072 if( IsFrontLayer( aLayer ) )
1074 else if( IsBackLayer( aLayer ) )
1076 else
1077 return FrontOuterLayers().solder_paste_margin_ratio; // Should not happen
1078}
1079
1080
1081std::optional<ZONE_CONNECTION>& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer )
1082{
1083 return CopperLayer( aLayer ).zone_connection;
1084}
1085
1086
1087const std::optional<ZONE_CONNECTION>& PADSTACK::ZoneConnection( PCB_LAYER_ID aLayer ) const
1088{
1089 return CopperLayer( aLayer ).zone_connection;
1090}
1091
1092
1093std::optional<int>& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer )
1094{
1095 return CopperLayer( aLayer ).thermal_spoke_width;
1096}
1097
1098
1099const std::optional<int>& PADSTACK::ThermalSpokeWidth( PCB_LAYER_ID aLayer ) const
1100{
1101 return CopperLayer( aLayer ).thermal_spoke_width;
1102}
1103
1104
1105std::optional<int>& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer )
1106{
1107 return CopperLayer( aLayer ).thermal_gap;
1108}
1109
1110
1111const std::optional<int>& PADSTACK::ThermalGap( PCB_LAYER_ID aLayer ) const
1112{
1113 return CopperLayer( aLayer ).thermal_gap;
1114}
1115
1116
1118{
1119 if( Shape( aLayer ) == PAD_SHAPE::OVAL || Shape( aLayer ) == PAD_SHAPE::RECTANGLE
1120 || Shape( aLayer ) == PAD_SHAPE::ROUNDRECT || Shape( aLayer ) == PAD_SHAPE::CHAMFERED_RECT )
1121 {
1122 return ANGLE_90;
1123 }
1124
1125 return ANGLE_45;
1126}
1127
1128
1130{
1131 if( CopperLayer( aLayer ).thermal_spoke_angle.has_value() )
1132 return CopperLayer( aLayer ).thermal_spoke_angle.value();
1133
1134 return DefaultThermalSpokeAngleForShape( aLayer );
1135}
1136
1137
1139{
1140 CopperLayer( aLayer ).thermal_spoke_angle = aAngle;
1141}
1142
1143
1144std::vector<std::shared_ptr<PCB_SHAPE>>& PADSTACK::Primitives( PCB_LAYER_ID aLayer )
1145{
1146 return CopperLayer( aLayer ).custom_shapes;
1147}
1148
1149
1150const std::vector<std::shared_ptr<PCB_SHAPE>>& PADSTACK::Primitives( PCB_LAYER_ID aLayer ) const
1151{
1152 return CopperLayer( aLayer ).custom_shapes;
1153}
1154
1155
1157{
1158 CopperLayer( aLayer ).custom_shapes.emplace_back( aShape );
1159}
1160
1161
1162void PADSTACK::AppendPrimitives( const std::vector<std::shared_ptr<PCB_SHAPE>>& aList,
1163 PCB_LAYER_ID aLayer )
1164{
1165 std::vector<std::shared_ptr<PCB_SHAPE>>& list = CopperLayer( aLayer ).custom_shapes;
1166
1167 for( const std::shared_ptr<PCB_SHAPE>& item : aList )
1168 {
1169 PCB_SHAPE* new_shape = static_cast<PCB_SHAPE*>( item->Clone() );
1170 new_shape->SetParent( m_parent );
1171 list.emplace_back( new_shape );
1172 }
1173}
1174
1175
1176void PADSTACK::ReplacePrimitives( const std::vector<std::shared_ptr<PCB_SHAPE>>& aList,
1177 PCB_LAYER_ID aLayer )
1178{
1179 ClearPrimitives( aLayer );
1180 AppendPrimitives( aList, aLayer );
1181}
1182
1183
1185{
1186 CopperLayer( aLayer ).custom_shapes.clear();
1187}
1188
1189
1191{
1192 if( m_mode == MODE::NORMAL )
1193 return m_copperProps[ALL_LAYERS];
1194
1196 {
1197 if( IsFrontLayer( aLayer ) )
1198 return m_copperProps[F_Cu];
1199 else if( IsBackLayer( aLayer ) )
1200 return m_copperProps[B_Cu];
1201 else
1203 }
1204
1205 return m_copperProps[aLayer];
1206}
1207
1208
1210{
1212 {
1213 if( IsFrontLayer( aLayer ) && m_copperProps.contains( F_Cu ) )
1214 return m_copperProps.at( F_Cu );
1215 else if( IsBackLayer( aLayer ) && m_copperProps.contains( B_Cu ) )
1216 return m_copperProps.at( B_Cu );
1217 else if( m_copperProps.contains( INNER_LAYERS ) )
1218 return m_copperProps.at( INNER_LAYERS );
1219 }
1220 else if( m_mode == MODE::CUSTOM )
1221 {
1222 if( IsFrontLayer( aLayer ) && m_copperProps.contains( F_Cu ) )
1223 return m_copperProps.at( F_Cu );
1224 else if( IsBackLayer( aLayer ) && m_copperProps.contains( B_Cu ) )
1225 return m_copperProps.at( B_Cu );
1226
1227 if( m_copperProps.count( aLayer ) )
1228 return m_copperProps.at( aLayer );
1229
1230 // For CUSTOM mode, fall back to ALL_LAYERS if available (e.g. for layers not yet
1231 // explicitly defined). If ALL_LAYERS is also absent (e.g. after a FlipLayers()
1232 // that renamed the only entry from F_Cu to B_Cu), return whatever entry is first.
1233 if( m_copperProps.count( ALL_LAYERS ) )
1234 return m_copperProps.at( ALL_LAYERS );
1235
1236 wxASSERT( !m_copperProps.empty() );
1237 return m_copperProps.begin()->second;
1238 }
1239
1240 return m_copperProps.at( ALL_LAYERS );
1241}
1242
1243
1244void PADSTACK::ForEachUniqueLayer( const std::function<void( PCB_LAYER_ID )>& aMethod ) const
1245{
1246 if( m_mode == MODE::NORMAL )
1247 {
1248 aMethod( ALL_LAYERS );
1249 }
1250 else if( m_mode == MODE::FRONT_INNER_BACK )
1251 {
1252 aMethod( F_Cu );
1253 aMethod( INNER_LAYERS );
1254 aMethod( B_Cu );
1255 }
1256 else
1257 {
1258 for( const auto& [layer, props] : m_copperProps )
1259 aMethod( layer );
1260 }
1261}
1262
1263
1264std::vector<PCB_LAYER_ID> PADSTACK::UniqueLayers() const
1265{
1266 std::vector<PCB_LAYER_ID> layers;
1267
1269 [&]( PCB_LAYER_ID layer )
1270 {
1271 layers.push_back( layer );
1272 } );
1273
1274 return layers;
1275}
1276
1277
1279{
1280 if( m_mode == MODE::NORMAL )
1281 return ALL_LAYERS;
1282
1283 if( m_mode == MODE::FRONT_INNER_BACK || IsNonCopperLayer( aLayer ) )
1284 {
1285 PCB_LAYER_ID candidate;
1286
1287 if( IsFrontLayer( aLayer ) )
1288 candidate = F_Cu;
1289 else if( IsBackLayer( aLayer ) )
1290 candidate = B_Cu;
1291 else
1292 candidate = INNER_LAYERS;
1293
1294 // FRONT_INNER_BACK always has all three sides.
1295 // In CUSTOM mode only return the side if the pad actually defines it.
1296 if( m_mode == MODE::FRONT_INNER_BACK || m_copperProps.count( candidate ) )
1297 return candidate;
1298 }
1299
1300 if( m_copperProps.count( aLayer ) )
1301 return aLayer;
1302
1303 // For CUSTOM mode, if ALL_LAYERS is present use it as the default; otherwise return the
1304 // first available layer (e.g. after FlipLayers renamed ALL_LAYERS from F_Cu to B_Cu).
1305 if( m_copperProps.count( ALL_LAYERS ) )
1306 return ALL_LAYERS;
1307
1308 wxASSERT( !m_copperProps.empty() );
1309 return m_copperProps.begin()->first;
1310}
1311
1312
1314{
1315 LSET layers;
1316
1317 if( m_mode == MODE::NORMAL && aOther.m_mode == MODE::NORMAL )
1318 {
1319 layers.set( ALL_LAYERS );
1320 }
1321 else
1322 {
1324 [&]( PCB_LAYER_ID layer )
1325 {
1326 layers.set( layer );
1327 } );
1328 aOther.ForEachUniqueLayer(
1329 [&]( PCB_LAYER_ID layer )
1330 {
1331 layers.set( layer );
1332 } );
1333 }
1334
1335 return layers;
1336}
1337
1338
1339std::optional<bool> PADSTACK::IsTented( PCB_LAYER_ID aSide ) const
1340{
1341 if( IsFrontLayer( aSide ) )
1343 else if( IsBackLayer( aSide ) )
1345 else
1346 return std::nullopt;
1347}
1348
1349
1350std::optional<bool> PADSTACK::IsCovered( PCB_LAYER_ID aSide ) const
1351{
1352 if( IsFrontLayer( aSide ) )
1354 else if( IsBackLayer( aSide ) )
1356 else
1357 return std::nullopt;
1358}
1359
1360
1361std::optional<bool> PADSTACK::IsPlugged( PCB_LAYER_ID aSide ) const
1362{
1363 if( IsFrontLayer( aSide ) )
1365 else if( IsBackLayer( aSide ) )
1367 else
1368 return std::nullopt;
1369}
1370
1371
1372std::optional<bool> PADSTACK::IsCapped() const
1373{
1374 return m_drill.is_capped;
1375}
1376
1377
1378std::optional<bool> PADSTACK::IsFilled() const
1379{
1380 return m_drill.is_filled;
1381}
1382
1383
1384#define TEST( a, b ) { if( a != b ) return a - b; }
1385
1386
1387int PADSTACK::Compare( const PADSTACK* aLeft, const PADSTACK* aRight )
1388{
1389 int diff;
1390
1391 TEST( (int) aLeft->m_mode, (int) aRight->m_mode );
1392
1393 if( aLeft->m_layerSet != aRight->m_layerSet )
1394 return aLeft->m_layerSet.Seq() < aRight->m_layerSet.Seq();
1395
1396 if( ( diff = wxString( aLeft->CustomName() ).Cmp( aRight->CustomName() ) ) != 0 )
1397 return diff;
1398
1400
1401 if( ( diff = aLeft->m_frontMaskProps.Compare( aRight->m_frontMaskProps ) ) != 0 )
1402 return diff;
1403
1404 if( ( diff = aLeft->m_backMaskProps.Compare( aRight->m_backMaskProps ) ) != 0 )
1405 return diff;
1406
1407 TEST( (int) aLeft->m_unconnectedLayerMode, (int) aRight->m_unconnectedLayerMode );
1408 TEST( (int) aLeft->m_customShapeInZoneMode, (int) aRight->m_customShapeInZoneMode );
1409
1410 if( ( diff = aLeft->m_drill.Compare( aRight->m_drill ) ) != 0 )
1411 return diff;
1412
1413 if( ( diff = aLeft->m_secondaryDrill.Compare( aRight->m_secondaryDrill ) ) != 0 )
1414 return diff;
1415
1416 if( ( diff = aLeft->m_tertiaryDrill.Compare( aRight->m_tertiaryDrill ) ) != 0 )
1417 return diff;
1418
1419 if( ( diff = aLeft->m_frontPostMachining.Compare( aRight->m_frontPostMachining ) ) != 0 )
1420 return diff;
1421
1422 if( ( diff = aLeft->m_backPostMachining.Compare( aRight->m_backPostMachining ) ) != 0 )
1423 return diff;
1424
1425 aLeft->ForEachUniqueLayer(
1426 [&]( PCB_LAYER_ID aLayer )
1427 {
1428 if( diff != 0 ) // we want to return the first non-matching layer
1429 return;
1430
1431 diff = aLeft->CopperLayer( aLayer ).Compare( aRight->CopperLayer( aLayer ) );
1432 } );
1433
1434 if( diff != 0 )
1435 return diff;
1436
1437 return 0;
1438}
1439
1440
1442{
1443 return m_copperProps.count( aLayer ) > 0;
1444}
1445
1446
1447double PADSTACK::Similarity( const PADSTACK& aOther ) const
1448{
1449 double similarity = 1.0;
1450
1451 if( m_mode != aOther.m_mode )
1452 similarity *= 0.9;
1453
1454 if( m_layerSet != aOther.m_layerSet )
1455 similarity *= 0.9;
1456
1457 if( CustomName() != aOther.CustomName() )
1458 similarity *= 0.9;
1459
1460 if( m_orientation != aOther.m_orientation )
1461 similarity *= 0.9;
1462
1463 if( m_frontMaskProps != aOther.m_frontMaskProps )
1464 similarity *= 0.9;
1465
1466 if( m_backMaskProps != aOther.m_backMaskProps )
1467 similarity *= 0.9;
1468
1470 similarity *= 0.9;
1471
1473 similarity *= 0.9;
1474
1475 if( m_drill != aOther.m_drill )
1476 similarity *= 0.9;
1477
1478 if( m_secondaryDrill != aOther.m_secondaryDrill )
1479 similarity *= 0.9;
1480
1481 if( m_tertiaryDrill != aOther.m_tertiaryDrill )
1482 similarity *= 0.9;
1483
1485 similarity *= 0.9;
1486
1488 similarity *= 0.9;
1489
1491 [&]( PCB_LAYER_ID aLayer )
1492 {
1493 similarity *= CopperLayer( aLayer ).Similarity( aOther.CopperLayer( aLayer ) );
1494 } );
1495
1496 return similarity;
1497}
1498
1499
1501{
1503 {
1504 std::unordered_map<PCB_LAYER_ID, COPPER_LAYER_PROPS> oldCopperProps = m_copperProps;
1505 m_copperProps.clear();
1506
1507 m_copperProps[aBoard->FlipLayer( F_Cu )] = oldCopperProps[F_Cu];
1508 m_copperProps[INNER_LAYERS] = oldCopperProps[INNER_LAYERS];
1509 m_copperProps[aBoard->FlipLayer( B_Cu )] = oldCopperProps[B_Cu];
1510 }
1511 else if( m_mode == MODE::CUSTOM )
1512 {
1513 std::unordered_map<PCB_LAYER_ID, COPPER_LAYER_PROPS> oldCopperProps = m_copperProps;
1514 m_copperProps.clear();
1515
1516 for( const auto& [layer, props] : oldCopperProps )
1517 m_copperProps[aBoard->FlipLayer( layer )] = props;
1518 }
1519
1520 std::swap( m_frontMaskProps, m_backMaskProps );
1521
1522 m_drill.start = aBoard->FlipLayer( m_drill.start );
1523 m_drill.end = aBoard->FlipLayer( m_drill.end );
1524
1525 m_secondaryDrill.start = aBoard->FlipLayer( m_secondaryDrill.start );
1526 m_secondaryDrill.end = aBoard->FlipLayer( m_secondaryDrill.end );
1527
1528 m_tertiaryDrill.start = aBoard->FlipLayer( m_tertiaryDrill.start );
1529 m_tertiaryDrill.end = aBoard->FlipLayer( m_tertiaryDrill.end );
1530
1532}
1533
1534
1536{
1537 return m_drill.start;
1538}
1539
1540
1542{
1543 return m_drill.end;
1544}
1545
1546
1547wxString PADSTACK::Name() const
1548{
1549 return CustomName();
1550}
1551
1552
1553const wxChar* PADSTACK::CustomName() const
1554{
1555 if( m_customName )
1556 return m_customName->wx_str();
1557
1558 return wxEmptyString;
1559}
1560
1561
1562void PADSTACK::SetCustomName( const wxString& aCustomName )
1563{
1564 if( aCustomName.IsEmpty() )
1565 {
1566 m_customName.reset();
1567 }
1568 else if( m_customName )
1569 {
1570 *m_customName = aCustomName;
1571 }
1572 else
1573 {
1574 m_customName = std::make_unique<wxString>( aCustomName );
1575 }
1576}
1577
1578
1582 size( 0, 0 ),
1583 offset( 0, 0 ),
1585 chamfered_rect_ratio( 0.0 ),
1587 trapezoid_delta_size( 0, 0 )
1588{
1589}
1590
1591
1593{
1594 return shape == aOther.shape &&
1595 anchor_shape == aOther.anchor_shape &&
1596 size == aOther.size &&
1597 offset == aOther.offset &&
1602}
1603
1604
1606{
1607 TEST( (int) shape, (int) aOther.shape );
1608 TEST( (int) anchor_shape, (int) aOther.anchor_shape );
1609 TEST( size.x, aOther.size.x );
1610
1612 TEST( size.y, aOther.size.y );
1613
1614 TEST( offset.x, aOther.offset.x );
1615 TEST( offset.y, aOther.offset.y );
1616
1617 if( abs( round_rect_radius_ratio - aOther.round_rect_radius_ratio ) > 0.0001 )
1618 return round_rect_radius_ratio > aOther.round_rect_radius_ratio ? 1 : -1;
1619
1620 if( abs( chamfered_rect_ratio - aOther.chamfered_rect_ratio ) > 0.0001 )
1621 return chamfered_rect_ratio > aOther.chamfered_rect_ratio ? 1 : -1;
1622
1624
1625 return 0;
1626}
1627
1628
1630{
1631 if( !( shape == aOther.shape ) ) return false;
1632 if( zone_connection != aOther.zone_connection ) return false;
1633 if( thermal_spoke_width != aOther.thermal_spoke_width ) return false;
1634 if( thermal_spoke_angle != aOther.thermal_spoke_angle ) return false;
1635 if( thermal_gap != aOther.thermal_gap ) return false;
1636 if( clearance != aOther.clearance ) return false;
1637
1638 if( custom_shapes.size() != aOther.custom_shapes.size() ) return false;
1639
1640 // Deep compare of shapes?
1641 // For now, just check pointers or size
1642 return true;
1643}
1644
1645
1647{
1648 double similarity = 1.0;
1649
1650 if( shape != aOther.shape )
1651 similarity *= 0.5;
1652
1653 if( zone_connection != aOther.zone_connection )
1654 similarity *= 0.9;
1655
1657 similarity *= 0.9;
1658
1660 similarity *= 0.9;
1661
1662 if( thermal_gap != aOther.thermal_gap )
1663 similarity *= 0.9;
1664
1665 if( clearance != aOther.clearance )
1666 similarity *= 0.9;
1667
1668 if( custom_shapes != aOther.custom_shapes )
1669 similarity *= 0.5;
1670
1671 return similarity;
1672}
1673
1674
1675#define TEST_OPT( a, b, v ) \
1676 { \
1677 if( a.has_value() != b.has_value() ) \
1678 return a.has_value() - b.has_value(); \
1679 if( (int) a.value_or( v ) - (int) b.value_or( v ) != 0 ) \
1680 return (int) a.value_or( v ) - (int) b.value_or( v ); \
1681 }
1682
1683#define TEST_OPT_ANGLE( a, b, v ) \
1684 { \
1685 if( a.has_value() != b.has_value() ) \
1686 return a.has_value() - b.has_value(); \
1687 if( abs( a.value_or( v ).AsDegrees() - b.value_or( v ).AsDegrees() ) > 0.001 ) \
1688 return a.value_or( v ).AsDegrees() > b.value_or( v ).AsDegrees() ? 1 : -1; \
1689 }
1690
1691
1693{
1694 int diff;
1695
1696 if( ( diff = shape.Compare( aOther.shape ) ) != 0 )
1697 return diff;
1698
1702 TEST_OPT( thermal_gap, aOther.thermal_gap, 0 );
1703 TEST_OPT( clearance, aOther.clearance, 0 );
1704
1705 if( ( diff = (int) custom_shapes.size() - (int) aOther.custom_shapes.size() ) != 0 )
1706 return diff;
1707
1708 for( int ii = 0; ii < (int) custom_shapes.size(); ++ii )
1709 {
1710 if( ( diff = custom_shapes[ii]->Compare( aOther.custom_shapes[ii].get() ) ) != 0 )
1711 return diff;
1712 }
1713
1714 return 0;
1715}
1716
1717
1728
1729
1731{
1734
1735 if( solder_paste_margin_ratio.has_value() != aOther.solder_paste_margin_ratio.has_value() )
1736 return solder_paste_margin_ratio.has_value() - aOther.solder_paste_margin_ratio.has_value();
1737 if( abs( solder_paste_margin_ratio.value_or( 0.0 ) - aOther.solder_paste_margin_ratio.value_or( 0.0 ) ) > 0.0001 )
1738 return solder_paste_margin_ratio.value_or( 0.0 ) > aOther.solder_paste_margin_ratio.value_or( 0.0 ) ? 1 : -1;
1739
1740 TEST_OPT( has_solder_mask, aOther.has_solder_mask, false );
1741 TEST_OPT( has_solder_paste, aOther.has_solder_paste, false );
1742 TEST_OPT( has_covering, aOther.has_covering, false );
1743 TEST_OPT( has_plugging, aOther.has_plugging, false );
1744
1745 return 0;
1746}
1747
1748
1750{
1751 return size == aOther.size &&
1752 shape == aOther.shape &&
1753 start == aOther.start &&
1754 end == aOther.end &&
1755 is_filled == aOther.is_filled &&
1756 is_capped == aOther.is_capped;
1757}
1758
1759
1761{
1762 TEST( (int) shape, (int) aOther.shape );
1763
1764 TEST( size.x, aOther.size.x );
1765
1767 TEST( size.y, aOther.size.y );
1768
1769 TEST( (int) start, (int) aOther.start );
1770 TEST( (int) end, (int) aOther.end );
1771
1772 TEST_OPT( is_filled, aOther.is_filled, false );
1773 TEST_OPT( is_capped, aOther.is_capped, false );
1774
1775 return 0;
1776}
1777
1778
1780{
1781 return mode == aOther.mode &&
1782 size == aOther.size &&
1783 depth == aOther.depth &&
1784 angle == aOther.angle;
1785}
1786
1787
1789{
1791 TEST( size, aOther.size );
1792 TEST( depth, aOther.depth );
1793 TEST( angle, aOther.angle );
1794
1795 return 0;
1796}
1797
1798
1800{
1801 m_mode = aMode;
1802}
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:372
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:131
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:496
bool Deserialize(const google::protobuf::Any &aContainer) override
Deserializes the given protobuf message into this object.
Definition padstack.cpp:236
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:956
void SetDrillShape(PAD_DRILL_SHAPE aShape)
Definition padstack.cpp:902
std::optional< double > & SolderPasteMarginRatio(PCB_LAYER_ID aLayer=F_Cu)
void SetBackdrillSize(bool aTop, std::optional< int > aSize)
Definition padstack.cpp:556
DRILL_PROPS m_drill
!
Definition padstack.h:563
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:950
UNCONNECTED_LAYER_MODE m_unconnectedLayerMode
Definition padstack.h:552
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:572
std::optional< int > & SolderMaskMargin(PCB_LAYER_ID aLayer=F_Cu)
void SetChamferRatio(double aRatio, PCB_LAYER_ID aLayer)
Definition padstack.cpp:979
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:83
void SetShape(PAD_SHAPE aShape, PCB_LAYER_ID aLayer)
Definition padstack.cpp:890
EDA_ANGLE DefaultThermalSpokeAngleForShape(PCB_LAYER_ID aLayer=F_Cu) const
VECTOR2I & TrapezoidDeltaSize(PCB_LAYER_ID aLayer)
Definition padstack.cpp:932
DRILL_PROPS m_secondaryDrill
! Secondary drill, used to define back-drilling starting from the bottom side
Definition padstack.h:566
VECTOR2I & Offset(PCB_LAYER_ID aLayer)
Definition padstack.cpp:908
MASK_LAYER_PROPS m_backMaskProps
! The overrides applied to back outer technical layers
Definition padstack.h:550
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:559
bool unpackCopperLayer(const kiapi::board::types::PadStackLayer &aProto)
Definition padstack.cpp:185
PAD_DRILL_SHAPE DrillShape() const
Definition padstack.cpp:896
void SetChamferPositions(int aPositions, PCB_LAYER_ID aLayer)
Definition padstack.cpp:997
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:569
void SetRoundRectRadius(double aRadius, PCB_LAYER_ID aLayer)
Definition padstack.cpp:963
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:884
DRILL_PROPS & Drill()
Definition padstack.h:351
void SetAnchorShape(PAD_SHAPE aShape, PCB_LAYER_ID aLayer)
Definition padstack.cpp:926
BOARD_ITEM * m_parent
! The BOARD_ITEM this PADSTACK belongs to; will be used as the parent for owned shapes
Definition padstack.h:528
PADSTACK(BOARD_ITEM *aParent)
Definition padstack.cpp:42
std::optional< bool > IsCapped() const
std::vector< PCB_LAYER_ID > UniqueLayers() const
void SetBackdrillEndLayer(bool aTop, PCB_LAYER_ID aLayer)
Definition padstack.cpp:613
LSET m_layerSet
! The board layers that this padstack is active on
Definition padstack.h:534
std::optional< int > GetBackdrillSize(bool aTop) const
Definition padstack.cpp:540
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:544
BACKDRILL_MODE GetBackdrillMode() const
Definition padstack.cpp:465
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
int & ChamferPositions(PCB_LAYER_ID aLayer)
Definition padstack.cpp:985
MASK_LAYER_PROPS m_frontMaskProps
! The overrides applied to front outer technical layers
Definition padstack.h:547
const VECTOR2I & Size(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:878
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:944
PCB_LAYER_ID GetBackdrillEndLayer(bool aTop) const
Definition padstack.cpp:597
PCB_LAYER_ID StartLayer() const
POST_MACHINING_PROPS & BackPostMachining()
Definition padstack.h:363
POST_MACHINING_PROPS m_frontPostMachining
Definition padstack.h:571
std::unique_ptr< wxString > m_customName
! An override for the IPC-7351 padstack name
Definition padstack.h:537
PAD_SHAPE AnchorShape(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:920
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:637
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:864
double ChamferRatio(PCB_LAYER_ID aLayer) const
Definition padstack.cpp:973
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:531
EDA_ANGLE m_orientation
! The rotation of the pad relative to an outer reference frame
Definition padstack.h:540
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