1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
6 * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26 #include <base_units.h>
27 #include <bitmaps.h>
28 #include <core/mirror.h>
29 #include <math/util.h> // for KiROUND
30 #include <eda_draw_frame.h>
31 #include <geometry/shape_circle.h>
32 #include <geometry/shape_segment.h>
33 #include <geometry/shape_simple.h>
34 #include <geometry/shape_rect.h>
35 #include <geometry/shape_compound.h>
36 #include <string_utils.h>
37 #include <i18n_utility.h>
38 #include <view/view.h>
39 #include <board.h>
40 #include <board_connected_item.h>
41 #include <board_design_settings.h>
42 #include <footprint.h>
43 #include <pad.h>
44 #include <pcb_shape.h>
45 #include <connectivity/connectivity_data.h>
46 #include <convert_to_biu.h>
47 #include <convert_basic_shapes_to_polygon.h>
48 #include <widgets/msgpanel.h>
49 #include <pcb_painter.h>
50 #include <wx/log.h>
51
52 #include <memory>
53 #include <macros.h>
54
55 using KIGFX::PCB_PAINTER;
56 using KIGFX::PCB_RENDER_SETTINGS;
57
58
PAD(FOOTPRINT * parent)59 PAD::PAD( FOOTPRINT* parent ) :
60 BOARD_CONNECTED_ITEM( parent, PCB_PAD_T )
61 {
62 m_size.x = m_size.y = Mils2iu( 60 ); // Default pad size 60 mils.
63 m_drill.x = m_drill.y = Mils2iu( 30 ); // Default drill size 30 mils.
64 m_orient = 0; // Pad rotation in 1/10 degrees.
65 m_lengthPadToDie = 0;
66
67 if( m_parent && m_parent->Type() == PCB_FOOTPRINT_T )
68 m_pos = GetParent()->GetPosition();
69
70 SetShape( PAD_SHAPE::CIRCLE ); // Default pad shape is PAD_CIRCLE.
71 SetAnchorPadShape( PAD_SHAPE::CIRCLE ); // Default shape for custom shaped pads
72 // is PAD_CIRCLE.
73 SetDrillShape( PAD_DRILL_SHAPE_CIRCLE ); // Default pad drill shape is a circle.
74 m_attribute = PAD_ATTRIB::PTH; // Default pad type is plated through hole
75 SetProperty( PAD_PROP::NONE ); // no special fabrication property
76 m_localClearance = 0;
77 m_localSolderMaskMargin = 0;
78 m_localSolderPasteMargin = 0;
79 m_localSolderPasteMarginRatio = 0.0;
80
81 // Parameters for round rect only:
82 m_roundedCornerScale = 0.25; // from IPC-7351C standard
83
84 // Parameters for chamfered rect only:
85 m_chamferScale = 0.2; // Size of chamfer: ratio of smallest of X,Y size
86 m_chamferPositions = RECT_NO_CHAMFER; // No chamfered corner
87
88 m_zoneConnection = ZONE_CONNECTION::INHERITED; // Use parent setting by default
89 m_thermalWidth = 0; // Use parent setting by default
90 m_thermalGap = 0; // Use parent setting by default
91
92 m_customShapeClearanceArea = CUST_PAD_SHAPE_IN_ZONE_OUTLINE;
93
94 // Set layers mask to default for a standard thru hole pad.
95 m_layerMask = PTHMask();
96
97 SetSubRatsnest( 0 ); // used in ratsnest calculations
98
99 SetDirty();
100 m_effectiveBoundingRadius = 0;
101 m_removeUnconnectedLayer = false;
102 m_keepTopBottomLayer = true;
103 }
104
105
PAD(const PAD & aOther)106 PAD::PAD( const PAD& aOther ) :
107 BOARD_CONNECTED_ITEM( aOther.GetParent(), PCB_PAD_T )
108 {
109 PAD::operator=( aOther );
110
111 const_cast<KIID&>( m_Uuid ) = aOther.m_Uuid;
112 }
113
114
operator =(const PAD & aOther)115 PAD& PAD::operator=( const PAD &aOther )
116 {
117 BOARD_CONNECTED_ITEM::operator=( aOther );
118
119 ImportSettingsFrom( aOther );
120 SetPadToDieLength( aOther.GetPadToDieLength() );
121 SetPosition( aOther.GetPosition() );
122 SetPos0( aOther.GetPos0() );
123 SetNumber( aOther.GetNumber() );
124 SetPinType( aOther.GetPinType() );
125 SetPinFunction( aOther.GetPinFunction() );
126 SetSubRatsnest( aOther.GetSubRatsnest() );
127 m_effectiveBoundingRadius = aOther.m_effectiveBoundingRadius;
128 m_removeUnconnectedLayer = aOther.m_removeUnconnectedLayer;
129 m_keepTopBottomLayer = aOther.m_keepTopBottomLayer;
130
131 return *this;
132 }
133
134
CanHaveNumber() const135 bool PAD::CanHaveNumber() const
136 {
137 // Aperture pads don't get a number
138 if( IsAperturePad() )
139 return false;
140
141 // NPTH pads don't get numbers
142 if( GetAttribute() == PAD_ATTRIB::NPTH )
143 return false;
144
145 return true;
146 }
147
148
IsLocked() const149 bool PAD::IsLocked() const
150 {
151 if( GetParent() && GetParent()->IsLocked() )
152 return true;
153
154 return BOARD_ITEM::IsLocked();
155 };
156
157
PTHMask()158 LSET PAD::PTHMask()
159 {
160 static LSET saved = LSET::AllCuMask() | LSET( 2, F_Mask, B_Mask );
161 return saved;
162 }
163
164
SMDMask()165 LSET PAD::SMDMask()
166 {
167 static LSET saved( 3, F_Cu, F_Paste, F_Mask );
168 return saved;
169 }
170
171
ConnSMDMask()172 LSET PAD::ConnSMDMask()
173 {
174 static LSET saved( 2, F_Cu, F_Mask );
175 return saved;
176 }
177
178
UnplatedHoleMask()179 LSET PAD::UnplatedHoleMask()
180 {
181 static LSET saved = LSET( 4, F_Cu, B_Cu, F_Mask, B_Mask );
182 return saved;
183 }
184
185
ApertureMask()186 LSET PAD::ApertureMask()
187 {
188 static LSET saved( 1, F_Paste );
189 return saved;
190 }
191
192
IsFlipped() const193 bool PAD::IsFlipped() const
194 {
195 if( GetParent() && GetParent()->GetLayer() == B_Cu )
196 return true;
197 return false;
198 }
199
200
FlashLayer(LSET aLayers) const201 bool PAD::FlashLayer( LSET aLayers ) const
202 {
203 for( auto layer : aLayers.Seq() )
204 {
205 if( FlashLayer( layer ) )
206 return true;
207 }
208
209 return false;
210 }
211
212
FlashLayer(int aLayer) const213 bool PAD::FlashLayer( int aLayer ) const
214 {
215 std::vector<KICAD_T> types
216 { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T, PCB_PAD_T, PCB_ZONE_T, PCB_FP_ZONE_T };
217
218 const BOARD* board = GetBoard();
219
220 switch( GetAttribute() )
221 {
222 case PAD_ATTRIB::PTH:
223 if( aLayer == UNDEFINED_LAYER )
224 return true;
225
226 /// Heat sink pads always get copper
227 if( GetProperty() == PAD_PROP::HEATSINK )
228 return IsOnLayer( static_cast<PCB_LAYER_ID>( aLayer ) );
229
230 if( !m_removeUnconnectedLayer )
231 return IsOnLayer( static_cast<PCB_LAYER_ID>( aLayer ) );
232
233 // Plated through hole pads need copper on the top/bottom layers for proper soldering
234 // Unless the user has removed them in the pad dialog
235 if( m_keepTopBottomLayer && ( aLayer == F_Cu || aLayer == B_Cu ) )
236 return IsOnLayer( static_cast<PCB_LAYER_ID>( aLayer ) );
237
238 return board && board->GetConnectivity()->IsConnectedOnLayer( this,
239 static_cast<int>( aLayer ),
240 types );
241
242 case PAD_ATTRIB::NPTH:
243 if( GetShape() == PAD_SHAPE::CIRCLE && GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE )
244 {
245 if( GetOffset() == wxPoint( 0, 0 ) && GetDrillSize().x >= GetSize().x )
246 return false;
247 }
248 else if( GetShape() == PAD_SHAPE::OVAL && GetDrillShape() == PAD_DRILL_SHAPE_OBLONG )
249 {
250 if( GetOffset() == wxPoint( 0, 0 )
251 && GetDrillSize().x >= GetSize().x && GetDrillSize().y >= GetSize().y )
252 {
253 return false;
254 }
255 }
256
257 KI_FALLTHROUGH;
258
259 case PAD_ATTRIB::SMD:
260 case PAD_ATTRIB::CONN:
261 default:
262 if( aLayer == UNDEFINED_LAYER )
263 return true;
264
265 return IsOnLayer( static_cast<PCB_LAYER_ID>( aLayer ) );
266 }
267 }
268
269
GetRoundRectCornerRadius() const270 int PAD::GetRoundRectCornerRadius() const
271 {
272 return KiROUND( std::min( m_size.x, m_size.y ) * m_roundedCornerScale );
273 }
274
275
SetRoundRectCornerRadius(double aRadius)276 void PAD::SetRoundRectCornerRadius( double aRadius )
277 {
278 int min_r = std::min( m_size.x, m_size.y );
279
280 if( min_r > 0 )
281 SetRoundRectRadiusRatio( aRadius / min_r );
282 }
283
284
SetRoundRectRadiusRatio(double aRadiusScale)285 void PAD::SetRoundRectRadiusRatio( double aRadiusScale )
286 {
287 m_roundedCornerScale = std::max( 0.0, std::min( aRadiusScale, 0.5 ) );
288
289 SetDirty();
290 }
291
292
SetChamferRectRatio(double aChamferScale)293 void PAD::SetChamferRectRatio( double aChamferScale )
294 {
295 m_chamferScale = std::max( 0.0, std::min( aChamferScale, 0.5 ) );
296
297 SetDirty();
298 }
299
300
GetEffectivePolygon() const301 const std::shared_ptr<SHAPE_POLY_SET>& PAD::GetEffectivePolygon() const
302 {
303 if( m_polyDirty )
304 BuildEffectivePolygon();
305
306 return m_effectivePolygon;
307 }
308
309
GetEffectiveShape(PCB_LAYER_ID aLayer) const310 std::shared_ptr<SHAPE> PAD::GetEffectiveShape( PCB_LAYER_ID aLayer ) const
311 {
312 if( m_shapesDirty )
313 BuildEffectiveShapes( aLayer );
314
315 return m_effectiveShape;
316 }
317
318
GetEffectiveHoleShape() const319 const SHAPE_SEGMENT* PAD::GetEffectiveHoleShape() const
320 {
321 if( m_shapesDirty )
322 BuildEffectiveShapes( UNDEFINED_LAYER );
323
324 return m_effectiveHoleShape.get();
325 }
326
327
GetBoundingRadius() const328 int PAD::GetBoundingRadius() const
329 {
330 if( m_polyDirty )
331 BuildEffectivePolygon();
332
333 return m_effectiveBoundingRadius;
334 }
335
336
BuildEffectiveShapes(PCB_LAYER_ID aLayer) const337 void PAD::BuildEffectiveShapes( PCB_LAYER_ID aLayer ) const
338 {
339 std::lock_guard<std::mutex> RAII_lock( m_shapesBuildingLock );
340
341 // If we had to wait for the lock then we were probably waiting for someone else to
342 // finish rebuilding the shapes. So check to see if they're clean now.
343 if( !m_shapesDirty )
344 return;
345
346 const BOARD* board = GetBoard();
347 int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF;
348
349 m_effectiveShape = std::make_shared<SHAPE_COMPOUND>();
350 m_effectiveHoleShape = nullptr;
351
352 auto add = [this]( SHAPE* aShape )
353 {
354 m_effectiveShape->AddShape( aShape );
355 };
356
357 wxPoint shapePos = ShapePos(); // Fetch only once; rotation involves trig
358 PAD_SHAPE effectiveShape = GetShape();
359
360 if( GetShape() == PAD_SHAPE::CUSTOM )
361 effectiveShape = GetAnchorPadShape();
362
363 switch( effectiveShape )
364 {
365 case PAD_SHAPE::CIRCLE:
366 add( new SHAPE_CIRCLE( shapePos, m_size.x / 2 ) );
367 break;
368
369 case PAD_SHAPE::OVAL:
370 if( m_size.x == m_size.y ) // the oval pad is in fact a circle
371 {
372 add( new SHAPE_CIRCLE( shapePos, m_size.x / 2 ) );
373 }
374 else
375 {
376 wxSize half_size = m_size / 2;
377 int half_width = std::min( half_size.x, half_size.y );
378 wxPoint half_len( half_size.x - half_width, half_size.y - half_width );
379 RotatePoint( &half_len, m_orient );
380 add( new SHAPE_SEGMENT( shapePos - half_len, shapePos + half_len, half_width * 2 ) );
381 }
382
383 break;
384
385 case PAD_SHAPE::RECT:
386 case PAD_SHAPE::TRAPEZOID:
387 case PAD_SHAPE::ROUNDRECT:
388 {
389 int r = ( effectiveShape == PAD_SHAPE::ROUNDRECT ) ? GetRoundRectCornerRadius() : 0;
390 wxPoint half_size( m_size.x / 2, m_size.y / 2 );
391 wxSize trap_delta( 0, 0 );
392
393 if( r )
394 {
395 half_size -= wxPoint( r, r );
396
397 // Avoid degenerated shapes (0 length segments) that always create issues
398 // For roundrect pad very near a circle, use only a circle
399 const int min_len = Millimeter2iu( 0.0001);
400
401 if( half_size.x < min_len && half_size.y < min_len )
402 {
403 add( new SHAPE_CIRCLE( shapePos, r ) );
404 break;
405 }
406 }
407 else if( effectiveShape == PAD_SHAPE::TRAPEZOID )
408 {
409 trap_delta = m_deltaSize / 2;
410 }
411
412 SHAPE_LINE_CHAIN corners;
413
414 corners.Append( -half_size.x - trap_delta.y, half_size.y + trap_delta.x );
415 corners.Append( half_size.x + trap_delta.y, half_size.y - trap_delta.x );
416 corners.Append( half_size.x - trap_delta.y, -half_size.y + trap_delta.x );
417 corners.Append( -half_size.x + trap_delta.y, -half_size.y - trap_delta.x );
418
419 corners.Rotate( -DECIDEG2RAD( m_orient ) );
420 corners.Move( shapePos );
421
422 // GAL renders rectangles faster than 4-point polygons so it's worth checking if our
423 // body shape is a rectangle.
424 if( corners.PointCount() == 4
425 &&
426 ( ( corners.CPoint( 0 ).y == corners.CPoint( 1 ).y
427 && corners.CPoint( 1 ).x == corners.CPoint( 2 ).x
428 && corners.CPoint( 2 ).y == corners.CPoint( 3 ).y
429 && corners.CPoint( 3 ).x == corners.CPoint( 0 ).x )
430 ||
431 ( corners.CPoint( 0 ).x == corners.CPoint( 1 ).x
432 && corners.CPoint( 1 ).y == corners.CPoint( 2 ).y
433 && corners.CPoint( 2 ).x == corners.CPoint( 3 ).x
434 && corners.CPoint( 3 ).y == corners.CPoint( 0 ).y )
435 )
436 )
437 {
438 int width = std::abs( corners.CPoint( 2 ).x - corners.CPoint( 0 ).x );
439 int height = std::abs( corners.CPoint( 2 ).y - corners.CPoint( 0 ).y );
440 VECTOR2I pos( std::min( corners.CPoint( 2 ).x, corners.CPoint( 0 ).x ),
441 std::min( corners.CPoint( 2 ).y, corners.CPoint( 0 ).y ) );
442
443 add( new SHAPE_RECT( pos, width, height ) );
444 }
445 else
446 {
447 add( new SHAPE_SIMPLE( corners ) );
448 }
449
450 if( r )
451 {
452 add( new SHAPE_SEGMENT( corners.CPoint( 0 ), corners.CPoint( 1 ), r * 2 ) );
453 add( new SHAPE_SEGMENT( corners.CPoint( 1 ), corners.CPoint( 2 ), r * 2 ) );
454 add( new SHAPE_SEGMENT( corners.CPoint( 2 ), corners.CPoint( 3 ), r * 2 ) );
455 add( new SHAPE_SEGMENT( corners.CPoint( 3 ), corners.CPoint( 0 ), r * 2 ) );
456 }
457 }
458 break;
459
460 case PAD_SHAPE::CHAMFERED_RECT:
461 {
462 SHAPE_POLY_SET outline;
463
464 TransformRoundChamferedRectToPolygon( outline, shapePos, GetSize(), m_orient,
465 GetRoundRectCornerRadius(), GetChamferRectRatio(),
466 GetChamferPositions(), 0, maxError, ERROR_INSIDE );
467
468 add( new SHAPE_SIMPLE( outline.COutline( 0 ) ) );
469 }
470 break;
471
472 default:
473 wxFAIL_MSG( "PAD::buildEffectiveShapes: Unsupported pad shape: "
474 + PAD_SHAPE_T_asString( effectiveShape ) );
475 break;
476 }
477
478 if( GetShape() == PAD_SHAPE::CUSTOM )
479 {
480 for( const std::shared_ptr<PCB_SHAPE>& primitive : m_editPrimitives )
481 {
482 for( SHAPE* shape : primitive->MakeEffectiveShapes() )
483 {
484 shape->Rotate( -DECIDEG2RAD( m_orient ) );
485 shape->Move( shapePos );
486 add( shape );
487 }
488 }
489 }
490
491 BOX2I bbox = m_effectiveShape->BBox();
492 m_effectiveBoundingBox = EDA_RECT( (wxPoint) bbox.GetPosition(),
493 wxSize( bbox.GetWidth(), bbox.GetHeight() ) );
494
495 // Hole shape
496 wxSize half_size = m_drill / 2;
497 int half_width = std::min( half_size.x, half_size.y );
498 wxPoint half_len( half_size.x - half_width, half_size.y - half_width );
499
500 RotatePoint( &half_len, m_orient );
501
502 m_effectiveHoleShape = std::make_shared<SHAPE_SEGMENT>( m_pos - half_len, m_pos + half_len,
503 half_width * 2 );
504 bbox = m_effectiveHoleShape->BBox();
505 m_effectiveBoundingBox.Merge( EDA_RECT( (wxPoint) bbox.GetPosition(),
506 wxSize( bbox.GetWidth(), bbox.GetHeight() ) ) );
507
508 // All done
509 m_shapesDirty = false;
510 }
511
512
BuildEffectivePolygon() const513 void PAD::BuildEffectivePolygon() const
514 {
515 std::lock_guard<std::mutex> RAII_lock( m_polyBuildingLock );
516
517 // If we had to wait for the lock then we were probably waiting for someone else to
518 // finish rebuilding the shapes. So check to see if they're clean now.
519 if( !m_polyDirty )
520 return;
521
522 const BOARD* board = GetBoard();
523 int maxError = board ? board->GetDesignSettings().m_MaxError : ARC_HIGH_DEF;
524
525 // Polygon
526 m_effectivePolygon = std::make_shared<SHAPE_POLY_SET>();
527 TransformShapeWithClearanceToPolygon( *m_effectivePolygon, UNDEFINED_LAYER, 0, maxError,
528 ERROR_INSIDE );
529
530 // Bounding radius
531 //
532 // PADSTACKS TODO: these will both need to cycle through all layers to get the largest
533 // values....
534 m_effectiveBoundingRadius = 0;
535
536 for( int cnt = 0; cnt < m_effectivePolygon->OutlineCount(); ++cnt )
537 {
538 const SHAPE_LINE_CHAIN& poly = m_effectivePolygon->COutline( cnt );
539
540 for( int ii = 0; ii < poly.PointCount(); ++ii )
541 {
542 int dist = KiROUND( ( poly.CPoint( ii ) - m_pos ).EuclideanNorm() );
543 m_effectiveBoundingRadius = std::max( m_effectiveBoundingRadius, dist );
544 }
545 }
546
547 // All done
548 m_polyDirty = false;
549 }
550
551
GetBoundingBox() const552 const EDA_RECT PAD::GetBoundingBox() const
553 {
554 if( m_shapesDirty )
555 BuildEffectiveShapes( UNDEFINED_LAYER );
556
557 return m_effectiveBoundingBox;
558 }
559
560
SetDrawCoord()561 void PAD::SetDrawCoord()
562 {
563 FOOTPRINT* parentFootprint = static_cast<FOOTPRINT*>( m_parent );
564
565 m_pos = m_pos0;
566
567 if( parentFootprint == nullptr )
568 return;
569
570 double angle = parentFootprint->GetOrientation();
571
572 RotatePoint( &m_pos.x, &m_pos.y, angle );
573 m_pos += parentFootprint->GetPosition();
574
575 SetDirty();
576 }
577
578
SetLocalCoord()579 void PAD::SetLocalCoord()
580 {
581 FOOTPRINT* parentFootprint = static_cast<FOOTPRINT*>( m_parent );
582
583 if( parentFootprint == nullptr )
584 {
585 m_pos0 = m_pos;
586 return;
587 }
588
589 m_pos0 = m_pos - parentFootprint->GetPosition();
590 RotatePoint( &m_pos0.x, &m_pos0.y, -parentFootprint->GetOrientation() );
591 }
592
593
SetAttribute(PAD_ATTRIB aAttribute)594 void PAD::SetAttribute( PAD_ATTRIB aAttribute )
595 {
596 m_attribute = aAttribute;
597
598 if( aAttribute == PAD_ATTRIB::SMD )
599 m_drill = wxSize( 0, 0 );
600
601 SetDirty();
602 }
603
604
SetProperty(PAD_PROP aProperty)605 void PAD::SetProperty( PAD_PROP aProperty )
606 {
607 m_property = aProperty;
608
609 SetDirty();
610 }
611
612
SetOrientation(double aAngle)613 void PAD::SetOrientation( double aAngle )
614 {
615 NORMALIZE_ANGLE_POS( aAngle );
616 m_orient = aAngle;
617
618 SetDirty();
619 }
620
621
Flip(const wxPoint & aCentre,bool aFlipLeftRight)622 void PAD::Flip( const wxPoint& aCentre, bool aFlipLeftRight )
623 {
624 if( aFlipLeftRight )
625 {
626 MIRROR( m_pos.x, aCentre.x );
627 MIRROR( m_pos0.x, 0 );
628 MIRROR( m_offset.x, 0 );
629 MIRROR( m_deltaSize.x, 0 );
630 }
631 else
632 {
633 MIRROR( m_pos.y, aCentre.y );
634 MIRROR( m_pos0.y, 0 );
635 MIRROR( m_offset.y, 0 );
636 MIRROR( m_deltaSize.y, 0 );
637 }
638
639 SetOrientation( -GetOrientation() );
640
641 auto mirrorBitFlags = []( int& aBitfield, int a, int b )
642 {
643 bool temp = aBitfield & a;
644
645 if( aBitfield & b )
646 aBitfield |= a;
647 else
648 aBitfield &= ~a;
649
650 if( temp )
651 aBitfield |= b;
652 else
653 aBitfield &= ~b;
654 };
655
656 if( aFlipLeftRight )
657 {
658 mirrorBitFlags( m_chamferPositions, RECT_CHAMFER_TOP_LEFT, RECT_CHAMFER_TOP_RIGHT );
659 mirrorBitFlags( m_chamferPositions, RECT_CHAMFER_BOTTOM_LEFT, RECT_CHAMFER_BOTTOM_RIGHT );
660 }
661 else
662 {
663 mirrorBitFlags( m_chamferPositions, RECT_CHAMFER_TOP_LEFT, RECT_CHAMFER_BOTTOM_LEFT );
664 mirrorBitFlags( m_chamferPositions, RECT_CHAMFER_TOP_RIGHT, RECT_CHAMFER_BOTTOM_RIGHT );
665 }
666
667 // flip pads layers
668 // PADS items are currently on all copper layers, or
669 // currently, only on Front or Back layers.
670 // So the copper layers count is not taken in account
671 SetLayerSet( FlipLayerMask( m_layerMask ) );
672
673 // Flip the basic shapes, in custom pads
674 FlipPrimitives( aFlipLeftRight );
675
676 SetDirty();
677 }
678
679
FlipPrimitives(bool aFlipLeftRight)680 void PAD::FlipPrimitives( bool aFlipLeftRight )
681 {
682 for( std::shared_ptr<PCB_SHAPE>& primitive : m_editPrimitives )
683 primitive->Flip( wxPoint( 0, 0 ), aFlipLeftRight );
684
685 SetDirty();
686 }
687
688
ShapePos() const689 wxPoint PAD::ShapePos() const
690 {
691 if( m_offset.x == 0 && m_offset.y == 0 )
692 return m_pos;
693
694 wxPoint loc_offset = m_offset;
695
696 RotatePoint( &loc_offset, m_orient );
697
698 wxPoint shape_pos = m_pos + loc_offset;
699
700 return shape_pos;
701 }
702
703
GetLocalClearanceOverrides(wxString * aSource) const704 int PAD::GetLocalClearanceOverrides( wxString* aSource ) const
705 {
706 // A pad can have specific clearance that overrides its NETCLASS clearance value
707 if( GetLocalClearance() )
708 return GetLocalClearance( aSource );
709
710 // A footprint can have a specific clearance value
711 if( GetParent() && GetParent()->GetLocalClearance() )
712 return GetParent()->GetLocalClearance( aSource );
713
714 return 0;
715 }
716
717
GetLocalClearance(wxString * aSource) const718 int PAD::GetLocalClearance( wxString* aSource ) const
719 {
720 if( aSource )
721 *aSource = _( "pad" );
722
723 return m_localClearance;
724 }
725
726
GetSolderMaskMargin() const727 int PAD::GetSolderMaskMargin() const
728 {
729 // The pad inherits the margin only to calculate a default shape,
730 // therefore only if it is also a copper layer
731 // Pads defined only on mask layers (and perhaps on other tech layers) use the shape
732 // defined by the pad settings only
733 bool isOnCopperLayer = ( m_layerMask & LSET::AllCuMask() ).any();
734
735 if( !isOnCopperLayer )
736 return 0;
737
738 int margin = m_localSolderMaskMargin;
739
740 FOOTPRINT* parentFootprint = GetParent();
741
742 if( parentFootprint )
743 {
744 if( margin == 0 )
745 {
746 if( parentFootprint->GetLocalSolderMaskMargin() )
747 margin = parentFootprint->GetLocalSolderMaskMargin();
748 }
749
750 if( margin == 0 )
751 {
752 const BOARD* brd = GetBoard();
753
754 if( brd )
755 margin = brd->GetDesignSettings().m_SolderMaskMargin;
756 }
757 }
758
759 // ensure mask have a size always >= 0
760 if( margin < 0 )
761 {
762 int minsize = -std::min( m_size.x, m_size.y ) / 2;
763
764 if( margin < minsize )
765 margin = minsize;
766 }
767
768 return margin;
769 }
770
771
GetSolderPasteMargin() const772 wxSize PAD::GetSolderPasteMargin() const
773 {
774 // The pad inherits the margin only to calculate a default shape,
775 // therefore only if it is also a copper layer.
776 // Pads defined only on mask layers (and perhaps on other tech layers) use the shape
777 // defined by the pad settings only
778 bool isOnCopperLayer = ( m_layerMask & LSET::AllCuMask() ).any();
779
780 if( !isOnCopperLayer )
781 return wxSize( 0, 0 );
782
783 int margin = m_localSolderPasteMargin;
784 double mratio = m_localSolderPasteMarginRatio;
785
786 FOOTPRINT* parentFootprint = GetParent();
787
788 if( parentFootprint )
789 {
790 if( margin == 0 )
791 margin = parentFootprint->GetLocalSolderPasteMargin();
792
793 auto brd = GetBoard();
794
795 if( margin == 0 && brd )
796 margin = brd->GetDesignSettings().m_SolderPasteMargin;
797
798 if( mratio == 0.0 )
799 mratio = parentFootprint->GetLocalSolderPasteMarginRatio();
800
801 if( mratio == 0.0 && brd )
802 {
803 mratio = brd->GetDesignSettings().m_SolderPasteMarginRatio;
804 }
805 }
806
807 wxSize pad_margin;
808 pad_margin.x = margin + KiROUND( m_size.x * mratio );
809 pad_margin.y = margin + KiROUND( m_size.y * mratio );
810
811 // ensure mask have a size always >= 0
812 if( pad_margin.x < -m_size.x / 2 )
813 pad_margin.x = -m_size.x / 2;
814
815 if( pad_margin.y < -m_size.y / 2 )
816 pad_margin.y = -m_size.y / 2;
817
818 return pad_margin;
819 }
820
821
GetEffectiveZoneConnection(wxString * aSource) const822 ZONE_CONNECTION PAD::GetEffectiveZoneConnection( wxString* aSource ) const
823 {
824 FOOTPRINT* parentFootprint = GetParent();
825
826 if( m_zoneConnection == ZONE_CONNECTION::INHERITED && parentFootprint )
827 {
828 if( aSource )
829 *aSource = _( "parent footprint" );
830
831 return parentFootprint->GetZoneConnection();
832 }
833 else
834 {
835 if( aSource )
836 *aSource = _( "pad" );
837
838 return m_zoneConnection;
839 }
840 }
841
842
GetEffectiveThermalSpokeWidth(wxString * aSource) const843 int PAD::GetEffectiveThermalSpokeWidth( wxString* aSource ) const
844 {
845 FOOTPRINT* parentFootprint = GetParent();
846
847 if( m_thermalWidth == 0 && parentFootprint )
848 {
849 if( aSource )
850 *aSource = _( "parent footprint" );
851
852 return parentFootprint->GetThermalWidth();
853 }
854
855 if( aSource )
856 *aSource = _( "pad" );
857
858 return m_thermalWidth;
859 }
860
861
GetEffectiveThermalGap(wxString * aSource) const862 int PAD::GetEffectiveThermalGap( wxString* aSource ) const
863 {
864 FOOTPRINT* parentFootprint = GetParent();
865
866 if( m_thermalGap == 0 && parentFootprint )
867 {
868 if( aSource )
869 *aSource = _( "parent footprint" );
870
871 return parentFootprint->GetThermalGap();
872 }
873
874 if( aSource )
875 *aSource = _( "pad" );
876
877 return m_thermalGap;
878 }
879
880
GetMsgPanelInfo(EDA_DRAW_FRAME * aFrame,std::vector<MSG_PANEL_ITEM> & aList)881 void PAD::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
882 {
883 EDA_UNITS units = aFrame->GetUserUnits();
884 wxString msg;
885 FOOTPRINT* parentFootprint = static_cast<FOOTPRINT*>( m_parent );
886
887 if( aFrame->GetName() == PCB_EDIT_FRAME_NAME )
888 {
889 if( parentFootprint )
890 aList.emplace_back( _( "Footprint" ), parentFootprint->GetReference() );
891 }
892
893 aList.emplace_back( _( "Pad" ), m_number );
894
895 if( !GetPinFunction().IsEmpty() )
896 aList.emplace_back( _( "Pin Name" ), GetPinFunction() );
897
898 if( !GetPinType().IsEmpty() )
899 aList.emplace_back( _( "Pin Type" ), GetPinType() );
900
901 if( aFrame->GetName() == PCB_EDIT_FRAME_NAME )
902 {
903 aList.emplace_back( _( "Net" ), UnescapeString( GetNetname() ) );
904
905 aList.emplace_back( _( "Net Class" ), UnescapeString( GetNetClass()->GetName() ) );
906
907 if( IsLocked() )
908 aList.emplace_back( _( "Status" ), _( "Locked" ) );
909 }
910
911 if( GetAttribute() == PAD_ATTRIB::SMD || GetAttribute() == PAD_ATTRIB::CONN )
912 aList.emplace_back( _( "Layer" ), layerMaskDescribe() );
913
914 // Show the pad shape, attribute and property
915 wxString props = ShowPadAttr();
916
917 if( GetProperty() != PAD_PROP::NONE )
918 props += ',';
919
920 switch( GetProperty() )
921 {
922 case PAD_PROP::NONE: break;
923 case PAD_PROP::BGA: props += _( "BGA" ); break;
924 case PAD_PROP::FIDUCIAL_GLBL: props += _( "Fiducial global" ); break;
925 case PAD_PROP::FIDUCIAL_LOCAL: props += _( "Fiducial local" ); break;
926 case PAD_PROP::TESTPOINT: props += _( "Test point" ); break;
927 case PAD_PROP::HEATSINK: props += _( "Heat sink" ); break;
928 case PAD_PROP::CASTELLATED: props += _( "Castellated" ); break;
929 }
930
931 aList.emplace_back( ShowPadShape(), props );
932
933 if( ( GetShape() == PAD_SHAPE::CIRCLE || GetShape() == PAD_SHAPE::OVAL ) &&
934 m_size.x == m_size.y )
935 {
936 aList.emplace_back( _( "Diameter" ), MessageTextFromValue( units, m_size.x ) );
937 }
938 else
939 {
940 aList.emplace_back( _( "Width" ), MessageTextFromValue( units, m_size.x ) );
941 aList.emplace_back( _( "Height" ), MessageTextFromValue( units, m_size.y ) );
942 }
943
944 double fp_orient_degrees = parentFootprint ? parentFootprint->GetOrientationDegrees() : 0;
945 double pad_orient_degrees = GetOrientationDegrees() - fp_orient_degrees;
946 pad_orient_degrees = NormalizeAngleDegrees( pad_orient_degrees, -180.0, +180.0 );
947
948 if( fp_orient_degrees != 0.0 )
949 msg.Printf( wxT( "%g(+ %g)" ), pad_orient_degrees, fp_orient_degrees );
950 else
951 msg.Printf( wxT( "%g" ), GetOrientationDegrees() );
952
953 aList.emplace_back( _( "Rotation" ), msg );
954
955 if( GetPadToDieLength() )
956 {
957 msg = MessageTextFromValue(units, GetPadToDieLength() );
958 aList.emplace_back( _( "Length in Package" ), msg );
959 }
960
961 if( m_drill.x > 0 || m_drill.y > 0 )
962 {
963 if( GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE )
964 {
965 aList.emplace_back( _( "Hole" ),
966 wxString::Format( "%s",
967 MessageTextFromValue( units, m_drill.x ) ) );
968 }
969 else
970 {
971 aList.emplace_back( _( "Hole X / Y" ),
972 wxString::Format( "%s / %s",
973 MessageTextFromValue( units, m_drill.x ),
974 MessageTextFromValue( units, m_drill.y ) ) );
975 }
976 }
977
978 wxString source;
979 int clearance = GetOwnClearance( GetLayer(), &source );
980
981 if( !source.IsEmpty() )
982 {
983 aList.emplace_back( wxString::Format( _( "Min Clearance: %s" ),
984 MessageTextFromValue( units, clearance ) ),
985 wxString::Format( _( "(from %s)" ),
986 source ) );
987 }
988 #if 0
989 // useful for debug only
990 aList.emplace_back( "UUID", m_Uuid.AsString() );
991 #endif
992 }
993
994
HitTest(const wxPoint & aPosition,int aAccuracy) const995 bool PAD::HitTest( const wxPoint& aPosition, int aAccuracy ) const
996 {
997 VECTOR2I delta = aPosition - GetPosition();
998 int boundingRadius = GetBoundingRadius() + aAccuracy;
999
1000 if( delta.SquaredEuclideanNorm() > SEG::Square( boundingRadius ) )
1001 return false;
1002
1003 return GetEffectivePolygon()->Contains( aPosition, -1, aAccuracy );
1004 }
1005
1006
HitTest(const EDA_RECT & aRect,bool aContained,int aAccuracy) const1007 bool PAD::HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const
1008 {
1009 EDA_RECT arect = aRect;
1010 arect.Normalize();
1011 arect.Inflate( aAccuracy );
1012
1013 EDA_RECT bbox = GetBoundingBox();
1014
1015 if( aContained )
1016 {
1017 return arect.Contains( bbox );
1018 }
1019 else
1020 {
1021 // Fast test: if aRect is outside the polygon bounding box,
1022 // rectangles cannot intersect
1023 if( !arect.Intersects( bbox ) )
1024 return false;
1025
1026 const std::shared_ptr<SHAPE_POLY_SET>& poly = GetEffectivePolygon();
1027
1028 int count = poly->TotalVertices();
1029
1030 for( int ii = 0; ii < count; ii++ )
1031 {
1032 auto vertex = poly->CVertex( ii );
1033 auto vertexNext = poly->CVertex(( ii + 1 ) % count );
1034
1035 // Test if the point is within aRect
1036 if( arect.Contains( ( wxPoint ) vertex ) )
1037 return true;
1038
1039 // Test if this edge intersects aRect
1040 if( arect.Intersects( ( wxPoint ) vertex, ( wxPoint ) vertexNext ) )
1041 return true;
1042 }
1043
1044 return false;
1045 }
1046 }
1047
1048
Compare(const PAD * aPadRef,const PAD * aPadCmp)1049 int PAD::Compare( const PAD* aPadRef, const PAD* aPadCmp )
1050 {
1051 int diff;
1052
1053 if( ( diff = static_cast<int>( aPadRef->GetShape() ) -
1054 static_cast<int>( aPadCmp->GetShape() ) ) != 0 )
1055 return diff;
1056
1057 if( ( diff = static_cast<int>( aPadRef->m_attribute ) -
1058 static_cast<int>( aPadCmp->m_attribute ) ) != 0 )
1059 return diff;
1060
1061 if( ( diff = aPadRef->m_drillShape - aPadCmp->m_drillShape ) != 0 )
1062 return diff;
1063
1064 if( ( diff = aPadRef->m_drill.x - aPadCmp->m_drill.x ) != 0 )
1065 return diff;
1066
1067 if( ( diff = aPadRef->m_drill.y - aPadCmp->m_drill.y ) != 0 )
1068 return diff;
1069
1070 if( ( diff = aPadRef->m_size.x - aPadCmp->m_size.x ) != 0 )
1071 return diff;
1072
1073 if( ( diff = aPadRef->m_size.y - aPadCmp->m_size.y ) != 0 )
1074 return diff;
1075
1076 if( ( diff = aPadRef->m_offset.x - aPadCmp->m_offset.x ) != 0 )
1077 return diff;
1078
1079 if( ( diff = aPadRef->m_offset.y - aPadCmp->m_offset.y ) != 0 )
1080 return diff;
1081
1082 if( ( diff = aPadRef->m_deltaSize.x - aPadCmp->m_deltaSize.x ) != 0 )
1083 return diff;
1084
1085 if( ( diff = aPadRef->m_deltaSize.y - aPadCmp->m_deltaSize.y ) != 0 )
1086 return diff;
1087
1088 if( ( diff = aPadRef->m_roundedCornerScale - aPadCmp->m_roundedCornerScale ) != 0 )
1089 return diff;
1090
1091 if( ( diff = aPadRef->m_chamferPositions - aPadCmp->m_chamferPositions ) != 0 )
1092 return diff;
1093
1094 if( ( diff = aPadRef->m_chamferScale - aPadCmp->m_chamferScale ) != 0 )
1095 return diff;
1096
1097 if( ( diff = static_cast<int>( aPadRef->m_editPrimitives.size() ) -
1098 static_cast<int>( aPadCmp->m_editPrimitives.size() ) ) != 0 )
1099 return diff;
1100
1101 // @todo: Compare custom pad primitives for pads that have the same number of primitives
1102 // here. Currently there is no compare function for PCB_SHAPE objects.
1103
1104 // Dick: specctra_export needs this
1105 // Lorenzo: gencad also needs it to implement padstacks!
1106
1107 #if __cplusplus >= 201103L
1108 long long d = aPadRef->m_layerMask.to_ullong() - aPadCmp->m_layerMask.to_ullong();
1109
1110 if( d < 0 )
1111 return -1;
1112 else if( d > 0 )
1113 return 1;
1114
1115 return 0;
1116 #else
1117 // these strings are not typically constructed, since we don't get here often.
1118 std::string s1 = aPadRef->m_layerMask.to_string();
1119 std::string s2 = aPadCmp->m_layerMask.to_string();
1120 return s1.compare( s2 );
1121 #endif
1122 }
1123
1124
Rotate(const wxPoint & aRotCentre,double aAngle)1125 void PAD::Rotate( const wxPoint& aRotCentre, double aAngle )
1126 {
1127 RotatePoint( &m_pos, aRotCentre, aAngle );
1128
1129 m_orient = NormalizeAngle360Min( m_orient + aAngle );
1130
1131 SetLocalCoord();
1132
1133 SetDirty();
1134 }
1135
1136
ShowPadShape() const1137 wxString PAD::ShowPadShape() const
1138 {
1139 switch( GetShape() )
1140 {
1141 case PAD_SHAPE::CIRCLE: return _( "Circle" );
1142 case PAD_SHAPE::OVAL: return _( "Oval" );
1143 case PAD_SHAPE::RECT: return _( "Rect" );
1144 case PAD_SHAPE::TRAPEZOID: return _( "Trap" );
1145 case PAD_SHAPE::ROUNDRECT: return _( "Roundrect" );
1146 case PAD_SHAPE::CHAMFERED_RECT: return _( "Chamferedrect" );
1147 case PAD_SHAPE::CUSTOM: return _( "CustomShape" );
1148 default: return wxT( "???" );
1149 }
1150 }
1151
1152
ShowPadAttr() const1153 wxString PAD::ShowPadAttr() const
1154 {
1155 switch( GetAttribute() )
1156 {
1157 case PAD_ATTRIB::PTH: return _( "PTH" );
1158 case PAD_ATTRIB::SMD: return _( "SMD" );
1159 case PAD_ATTRIB::CONN: return _( "Conn" );
1160 case PAD_ATTRIB::NPTH: return _( "NPTH" );
1161 default: return wxT( "???" );
1162 }
1163 }
1164
1165
GetSelectMenuText(EDA_UNITS aUnits) const1166 wxString PAD::GetSelectMenuText( EDA_UNITS aUnits ) const
1167 {
1168 if( GetNumber().IsEmpty() )
1169 {
1170 if( GetAttribute() == PAD_ATTRIB::SMD || GetAttribute() == PAD_ATTRIB::CONN )
1171 {
1172 return wxString::Format( _( "Pad %s of %s on %s" ),
1173 GetNetnameMsg(),
1174 GetParent()->GetReference(),
1175 layerMaskDescribe() );
1176 }
1177 else if( GetAttribute() == PAD_ATTRIB::NPTH && !FlashLayer( UNDEFINED_LAYER ) )
1178 {
1179 return wxString::Format( _( "Through hole pad %s of %s" ),
1180 wxT( "(" ) + _( "NPTH, Mechanical" ) + wxT( ")" ),
1181 GetParent()->GetReference() );
1182 }
1183 else
1184 {
1185 return wxString::Format( _( "Through hole pad %s of %s" ),
1186 GetNetnameMsg(),
1187 GetParent()->GetReference() );
1188 }
1189 }
1190 else
1191 {
1192 if( GetAttribute() == PAD_ATTRIB::SMD || GetAttribute() == PAD_ATTRIB::CONN )
1193 {
1194 return wxString::Format( _( "Pad %s %s of %s on %s" ),
1195 GetNumber(),
1196 GetNetnameMsg(),
1197 GetParent()->GetReference(),
1198 layerMaskDescribe() );
1199 }
1200 else if( GetAttribute() == PAD_ATTRIB::NPTH && !FlashLayer( UNDEFINED_LAYER ) )
1201 {
1202 return wxString::Format( _( "Through hole pad %s of %s" ),
1203 wxT( "(" ) + _( "NPTH, Mechanical" ) + wxT( ")" ),
1204 GetParent()->GetReference() );
1205 }
1206 else
1207 {
1208 return wxString::Format( _( "Through hole pad %s %s of %s" ),
1209 GetNumber(),
1210 GetNetnameMsg(),
1211 GetParent()->GetReference() );
1212 }
1213 }
1214 }
1215
1216
GetMenuImage() const1217 BITMAPS PAD::GetMenuImage() const
1218 {
1219 return BITMAPS::pad;
1220 }
1221
1222
Clone() const1223 EDA_ITEM* PAD::Clone() const
1224 {
1225 return new PAD( *this );
1226 }
1227
1228
ViewGetLayers(int aLayers[],int & aCount) const1229 void PAD::ViewGetLayers( int aLayers[], int& aCount ) const
1230 {
1231 aCount = 0;
1232
1233 // These 2 types of pads contain a hole
1234 if( m_attribute == PAD_ATTRIB::PTH )
1235 {
1236 aLayers[aCount++] = LAYER_PAD_PLATEDHOLES;
1237 aLayers[aCount++] = LAYER_PAD_HOLEWALLS;
1238 }
1239
1240 if( m_attribute == PAD_ATTRIB::NPTH )
1241 aLayers[aCount++] = LAYER_NON_PLATEDHOLES;
1242
1243 if( IsOnLayer( F_Cu ) && IsOnLayer( B_Cu ) )
1244 {
1245 // Multi layer pad
1246 aLayers[aCount++] = LAYER_PADS_TH;
1247 aLayers[aCount++] = LAYER_PAD_NETNAMES;
1248 }
1249 else if( IsOnLayer( F_Cu ) )
1250 {
1251 aLayers[aCount++] = LAYER_PAD_FR;
1252
1253 // Is this a PTH pad that has only front copper? If so, we need to also display the
1254 // net name on the PTH netname layer so that it isn't blocked by the drill hole.
1255 if( m_attribute == PAD_ATTRIB::PTH )
1256 aLayers[aCount++] = LAYER_PAD_NETNAMES;
1257 else
1258 aLayers[aCount++] = LAYER_PAD_FR_NETNAMES;
1259 }
1260 else if( IsOnLayer( B_Cu ) )
1261 {
1262 aLayers[aCount++] = LAYER_PAD_BK;
1263
1264 // Is this a PTH pad that has only back copper? If so, we need to also display the
1265 // net name on the PTH netname layer so that it isn't blocked by the drill hole.
1266 if( m_attribute == PAD_ATTRIB::PTH )
1267 aLayers[aCount++] = LAYER_PAD_NETNAMES;
1268 else
1269 aLayers[aCount++] = LAYER_PAD_BK_NETNAMES;
1270 }
1271 else
1272 {
1273 // Internal layers only. (Not yet supported in GUI, but is being used by Python
1274 // footprint generators and will be needed anyway once pad stacks are supported.)
1275 for ( int internal = In1_Cu; internal < In30_Cu; ++internal )
1276 {
1277 if( IsOnLayer( (PCB_LAYER_ID) internal ) )
1278 aLayers[aCount++] = internal;
1279 }
1280 }
1281
1282 // Check non-copper layers. This list should include all the layers that the
1283 // footprint editor allows a pad to be placed on.
1284 static const PCB_LAYER_ID layers_mech[] = { F_Mask, B_Mask, F_Paste, B_Paste,
1285 F_Adhes, B_Adhes, F_SilkS, B_SilkS, Dwgs_User, Eco1_User, Eco2_User };
1286
1287 for( PCB_LAYER_ID each_layer : layers_mech )
1288 {
1289 if( IsOnLayer( each_layer ) )
1290 aLayers[aCount++] = each_layer;
1291 }
1292
1293 #ifdef DEBUG
1294 if( aCount == 0 ) // Should not occur
1295 {
1296 wxString msg;
1297 msg.Printf( wxT( "footprint %s, pad %s: could not find valid layer for pad" ),
1298 GetParent() ? GetParent()->GetReference() : "<null>",
1299 GetNumber().IsEmpty() ? "(unnumbered)" : GetNumber() );
1300 wxLogDebug( msg );
1301 }
1302 #endif
1303 }
1304
1305
ViewGetLOD(int aLayer,KIGFX::VIEW * aView) const1306 double PAD::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
1307 {
1308 constexpr double HIDE = std::numeric_limits<double>::max();
1309
1310 PCB_PAINTER* painter = static_cast<PCB_PAINTER*>( aView->GetPainter() );
1311 PCB_RENDER_SETTINGS* renderSettings = painter->GetSettings();
1312 const BOARD* board = GetBoard();
1313 LSET visible = LSET::AllLayersMask();
1314
1315 // Meta control for hiding all pads
1316 if( !aView->IsLayerVisible( LAYER_PADS ) )
1317 return HIDE;
1318
1319 // Handle board visibility
1320 if( board )
1321 visible = board->GetVisibleLayers() & board->GetEnabledLayers();
1322
1323 // Handle Render tab switches
1324 if( ( GetAttribute() == PAD_ATTRIB::PTH || GetAttribute() == PAD_ATTRIB::NPTH )
1325 && !aView->IsLayerVisible( LAYER_PADS_TH ) )
1326 {
1327 return HIDE;
1328 }
1329
1330 if( !IsFlipped() && !aView->IsLayerVisible( LAYER_MOD_FR ) )
1331 return HIDE;
1332
1333 if( IsFlipped() && !aView->IsLayerVisible( LAYER_MOD_BK ) )
1334 return HIDE;
1335
1336 if( IsFrontLayer( (PCB_LAYER_ID) aLayer ) && !aView->IsLayerVisible( LAYER_PAD_FR ) )
1337 return HIDE;
1338
1339 if( IsBackLayer( (PCB_LAYER_ID) aLayer ) && !aView->IsLayerVisible( LAYER_PAD_BK ) )
1340 return HIDE;
1341
1342 if( aLayer == LAYER_PADS_TH )
1343 {
1344 if( !FlashLayer( visible ) )
1345 return HIDE;
1346 }
1347 else if( IsHoleLayer( aLayer ) )
1348 {
1349 if( !( visible & LSET::PhysicalLayersMask() ).any() )
1350 return HIDE;
1351 }
1352 else if( IsNetnameLayer( aLayer ) )
1353 {
1354 if( renderSettings->GetHighContrast() )
1355 {
1356 // Hide netnames unless pad is flashed to a high-contrast layer
1357 if( !FlashLayer( renderSettings->GetPrimaryHighContrastLayer() ) )
1358 return HIDE;
1359 }
1360 else
1361 {
1362 // Hide netnames unless pad is flashed to a visible layer
1363 if( !FlashLayer( visible ) )
1364 return HIDE;
1365 }
1366
1367 // Netnames will be shown only if zoom is appropriate
1368 int divisor = std::min( GetBoundingBox().GetWidth(), GetBoundingBox().GetHeight() );
1369
1370 // Pad sizes can be zero briefly when someone is typing a number like "0.5"
1371 // in the pad properties dialog
1372 if( divisor == 0 )
1373 return HIDE;
1374
1375 return ( double ) Millimeter2iu( 5 ) / divisor;
1376 }
1377
1378 if( aLayer == LAYER_PADS_TH
1379 && GetShape() != PAD_SHAPE::CUSTOM
1380 && GetSizeX() <= GetDrillSizeX()
1381 && GetSizeY() <= GetDrillSizeY() )
1382 {
1383 // Don't tweak the drawing code with a degenerate pad
1384 return HIDE;
1385 }
1386
1387 // Passed all tests; show.
1388 return 0.0;
1389 }
1390
1391
ViewBBox() const1392 const BOX2I PAD::ViewBBox() const
1393 {
1394 // Bounding box includes soldermask too. Remember mask and/or paste
1395 // margins can be < 0
1396 int solderMaskMargin = std::max( GetSolderMaskMargin(), 0 );
1397 VECTOR2I solderPasteMargin = VECTOR2D( GetSolderPasteMargin() );
1398 EDA_RECT bbox = GetBoundingBox();
1399
1400 // get the biggest possible clearance
1401 int clearance = 0;
1402
1403 for( PCB_LAYER_ID layer : GetLayerSet().Seq() )
1404 clearance = std::max( clearance, GetOwnClearance( layer ) );
1405
1406 // Look for the biggest possible bounding box
1407 int xMargin = std::max( solderMaskMargin, solderPasteMargin.x ) + clearance;
1408 int yMargin = std::max( solderMaskMargin, solderPasteMargin.y ) + clearance;
1409
1410 return BOX2I( VECTOR2I( bbox.GetOrigin() ) - VECTOR2I( xMargin, yMargin ),
1411 VECTOR2I( bbox.GetSize() ) + VECTOR2I( 2 * xMargin, 2 * yMargin ) );
1412 }
1413
1414
GetParent() const1415 FOOTPRINT* PAD::GetParent() const
1416 {
1417 return dynamic_cast<FOOTPRINT*>( m_parent );
1418 }
1419
1420
ImportSettingsFrom(const PAD & aMasterPad)1421 void PAD::ImportSettingsFrom( const PAD& aMasterPad )
1422 {
1423 SetShape( aMasterPad.GetShape() );
1424 SetLayerSet( aMasterPad.GetLayerSet() );
1425 SetAttribute( aMasterPad.GetAttribute() );
1426 SetProperty( aMasterPad.GetProperty() );
1427
1428 // I am not sure the m_LengthPadToDie must be imported, because this is
1429 // a parameter really specific to a given pad (JPC).
1430 // So this is currently non imported
1431 #if 0
1432 SetPadToDieLength( aMasterPad.GetPadToDieLength() );
1433 #endif
1434
1435 // The pad orientation, for historical reasons is the
1436 // pad rotation + parent rotation.
1437 // So we have to manage this parent rotation
1438 double pad_rot = aMasterPad.GetOrientation();
1439
1440 if( aMasterPad.GetParent() )
1441 pad_rot -= aMasterPad.GetParent()->GetOrientation();
1442
1443 if( GetParent() )
1444 pad_rot += GetParent()->GetOrientation();
1445
1446 SetOrientation( pad_rot );
1447
1448 SetSize( aMasterPad.GetSize() );
1449 SetDelta( wxSize( 0, 0 ) );
1450 SetOffset( aMasterPad.GetOffset() );
1451 SetDrillSize( aMasterPad.GetDrillSize() );
1452 SetDrillShape( aMasterPad.GetDrillShape() );
1453 SetRoundRectRadiusRatio( aMasterPad.GetRoundRectRadiusRatio() );
1454 SetChamferRectRatio( aMasterPad.GetChamferRectRatio() );
1455 SetChamferPositions( aMasterPad.GetChamferPositions() );
1456
1457 switch( aMasterPad.GetShape() )
1458 {
1459 case PAD_SHAPE::TRAPEZOID:
1460 SetDelta( aMasterPad.GetDelta() );
1461 break;
1462
1463 case PAD_SHAPE::CIRCLE:
1464 // ensure size.y == size.x
1465 SetSize( wxSize( GetSize().x, GetSize().x ) );
1466 break;
1467
1468 default:
1469 ;
1470 }
1471
1472 switch( aMasterPad.GetAttribute() )
1473 {
1474 case PAD_ATTRIB::SMD:
1475 case PAD_ATTRIB::CONN:
1476 // These pads do not have hole (they are expected to be only on one
1477 // external copper layer)
1478 SetDrillSize( wxSize( 0, 0 ) );
1479 break;
1480
1481 default:
1482 ;
1483 }
1484
1485 // copy also local settings:
1486 SetLocalClearance( aMasterPad.GetLocalClearance() );
1487 SetLocalSolderMaskMargin( aMasterPad.GetLocalSolderMaskMargin() );
1488 SetLocalSolderPasteMargin( aMasterPad.GetLocalSolderPasteMargin() );
1489 SetLocalSolderPasteMarginRatio( aMasterPad.GetLocalSolderPasteMarginRatio() );
1490
1491 SetZoneConnection( aMasterPad.GetEffectiveZoneConnection() );
1492 SetThermalSpokeWidth( aMasterPad.GetThermalSpokeWidth() );
1493 SetThermalGap( aMasterPad.GetThermalGap() );
1494
1495 SetCustomShapeInZoneOpt( aMasterPad.GetCustomShapeInZoneOpt() );
1496
1497 // Add or remove custom pad shapes:
1498 ReplacePrimitives( aMasterPad.GetPrimitives() );
1499 SetAnchorPadShape( aMasterPad.GetAnchorPadShape() );
1500
1501 SetDirty();
1502 }
1503
1504
SwapData(BOARD_ITEM * aImage)1505 void PAD::SwapData( BOARD_ITEM* aImage )
1506 {
1507 assert( aImage->Type() == PCB_PAD_T );
1508
1509 std::swap( *((PAD*) this), *((PAD*) aImage) );
1510 }
1511
1512
TransformHoleWithClearanceToPolygon(SHAPE_POLY_SET & aCornerBuffer,int aInflateValue,int aError,ERROR_LOC aErrorLoc) const1513 bool PAD::TransformHoleWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, int aInflateValue,
1514 int aError, ERROR_LOC aErrorLoc ) const
1515 {
1516 wxSize drillsize = GetDrillSize();
1517
1518 if( !drillsize.x || !drillsize.y )
1519 return false;
1520
1521 const SHAPE_SEGMENT* seg = GetEffectiveHoleShape();
1522
1523 TransformOvalToPolygon( aCornerBuffer, (wxPoint) seg->GetSeg().A, (wxPoint) seg->GetSeg().B,
1524 seg->GetWidth() + aInflateValue * 2, aError, aErrorLoc );
1525
1526 return true;
1527 }
1528
1529
TransformShapeWithClearanceToPolygon(SHAPE_POLY_SET & aCornerBuffer,PCB_LAYER_ID aLayer,int aClearanceValue,int aError,ERROR_LOC aErrorLoc,bool ignoreLineWidth) const1530 void PAD::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
1531 PCB_LAYER_ID aLayer, int aClearanceValue,
1532 int aError, ERROR_LOC aErrorLoc,
1533 bool ignoreLineWidth ) const
1534 {
1535 wxASSERT_MSG( !ignoreLineWidth, "IgnoreLineWidth has no meaning for pads." );
1536
1537 // minimal segment count to approximate a circle to create the polygonal pad shape
1538 // This minimal value is mainly for very small pads, like SM0402.
1539 // Most of time pads are using the segment count given by aError value.
1540 const int pad_min_seg_per_circle_count = 16;
1541 double angle = m_orient;
1542 int dx = m_size.x / 2;
1543 int dy = m_size.y / 2;
1544
1545 wxPoint padShapePos = ShapePos(); // Note: for pad having a shape offset,
1546 // the pad position is NOT the shape position
1547
1548 switch( GetShape() )
1549 {
1550 case PAD_SHAPE::CIRCLE:
1551 case PAD_SHAPE::OVAL:
1552 // Note: dx == dy is not guaranteed for circle pads in legacy boards
1553 if( dx == dy || ( GetShape() == PAD_SHAPE::CIRCLE ) )
1554 {
1555 TransformCircleToPolygon( aCornerBuffer, padShapePos, dx + aClearanceValue, aError,
1556 aErrorLoc, pad_min_seg_per_circle_count );
1557 }
1558 else
1559 {
1560 int half_width = std::min( dx, dy );
1561 wxPoint delta( dx - half_width, dy - half_width );
1562
1563 RotatePoint( &delta, angle );
1564
1565 TransformOvalToPolygon( aCornerBuffer, padShapePos - delta, padShapePos + delta,
1566 ( half_width + aClearanceValue ) * 2, aError, aErrorLoc,
1567 pad_min_seg_per_circle_count );
1568 }
1569
1570 break;
1571
1572 case PAD_SHAPE::TRAPEZOID:
1573 case PAD_SHAPE::RECT:
1574 {
1575 int ddx = GetShape() == PAD_SHAPE::TRAPEZOID ? m_deltaSize.x / 2 : 0;
1576 int ddy = GetShape() == PAD_SHAPE::TRAPEZOID ? m_deltaSize.y / 2 : 0;
1577
1578 SHAPE_POLY_SET outline;
1579 TransformTrapezoidToPolygon( outline, padShapePos, m_size, angle, ddx, ddy,
1580 aClearanceValue, aError, aErrorLoc );
1581 aCornerBuffer.Append( outline );
1582 break;
1583 }
1584
1585 case PAD_SHAPE::CHAMFERED_RECT:
1586 case PAD_SHAPE::ROUNDRECT:
1587 {
1588 bool doChamfer = GetShape() == PAD_SHAPE::CHAMFERED_RECT;
1589
1590 SHAPE_POLY_SET outline;
1591 TransformRoundChamferedRectToPolygon( outline, padShapePos, m_size, angle,
1592 GetRoundRectCornerRadius(),
1593 doChamfer ? GetChamferRectRatio() : 0,
1594 doChamfer ? GetChamferPositions() : 0,
1595 aClearanceValue, aError, aErrorLoc );
1596 aCornerBuffer.Append( outline );
1597 break;
1598 }
1599
1600 case PAD_SHAPE::CUSTOM:
1601 {
1602 SHAPE_POLY_SET outline;
1603 MergePrimitivesAsPolygon( &outline, aErrorLoc );
1604 outline.Rotate( -DECIDEG2RAD( m_orient ) );
1605 outline.Move( VECTOR2I( m_pos ) );
1606
1607 if( aClearanceValue )
1608 {
1609 int numSegs = std::max( GetArcToSegmentCount( aClearanceValue, aError, 360.0 ),
1610 pad_min_seg_per_circle_count );
1611 int clearance = aClearanceValue;
1612
1613 if( aErrorLoc == ERROR_OUTSIDE )
1614 {
1615 int actual_error = CircleToEndSegmentDeltaRadius( clearance, numSegs );
1616 clearance += GetCircleToPolyCorrection( actual_error );
1617 }
1618
1619 outline.Inflate( clearance, numSegs );
1620 outline.Simplify( SHAPE_POLY_SET::PM_FAST );
1621 outline.Fracture( SHAPE_POLY_SET::PM_FAST );
1622 }
1623
1624 aCornerBuffer.Append( outline );
1625 break;
1626 }
1627
1628 default:
1629 wxFAIL_MSG( "PAD::TransformShapeWithClearanceToPolygon no implementation for "
1630 + PAD_SHAPE_T_asString( GetShape() ) );
1631 break;
1632 }
1633 }
1634
1635
1636 static struct PAD_DESC
1637 {
PAD_DESCPAD_DESC1638 PAD_DESC()
1639 {
1640 ENUM_MAP<PAD_ATTRIB>::Instance()
1641 .Map( PAD_ATTRIB::PTH, _HKI( "Through-hole" ) )
1642 .Map( PAD_ATTRIB::SMD, _HKI( "SMD" ) )
1643 .Map( PAD_ATTRIB::CONN, _HKI( "Edge connector" ) )
1644 .Map( PAD_ATTRIB::NPTH, _HKI( "NPTH, mechanical" ) );
1645
1646 ENUM_MAP<PAD_SHAPE>::Instance()
1647 .Map( PAD_SHAPE::CIRCLE, _HKI( "Circle" ) )
1648 .Map( PAD_SHAPE::RECT, _HKI( "Rectangle" ) )
1649 .Map( PAD_SHAPE::OVAL, _HKI( "Oval" ) )
1650 .Map( PAD_SHAPE::TRAPEZOID, _HKI( "Trapezoid" ) )
1651 .Map( PAD_SHAPE::ROUNDRECT, _HKI( "Rounded rectangle" ) )
1652 .Map( PAD_SHAPE::CHAMFERED_RECT, _HKI( "Chamfered rectangle" ) )
1653 .Map( PAD_SHAPE::CUSTOM, _HKI( "Custom" ) );
1654
1655 ENUM_MAP<PAD_PROP>::Instance()
1656 .Map( PAD_PROP::NONE, _HKI( "None" ) )
1657 .Map( PAD_PROP::BGA, _HKI( "BGA pad" ) )
1658 .Map( PAD_PROP::FIDUCIAL_GLBL, _HKI( "Fiducial, global to board" ) )
1659 .Map( PAD_PROP::FIDUCIAL_LOCAL, _HKI( "Fiducial, local to footprint" ) )
1660 .Map( PAD_PROP::TESTPOINT, _HKI( "Test point pad" ) )
1661 .Map( PAD_PROP::HEATSINK, _HKI( "Heatsink pad" ) )
1662 .Map( PAD_PROP::CASTELLATED, _HKI( "Castellated pad" ) );
1663
1664 PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
1665 REGISTER_TYPE( PAD );
1666 propMgr.InheritsAfter( TYPE_HASH( PAD ), TYPE_HASH( BOARD_CONNECTED_ITEM ) );
1667
1668 auto padType = new PROPERTY_ENUM<PAD, PAD_ATTRIB>( _HKI( "Pad Type" ),
1669 &PAD::SetAttribute, &PAD::GetAttribute );
1670 propMgr.AddProperty( padType );
1671
1672 auto shape = new PROPERTY_ENUM<PAD, PAD_SHAPE>( _HKI( "Shape" ),
1673 &PAD::SetShape, &PAD::GetShape );
1674 propMgr.AddProperty( shape );
1675
1676 propMgr.AddProperty( new PROPERTY<PAD, wxString>( _HKI( "Pad Number" ),
1677 &PAD::SetNumber, &PAD::GetNumber ) );
1678 propMgr.AddProperty( new PROPERTY<PAD, wxString>( _HKI( "Pin Name" ),
1679 &PAD::SetPinFunction, &PAD::GetPinFunction ) );
1680 propMgr.AddProperty( new PROPERTY<PAD, wxString>( _HKI( "Pin Type" ),
1681 &PAD::SetPinType, &PAD::GetPinType ) );
1682 propMgr.AddProperty( new PROPERTY<PAD, double>( _HKI( "Orientation" ),
1683 &PAD::SetOrientationDegrees, &PAD::GetOrientationDegrees,
1684 PROPERTY_DISPLAY::DEGREE ) );
1685 propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Size X" ),
1686 &PAD::SetSizeX, &PAD::GetSizeX,
1687 PROPERTY_DISPLAY::DISTANCE ) );
1688 propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Size Y" ),
1689 &PAD::SetSizeY, &PAD::GetSizeY,
1690 PROPERTY_DISPLAY::DISTANCE ) );
1691 propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Hole Size X" ),
1692 &PAD::SetDrillSizeX, &PAD::GetDrillSizeX,
1693 PROPERTY_DISPLAY::DISTANCE ) );
1694 propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Hole Size Y" ),
1695 &PAD::SetDrillSizeY, &PAD::GetDrillSizeY,
1696 PROPERTY_DISPLAY::DISTANCE ) );
1697 propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Pad To Die Length" ),
1698 &PAD::SetPadToDieLength, &PAD::GetPadToDieLength,
1699 PROPERTY_DISPLAY::DISTANCE ) );
1700 propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Soldermask Margin Override" ),
1701 &PAD::SetLocalSolderMaskMargin, &PAD::GetLocalSolderMaskMargin,
1702 PROPERTY_DISPLAY::DISTANCE ) );
1703 propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Solderpaste Margin Override" ),
1704 &PAD::SetLocalSolderPasteMargin, &PAD::GetLocalSolderPasteMargin,
1705 PROPERTY_DISPLAY::DISTANCE ) );
1706 propMgr.AddProperty( new PROPERTY<PAD, double>( _HKI( "Solderpaste Margin Ratio Override" ),
1707 &PAD::SetLocalSolderPasteMarginRatio, &PAD::GetLocalSolderPasteMarginRatio ) );
1708 propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Thermal Relief Width" ),
1709 &PAD::SetThermalSpokeWidth, &PAD::GetThermalSpokeWidth,
1710 PROPERTY_DISPLAY::DISTANCE ) );
1711 propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Thermal Relief Gap" ),
1712 &PAD::SetThermalGap, &PAD::GetThermalGap,
1713 PROPERTY_DISPLAY::DISTANCE ) );
1714 propMgr.AddProperty( new PROPERTY_ENUM<PAD, PAD_PROP>( _HKI( "Fabrication Property" ),
1715 &PAD::SetProperty, &PAD::GetProperty ) );
1716
1717 auto roundRadiusRatio = new PROPERTY<PAD, double>( _HKI( "Round Radius Ratio" ),
1718 &PAD::SetRoundRectRadiusRatio, &PAD::GetRoundRectRadiusRatio );
1719 roundRadiusRatio->SetAvailableFunc(
1720 [=]( INSPECTABLE* aItem ) -> bool
1721 {
1722 return aItem->Get( shape ) == static_cast<int>( PAD_SHAPE::ROUNDRECT );
1723 } );
1724 propMgr.AddProperty( roundRadiusRatio );
1725
1726 propMgr.AddProperty( new PROPERTY<PAD, int>( _HKI( "Clearance Override" ),
1727 &PAD::SetLocalClearance, &PAD::GetLocalClearance,
1728 PROPERTY_DISPLAY::DISTANCE ) );
1729 propMgr.AddProperty( new PROPERTY<PAD, wxString>( _HKI( "Parent" ),
1730 NO_SETTER( PAD, wxString ), &PAD::GetParentAsString ) );
1731
1732 // TODO delta, drill shape offset, layer set, zone connection
1733 }
1734 } _PAD_DESC;
1735
1736 ENUM_TO_WXANY( PAD_ATTRIB );
1737 ENUM_TO_WXANY( PAD_SHAPE );
1738 ENUM_TO_WXANY( PAD_PROP );
1739