KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_library_parity.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.
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 <cmath>
21#include <layer_range.h>
22#include <layer_utils.h>
23#include <kiway.h>
24#include <macros.h>
27#include <board.h>
28#include <pcb_shape.h>
29#include <pcb_barcode.h>
30#include <zone.h>
31#include <footprint.h>
32#include <pad.h>
33#include <drc/drc_engine.h>
34#include <drc/drc_item.h>
36#include <project_pcb.h>
37#include <string_utils.h>
38
39
40/*
41 Library parity test.
42
43 Errors generated:
44 - DRCE_LIB_FOOTPRINT_ISSUES
45 - DRCE_LIB_FOOTPRINT_MISMATCH
46*/
47
49{
50public:
55
57
58 virtual bool Run() override;
59
60 virtual const wxString GetName() const override { return wxT( "library_parity" ); };
61};
62
63
64//
65// The TEST*() macros have two modes:
66// In "Report" mode (aReporter != nullptr) all properties are checked and reported on.
67// In "DRC" mode (aReporter == nulltpr) properties are only checked until a difference is found.
68//
69#define TEST( a, b, msg ) \
70 do { \
71 if( a != b ) \
72 { \
73 diff = true; \
74 \
75 if( aReporter && wxString( msg ).length() ) \
76 aReporter->Report( msg ); \
77 } \
78 \
79 if( diff && !aReporter ) \
80 return diff; \
81 } while (0)
82
83#define EPSILON 10
84#define TEST_PT( a, b, msg ) \
85 do { \
86 if( abs( a.x - b.x ) > EPSILON \
87 || abs( a.y - b.y ) > EPSILON ) \
88 { \
89 diff = true; \
90 \
91 if( aReporter && wxString( msg ).length() ) \
92 aReporter->Report( msg ); \
93 } \
94 \
95 if( diff && !aReporter ) \
96 return diff; \
97 } while (0)
98
99#define EPSILON_D 0.000010
100#define TEST_D( a, b, msg ) \
101 do { \
102 if( abs( a - b ) > EPSILON_D ) \
103 { \
104 diff = true; \
105 \
106 if( aReporter && wxString( msg ).length() ) \
107 aReporter->Report( msg ); \
108 } \
109 \
110 if( diff && !aReporter ) \
111 return diff; \
112 } while (0)
113
114#define ITEM_DESC( item ) ( item )->GetItemDescription( &g_unitsProvider, true )
115#define PAD_DESC( pad ) wxString::Format( _( "Pad %s" ), ( pad )->GetNumber() )
116
117
119
120
121LSET getBoardNormalizedLayerSet( const BOARD_ITEM* aLibItem, const BOARD* aBoard )
122{
123 LSET lset = aLibItem->GetLayerSet();
124
125 if( aBoard )
126 lset &= aBoard->GetEnabledLayers();
127
128 return lset;
129}
130
131
132static bool boardLayersMatchWithInnerLayerExpansion( const LSET& aItem, const LSET& bLib,
133 bool aAllowCuExpansion )
134{
135 if( !aAllowCuExpansion )
136 {
137 return aItem == bLib;
138 }
139
140 // first test non copper layers, these should exact match
141 const LSET nonCuMask = LSET::AllNonCuMask();
142 const LSET aNonCu = aItem & nonCuMask;
143 const LSET bNonCu = bLib & nonCuMask;
144
145 if( aNonCu != bNonCu )
146 return false;
147
148 // top and bottom copper must match exactly
149 if( ( aItem & LSET::ExternalCuMask() ) != ( bLib & LSET::ExternalCuMask() ) )
150 return false;
151
152 // technically we can ignore the inner layers entirely due to aAllowCuExpansion at this point
153 // since its assumed the layers got expanded elsewhere
154 // but it feels weird not to sanity this
155
156 // extract the inner layers to compare prescence
157 const LSET aInner = ( aItem & LSET::AllCuMask() ) & ~( LSET::ExternalCuMask() );
158 const LSET bInner = ( bLib & LSET::AllCuMask() ) & ~( LSET::ExternalCuMask() );
159
160 const LSET missingInnerInA = bInner & ~aInner;
161 if( missingInnerInA.count() )
162 return false;
163
164 return true;
165}
166
167
168bool primitiveNeedsUpdate( const std::shared_ptr<PCB_SHAPE>& a,
169 const std::shared_ptr<PCB_SHAPE>& b )
170{
171 REPORTER* aReporter = nullptr;
172 bool diff = false;
173
174 TEST( a->GetShape(), b->GetShape(), "" );
175
176 switch( a->GetShape() )
177 {
179 {
180 BOX2I aRect( a->GetStart(), a->GetEnd() - a->GetStart() );
181 BOX2I bRect( b->GetStart(), b->GetEnd() - b->GetStart() );
182
183 aRect.Normalize();
184 bRect.Normalize();
185
186 TEST_PT( aRect.GetOrigin(), bRect.GetOrigin(), "" );
187 TEST_PT( aRect.GetEnd(), bRect.GetEnd(), "" );
188 break;
189 }
190
191 case SHAPE_T::SEGMENT:
192 case SHAPE_T::CIRCLE:
193 TEST_PT( a->GetStart(), b->GetStart(), "" );
194 TEST_PT( a->GetEnd(), b->GetEnd(), "" );
195 break;
196
197 case SHAPE_T::ARC:
198 TEST_PT( a->GetStart(), b->GetStart(), "" );
199 TEST_PT( a->GetEnd(), b->GetEnd(), "" );
200
201 // Arc center is calculated and so may have round-off errors when parents are
202 // differentially rotated.
203 if( ( a->GetArcMid() - b->GetArcMid() ).EuclideanNorm() > pcbIUScale.mmToIU( 0.0005 ) )
204 return true;
205
206 break;
207
208 case SHAPE_T::BEZIER:
209 TEST_PT( a->GetStart(), b->GetStart(), "" );
210 TEST_PT( a->GetEnd(), b->GetEnd(), "" );
211 TEST_PT( a->GetBezierC1(), b->GetBezierC1(), "" );
212 TEST_PT( a->GetBezierC2(), b->GetBezierC2(), "" );
213 break;
214
215 case SHAPE_T::ELLIPSE:
217 TEST_PT( a->GetEllipseCenter(), b->GetEllipseCenter(), "" );
218 TEST( a->GetEllipseMajorRadius(), b->GetEllipseMajorRadius(), "" );
219 TEST( a->GetEllipseMinorRadius(), b->GetEllipseMinorRadius(), "" );
220 TEST( a->GetEllipseRotation(), b->GetEllipseRotation(), "" );
221
222 if( a->GetShape() == SHAPE_T::ELLIPSE_ARC )
223 {
224 TEST( a->GetEllipseStartAngle(), b->GetEllipseStartAngle(), "" );
225 TEST( a->GetEllipseEndAngle(), b->GetEllipseEndAngle(), "" );
226 }
227 break;
228
229 case SHAPE_T::POLY:
230 {
231 TEST( a->GetPolyShape().TotalVertices(), b->GetPolyShape().TotalVertices(), "" );
232
233 for( int poly = 0; poly < static_cast<int>( a->GetPolyShape().CPolygons().size() ); poly++ )
234 {
235 const SHAPE_POLY_SET::POLYGON aPolygon = a->GetPolyShape().CPolygon( poly );
236 const SHAPE_POLY_SET::POLYGON bPolygon = b->GetPolyShape().CPolygon( poly );
237
238 if( aPolygon.size() == 0 || bPolygon.size() == 0
239 || !aPolygon[0].CompareGeometry( bPolygon[0], true, EPSILON ) )
240 {
241 diff = true;
242 return diff;
243 }
244 }
245
246 break;
247 }
248
249 default:
250 UNIMPLEMENTED_FOR( a->SHAPE_T_asString() );
251 }
252
253 TEST( a->GetStroke(), b->GetStroke(), "" );
254 TEST( a->GetFillMode(), b->GetFillMode(), "" );
255
256 return diff;
257}
258
259
260bool padHasOverrides( const PAD* a, const PAD* b, REPORTER& aReporter )
261{
262 bool diff = false;
263
264#define REPORT_MSG( s, p ) aReporter.Report( wxString::Format( s, p ) )
265
266 if( a->GetLocalClearance().has_value() && a->GetLocalClearance() != b->GetLocalClearance() )
267 {
268 diff = true;
269 REPORT_MSG( _( "%s has clearance override." ), PAD_DESC( a ) );
270 }
271
272 if( a->GetLocalSolderMaskMargin().has_value()
274 {
275 diff = true;
276 REPORT_MSG( _( "%s has solder mask expansion override." ), PAD_DESC( a ) );
277 }
278
279
280 if( a->GetLocalSolderPasteMargin().has_value()
282 {
283 diff = true;
284 REPORT_MSG( _( "%s has solder paste clearance override." ), PAD_DESC( a ) );
285 }
286
289 {
290 diff = true;
291 REPORT_MSG( _( "%s has solder paste clearance override." ), PAD_DESC( a ) );
292 }
293
296 {
297 diff = true;
298 REPORT_MSG( _( "%s has zone connection override." ), PAD_DESC( a ) );
299 }
300
301 if( a->GetLocalThermalGapOverride().has_value()
302 && a->GetThermalGap() != b->GetThermalGap() )
303 {
304 diff = true;
305 REPORT_MSG( _( "%s has thermal relief gap override." ), PAD_DESC( a ) );
306 }
307
308 if( a->GetLocalThermalSpokeWidthOverride().has_value()
310 {
311 diff = true;
312 REPORT_MSG( _( "%s has thermal relief spoke width override." ), PAD_DESC( a ) );
313 }
314
316 {
317 diff = true;
318 REPORT_MSG( _( "%s has thermal relief spoke angle override." ), PAD_DESC( a ) );
319 }
320
322 {
323 diff = true;
324 REPORT_MSG( _( "%s has zone knockout setting override." ), PAD_DESC( a ) );
325 }
326
327 return diff;
328}
329
330
331// A half turn leaves a pad as it was only when the pad is symmetric about its own centre, a circle ignores any turn,
332// so a flip that lands on the other of the two angles has not changed such a pad
333bool padOrientationDiffers( const PAD* a, const PAD* bLib )
334{
335 double delta = std::fmod( std::abs( a->GetFPRelativeOrientation().Normalize().AsDegrees()
337 360.0 );
338
339 if( delta > 180.0 )
340 delta = 360.0 - delta;
341
342 if( delta <= EPSILON_D )
343 return false;
344
345 if( a->Padstack().Mode() != bLib->Padstack().Mode()
346 || a->Padstack().Drill().shape != bLib->Padstack().Drill().shape )
347 {
348 return true;
349 }
350
351 // a slot turns with the pad even when the copper does not
352 auto roundDrill =
353 []( const PAD* aPad )
354 {
355 return aPad->GetDrillShape() == PAD_DRILL_SHAPE::CIRCLE
356 || aPad->GetDrillSize().x == aPad->GetDrillSize().y;
357 };
358
359 bool circular = roundDrill( a ) && roundDrill( bLib );
360 bool halfTurnSymmetric = true;
361
362 for( PCB_LAYER_ID layer : a->Padstack().UniqueLayers() )
363 {
364 PAD_SHAPE shape = a->GetShape( layer );
365
366 if( shape != bLib->GetShape( layer ) )
367 return true;
368
369 circular &= shape == PAD_SHAPE::CIRCLE;
370 halfTurnSymmetric &= shape == PAD_SHAPE::CIRCLE || shape == PAD_SHAPE::RECTANGLE
371 || shape == PAD_SHAPE::OVAL || shape == PAD_SHAPE::ROUNDRECT;
372
373 // an off-centre shape or a skewed one moves when the pad turns
374 if( a->GetOffset( layer ) != VECTOR2I( 0, 0 ) || bLib->GetOffset( layer ) != VECTOR2I( 0, 0 )
375 || a->GetDelta( layer ) != VECTOR2I( 0, 0 ) || bLib->GetDelta( layer ) != VECTOR2I( 0, 0 ) )
376 {
377 return true;
378 }
379 }
380
381 return !circular && !( halfTurnSymmetric && std::abs( delta - 180.0 ) <= EPSILON_D );
382}
383
384
385bool padNeedsUpdate( const PAD* a, const PAD* bLib, REPORTER* aReporter )
386{
387 bool diff = false;
388
390 wxString::Format( _( "%s pad to die length differs." ), PAD_DESC( a ) ) );
392 wxString::Format( _( "%s position differs." ), PAD_DESC( a ) ) );
393
394 TEST( a->GetNumber(), bLib->GetNumber(),
395 wxString::Format( _( "%s has different numbers." ), PAD_DESC( a ) ) );
396
397 // These are assigned from the schematic and not from the library
398 // TEST( a->GetPinFunction(), b->GetPinFunction() );
399 // TEST( a->GetPinType(), b->GetPinType() );
400
401 bool layerSettingsDiffer = a->GetRemoveUnconnected() != bLib->GetRemoveUnconnected();
402
403 // NB: KeepTopBottom is undefined if RemoveUnconnected is NOT set.
404 if( a->GetRemoveUnconnected() )
405 layerSettingsDiffer |= a->GetKeepTopBottom() != bLib->GetKeepTopBottom();
406
407 bool allowExpansion = false;
408 if( const FOOTPRINT* fp = a->GetParentFootprint() )
409 allowExpansion = ( fp->GetStackupMode() == FOOTPRINT_STACKUP::EXPAND_INNER_LAYERS );
410
411 if( layerSettingsDiffer
414 allowExpansion ) )
415 {
416 diff = true;
417
418 if( aReporter )
419 aReporter->Report( wxString::Format( _( "%s layers differ." ), PAD_DESC( a ) ) );
420 else
421 return true;
422 }
423
424 TEST( a->GetAttribute(), bLib->GetAttribute(),
425 wxString::Format( _( "%s pad type differs." ), PAD_DESC( a ) ) );
426 TEST( a->GetProperty(), bLib->GetProperty(),
427 wxString::Format( _( "%s fabrication property differs." ), PAD_DESC( a ) ) );
428
429 // The pad orientation, for historical reasons is the pad rotation + parent rotation.
430 if( padOrientationDiffers( a, bLib ) )
431 {
432 diff = true;
433
434 if( aReporter )
435 aReporter->Report( wxString::Format( _( "%s orientation differs." ), PAD_DESC( a ) ) );
436 else
437 return true;
438 }
439
440 std::vector<PCB_LAYER_ID> layers = a->Padstack().UniqueLayers();
441 const BOARD* board = a->GetBoard();
442 wxString layerName;
443
444 for( PCB_LAYER_ID layer : layers )
445 {
446 layerName = board ? board->GetLayerName( layer ) : LayerName( layer );
447
448 TEST( a->GetShape( layer ), bLib->GetShape( layer ),
449 wxString::Format( _( "%s pad shape type differs on layer %s." ),
450 PAD_DESC( a ),
451 layerName ) );
452
453 TEST( a->GetSize( layer ), bLib->GetSize( layer ),
454 wxString::Format( _( "%s size differs on layer %s." ),
455 PAD_DESC( a ),
456 layerName ) );
457
458 TEST( a->GetDelta( layer ), bLib->GetDelta( layer ),
459 wxString::Format( _( "%s trapezoid delta differs on layer %s." ),
460 PAD_DESC( a ),
461 layerName ) );
462
463 if( a->GetShape( layer ) == PAD_SHAPE::ROUNDRECT || a->GetShape( layer ) == PAD_SHAPE::CHAMFERED_RECT)
464 {
465 TEST_D( a->GetRoundRectRadiusRatio( layer ),
466 bLib->GetRoundRectRadiusRatio( layer ),
467 wxString::Format( _( "%s rounded corners differ on layer %s." ),
468 PAD_DESC( a ),
469 layerName ) );
470 }
471
472 if( a->GetShape( layer ) == PAD_SHAPE::CHAMFERED_RECT)
473 {
474 TEST_D( a->GetChamferRectRatio( layer ),
475 bLib->GetChamferRectRatio( layer ),
476 wxString::Format( _( "%s chamfered corner sizes differ on layer %s." ),
477 PAD_DESC( a ),
478 layerName ) );
479
480 TEST( a->GetChamferPositions( layer ),
481 bLib->GetChamferPositions( layer ),
482 wxString::Format( _( "%s chamfered corners differ on layer %s." ),
483 PAD_DESC( a ),
484 layerName ) );
485 }
486
487 TEST_PT( a->GetOffset( layer ), bLib->GetOffset( layer ),
488 wxString::Format( _( "%s shape offset from hole differs on layer %s." ),
489 PAD_DESC( a ),
490 layerName ) );
491 }
492
493 TEST( a->GetDrillShape(), bLib->GetDrillShape(),
494 wxString::Format( _( "%s drill shape differs." ), PAD_DESC( a ) ) );
495 TEST( a->GetDrillSize(), bLib->GetDrillSize(),
496 wxString::Format( _( "%s drill size differs." ), PAD_DESC( a ) ) );
497
498 // Clearance and zone connection overrides are as likely to be set at the board level as in
499 // the library.
500 //
501 // If we ignore them and someone *does* change one of them in the library, then stale
502 // footprints won't be caught.
503 //
504 // On the other hand, if we report them then boards that override at the board level are
505 // going to be VERY noisy.
506 //
507 // So we just do it when we have a reporter.
508 if( aReporter && padHasOverrides( a, bLib, *aReporter ) )
509 diff = true;
510
511 bool primitivesDiffer = false;
512 PCB_LAYER_ID firstDifferingLayer = UNDEFINED_LAYER;
513
515 [&]( PCB_LAYER_ID aLayer )
516 {
517 if( a->GetPrimitives( aLayer ).size() != bLib->GetPrimitives( aLayer ).size() )
518 {
519 primitivesDiffer = true;
520 }
521 else
522 {
523 for( size_t ii = 0; ii < a->GetPrimitives( aLayer ).size(); ++ii )
524 {
525 if( primitiveNeedsUpdate( a->GetPrimitives( aLayer )[ii],
526 bLib->GetPrimitives( aLayer )[ii] ) )
527 {
528 primitivesDiffer = true;
529 break;
530 }
531 }
532 }
533
534 if( primitivesDiffer && firstDifferingLayer == UNDEFINED_LAYER )
535 firstDifferingLayer = aLayer;
536 } );
537
538
539 if( primitivesDiffer )
540 {
541 diff = true;
542 layerName = board ? board->GetLayerName( firstDifferingLayer )
543 : LayerName( firstDifferingLayer );
544
545 if( aReporter )
546 {
547 aReporter->Report( wxString::Format( _( "%s shape primitives differ on layer %s." ),
548 PAD_DESC( a ),
549 layerName ) );
550 }
551 else
552 {
553 return true;
554 }
555 }
556
557 return diff;
558}
559
560
561bool barcodeNeedsUpdate( const PCB_BARCODE& curr_barcode, const PCB_BARCODE& ref_barcode )
562{
563 REPORTER* aReporter = nullptr;
564 bool diff = false;
565
566 TEST( curr_barcode.GetText(), ref_barcode.GetText(),
567 wxString::Format( _( "%s text differs." ), ITEM_DESC( &curr_barcode ) ) );
568
569 TEST_PT( curr_barcode.GetPosition(), ref_barcode.GetPosition(),
570 wxString::Format( _( "%s position differs." ), ITEM_DESC( &curr_barcode ) ) );
571
572 TEST( curr_barcode.GetWidth(), ref_barcode.GetWidth(),
573 wxString::Format( _( "%s width differs." ), ITEM_DESC( &curr_barcode ) ) );
574 TEST( curr_barcode.GetHeight(), ref_barcode.GetHeight(),
575 wxString::Format( _( "%s height differs." ), ITEM_DESC( &curr_barcode ) ) );
576
577 TEST( curr_barcode.GetTextSize(), ref_barcode.GetTextSize(),
578 wxString::Format( _( "%s text size differs." ), ITEM_DESC( &curr_barcode ) ) );
579
580 TEST( (int) curr_barcode.GetKind(), (int) ref_barcode.GetKind(),
581 wxString::Format( _( "%s code differs." ), ITEM_DESC( &curr_barcode ) ) );
582 TEST( (int) curr_barcode.GetErrorCorrection(), (int) ref_barcode.GetErrorCorrection(),
583 wxString::Format( _( "%s error correction level differs." ), ITEM_DESC( &curr_barcode ) ) );
584
585 return diff;
586}
587
588
589bool shapeNeedsUpdate( const PCB_SHAPE& curr_shape, const PCB_SHAPE& ref_shape )
590{
591 // curr_shape and ref_shape are expected to be normalized, for a more reliable test.
592 REPORTER* aReporter = nullptr;
593 bool diff = false;
594
595 TEST( curr_shape.GetShape(), ref_shape.GetShape(), "" );
596
597 switch( curr_shape.GetShape() )
598 {
600 {
601 BOX2I aRect( curr_shape.GetLibraryStart(), curr_shape.GetLibraryEnd() - curr_shape.GetLibraryStart() );
602 BOX2I bRect( ref_shape.GetLibraryStart(), ref_shape.GetLibraryEnd() - ref_shape.GetLibraryStart() );
603
604 aRect.Normalize();
605 bRect.Normalize();
606
607 TEST_PT( aRect.GetOrigin(), bRect.GetOrigin(), "" );
608 TEST_PT( aRect.GetEnd(), bRect.GetEnd(), "" );
609 break;
610 }
611
612 case SHAPE_T::SEGMENT:
613 case SHAPE_T::CIRCLE:
614 TEST_PT( curr_shape.GetLibraryStart(), ref_shape.GetLibraryStart(), "" );
615 TEST_PT( curr_shape.GetLibraryEnd(), ref_shape.GetLibraryEnd(), "" );
616 break;
617
618 case SHAPE_T::ARC:
619 TEST_PT( curr_shape.GetLibraryStart(), ref_shape.GetLibraryStart(), "" );
620 TEST_PT( curr_shape.GetLibraryEnd(), ref_shape.GetLibraryEnd(), "" );
621
622 if( ( curr_shape.GetLibraryArcMid() - ref_shape.GetLibraryArcMid() ).EuclideanNorm()
623 > pcbIUScale.mmToIU( 0.0005 ) )
624 return true;
625
626 break;
627
628 case SHAPE_T::BEZIER:
629 TEST_PT( curr_shape.GetLibraryStart(), ref_shape.GetLibraryStart(), "" );
630 TEST_PT( curr_shape.GetLibraryEnd(), ref_shape.GetLibraryEnd(), "" );
631 TEST_PT( curr_shape.GetLibraryBezierC1(), ref_shape.GetLibraryBezierC1(), "" );
632 TEST_PT( curr_shape.GetLibraryBezierC2(), ref_shape.GetLibraryBezierC2(), "" );
633 break;
634
635 case SHAPE_T::ELLIPSE:
637 TEST_PT( curr_shape.GetEllipseCenter(), ref_shape.GetEllipseCenter(), "" );
638 TEST( curr_shape.GetEllipseMajorRadius(), ref_shape.GetEllipseMajorRadius(), "" );
639 TEST( curr_shape.GetEllipseMinorRadius(), ref_shape.GetEllipseMinorRadius(), "" );
640 TEST( curr_shape.GetEllipseRotation(), ref_shape.GetEllipseRotation(), "" );
641
642 if( curr_shape.GetShape() == SHAPE_T::ELLIPSE_ARC )
643 {
644 TEST( curr_shape.GetEllipseStartAngle(), ref_shape.GetEllipseStartAngle(), "" );
645 TEST( curr_shape.GetEllipseEndAngle(), ref_shape.GetEllipseEndAngle(), "" );
646 }
647 break;
648
649 case SHAPE_T::POLY:
650 {
651 SHAPE_POLY_SET aLib = curr_shape.GetLibraryPolyShape();
652 SHAPE_POLY_SET bLib = ref_shape.GetLibraryPolyShape();
653
654 TEST( aLib.TotalVertices(), bLib.TotalVertices(), "" );
655
656 for( int poly = 0; poly < static_cast<int>( aLib.CPolygons().size() ); poly++ )
657 {
658 const SHAPE_POLY_SET::POLYGON curr_polygon = aLib.CPolygon( poly );
659 const SHAPE_POLY_SET::POLYGON ref_polygon = bLib.CPolygon( poly );
660
661 if( curr_polygon.size() == 0 || ref_polygon.size() == 0
662 || !curr_polygon[0].CompareGeometry( ref_polygon[0], true, EPSILON ) )
663 {
664 diff = true;
665 return diff;
666 }
667 }
668 break;
669 }
670
671 default:
672 UNIMPLEMENTED_FOR( curr_shape.SHAPE_T_asString() );
673 }
674
675 if( curr_shape.IsOnCopperLayer() )
676 TEST( curr_shape.GetStroke(), ref_shape.GetStroke(), "" );
677
678 TEST( curr_shape.GetFillMode(), ref_shape.GetFillMode(), "" );
679
680 TEST( curr_shape.GetLayer(), ref_shape.GetLayer(), "" );
681
682 return diff;
683}
684
685
686bool zoneNeedsUpdate( const ZONE* a, const ZONE* b, REPORTER* aReporter )
687{
688 bool diff = false;
689
691 wxString::Format( _( "%s corner smoothing setting differs." ), ITEM_DESC( a ) ) );
693 wxString::Format( _( "%s corner smoothing radius differs." ), ITEM_DESC( a ) ) );
694 TEST( a->GetZoneName(), b->GetZoneName(),
695 wxString::Format( _( "%s name differs." ), ITEM_DESC( a ) ) );
697 wxString::Format( _( "%s priority differs." ), ITEM_DESC( a ) ) );
698
699 TEST( a->GetIsRuleArea(), b->GetIsRuleArea(),
700 wxString::Format( _( "%s keep-out property differs." ), ITEM_DESC( a ) ) );
702 wxString::Format( _( "%s keep out zone fill setting differs." ), ITEM_DESC( a ) ) );
704 wxString::Format( _( "%s keep out footprints setting differs." ), ITEM_DESC( a ) ) );
706 wxString::Format( _( "%s keep out pads setting differs." ), ITEM_DESC( a ) ) );
708 wxString::Format( _( "%s keep out tracks setting differs." ), ITEM_DESC( a ) ) );
710 wxString::Format( _( "%s keep out vias setting differs." ), ITEM_DESC( a ) ) );
711
712 // In1_Cu is used to indicate whether inner layer expansion is allowed on rulesets
713 // Kind of annoying footprint pads use both in1_cu and have an stackup mode setting
714 bool innerLayerExpansionAllowed = b->GetLayerSet().Contains( In1_Cu );
715
717 innerLayerExpansionAllowed ) )
718 {
719 diff = true;
720
721 if( aReporter )
722 {
723 aReporter->Report( wxString::Format( _( "%s layers differ." ), ITEM_DESC( a ) ) );
724 }
725 else
726 {
727 return true;
728 }
729 }
730
732 wxString::Format( _( "%s pad connection property differs." ), ITEM_DESC( a ) ) );
734 wxString::Format( _( "%s local clearance differs." ), ITEM_DESC( a ) ) );
736 wxString::Format( _( "%s thermal relief gap differs." ), ITEM_DESC( a ) ) );
738 wxString::Format( _( "%s thermal relief spoke width differs." ), ITEM_DESC( a ) ) );
739
741 wxString::Format( _( "%s min thickness differs." ), ITEM_DESC( a ) ) );
742
744 wxString::Format( _( "%s remove islands setting differs." ), ITEM_DESC( a ) ) );
746 wxString::Format( _( "%s minimum island size setting differs." ), ITEM_DESC( a ) ) );
747
748 TEST( a->GetFillMode(), b->GetFillMode(),
749 wxString::Format( _( "%s fill type differs." ), ITEM_DESC( a ) ) );
751 wxString::Format( _( "%s hatch width differs." ), ITEM_DESC( a ) ) );
752 TEST( a->GetHatchGap(), b->GetHatchGap(),
753 wxString::Format( _( "%s hatch gap differs." ), ITEM_DESC( a ) ) );
755 wxString::Format( _( "%s hatch orientation differs." ), ITEM_DESC( a ) ) );
757 wxString::Format( _( "%s hatch smoothing level differs." ), ITEM_DESC( a ) ) );
759 wxString::Format( _( "%s hatch smoothing amount differs." ), ITEM_DESC( a ) ) );
761 wxString::Format( _( "%s minimum hatch hole setting differs." ), ITEM_DESC( a ) ) );
762
763 // This is just a display property
764 // TEST( a->GetHatchBorderAlgorithm(), b->GetHatchBorderAlgorithm() );
765
766 SHAPE_POLY_SET aLibOutline = a->GetLibraryOutline();
767 SHAPE_POLY_SET bLibOutline = b->GetLibraryOutline();
768
769 TEST( aLibOutline.TotalVertices(), bLibOutline.TotalVertices(),
770 wxString::Format( _( "%s outline corner count differs." ), ITEM_DESC( a ) ) );
771
772 bool cornersDiffer = aLibOutline.OutlineCount() != bLibOutline.OutlineCount();
773
774 for( int poly = 0; !cornersDiffer && poly < aLibOutline.OutlineCount(); poly++ )
775 {
776 const SHAPE_POLY_SET::POLYGON& aPolygon = aLibOutline.CPolygon( poly );
777 const SHAPE_POLY_SET::POLYGON& bPolygon = bLibOutline.CPolygon( poly );
778
779 if( aPolygon.size() != bPolygon.size() )
780 {
781 cornersDiffer = true;
782 break;
783 }
784
785 for( size_t contour = 0; contour < aPolygon.size() && !cornersDiffer; contour++ )
786 {
787 if( !aPolygon[contour].CompareGeometry( bPolygon[contour], true, EPSILON ) )
788 cornersDiffer = true;
789 }
790 }
791
792 if( cornersDiffer )
793 {
794 diff = true;
795
796 if( aReporter )
797 aReporter->Report( wxString::Format( _( "%s corners differ." ), ITEM_DESC( a ) ) );
798 }
799
800 return diff;
801}
802
803
809bool stackupNeedsUpdate( const FOOTPRINT& a, const FOOTPRINT& b, REPORTER* aReporter )
810{
811 bool diff = false;
812
814 wxString::Format( _( "Footprint stackup mode differs." ) ) );
815
816 const LSET& aLayers = a.GetLayerSet();
817 const LSET& bLayers = b.GetLayerSet();
818
819 TEST( aLayers, bLayers,
820 wxString::Format( _( "Footprint layers differ." ) ) );
821
822 return diff;
823}
824
825
834bool footprintVsBoardStackup( const FOOTPRINT& aFp, const BOARD& aBoard, REPORTER* aReporter )
835{
837 return false;
838
839 // Filter only layers that can differ between footprint and board
840 const LSET& fpLayers = aFp.GetStackupLayers();
841 const LSET& brdLayers = aBoard.GetEnabledLayers() & ( LSET::AllCuMask() | LSET::UserDefinedLayersMask() );
842
843 bool mismatch = false;
844
845 // Any layer in the FP and not on the board is flagged
846 const LSET onlyInFp = fpLayers & ~brdLayers;
847
848 if( onlyInFp.count() )
849 {
850 mismatch = true;
851 if( aReporter )
852 aReporter->Report( wxString::Format( _( "Footprint has %lu layers not on board: %s" ), onlyInFp.count(),
853 LAYER_UTILS::AccumulateNames( onlyInFp, &aBoard ) ) );
854 }
855
856 // Only look at copper layers here: user layers on the board and not in the FP is normal
857 const LSET cuOnlyInBoard = ( brdLayers & ~fpLayers ) & LSET::AllCuMask();
858
859 if( cuOnlyInBoard.count() )
860 {
861 mismatch = true;
862 if( aReporter )
863 aReporter->Report( wxString::Format( _( "Board has %lu copper layers not in footprint: %s" ),
864 cuOnlyInBoard.count(),
865 LAYER_UTILS::AccumulateNames( cuOnlyInBoard, &aBoard ) ) );
866 }
867
868 return mismatch;
869}
870
871
872bool FOOTPRINT::FootprintNeedsUpdate( const FOOTPRINT* aLibFP, int aCompareFlags,
873 REPORTER* aReporter )
874{
875 wxASSERT( aLibFP );
876 bool diff = false;
877
878 std::unique_ptr<FOOTPRINT> temp( static_cast<FOOTPRINT*>( aLibFP->Clone() ) );
879
880 temp->SetParent( GetBoard() );
881
882
883 if( !( aCompareFlags & COMPARE_FLAGS::INSTANCE_TO_INSTANCE ) )
884 {
885 if( IsFlipped() != temp->IsFlipped() )
886 temp->Flip( { 0, 0 }, FLIP_DIRECTION::TOP_BOTTOM );
887
888 // Set rotation before trying to compare footprints, especially when rotation
889 // is not multiple of 90 deg (some shapes like rectangles can be converted to polygons).
890 // Moreover, NormalizeForCompare needs correct orientation to normalize some shapes
891 // like rectangles
892 if( GetOrientation() != temp->GetOrientation() )
893 temp->SetOrientation( GetOrientation() );
894 }
895
896 for( BOARD_ITEM* item : temp->GraphicalItems() )
897 item->NormalizeForCompare();
898
899 // This temporary footprint must not have a parent when it goes out of scope because it
900 // must not trigger the IncrementTimestamp call in ~FOOTPRINT.
901 temp->SetParent( nullptr );
902
903 aLibFP = temp.get();
904
905 // These checks don't set off errors, they're just informational
906 footprintVsBoardStackup( *this, *GetBoard(), aReporter );
907
908#define TEST_ATTR( a, b, attr, msg ) TEST( ( a & attr ), ( b & attr ), msg )
909
911 _( "Footprint types differ." ) );
912
914 wxString::Format( _( "'%s' settings differ." ),
915 _( "Allow bridged solder mask apertures between pads" ) ) );
916
917 if( !( aCompareFlags & COMPARE_FLAGS::DRC ) )
918 {
919 // These tests are skipped for DRC: they are presumed to relate to a given design.
921 wxString::Format( _( "'%s' settings differ." ),
922 _( "Not in schematic" ) ) );
923
925 wxString::Format( _( "'%s' settings differ." ),
926 _( "Exclude from position files" ) ) );
927
929 wxString::Format( _( "'%s' settings differ." ),
930 _( "Exclude from bill of materials" ) ) );
931
933 wxString::Format( _( "'%s' settings differ." ),
934 _( "Exclude from simulation" ) ) );
935
937 wxString::Format( _( "'%s' settings differ." ),
938 _( "Do not populate" ) ) );
939 }
940
941#define REPORT( msg ) { if( aReporter ) aReporter->Report( msg ); }
942#define CHECKPOINT { if( diff && !aReporter ) return diff; }
943
944 if( stackupNeedsUpdate( *this, *aLibFP, aReporter ) )
945 {
946 diff = true;
947 REPORT( _( "Footprint stackup differs." ) );
948 }
949
950 // Clearance and zone connection overrides are as likely to be set at the board level as in
951 // the library.
952 //
953 // If we ignore them and someone *does* change one of them in the library, then stale
954 // footprints won't be caught.
955 //
956 // On the other hand, if we report them then boards that override at the board level are
957 // going to be VERY noisy.
958 //
959 // For report them as different, but we DON'T generate DRC errors on them.
960 if( !( aCompareFlags & COMPARE_FLAGS::DRC ) )
961 {
962 if( GetLocalClearance().has_value() && GetLocalClearance() != aLibFP->GetLocalClearance() )
963 {
964 diff = true;
965 REPORT( _( "Pad clearance overridden." ) );
966 }
967
968 if( GetLocalSolderMaskMargin().has_value()
970 {
971 diff = true;
972 REPORT( _( "Solder mask expansion overridden." ) );
973 }
974
975
976 if( GetLocalSolderPasteMargin().has_value()
978 {
979 diff = true;
980 REPORT( _( "Solder paste absolute clearance overridden." ) );
981 }
982
985 {
986 diff = true;
987 REPORT( _( "Solder paste relative clearance overridden." ) );
988 }
989
992 {
993 diff = true;
994 REPORT( _( "Zone connection overridden." ) );
995 }
996 }
997
998 TEST( GetNetTiePadGroups().size(), aLibFP->GetNetTiePadGroups().size(),
999 _( "Net tie pad groups differ." ) );
1000
1001 for( size_t ii = 0; ii < GetNetTiePadGroups().size(); ++ii )
1002 {
1003 TEST( GetNetTiePadGroups()[ii], aLibFP->GetNetTiePadGroups()[ii],
1004 _( "Net tie pad groups differ." ) );
1005 }
1006
1007 // Text items are really problematic. We don't want to test the reference, but after that
1008 // it gets messy.
1009 //
1010 // What about the value? Depends on whether or not it's a singleton part.
1011 //
1012 // And what about other texts? They might be added only to instances on the board, or even
1013 // changed for instances on the board. Or they might want to be tested for equality.
1014 //
1015 // Currently we punt and ignore all the text items.
1016
1017 // Drawings and pads are also somewhat problematic as there's no guarantee that they'll be
1018 // in the same order in the two footprints. Rather than building some sophisticated hashing
1019 // algorithm we use the footprint sorting functions to attempt to sort them in the same
1020 // order.
1021
1022 // However FOOTPRINT::cmp_drawings uses PCB_SHAPE coordinates and other infos, so we have
1023 // already normalized graphic items in model footprint from library, so we need to normalize
1024 // graphic items in the footprint to test (*this). So normalize them using a copy of this
1025 FOOTPRINT dummy( *this );
1026 dummy.SetParentGroup( nullptr );
1027 dummy.SetParent( nullptr );
1028
1029 if( BOARD* board = GetBoard() )
1030 board->UncacheItemSubtreeById( dummy );
1031
1032 for( BOARD_ITEM* item : dummy.GraphicalItems() )
1033 item->NormalizeForCompare();
1034
1035 std::set<BOARD_ITEM*, FOOTPRINT::cmp_drawings> aShapes;
1036 std::copy_if( dummy.GraphicalItems().begin(), dummy.GraphicalItems().end(),
1037 std::inserter( aShapes, aShapes.begin() ),
1038 []( BOARD_ITEM* item )
1039 {
1040 return item->Type() == PCB_SHAPE_T;
1041 } );
1042
1043 std::set<BOARD_ITEM*, FOOTPRINT::cmp_drawings> bShapes;
1044 std::copy_if( aLibFP->GraphicalItems().begin(), aLibFP->GraphicalItems().end(),
1045 std::inserter( bShapes, bShapes.begin() ),
1046 []( BOARD_ITEM* item )
1047 {
1048 return item->Type() == PCB_SHAPE_T;
1049 } );
1050
1051 if( aShapes.size() != bShapes.size() )
1052 {
1053 diff = true;
1054 REPORT( _( "Graphic item count differs." ) );
1055 }
1056 else
1057 {
1058 for( auto aIt = aShapes.begin(), bIt = bShapes.begin(); aIt != aShapes.end(); aIt++, bIt++ )
1059 {
1060 // aShapes and bShapes are the tested footprint PCB_SHAPE and the model PCB_SHAPE.
1061 // These shapes are already normalized.
1062 PCB_SHAPE* curr_shape = static_cast<PCB_SHAPE*>( *aIt );
1063 PCB_SHAPE* test_shape = static_cast<PCB_SHAPE*>( *bIt );
1064
1065 if( shapeNeedsUpdate( *curr_shape, *test_shape ) )
1066 {
1067 diff = true;
1068 REPORT( wxString::Format( _( "%s differs." ), ITEM_DESC( *aIt ) ) );
1069 }
1070 }
1071 }
1072
1073 CHECKPOINT;
1074
1075 std::set<BOARD_ITEM*, FOOTPRINT::cmp_drawings> aBarcodes;
1076 std::copy_if( dummy.GraphicalItems().begin(), dummy.GraphicalItems().end(),
1077 std::inserter( aBarcodes, aBarcodes.begin() ),
1078 []( BOARD_ITEM* item )
1079 {
1080 return item->Type() == PCB_BARCODE_T;
1081 } );
1082
1083 std::set<BOARD_ITEM*, FOOTPRINT::cmp_drawings> bBarcodes;
1084 std::copy_if( aLibFP->GraphicalItems().begin(), aLibFP->GraphicalItems().end(),
1085 std::inserter( bBarcodes, bBarcodes.begin() ),
1086 []( BOARD_ITEM* item )
1087 {
1088 return item->Type() == PCB_BARCODE_T;
1089 } );
1090
1091 if( aBarcodes.size() != bBarcodes.size() )
1092 {
1093 diff = true;
1094 REPORT( _( "Barcode count differs." ) );
1095 }
1096 else
1097 {
1098 for( auto aIt = aBarcodes.begin(), bIt = bBarcodes.begin(); aIt != aBarcodes.end(); aIt++, bIt++ )
1099 {
1100 // aBarcodes and bBarcodes are the tested footprint PCB_BARCODE and the model PCB_BARCODE.
1101 // These shapes are already normalized.
1102 PCB_BARCODE* curr_barcode = static_cast<PCB_BARCODE*>( *aIt );
1103 PCB_BARCODE* test_barcode = static_cast<PCB_BARCODE*>( *bIt );
1104
1105 if( barcodeNeedsUpdate( *curr_barcode, *test_barcode ) )
1106 {
1107 diff = true;
1108 REPORT( wxString::Format( _( "%s differs." ), ITEM_DESC( *aIt ) ) );
1109 }
1110 }
1111 }
1112
1113 CHECKPOINT;
1114
1115 std::set<PAD*, FOOTPRINT::cmp_pads> aPads( Pads().begin(), Pads().end() );
1116 std::set<PAD*, FOOTPRINT::cmp_pads> bLibPads( aLibFP->Pads().begin(), aLibFP->Pads().end() );
1117
1118 if( aPads.size() != bLibPads.size() )
1119 {
1120 diff = true;
1121 REPORT( _( "Pad count differs." ) );
1122 }
1123 else
1124 {
1125 for( auto aIt = aPads.begin(), bLibIt = bLibPads.begin(); aIt != aPads.end(); aIt++, bLibIt++ )
1126 {
1127 if( padNeedsUpdate( *aIt, *bLibIt, aReporter ) )
1128 diff = true;
1129 else if( aReporter && padHasOverrides( *aIt, *bLibIt, *aReporter ) )
1130 diff = true;
1131 }
1132 }
1133
1134 CHECKPOINT;
1135
1136 std::set<ZONE*, FOOTPRINT::cmp_zones> aZones( Zones().begin(), Zones().end() );
1137 std::set<ZONE*, FOOTPRINT::cmp_zones> bZones( aLibFP->Zones().begin(), aLibFP->Zones().end() );
1138
1139 if( aZones.size() != bZones.size() )
1140 {
1141 diff = true;
1142 REPORT( _( "Rule area count differs." ) );
1143 }
1144 else
1145 {
1146 for( auto aIt = aZones.begin(), bIt = bZones.begin(); aIt != aZones.end(); aIt++, bIt++ )
1147 diff |= zoneNeedsUpdate( *aIt, *bIt, aReporter );
1148 }
1149
1150 return diff;
1151}
1152
1153
1155{
1156 BOARD* board = m_drcEngine->GetBoard();
1157 PROJECT* project = board->GetProject();
1158
1159 if( !project )
1160 {
1161 REPORT_AUX( _( "No project loaded, skipping library parity tests." ) );
1162 return true; // Continue with other tests
1163 }
1164
1165 if( !reportPhase( _( "Loading footprint library table..." ) ) )
1166 return false; // DRC cancelled
1167
1168 std::map<LIB_ID, std::shared_ptr<FOOTPRINT>> libFootprintCache;
1169
1171 wxString msg;
1172 int ii = 0;
1173 const int progressDelta = 250;
1174
1175 if( !reportPhase( _( "Checking board footprints against library..." ) ) )
1176 return false;
1177
1178 for( FOOTPRINT* footprint : board->Footprints() )
1179 {
1180 if( m_drcEngine->IsErrorLimitExceeded( DRCE_LIB_FOOTPRINT_ISSUES )
1181 && m_drcEngine->IsErrorLimitExceeded( DRCE_LIB_FOOTPRINT_MISMATCH ) )
1182 {
1183 return true; // Continue with other tests
1184 }
1185
1186 if( !reportProgress( ii++, (int) board->Footprints().size(), progressDelta ) )
1187 return false; // DRC cancelled
1188
1189 LIB_ID fpID = footprint->GetFPID();
1190 wxString libName = fpID.GetLibNickname();
1191 wxString fpName = fpID.GetLibItemName();
1192 LIBRARY_TABLE_ROW* libTableRow = nullptr;
1193
1194 if( libName.IsEmpty() )
1195 {
1196 // Not much we can do here
1197 continue;
1198 }
1199
1200 if( std::optional<LIBRARY_TABLE_ROW*> optRow = adapter->GetRow( libName ); optRow )
1201 libTableRow = *optRow;
1202
1203 if( !libTableRow )
1204 {
1205 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_LIB_FOOTPRINT_ISSUES ) )
1206 {
1207 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
1208 msg.Printf( _( "The current configuration does not include the footprint library '%s'" ),
1209 UnescapeString( libName ) );
1210 drcItem->SetErrorMessage( msg );
1211 drcItem->SetItems( footprint );
1212 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
1213 }
1214
1215 continue;
1216 }
1217 else if( !adapter->HasLibrary( libName, true ) )
1218 {
1219 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_LIB_FOOTPRINT_ISSUES ) )
1220 {
1221 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
1222 msg.Printf( _( "The footprint library '%s' is not enabled in the current configuration" ),
1223 UnescapeString( libName ) );
1224 drcItem->SetErrorMessage( msg );
1225 drcItem->SetItems( footprint );
1226 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
1227 }
1228
1229 continue;
1230 }
1231 else if( !adapter->IsLibraryLoaded( libName ) )
1232 {
1233 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_LIB_FOOTPRINT_ISSUES ) )
1234 {
1235 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
1236 msg.Printf( _( "The footprint library '%s' was not found at '%s'" ),
1237 UnescapeString( libName ),
1238 LIBRARY_MANAGER::GetFullURI( libTableRow, true ) );
1239 drcItem->SetErrorMessage( msg );
1240 drcItem->SetItems( footprint );
1241 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
1242 }
1243
1244 continue;
1245 }
1246
1247 auto cacheIt = libFootprintCache.find( fpID );
1248 std::shared_ptr<FOOTPRINT> libFootprint;
1249
1250 if( cacheIt != libFootprintCache.end() )
1251 {
1252 libFootprint = cacheIt->second;
1253 }
1254 else
1255 {
1256 try
1257 {
1258 libFootprint.reset( adapter->LoadFootprint( libName, fpName, true ) );
1259
1260 if( libFootprint )
1261 libFootprintCache[ fpID ] = libFootprint;
1262 }
1263 catch( const IO_ERROR& )
1264 {
1265 }
1266 }
1267
1268 if( !libFootprint )
1269 {
1270 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_LIB_FOOTPRINT_ISSUES ) )
1271 {
1272 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
1273 msg.Printf( _( "Footprint '%s' not found in library '%s'" ),
1274 fpName,
1275 libName );
1276 drcItem->SetErrorMessage( msg );
1277 drcItem->SetItems( footprint );
1278 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
1279 }
1280 }
1281 else if( footprint->FootprintNeedsUpdate( libFootprint.get(), BOARD_ITEM::COMPARE_FLAGS::DRC ) )
1282 {
1283 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_LIB_FOOTPRINT_MISMATCH ) )
1284 {
1285 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_MISMATCH );
1286 msg.Printf( _( "Footprint '%s' does not match copy in library '%s'" ),
1287 fpName,
1288 libName );
1289 drcItem->SetErrorMessage( msg );
1290 drcItem->SetItems( footprint );
1291 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
1292 }
1293 }
1294 }
1295
1296 return true;
1297}
1298
1299
1300namespace detail
1301{
1303}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
BOARD_ITEM(BOARD_ITEM *aParent, KICAD_T idtype, PCB_LAYER_ID aLayer=F_Cu)
Definition board_item.h:87
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
FOOTPRINT * GetParentFootprint() const
VECTOR2I GetFPRelativePosition() const
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition board_item.h:355
@ INSTANCE_TO_INSTANCE
Definition board_item.h:585
virtual bool IsOnCopperLayer() const
Definition board_item.h:190
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
const FOOTPRINTS & Footprints() const
Definition board.h:465
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:932
PROJECT * GetProject() const
Definition board.h:774
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1174
constexpr const Vec GetEnd() const
Definition box2.h:209
constexpr BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition box2.h:143
constexpr const Vec & GetOrigin() const
Definition box2.h:207
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:492
virtual ~DRC_TEST_PROVIDER_LIBRARY_PARITY()=default
virtual bool Run() override
Run this provider against the given PCB with configured options (if any).
virtual const wxString GetName() const override
virtual bool reportPhase(const wxString &aStageName)
void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, const std::function< void(PCB_MARKER *)> &aPathGenerator=[](PCB_MARKER *){})
virtual bool reportProgress(size_t aCount, size_t aSize, size_t aDelta=1)
EDA_ANGLE Normalize()
Definition eda_angle.h:228
double AsDegrees() const
Definition eda_angle.h:115
int GetEllipseMinorRadius() const
Definition eda_shape.h:403
const VECTOR2I & GetEllipseCenter() const
Definition eda_shape.h:385
EDA_ANGLE GetEllipseEndAngle() const
Definition eda_shape.h:431
FILL_T GetFillMode() const
Definition eda_shape.h:150
int GetEllipseMajorRadius() const
Definition eda_shape.h:394
EDA_ANGLE GetEllipseRotation() const
Definition eda_shape.h:412
SHAPE_T GetShape() const
Definition eda_shape.h:177
EDA_ANGLE GetEllipseStartAngle() const
Definition eda_shape.h:422
wxString SHAPE_T_asString() const
An interface to the global shared library manager that is schematic-specific and linked to one projec...
FOOTPRINT * LoadFootprint(const wxString &aNickname, const wxString &aName, bool aKeepUUID)
Load a FOOTPRINT having aName from the library given by aNickname.
bool AllowSolderMaskBridges() const
Definition footprint.h:518
ZONE_CONNECTION GetLocalZoneConnection() const
Definition footprint.h:491
EDA_ANGLE GetOrientation() const
Definition footprint.h:382
ZONES & Zones()
Definition footprint.h:361
bool FootprintNeedsUpdate(const FOOTPRINT *aLibFP, int aCompareFlags=0, REPORTER *aReporter=nullptr)
Return true if a board footprint differs from the library version.
std::optional< int > GetLocalSolderPasteMargin() const
Definition footprint.h:484
EDA_ITEM * Clone() const override
Create a duplicate of this item with linked list members set to NULL.
std::optional< int > GetLocalClearance() const
Definition footprint.h:478
std::deque< PAD * > & Pads()
Definition footprint.h:355
int GetAttributes() const
Definition footprint.h:509
FOOTPRINT(BOARD *parent)
bool IsFlipped() const
Definition footprint.h:620
const std::vector< wxString > & GetNetTiePadGroups() const
Definition footprint.h:567
const LSET & GetStackupLayers() const
Definition footprint.h:507
std::optional< double > GetLocalSolderPasteMarginRatio() const
Definition footprint.h:487
std::optional< int > GetLocalSolderMaskMargin() const
Definition footprint.h:481
FOOTPRINT_STACKUP GetStackupMode() const
Definition footprint.h:500
DRAWINGS & GraphicalItems()
Definition footprint.h:358
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
bool IsLibraryLoaded(const wxString &aNickname)
bool HasLibrary(const wxString &aNickname, bool aCheckEnabled=false) const
Test for the existence of aNickname in the library tables.
std::optional< LIBRARY_TABLE_ROW * > GetRow(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
Like LIBRARY_MANAGER::GetRow but filtered to the LIBRARY_TABLE_TYPE of this adapter.
std::optional< wxString > GetFullURI(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, bool aSubstituted=false)
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
const UTF8 & GetLibItemName() const
Definition lib_id.h:98
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition lib_id.h:83
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
static LSET AllNonCuMask()
Return a mask holding all layer minus CU layers.
Definition lset.cpp:623
static const LSET & ExternalCuMask()
Return a mask holding the Front and Bottom layers.
Definition lset.cpp:630
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:595
static LSET UserDefinedLayersMask(int aUserDefinedLayerCount=MAX_USER_DEFINED_LAYERS)
Return a mask with the requested number of user defined layers.
Definition lset.cpp:700
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition lset.h:63
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::...
DRILL_PROPS & Drill()
Definition padstack.h:358
std::vector< PCB_LAYER_ID > UniqueLayers() const
MODE Mode() const
Definition padstack.h:346
Definition pad.h:61
PAD_PROP GetProperty() const
Definition pad.h:563
bool GetRemoveUnconnected() const
Definition pad.h:874
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
std::optional< double > GetLocalSolderPasteMarginRatio() const
Definition pad.h:600
PAD_ATTRIB GetAttribute() const
Definition pad.h:560
const wxString & GetNumber() const
Definition pad.h:143
const VECTOR2I & GetDelta(PCB_LAYER_ID aLayer) const
Definition pad.h:307
EDA_ANGLE GetThermalSpokeAngle() const
Definition pad.h:757
VECTOR2I GetOffset(PCB_LAYER_ID aLayer) const
Definition pad.cpp:837
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
bool GetKeepTopBottom() const
Definition pad.h:889
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
const PADSTACK & Padstack() const
Definition pad.h:331
PAD_DRILL_SHAPE GetDrillShape() const
Definition pad.h:434
int GetChamferPositions(PCB_LAYER_ID aLayer) const
Definition pad.h:852
std::optional< int > GetLocalSolderPasteMargin() const
Definition pad.h:593
std::optional< int > GetLocalSolderMaskMargin() const
Definition pad.h:586
EDA_ANGLE GetFPRelativeOrientation() const
Definition pad.cpp:1807
double GetChamferRectRatio(PCB_LAYER_ID aLayer) const
Definition pad.h:834
std::optional< int > GetLocalThermalSpokeWidthOverride() const
Definition pad.h:741
ZONE_CONNECTION GetLocalZoneConnection() const
Definition pad.h:611
CUSTOM_SHAPE_ZONE_MODE GetCustomShapeInZoneOpt() const
Definition pad.h:227
int GetThermalGap() const
Definition pad.h:773
int GetLocalThermalGapOverride(wxString *aSource) const
Definition pad.cpp:2264
int GetPadToDieLength() const
Definition pad.h:578
VECTOR2I GetPosition() const override
Get the position (center) of the barcode in internal units.
wxString GetText() const
int GetTextSize() const
int GetHeight() const
Get the barcode height (in internal units).
BARCODE_ECC_T GetErrorCorrection() const
BARCODE_T GetKind() const
Returns the type of the barcode (QR, CODE_39, etc.).
int GetWidth() const
Get the barcode width (in internal units).
VECTOR2I GetLibraryBezierC1() const
VECTOR2I GetLibraryEnd() const
Definition pcb_shape.h:233
VECTOR2I GetLibraryStart() const
Definition pcb_shape.h:232
STROKE_PARAMS GetStroke() const override
SHAPE_POLY_SET GetLibraryPolyShape() const
VECTOR2I GetLibraryBezierC2() const
VECTOR2I GetLibraryArcMid() const
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition pcb_shape.h:70
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
Container for project specific data.
Definition project.h:63
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
Represent a set of closed polygons.
int TotalVertices() const
Return total number of vertices stored in the set.
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
int OutlineCount() const
Return the number of outlines in the set.
const POLYGON & CPolygon(int aIndex) const
const std::vector< POLYGON > & CPolygons() const
Handle a list of polygons defining a copper zone.
Definition zone.h:70
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:832
std::optional< int > GetLocalClearance() const override
Definition zone.cpp:1097
bool GetDoNotAllowVias() const
Definition zone.h:850
bool GetDoNotAllowPads() const
Definition zone.h:852
bool GetDoNotAllowTracks() const
Definition zone.h:851
ISLAND_REMOVAL_MODE GetIslandRemovalMode() const
Definition zone.h:861
long long int GetMinIslandArea() const
Definition zone.h:864
const wxString & GetZoneName() const
Definition zone.h:160
int GetMinThickness() const
Definition zone.h:315
ZONE_SETTINGS::CORNER_SMOOTHING GetCornerSmoothingType() const
Definition zone.h:771
ZONE_CONNECTION GetPadConnection() const
Definition zone.h:312
int GetHatchThickness() const
Definition zone.h:325
double GetHatchHoleMinArea() const
Definition zone.h:340
int GetThermalReliefSpokeWidth() const
Definition zone.h:259
EDA_ANGLE GetHatchOrientation() const
Definition zone.h:331
bool GetDoNotAllowFootprints() const
Definition zone.h:853
ZONE_FILL_MODE GetFillMode() const
Definition zone.h:238
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:133
int GetHatchGap() const
Definition zone.h:328
double GetHatchSmoothingValue() const
Definition zone.h:337
bool GetDoNotAllowZoneFills() const
Definition zone.h:849
int GetHatchSmoothingLevel() const
Definition zone.h:334
unsigned int GetCornerRadius() const
Definition zone.h:775
SHAPE_POLY_SET GetLibraryOutline() const
Definition zone.cpp:945
int GetThermalReliefGap() const
Definition zone.h:248
unsigned GetAssignedPriority() const
Definition zone.h:122
@ DRCE_LIB_FOOTPRINT_ISSUES
Definition drc_item.h:96
@ DRCE_LIB_FOOTPRINT_MISMATCH
Definition drc_item.h:97
#define REPORT_AUX(s)
UNITS_PROVIDER g_unitsProvider(pcbIUScale, EDA_UNITS::MM)
bool padOrientationDiffers(const PAD *a, const PAD *bLib)
bool footprintVsBoardStackup(const FOOTPRINT &aFp, const BOARD &aBoard, REPORTER *aReporter)
Report board->footprint stackup differences.
#define TEST_PT(a, b, msg)
bool padNeedsUpdate(const PAD *a, const PAD *bLib, REPORTER *aReporter)
#define PAD_DESC(pad)
bool primitiveNeedsUpdate(const std::shared_ptr< PCB_SHAPE > &a, const std::shared_ptr< PCB_SHAPE > &b)
static bool boardLayersMatchWithInnerLayerExpansion(const LSET &aItem, const LSET &bLib, bool aAllowCuExpansion)
#define TEST(a, b, msg)
bool padHasOverrides(const PAD *a, const PAD *b, REPORTER &aReporter)
bool shapeNeedsUpdate(const PCB_SHAPE &curr_shape, const PCB_SHAPE &ref_shape)
bool zoneNeedsUpdate(const ZONE *a, const ZONE *b, REPORTER *aReporter)
bool barcodeNeedsUpdate(const PCB_BARCODE &curr_barcode, const PCB_BARCODE &ref_barcode)
#define TEST_ATTR(a, b, attr, msg)
#define TEST_D(a, b, msg)
bool stackupNeedsUpdate(const FOOTPRINT &a, const FOOTPRINT &b, REPORTER *aReporter)
Compare the stackup related settings of two footprints.
#define CHECKPOINT
LSET getBoardNormalizedLayerSet(const BOARD_ITEM *aLibItem, const BOARD *aBoard)
#define REPORT_MSG(s, p)
#define ITEM_DESC(item)
#define _(s)
#define EPSILON
#define TEST(a, b)
@ ELLIPSE
Definition eda_shape.h:62
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ ELLIPSE_ARC
Definition eda_shape.h:63
@ FP_SMD
Definition footprint.h:87
@ FP_DNP
Definition footprint.h:92
@ FP_EXCLUDE_FROM_POS_FILES
Definition footprint.h:88
@ FP_BOARD_ONLY
Definition footprint.h:90
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:89
@ FP_EXCLUDE_FROM_SIM
Definition footprint.h:93
@ FP_THROUGH_HOLE
Definition footprint.h:86
@ EXPAND_INNER_LAYERS
The 'normal' stackup handling, where there is a single inner layer (In1) and rule areas using it expa...
Definition footprint.h:111
wxString LayerName(int aLayer)
Returns the default display name for a given layer.
Definition layer_id.cpp:31
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ In1_Cu
Definition layer_ids.h:62
#define REPORT(msg)
#define ITEM_DESC(item)
This file contains miscellaneous commonly used macros and functions.
#define UNIMPLEMENTED_FOR(type)
Definition macros.h:92
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:25
wxString AccumulateNames(const LSEQ &aLayers, const BOARD *aBoard)
Accumulate layer names from a layer set into a comma separated string.
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
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
@ RECTANGLE
Definition padstack.h:53
BARCODE class definition.
std::vector< FAB_LAYER_COLOR > dummy
wxString UnescapeString(const wxString &aSource)
PAD_DRILL_SHAPE shape
Definition padstack.h:274
VECTOR2I end
int delta
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708