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