KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pad_utils.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "pad_utils.h"
21
22#include <algorithm>
23
24#include <wx/intl.h>
25
28#include <math/util.h>
29#include <pad.h>
30#include <padstack.h>
31#include <pcb_shape.h>
32#include <units_provider.h>
33
34
36{
37 const double defaultProportion = 0.25;
38 const double minimumSizeIU = pcbIUScale.mmToIU( 0.25 );
39
40 const VECTOR2I& size = aPad.GetSize( aLayer );
41 const int padMinSizeIU = std::min( size.x, size.y );
42 const double defaultRadiusIU = std::min( minimumSizeIU, padMinSizeIU * defaultProportion );
43
44 // Convert back to a ratio
45 return defaultRadiusIU / padMinSizeIU;
46}
47
48
50{
51 const PAD_SHAPE shape = aPad.GetShape( aLayer );
52 return shape == PAD_SHAPE::ROUNDRECT || shape == PAD_SHAPE::CHAMFERED_RECT;
53}
54
55
57{
58 aPadstack.Drill() = {};
59 aPadstack.SecondaryDrill() = {};
60 aPadstack.TertiaryDrill() = {};
61 aPadstack.FrontPostMachining() = {};
62 aPadstack.BackPostMachining() = {};
63}
64
65
66namespace
67{
68
69void checkPadLayer( const PAD& aPad, PCB_LAYER_ID aLayer, const UNITS_PROVIDER& aUnitsProvider, bool aForPadProperties,
70 const PAD_UTILS::PAD_CHECK_HANDLER& aErrorHandler )
71{
72 VECTOR2I pad_size = aPad.GetSize( aLayer );
73
74 if( aPad.GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
75 pad_size = aPad.GetBoundingBox().GetSize();
76 else if( pad_size.x <= 0 || ( pad_size.y <= 0 && aPad.GetShape( aLayer ) != PAD_SHAPE::CIRCLE ) )
77 aErrorHandler( { PAD_UTILS::PAD_CHECK_CODE::PAD_SIZE_NOT_POSITIVE, _( "Pad must have a positive size" ) } );
78
79 // Test hole against pad shape
80 if( aPad.IsOnCopperLayer() && aPad.GetDrillSize().x > 0 )
81 {
82 // Ensure the drill size can be handled in next calculations.
83 // Use min size = 4 IU to be able to build a polygon from a hole shape
84 const int min_drill_size = 4;
85
86 if( aPad.GetDrillSize().x <= min_drill_size || aPad.GetDrillSize().y <= min_drill_size )
87 {
89 wxString::Format( _( "PTH pad hole size must be larger than %s" ),
90 aUnitsProvider.StringFromValue( min_drill_size, true ) ) } );
91 }
92
93 SHAPE_POLY_SET padOutline;
94
95 aPad.TransformShapeToPolygon( padOutline, aLayer, 0, aPad.GetMaxError(), ERROR_INSIDE );
96
97 if( aPad.GetAttribute() == PAD_ATTRIB::PTH )
98 {
99 // Test if there is copper area outside hole
100 std::shared_ptr<SHAPE_SEGMENT> hole = aPad.GetEffectiveHoleShape();
101 SHAPE_POLY_SET holeOutline;
102
103 TransformOvalToPolygon( holeOutline, hole->GetSeg().A, hole->GetSeg().B, hole->GetWidth(),
104 aPad.GetMaxError(), ERROR_OUTSIDE );
105
106 SHAPE_POLY_SET copper = padOutline;
107 copper.BooleanSubtract( holeOutline );
108
109 if( copper.IsEmpty() )
110 {
112 _( "PTH pad hole leaves no copper" ) } );
113 }
114 else if( aForPadProperties )
115 {
116 // Test if the pad hole is fully inside the copper area. Note that we only run
117 // this check for pad properties because we run the more complete annular ring
118 // checker on the board (which handles multiple pads with the same name).
119 holeOutline.BooleanSubtract( padOutline );
120
121 if( !holeOutline.IsEmpty() )
122 {
124 _( "PTH pad hole not fully inside copper" ) } );
125 }
126 }
127 }
128 else
129 {
130 // Test only if the pad hole's centre is inside the copper area
131 if( !padOutline.Collide( aPad.GetPosition() ) )
132 {
134 _( "pad hole not inside pad shape" ) } );
135 }
136 }
137 }
138
139 if( aPad.GetLocalClearance().value_or( 0 ) < 0 )
140 {
142 _( "negative local clearance values have no effect" ) } );
143 }
144
145 // Some pads need a negative solder mask clearance (mainly for BGA with small pads)
146 // However the negative solder mask clearance must not create negative mask size
147 // Therefore test for minimal acceptable negative value
148 std::optional<int> solderMaskMargin = aPad.GetLocalSolderMaskMargin();
149
150 if( solderMaskMargin.has_value() && solderMaskMargin.value() < 0 )
151 {
152 int absMargin = abs( solderMaskMargin.value() );
153
154 if( aPad.GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
155 {
156 for( const std::shared_ptr<PCB_SHAPE>& shape : aPad.GetPrimitives( aLayer ) )
157 {
158 BOX2I shapeBBox = shape->GetBoundingBox();
159
160 if( absMargin > shapeBBox.GetWidth() || absMargin > shapeBBox.GetHeight() )
161 {
163 _( "negative solder mask clearance is larger than some shape primitives; "
164 "results may be surprising" ) } );
165
166 break;
167 }
168 }
169 }
170 else if( absMargin > pad_size.x || absMargin > pad_size.y )
171 {
173 _( "negative solder mask clearance is larger than pad; no solder mask will "
174 "be generated" ) } );
175 }
176 }
177
178 // Some pads need a positive solder paste clearance (mainly for BGA with small pads)
179 // However, a positive value can create issues if the resulting shape is too big.
180 // (like a solder paste creating a solder paste area on a neighbor pad or on the solder mask)
181 // So we could ask for user to confirm the choice
182 // For now we just check for disappearing paste
183 wxSize paste_size;
184 int paste_margin = aPad.GetLocalSolderPasteMargin().value_or( 0 );
185 auto mratio = aPad.GetLocalSolderPasteMarginRatio();
186
187 paste_size.x = pad_size.x + paste_margin + KiROUND( pad_size.x * mratio.value_or( 0 ) );
188 paste_size.y = pad_size.y + paste_margin + KiROUND( pad_size.y * mratio.value_or( 0 ) );
189
190 if( paste_size.x <= 0 || paste_size.y <= 0 )
191 {
193 _( "negative solder paste margin is larger than pad; no solder paste mask will "
194 "be generated" ) } );
195 }
196
197 if( aPad.GetShape( aLayer ) == PAD_SHAPE::ROUNDRECT )
198 {
199 if( aPad.GetRoundRectRadiusRatio( aLayer ) < 0.0 )
200 {
202 _( "negative corner radius is not allowed" ) } );
203 }
204 else if( aPad.GetRoundRectRadiusRatio( aLayer ) > 50.0 )
205 {
207 _( "corner size will make pad circular" ) } );
208 }
209 }
210 else if( aPad.GetShape( aLayer ) == PAD_SHAPE::CHAMFERED_RECT )
211 {
212 if( aPad.GetChamferRectRatio( aLayer ) < 0.0 )
213 {
215 _( "negative corner chamfer is not allowed" ) } );
216 }
217 else if( aPad.GetChamferRectRatio( aLayer ) > 50.0 )
218 {
220 _( "corner chamfer is too large" ) } );
221 }
222 }
223 else if( aPad.GetShape( aLayer ) == PAD_SHAPE::TRAPEZOID )
224 {
225 if( ( aPad.GetDelta( aLayer ).x < 0 && aPad.GetDelta( aLayer ).x < -aPad.GetSize( aLayer ).y )
226 || ( aPad.GetDelta( aLayer ).x > 0 && aPad.GetDelta( aLayer ).x > aPad.GetSize( aLayer ).y )
227 || ( aPad.GetDelta( aLayer ).y < 0 && aPad.GetDelta( aLayer ).y < -aPad.GetSize( aLayer ).x )
228 || ( aPad.GetDelta( aLayer ).y > 0 && aPad.GetDelta( aLayer ).y > aPad.GetSize( aLayer ).x ) )
229 {
231 _( "trapezoid delta is too large" ) } );
232 }
233 }
234
235 if( aPad.GetShape( aLayer ) == PAD_SHAPE::CUSTOM )
236 {
237 SHAPE_POLY_SET mergedPolygon;
238 aPad.MergePrimitivesAsPolygon( aLayer, &mergedPolygon );
239
240 if( mergedPolygon.OutlineCount() > 1 )
241 {
243 _( "custom pad shape must resolve to a single polygon" ) });
244 }
245 }
246}
247
248} // namespace
249
250
251void PAD_UTILS::CheckPad( const PAD& aPad, const UNITS_PROVIDER& aUnitsProvider, bool aForPadProperties,
252 const PAD_CHECK_HANDLER& aErrorHandler )
253{
255 [&]( PCB_LAYER_ID aLayer )
256 {
257 checkPadLayer( aPad, aLayer, aUnitsProvider, aForPadProperties, aErrorHandler );
258 } );
259
260 const LSET padlayers_mask = aPad.GetLayerSet();
261 const VECTOR2I drill_size = aPad.GetDrillSize();
262
263 if( !padlayers_mask[F_Cu] && !padlayers_mask[B_Cu] )
264 {
265 if( ( drill_size.x || drill_size.y ) && aPad.GetAttribute() != PAD_ATTRIB::NPTH )
266 {
268 _( "plated through holes normally have a copper pad on at least one outer layer" ) } );
269 }
270 }
271
273 && aPad.GetAttribute() == PAD_ATTRIB::NPTH )
274 {
275 aErrorHandler( { PAD_CHECK_CODE::FIDUCIAL_NOT_PLATED, _( "'fiducial' pads are normally plated" ) } );
276 }
277
279 {
280 aErrorHandler( { PAD_CHECK_CODE::TESTPOINT_NOT_PLATED, _( "'testpoint' pads are normally plated" ) } );
281 }
282
284 {
285 aErrorHandler( { PAD_CHECK_CODE::HEATSINK_NOT_PLATED, _( "'heatsink' pads are normally plated" ) } );
286 }
287
289 {
290 aErrorHandler( { PAD_CHECK_CODE::CASTELLATED_NOT_PTH, _( "'castellated' pads are normally PTH" ) } );
291 }
292
293 if( aPad.GetProperty() == PAD_PROP::BGA && aPad.GetAttribute() != PAD_ATTRIB::SMD )
294 {
295 aErrorHandler( { PAD_CHECK_CODE::BGA_NOT_SMD, _( "'BGA' property is for SMD pads" ) } );
296 }
297
299 {
300 aErrorHandler( { PAD_CHECK_CODE::MECHANICAL_NOT_PTH, _( "'mechanical' pads are normally PTH" ) } );
301 }
302
303 if( aPad.GetProperty() == PAD_PROP::PRESSFIT
304 && ( aPad.GetAttribute() != PAD_ATTRIB::PTH || !aPad.HasDrilledHole() ) )
305 {
306 aErrorHandler( { PAD_CHECK_CODE::PRESSFIT_NOT_PTH,
307 _( "'press-fit' pads are normally PTH with round holes" ) } );
308 }
309
310 switch( aPad.GetAttribute() )
311 {
312 case PAD_ATTRIB::NPTH: // Not plated, but through hole, a hole is expected
313 case PAD_ATTRIB::PTH: // Pad through hole, a hole is also expected
314 if( drill_size.x <= 0
315 || ( drill_size.y <= 0 && aPad.GetDrillShape() == PAD_DRILL_SHAPE::OBLONG ) )
316 {
317 aErrorHandler( { PAD_CHECK_CODE::TH_NO_HOLE, wxEmptyString } );
318 }
319 break;
320
321 case PAD_ATTRIB::CONN: // Connector pads are smd pads, just they do not have solder paste.
322 if( padlayers_mask[B_Paste] || padlayers_mask[F_Paste] )
323 {
324 aErrorHandler( { PAD_CHECK_CODE::CONN_HAS_PASTE,
325 _( "connector pads normally have no solder paste; use a SMD pad instead" ) } );
326 }
328
329 case PAD_ATTRIB::SMD: // SMD and Connector pads (One external copper layer only)
330 {
331 if( drill_size.x > 0 || drill_size.y > 0 )
332 {
333 aErrorHandler( { PAD_CHECK_CODE::SMD_HAS_HOLE, _( "SMD pad has a hole" ) } );
334 }
335
336 LSET innerlayers_mask = padlayers_mask & LSET::InternalCuMask();
337
338 if( aPad.IsOnLayer( F_Cu ) && aPad.IsOnLayer( B_Cu ) )
339 {
341 _( "SMD pad has copper on both sides of the board" ) } );
342 }
343 else if( aPad.IsOnLayer( F_Cu ) )
344 {
345 if( aPad.IsOnLayer( B_Mask ) )
346 {
348 _( "SMD pad has copper and mask layers on different sides of the board" ) } );
349 }
350 else if( aPad.IsOnLayer( B_Paste ) )
351 {
353 _( "SMD pad has copper and paste layers on different sides of the board" ) } );
354 }
355 }
356 else if( aPad.IsOnLayer( B_Cu ) )
357 {
358 if( aPad.IsOnLayer( F_Mask ) )
359 {
361 _( "SMD pad has copper and mask layers on different sides of the board" ) } );
362 }
363 else if( aPad.IsOnLayer( F_Paste ) )
364 {
366 _( "SMD pad has copper and paste layers on different sides of the board" ) } );
367 }
368 }
369 else if( innerlayers_mask.count() != 0 )
370 {
371 aErrorHandler( { PAD_CHECK_CODE::PAD_NO_OUTER_COPPER, _( "SMD pad has no outer layers" ) } );
372 }
373
374 break;
375 }
376 }
377}
@ ERROR_OUTSIDE
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
int GetMaxError() const
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr const SizeVec & GetSize() const
Definition box2.h:203
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & InternalCuMask()
Return a complete set of internal copper layers which is all Cu layers except F_Cu and B_Cu.
Definition lset.cpp:573
A PADSTACK defines the characteristics of a single or multi-layer pad, in the IPC sense of the word.
Definition padstack.h:156
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::...
POST_MACHINING_PROPS & FrontPostMachining()
Definition padstack.h:367
DRILL_PROPS & TertiaryDrill()
Definition padstack.h:364
DRILL_PROPS & Drill()
Definition padstack.h:358
DRILL_PROPS & SecondaryDrill()
Definition padstack.h:361
POST_MACHINING_PROPS & BackPostMachining()
Definition padstack.h:370
Definition pad.h:61
PAD_PROP GetProperty() const
Definition pad.h:563
LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition pad.h:557
const std::vector< std::shared_ptr< PCB_SHAPE > > & GetPrimitives(PCB_LAYER_ID aLayer) const
Accessor to the basic shape list for custom-shaped pads.
Definition pad.h:375
void MergePrimitivesAsPolygon(PCB_LAYER_ID aLayer, SHAPE_POLY_SET *aMergedPolygon, ERROR_LOC aErrorLoc=ERROR_INSIDE) const
Merge all basic shapes to a SHAPE_POLY_SET.
Definition pad.cpp:3571
std::shared_ptr< SHAPE_SEGMENT > GetEffectiveHoleShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, DRC_CONSTRAINT_T aUsage=NULL_CONSTRAINT) const override
Return a SHAPE_SEGMENT object representing the pad's hole.
Definition pad.cpp:1361
const BOX2I GetBoundingBox() const override
The bounding box is cached, so this will be efficient most of the time.
Definition pad.cpp:1673
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
Definition pad.h:924
std::optional< double > GetLocalSolderPasteMarginRatio() const
Definition pad.h:600
PAD_ATTRIB GetAttribute() const
Definition pad.h:560
const VECTOR2I & GetDelta(PCB_LAYER_ID aLayer) const
Definition pad.h:307
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc=ERROR_INSIDE, bool aIgnoreLineWidth=false) const override
Convert the pad shape to a closed polygon.
Definition pad.cpp:3136
VECTOR2I GetPosition() const override
Definition pad.cpp:255
VECTOR2I GetDrillSize() const
Definition pad.h:320
double GetRoundRectRadiusRatio(PCB_LAYER_ID aLayer) const
Definition pad.h:811
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition pad.h:205
VECTOR2I GetSize(PCB_LAYER_ID aLayer) const
Definition pad.cpp:297
std::optional< int > GetLocalClearance() const override
Return any local clearances set in the "classic" (ie: pre-rule) system.
Definition pad.h:583
bool IsOnCopperLayer() const override
Definition pad.cpp:1969
const PADSTACK & Padstack() const
Definition pad.h:331
PAD_DRILL_SHAPE GetDrillShape() const
Definition pad.h:434
std::optional< int > GetLocalSolderPasteMargin() const
Definition pad.h:593
std::optional< int > GetLocalSolderMaskMargin() const
Definition pad.h:586
double GetChamferRectRatio(PCB_LAYER_ID aLayer) const
Definition pad.h:834
bool HasDrilledHole() const override
Definition pad.h:118
Represent a set of closed polygons.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
int OutlineCount() const
Return the number of outlines in the set.
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
wxString StringFromValue(double aValue, bool aAddUnitLabel=false, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
Converts aValue in internal units into a united string.
void TransformOvalToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a oblong shape to a polygon, using multiple segments.
#define _(s)
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_Paste
Definition layer_ids.h:100
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ B_Paste
Definition layer_ids.h:101
@ F_Cu
Definition layer_ids.h:60
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:79
bool PadHasMeaningfulRoundingRadius(const PAD &aPad, PCB_LAYER_ID aLayer)
Returns true if the pad's rounding ratio is valid (i.e.
Definition pad_utils.cpp:49
void RemoveAllDrillsFromPadstack(PADSTACK &aPadstack)
Remove the primary drill, the secondary and tertiary drills, and any post-machining operations from a...
Definition pad_utils.cpp:56
double GetDefaultIpcRoundingRatio(const PAD &aPad, PCB_LAYER_ID aLayer)
Get a sensible default for a rounded rectangle pad's rounding ratio.
Definition pad_utils.cpp:35
void CheckPad(const PAD &aPad, const UNITS_PROVIDER &aUnitsProvider, bool aForPadProperties, const PAD_CHECK_HANDLER &aErrorHandler)
Run non-board-specific checks on a pad.
std::function< void(const PAD_CHECK_RESULT &)> PAD_CHECK_HANDLER
Definition pad_utils.h:113
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:98
@ PTH
Plated through hole pad.
Definition padstack.h:97
@ CONN
Like smd, does not appear on the solder paste layer (default) Note: also has a special attribute in G...
Definition padstack.h:99
PAD_SHAPE
The set of pad shapes, used with PAD::{Set,Get}Shape()
Definition padstack.h:51
@ CHAMFERED_RECT
Definition padstack.h:59
@ ROUNDRECT
Definition padstack.h:56
@ TRAPEZOID
Definition padstack.h:55
@ FIDUCIAL_LOCAL
a fiducial (usually a smd) local to the parent footprint
Definition padstack.h:117
@ FIDUCIAL_GLBL
a fiducial (usually a smd) for the full board
Definition padstack.h:116
@ MECHANICAL
a pad used for mechanical support
Definition padstack.h:121
@ PRESSFIT
a PTH with a hole diameter with tight tolerances for press fit pin
Definition padstack.h:122
@ HEATSINK
a pad used as heat sink, usually in SMD footprints
Definition padstack.h:119
@ TESTPOINT
a test point pad
Definition padstack.h:118
@ CASTELLATED
a pad with a castellated through hole
Definition padstack.h:120
@ BGA
Smd pad, used in BGA footprints.
Definition padstack.h:115
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708