KiCad PCB EDA Suite
Loading...
Searching...
No Matches
footprint_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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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 "footprint_utils.h"
21
22#include <algorithm>
23#include <map>
24#include <set>
25#include <unordered_map>
26
27#include <wx/intl.h>
28
29#include <board.h>
30#include <core/kicad_algo.h>
31#include <core/mirror.h>
32#include <drc/drc_item.h>
33#include <footprint.h>
35#include <item_realignment.h>
36#include <pad.h>
37#include <pad_utils.h>
38#include <pcb_field.h>
39#include <trigo.h>
40#include <zone.h>
41
42
43bool ComputeFootprintShift( const FOOTPRINT& aExisting, const FOOTPRINT& aNew, VECTOR2I& aShift,
44 EDA_ANGLE& aAngleShift )
45{
46 // First, we will collect a list of "useful" corresponding points in the two footprints.
47 // To be useful, a point from a pad must have a unique pad number, as in the general case, we
48 // won't know which of multiple same-named pads in one footprint correspond to which in the
49 // other.
50 //
51 // Theoretically, we could use graphics items and other hints like pad shape matching
52 // but these would be hard to unambiguously match in the general case and pad numbers should
53 // cover 99% of cases.
54
55 const auto getUniquelyNumberedPads =
56 []( const FOOTPRINT& fp ) -> std::unordered_map<wxString, VECTOR2I>
57 {
58 std::unordered_map<wxString, VECTOR2I> result;
59 std::unordered_set<wxString> seenDuplicate;
60
61 for( PAD* pad : fp.Pads() )
62 {
63 const wxString& number = pad->GetNumber();
64
65 // Already seen a pad with this number, so not unique
66 // (and it won't be found in result)
67 if( seenDuplicate.find( number ) != seenDuplicate.end() )
68 continue;
69
70 // Already have a pad with this number, so not unique.
71 // Remove the previous entry from the result and mark this number as seen.
72 if( result.find( number ) != result.end() )
73 {
74 result.erase( number );
75 seenDuplicate.insert( number );
76 continue;
77 }
78
79 // Use the exact library-frame position. PAD::GetFPRelativePosition()
80 // applies the footprint transform in integer coordinates, which can
81 // cause rounding errors.
82 result[number] = pad->GetLibraryPosition();
83 }
84
85 return result;
86 };
87
88 std::unordered_map<wxString, VECTOR2I> existingPads = getUniquelyNumberedPads( aExisting );
89 std::unordered_map<wxString, VECTOR2I> newPads = getUniquelyNumberedPads( aNew );
90
91 // Pads of footprints on opposite sides are mirror images in the library frame, so they
92 // cannot be aligned by a rotation and translation. Mirror the new pads onto the existing
93 // footprint's side to match. FOOTPRINT::Flip() always mirrors children about the
94 // footprint's library origin, whatever the flip centre, so this mirror is about (0,0).
95 if( aExisting.IsFlipped() != aNew.IsFlipped() )
96 {
97 for( auto& [number, pos] : newPads )
98 MIRROR( pos.y, 0 );
99 }
100
101 std::vector<VECTOR2I> existingPoints;
102 std::vector<VECTOR2I> newPoints;
103
104 // The matching points are the ones with the same unique pad number in both footprints
105 // If a pad is missing in the new footprint, it won't be included.
106 for( const auto& [number, pos] : existingPads )
107 {
108 auto it = newPads.find( number );
109
110 if( it == newPads.end() )
111 continue;
112
113 existingPoints.push_back( pos );
114 newPoints.push_back( it->second );
115 }
116
117 ORTHO_ITEM_REALIGNER aligner;
118
119 std::optional<ITEM_REALIGNER_BASE::TRANSFORM> transform =
120 aligner.GetTransform( existingPoints, newPoints );
121
122 if( !transform.has_value() )
123 {
124 // Failed to compute a transform - perhaps there were no useful pads
125 aShift = VECTOR2I( 0, 0 );
126 aAngleShift = ANGLE_0;
127 return false;
128 }
129
130 aShift = GetRotated( transform->m_Translation, aExisting.GetOrientation() );
131 aAngleShift = transform->m_Rotation;
132
133 return true;
134}
135
136
138 const FOOTPRINT& aFootprint, const std::function<void( const wxString& )>& aErrorHandler )
139{
140 int likelyAttr = ( aFootprint.GetLikelyAttribute() & ( FP_SMD | FP_THROUGH_HOLE ) );
141 int setAttr = ( aFootprint.GetAttributes() & ( FP_SMD | FP_THROUGH_HOLE ) );
142
143 if( setAttr && likelyAttr && setAttr != likelyAttr )
144 {
145 wxString msg;
146
147 switch( likelyAttr )
148 {
149 case FP_THROUGH_HOLE:
150 msg.Printf( _( "(expected 'Through hole'; actual '%s')" ), aFootprint.GetTypeName() );
151 break;
152 case FP_SMD:
153 msg.Printf( _( "(expected 'SMD'; actual '%s')" ), aFootprint.GetTypeName() );
154 break;
155 }
156
157 if( aErrorHandler )
158 aErrorHandler( msg );
159 }
160}
161
162
163void FOOTPRINT_UTILS::CheckPads( const FOOTPRINT& aFootprint, const UNITS_PROVIDER& aUnitsProvider,
164 const PAD_CHECK_HANDLER& aErrorHandler )
165{
166 if( aErrorHandler == nullptr )
167 return;
168
169 for( const PAD* pad : aFootprint.Pads() )
170 {
171 PAD_UTILS::CheckPad( *pad, aUnitsProvider, false,
172 [&]( const PAD_UTILS::PAD_CHECK_RESULT& aResult )
173 {
174 aErrorHandler( *pad, aResult );
175 } );
176 }
177}
178
179
181 const FOOTPRINT& aFootprint,
182 const std::function<void( const PAD*, const PAD*, int aErrorCode,
183 const VECTOR2I& )>& aErrorHandler )
184{
185 std::unordered_map<PTR_PTR_CACHE_KEY, int> checkedPairs;
186
187 for( PAD* pad : aFootprint.Pads() )
188 {
189 std::vector<PAD*> netTiePads = aFootprint.GetNetTiePads( pad );
190
191 for( PAD* other : aFootprint.Pads() )
192 {
193 if( other == pad )
194 continue;
195
196 // store canonical order so we don't collide in both directions (a:b and b:a)
197 PAD* a = pad;
198 PAD* b = other;
199
200 if( static_cast<void*>( a ) > static_cast<void*>( b ) )
201 std::swap( a, b );
202
203 if( checkedPairs.find( { a, b } ) == checkedPairs.end() )
204 {
205 checkedPairs[ { a, b } ] = 1;
206
207 if( pad->HasDrilledHole() && other->HasDrilledHole() )
208 {
209 VECTOR2I pos = pad->GetPosition();
210
211 if( pad->GetPosition() == other->GetPosition() )
212 {
213 aErrorHandler( pad, other, DRCE_DRILLED_HOLES_COLOCATED, pos );
214 }
215 else
216 {
217 std::shared_ptr<SHAPE_SEGMENT> holeA = pad->GetEffectiveHoleShape( UNDEFINED_LAYER,
219 std::shared_ptr<SHAPE_SEGMENT> holeB = other->GetEffectiveHoleShape( UNDEFINED_LAYER,
221
222 if( holeA->Collide( holeB->GetSeg(), 0 ) )
223 aErrorHandler( pad, other, DRCE_DRILLED_HOLES_TOO_CLOSE, pos );
224 }
225 }
226
227 if( pad->SameLogicalPadAs( other ) || alg::contains( netTiePads, other ) )
228 continue;
229
230 if( !( ( pad->GetLayerSet() & other->GetLayerSet() ) & LSET::AllCuMask() ).any() )
231 continue;
232
233 if( pad->GetBoundingBox().Intersects( other->GetBoundingBox() ) )
234 {
235 VECTOR2I pos;
236
237 for( PCB_LAYER_ID l : pad->Padstack().RelevantShapeLayers( other->Padstack() ) )
238 {
239 SHAPE* padShape = pad->GetEffectiveShape( l ).get();
240 SHAPE* otherShape = other->GetEffectiveShape( l ).get();
241
242 if( padShape->Collide( otherShape, 0, nullptr, &pos ) )
243 aErrorHandler( pad, other, DRCE_SHORTING_ITEMS, pos );
244 }
245 }
246 }
247 }
248 }
249}
250
251
253 const FOOTPRINT& aFootprint,
254 const std::function<void( const BOARD_ITEM*, const BOARD_ITEM*, const BOARD_ITEM*,
255 const VECTOR2I& )>& aErrorHandler )
256{
257 // First build a map from pad numbers to allowed-shorting-group indexes. This ends up being
258 // something like O(3n), but it still beats O(n^2) for large numbers of pads.
259
260 std::map<wxString, int> padNumberToGroupIdxMap = aFootprint.MapPadNumbersToNetTieGroups();
261
262 // Now collect all the footprint items which are on copper layers
263
264 std::vector<BOARD_ITEM*> copperItems;
265
266 for( BOARD_ITEM* item : aFootprint.GraphicalItems() )
267 {
268 if( item->IsOnCopperLayer() )
269 copperItems.push_back( item );
270
271 item->RunOnChildren(
272 [&]( BOARD_ITEM* descendent )
273 {
274 if( descendent->IsOnCopperLayer() )
275 copperItems.push_back( descendent );
276 },
278 }
279
280 for( ZONE* zone : aFootprint.Zones() )
281 {
282 if( !zone->GetIsRuleArea() && zone->IsOnCopperLayer() )
283 copperItems.push_back( zone );
284 }
285
286 for( PCB_FIELD* field : aFootprint.GetFields() )
287 {
288 if( field->IsOnCopperLayer() )
289 copperItems.push_back( field );
290 }
291
292 for( PCB_LAYER_ID layer : { F_Cu, In1_Cu, B_Cu } )
293 {
294 // Next, build a polygon-set for the copper on this layer. We don't really care about
295 // nets here, we just want to end up with a set of outlines describing the distinct
296 // copper polygons of the footprint.
297
298 SHAPE_POLY_SET copperOutlines;
299 std::map<int, std::vector<const PAD*>> outlineIdxToPadsMap;
300
301 for( BOARD_ITEM* item : copperItems )
302 {
303 if( item->IsOnLayer( layer ) )
304 {
305 item->TransformShapeToPolygon( copperOutlines, layer, 0, aFootprint.GetMaxError(),
307 }
308 }
309
310 copperOutlines.Simplify();
311
312 // Index each pad to the outline in the set that it is part of.
313
314 for( const PAD* pad : aFootprint.Pads() )
315 {
316 for( int ii = 0; ii < copperOutlines.OutlineCount(); ++ii )
317 {
318 if( pad->GetEffectiveShape( layer )->Collide( &copperOutlines.Outline( ii ), 0 ) )
319 outlineIdxToPadsMap[ ii ].emplace_back( pad );
320 }
321 }
322
323 // Finally, ensure that each outline which contains multiple pads has all its pads
324 // listed in an allowed-shorting group.
325
326 for( const auto& [ outlineIdx, pads ] : outlineIdxToPadsMap )
327 {
328 if( pads.size() > 1 )
329 {
330 const PAD* firstPad = pads[0];
331 int firstGroupIdx = padNumberToGroupIdxMap[ firstPad->GetNumber() ];
332
333 for( size_t ii = 1; ii < pads.size(); ++ii )
334 {
335 const PAD* thisPad = pads[ii];
336 int thisGroupIdx = padNumberToGroupIdxMap[ thisPad->GetNumber() ];
337
338 if( thisGroupIdx < 0 || thisGroupIdx != firstGroupIdx )
339 {
340 BOARD_ITEM* shortingItem = nullptr;
341 VECTOR2I pos = ( firstPad->GetPosition() + thisPad->GetPosition() ) / 2;
342
343 pos = copperOutlines.Outline( outlineIdx ).NearestPoint( pos );
344
345 for( BOARD_ITEM* item : copperItems )
346 {
347 if( item->HitTest( pos, 1 ) )
348 {
349 shortingItem = item;
350 break;
351 }
352 }
353
354 if( shortingItem )
355 aErrorHandler( shortingItem, firstPad, thisPad, pos );
356 else
357 aErrorHandler( firstPad, thisPad, nullptr, pos );
358 }
359 }
360 }
361 }
362 }
363}
364
365
367 const FOOTPRINT& aFootprint, const std::function<void( const wxString& )>& aErrorHandler )
368{
369 std::set<wxString> padNumbers;
370 wxString msg;
371
372 for( const auto& [ padNumber, _ ] : aFootprint.MapPadNumbersToNetTieGroups() )
373 {
374 const PAD* pad = aFootprint.FindPadByNumber( padNumber );
375
376 if( !pad )
377 {
378 msg.Printf( _( "(net-tie pad group contains unknown pad number %s)" ), padNumber );
379 aErrorHandler( msg );
380 }
381 else if( !padNumbers.insert( pad->GetNumber() ).second )
382 {
383 msg.Printf( _( "(pad %s appears in more than one net-tie pad group)" ), padNumber );
384 aErrorHandler( msg );
385 }
386 }
387}
388
389
391 const FOOTPRINT& aFootprint,
392 const std::function<void( BOARD_ITEM*, BOARD_ITEM*, const VECTOR2I& )>& aErrorHandler )
393{
394 auto checkColliding =
395 [&]( BOARD_ITEM* item, BOARD_ITEM* other )
396 {
397 for( PCB_LAYER_ID silk : { F_SilkS, B_SilkS } )
398 {
399 PCB_LAYER_ID mask = silk == F_SilkS ? F_Mask : B_Mask;
400
401 if( !item->IsOnLayer( silk ) || !other->IsOnLayer( mask ) )
402 continue;
403
404 std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape( silk );
405 std::shared_ptr<SHAPE> otherShape = other->GetEffectiveShape( mask );
406 int actual;
407 VECTOR2I pos;
408
409 if( itemShape->Collide( otherShape.get(), 0, &actual, &pos ) )
410 aErrorHandler( item, other, pos );
411 }
412 };
413
414 for( BOARD_ITEM* item : aFootprint.GraphicalItems() )
415 {
416 for( BOARD_ITEM* other : aFootprint.GraphicalItems() )
417 {
418 if( other != item )
419 checkColliding( item, other );
420 }
421
422 for( PAD* pad : aFootprint.Pads() )
423 checkColliding( item, pad );
424 }
425}
@ ERROR_OUTSIDE
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
virtual bool IsOnLayer(PCB_LAYER_ID aLayer) const
Test to see if this object is on the given layer.
Definition board_item.h:418
virtual bool IsOnCopperLayer() const
Definition board_item.h:190
virtual std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, FLASHING aFlash=FLASHING::DEFAULT, DRC_CONSTRAINT_T aUsage=NULL_CONSTRAINT) const
Some pad shapes can be complex (rounded/chamfered rectangle), even without considering custom shapes.
int GetMaxError() const
std::vector< PAD * > GetNetTiePads(PAD *aPad) const
EDA_ANGLE GetOrientation() const
Definition footprint.h:382
ZONES & Zones()
Definition footprint.h:361
std::map< wxString, int > MapPadNumbersToNetTieGroups() const
std::deque< PAD * > & Pads()
Definition footprint.h:355
int GetAttributes() const
Definition footprint.h:509
bool IsFlipped() const
Definition footprint.h:620
wxString GetTypeName() const
Get the type of footprint.
int GetLikelyAttribute() const
Return the most likely attribute based on pads.
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
DRAWINGS & GraphicalItems()
Definition footprint.h:358
PAD * FindPadByNumber(const wxString &aPadNumber, PAD *aSearchAfterMe=nullptr) const
Return a PAD with a matching number.
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
This is a relatively straight-forward implementation of the ITEM_REALIGNER_BASE that should handle mo...
std::optional< TRANSFORM > GetTransform(const std::vector< VECTOR2I > &aPtsA, const std::vector< VECTOR2I > &aPtsB) const override
Compute the best fit transform to align the two sets of points.
Definition pad.h:61
const wxString & GetNumber() const
Definition pad.h:143
VECTOR2I GetPosition() const override
Definition pad.cpp:255
const VECTOR2I NearestPoint(const VECTOR2I &aP, bool aAllowInternalShapePoints=true) const
Find a point on the line chain that is closest to point aP.
Represent a set of closed polygons.
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int OutlineCount() const
Return the number of outlines in the set.
An abstract shape on 2D plane.
Definition shape.h:124
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition shape.h:181
Handle a list of polygons defining a copper zone.
Definition zone.h:70
@ HOLE_TO_HOLE_CONSTRAINT
@ DRCE_DRILLED_HOLES_TOO_CLOSE
Definition drc_item.h:61
@ DRCE_SHORTING_ITEMS
Definition drc_item.h:49
@ DRCE_DRILLED_HOLES_COLOCATED
Definition drc_item.h:62
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
@ RECURSE
Definition eda_item.h:51
@ FP_SMD
Definition footprint.h:87
@ FP_THROUGH_HOLE
Definition footprint.h:86
bool ComputeFootprintShift(const FOOTPRINT &aExisting, const FOOTPRINT &aNew, VECTOR2I &aShift, EDA_ANGLE &aAngleShift)
Compute position and angle shift between two footprints.
Collection of reusable/testable functions for footprint manipulation and validation.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ F_SilkS
Definition layer_ids.h:96
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ In1_Cu
Definition layer_ids.h:62
@ B_SilkS
Definition layer_ids.h:97
@ F_Cu
Definition layer_ids.h:60
constexpr void MIRROR(T &aPoint, const T &aMirrorRef)
Updates aPoint with the mirror of aPoint relative to the aMirrorRef.
Definition mirror.h:41
void CheckClippedSilk(const FOOTPRINT &aFootprint, const std::function< void(BOARD_ITEM *, BOARD_ITEM *, const VECTOR2I &)> &aErrorHandler)
Check for silkscreen graphics clipped by solder mask apertures.
void CheckFootprintAttributes(const FOOTPRINT &aFootprint, const std::function< void(const wxString &)> &aErrorHandler)
Check that the footprint's SMD/through-hole attribute matches its pads.
void CheckNetTies(const FOOTPRINT &aFootprint, const std::function< void(const BOARD_ITEM *, const BOARD_ITEM *, const BOARD_ITEM *, const VECTOR2I &)> &aErrorHandler)
Check for un-allowed shorting of pads in net-tie footprints.
void CheckShortingPads(const FOOTPRINT &aFootprint, const std::function< void(const PAD *, const PAD *, int aErrorCode, const VECTOR2I &)> &aErrorHandler)
Check for overlapping, different-numbered, non-net-tie pads.
void CheckPads(const FOOTPRINT &aFootprint, const UNITS_PROVIDER &aUnitsProvider, const PAD_CHECK_HANDLER &aErrorHandler)
Run non-board-specific DRC checks on footprint's pads.
std::function< void(const PAD &, const PAD_UTILS::PAD_CHECK_RESULT &)> PAD_CHECK_HANDLER
void CheckNetTiePadGroups(const FOOTPRINT &aFootprint, const std::function< void(const wxString &)> &aErrorHandler)
Sanity check net-tie pad groups.
void CheckPad(const PAD &aPad, const UNITS_PROVIDER &aUnitsProvider, bool aForPadProperties, const PAD_CHECK_HANDLER &aErrorHandler)
Run non-board-specific checks on a pad.
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:96
A single finding from CheckPad().
Definition pad_utils.h:107
int actual
wxString result
Test unit parsing edge cases and error handling.
VECTOR2I GetRotated(const VECTOR2I &aVector, const EDA_ANGLE &aAngle)
Return a new VECTOR2I that is the result of rotating aVector by aAngle.
Definition trigo.h:73
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708