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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <layer_range.h>
25#include <kiway.h>
26#include <macros.h>
28#include <fp_lib_table.h>
29#include <board.h>
30#include <pcb_shape.h>
31#include <zone.h>
32#include <footprint.h>
33#include <pad.h>
34#include <drc/drc_engine.h>
35#include <drc/drc_item.h>
37#include <project_pcb.h>
38
39/*
40 Library parity test.
41
42 Errors generated:
43 - DRCE_LIB_FOOTPRINT_ISSUES
44 - DRCE_LIB_FOOTPRINT_MISMATCH
45*/
46
48{
49public:
51 {
52 m_isRuleDriven = false;
53 }
54
56
57 virtual bool Run() override;
58
59 virtual const wxString GetName() const override { return wxT( "library_parity" ); };
60};
61
62
63//
64// The TEST*() macros have two modes:
65// In "Report" mode (aReporter != nullptr) all properties are checked and reported on.
66// In "DRC" mode (aReporter == nulltpr) properties are only checked until a difference is found.
67//
68#define TEST( a, b, msg ) \
69 do { \
70 if( a != b ) \
71 { \
72 diff = true; \
73 \
74 if( aReporter && wxString( msg ).length() ) \
75 aReporter->Report( msg ); \
76 } \
77 \
78 if( diff && !aReporter ) \
79 return diff; \
80 } while (0)
81
82#define EPSILON 2
83#define TEST_PT( a, b, msg ) \
84 do { \
85 if( abs( a.x - b.x ) > EPSILON \
86 || abs( a.y - b.y ) > EPSILON ) \
87 { \
88 diff = true; \
89 \
90 if( aReporter && wxString( msg ).length() ) \
91 aReporter->Report( msg ); \
92 } \
93 \
94 if( diff && !aReporter ) \
95 return diff; \
96 } while (0)
97
98#define EPSILON_D 0.000002
99#define TEST_D( a, b, msg ) \
100 do { \
101 if( abs( a - b ) > EPSILON_D ) \
102 { \
103 diff = true; \
104 \
105 if( aReporter && wxString( msg ).length() ) \
106 aReporter->Report( msg ); \
107 } \
108 \
109 if( diff && !aReporter ) \
110 return diff; \
111 } while (0)
112
113#define ITEM_DESC( item ) ( item )->GetItemDescription( &g_unitsProvider, true )
114#define PAD_DESC( pad ) wxString::Format( _( "Pad %s" ), ( pad )->GetNumber() )
115
116
118
119
120LSET getBoardNormalizedLayerSet( const BOARD_ITEM* aLibItem, const BOARD* aBoard )
121{
122 LSET lset = aLibItem->GetLayerSet();
123
124 if( aBoard )
125 lset &= aBoard->GetEnabledLayers();
126
127 return lset;
128}
129
130
131bool primitiveNeedsUpdate( const std::shared_ptr<PCB_SHAPE>& a,
132 const std::shared_ptr<PCB_SHAPE>& b )
133{
134 REPORTER* aReporter = nullptr;
135 bool diff = false;
136
137 TEST( a->GetShape(), b->GetShape(), "" );
138
139 switch( a->GetShape() )
140 {
142 {
143 BOX2I aRect( a->GetStart(), a->GetEnd() - a->GetStart() );
144 BOX2I bRect( b->GetStart(), b->GetEnd() - b->GetStart() );
145
146 aRect.Normalize();
147 bRect.Normalize();
148
149 TEST_PT( aRect.GetOrigin(), bRect.GetOrigin(), "" );
150 TEST_PT( aRect.GetEnd(), bRect.GetEnd(), "" );
151 break;
152 }
153
154 case SHAPE_T::SEGMENT:
155 case SHAPE_T::CIRCLE:
156 TEST_PT( a->GetStart(), b->GetStart(), "" );
157 TEST_PT( a->GetEnd(), b->GetEnd(), "" );
158 break;
159
160 case SHAPE_T::ARC:
161 TEST_PT( a->GetStart(), b->GetStart(), "" );
162 TEST_PT( a->GetEnd(), b->GetEnd(), "" );
163
164 // Arc center is calculated and so may have round-off errors when parents are
165 // differentially rotated.
166 if( ( a->GetArcMid() - b->GetArcMid() ).EuclideanNorm() > pcbIUScale.mmToIU( 0.0005 ) )
167 return true;
168
169 break;
170
171 case SHAPE_T::BEZIER:
172 TEST_PT( a->GetStart(), b->GetStart(), "" );
173 TEST_PT( a->GetEnd(), b->GetEnd(), "" );
174 TEST_PT( a->GetBezierC1(), b->GetBezierC1(), "" );
175 TEST_PT( a->GetBezierC2(), b->GetBezierC2(), "" );
176 break;
177
178 case SHAPE_T::POLY:
179 TEST( a->GetPolyShape().TotalVertices(), b->GetPolyShape().TotalVertices(), "" );
180
181 for( int ii = 0; ii < a->GetPolyShape().TotalVertices(); ++ii )
182 TEST_PT( a->GetPolyShape().CVertex( ii ), b->GetPolyShape().CVertex( ii ), "" );
183
184 break;
185
186 default:
187 UNIMPLEMENTED_FOR( a->SHAPE_T_asString() );
188 }
189
190 TEST( a->GetStroke(), b->GetStroke(), "" );
191 TEST( a->GetFillMode(), b->GetFillMode(), "" );
192
193 return diff;
194}
195
196
197bool padHasOverrides( const PAD* a, const PAD* b, REPORTER& aReporter )
198{
199 bool diff = false;
200
201#define REPORT_MSG( s, p ) aReporter.Report( wxString::Format( s, p ) )
202
203 if( a->GetLocalClearance().has_value() && a->GetLocalClearance() != b->GetLocalClearance() )
204 {
205 diff = true;
206 REPORT_MSG( _( "%s has clearance override." ), PAD_DESC( a ) );
207 }
208
209 if( a->GetLocalSolderMaskMargin().has_value()
211 {
212 diff = true;
213 REPORT_MSG( _( "%s has solder mask expansion override." ), PAD_DESC( a ) );
214 }
215
216
217 if( a->GetLocalSolderPasteMargin().has_value()
219 {
220 diff = true;
221 REPORT_MSG( _( "%s has solder paste clearance override." ), PAD_DESC( a ) );
222 }
223
226 {
227 diff = true;
228 REPORT_MSG( _( "%s has solder paste clearance override." ), PAD_DESC( a ) );
229 }
230
233 {
234 diff = true;
235 REPORT_MSG( _( "%s has zone connection override." ), PAD_DESC( a ) );
236 }
237
238 if( a->GetLocalThermalGapOverride().has_value()
239 && a->GetThermalGap() != b->GetThermalGap() )
240 {
241 diff = true;
242 REPORT_MSG( _( "%s has thermal relief gap override." ), PAD_DESC( a ) );
243 }
244
245 if( a->GetLocalThermalSpokeWidthOverride().has_value()
247 {
248 diff = true;
249 REPORT_MSG( _( "%s has thermal relief spoke width override." ), PAD_DESC( a ) );
250 }
251
253 {
254 diff = true;
255 REPORT_MSG( _( "%s has thermal relief spoke angle override." ), PAD_DESC( a ) );
256 }
257
259 {
260 diff = true;
261 REPORT_MSG( _( "%s has zone knockout setting override." ), PAD_DESC( a ) );
262 }
263
264 return diff;
265}
266
267
268bool padNeedsUpdate( const PAD* a, const PAD* b, REPORTER* aReporter )
269{
270 bool diff = false;
271
273 wxString::Format( _( "%s pad to die length differs." ), PAD_DESC( a ) ) );
275 wxString::Format( _( "%s position differs." ), PAD_DESC( a ) ) );
276
277 TEST( a->GetNumber(), b->GetNumber(),
278 wxString::Format( _( "%s has different numbers." ), PAD_DESC( a ) ) );
279
280 // These are assigned from the schematic and not from the library
281 // TEST( a->GetPinFunction(), b->GetPinFunction() );
282 // TEST( a->GetPinType(), b->GetPinType() );
283
284 bool layerSettingsDiffer = a->GetRemoveUnconnected() != b->GetRemoveUnconnected();
285
286 // NB: KeepTopBottom is undefined if RemoveUnconnected is NOT set.
287 if( a->GetRemoveUnconnected() )
288 layerSettingsDiffer |= a->GetKeepTopBottom() != b->GetKeepTopBottom();
289
290 if( layerSettingsDiffer
292 {
293 diff = true;
294
295 if( aReporter )
296 aReporter->Report( wxString::Format( _( "%s layers differ." ), PAD_DESC( a ) ) );
297 else
298 return true;
299 }
300
301 TEST( a->GetAttribute(), b->GetAttribute(),
302 wxString::Format( _( "%s pad type differs." ), PAD_DESC( a ) ) );
303 TEST( a->GetProperty(), b->GetProperty(),
304 wxString::Format( _( "%s fabrication property differs." ), PAD_DESC( a ) ) );
305
306 // The pad orientation, for historical reasons is the pad rotation + parent rotation.
309 wxString::Format( _( "%s orientation differs." ), PAD_DESC( a ) ) );
310
311 std::vector<PCB_LAYER_ID> layers = a->Padstack().UniqueLayers();
312 const BOARD* board = a->GetBoard();
313 wxString layerName;
314
315 for( PCB_LAYER_ID layer : layers )
316 {
317 layerName = board ? board->GetLayerName( layer ) : LayerName( layer );
318
319 TEST( a->GetShape( layer ), b->GetShape( layer ),
320 wxString::Format( _( "%s pad shape type differs on layer %s." ),
321 PAD_DESC( a ),
322 layerName ) );
323
324 TEST( a->GetSize( layer ), b->GetSize( layer ),
325 wxString::Format( _( "%s size differs on layer %s." ),
326 PAD_DESC( a ),
327 layerName ) );
328
329 TEST( a->GetDelta( layer ), b->GetDelta( layer ),
330 wxString::Format( _( "%s trapezoid delta differs on layer %s." ),
331 PAD_DESC( a ),
332 layerName ) );
333
334 TEST_D( a->GetRoundRectRadiusRatio( layer ),
335 b->GetRoundRectRadiusRatio( layer ),
336 wxString::Format( _( "%s rounded corners differ on layer %s." ),
337 PAD_DESC( a ),
338 layerName ) );
339
340 TEST_D( a->GetChamferRectRatio( layer ),
341 b->GetChamferRectRatio( layer ),
342 wxString::Format( _( "%s chamfered corner sizes differ on layer %s." ),
343 PAD_DESC( a ),
344 layerName ) );
345
346 TEST( a->GetChamferPositions( layer ),
347 b->GetChamferPositions( layer ),
348 wxString::Format( _( "%s chamfered corners differ on layer %s." ),
349 PAD_DESC( a ),
350 layerName ) );
351
352 TEST_PT( a->GetOffset( layer ), b->GetOffset( layer ),
353 wxString::Format( _( "%s shape offset from hole differs on layer %s." ),
354 PAD_DESC( a ),
355 layerName ) );
356 }
357
358 TEST( a->GetDrillShape(), b->GetDrillShape(),
359 wxString::Format( _( "%s drill shape differs." ), PAD_DESC( a ) ) );
360 TEST( a->GetDrillSize(), b->GetDrillSize(),
361 wxString::Format( _( "%s drill size differs." ), PAD_DESC( a ) ) );
362
363 // Clearance and zone connection overrides are as likely to be set at the board level as in
364 // the library.
365 //
366 // If we ignore them and someone *does* change one of them in the library, then stale
367 // footprints won't be caught.
368 //
369 // On the other hand, if we report them then boards that override at the board level are
370 // going to be VERY noisy.
371 //
372 // So we just do it when we have a reporter.
373 if( aReporter && padHasOverrides( a, b, *aReporter ) )
374 diff = true;
375
376 bool primitivesDiffer = false;
377 PCB_LAYER_ID firstDifferingLayer = UNDEFINED_LAYER;
378
380 [&]( PCB_LAYER_ID aLayer )
381 {
382 if( a->GetPrimitives( aLayer ).size() != b->GetPrimitives( aLayer ).size() )
383 {
384 primitivesDiffer = true;
385 }
386 else
387 {
388 for( size_t ii = 0; ii < a->GetPrimitives( aLayer ).size(); ++ii )
389 {
390 if( primitiveNeedsUpdate( a->GetPrimitives( aLayer )[ii],
391 b->GetPrimitives( aLayer )[ii] ) )
392 {
393 primitivesDiffer = true;
394 break;
395 }
396 }
397 }
398
399 if( primitivesDiffer && firstDifferingLayer == UNDEFINED_LAYER )
400 firstDifferingLayer = aLayer;
401 } );
402
403
404 if( primitivesDiffer )
405 {
406 diff = true;
407 layerName = board ? board->GetLayerName( firstDifferingLayer )
408 : LayerName( firstDifferingLayer );
409
410 if( aReporter )
411 {
412 aReporter->Report( wxString::Format( _( "%s shape primitives differ on layer %s." ),
413 PAD_DESC( a ),
414 layerName ) );
415 }
416 else
417 {
418 return true;
419 }
420 }
421
422 return diff;
423}
424
425
426bool shapeNeedsUpdate( const PCB_SHAPE& curr_shape, const PCB_SHAPE& ref_shape )
427{
428 // curr_shape and ref_shape are expected to be normalized, for a more reliable test.
429 REPORTER* aReporter = nullptr;
430 bool diff = false;
431
432 TEST( curr_shape.GetShape(), ref_shape.GetShape(), "" );
433
434 switch( curr_shape.GetShape() )
435 {
437 {
438 BOX2I aRect( curr_shape.GetStart(), curr_shape.GetEnd() - curr_shape.GetStart() );
439 BOX2I bRect( ref_shape.GetStart(), ref_shape.GetEnd() - ref_shape.GetStart() );
440
441 aRect.Normalize();
442 bRect.Normalize();
443
444 TEST_PT( aRect.GetOrigin(), bRect.GetOrigin(), "" );
445 TEST_PT( aRect.GetEnd(), bRect.GetEnd(), "" );
446 break;
447 }
448
449 case SHAPE_T::SEGMENT:
450 case SHAPE_T::CIRCLE:
451 TEST_PT( curr_shape.GetStart(), ref_shape.GetStart(), "" );
452 TEST_PT( curr_shape.GetEnd(), ref_shape.GetEnd(), "" );
453 break;
454
455 case SHAPE_T::ARC:
456 TEST_PT( curr_shape.GetStart(), ref_shape.GetStart(), "" );
457 TEST_PT( curr_shape.GetEnd(), ref_shape.GetEnd(), "" );
458
459 // Arc center is calculated and so may have round-off errors when parents are
460 // differentially rotated.
461 if( ( curr_shape.GetArcMid() - ref_shape.GetArcMid() ).EuclideanNorm() > pcbIUScale.mmToIU( 0.0005 ) )
462 return true;
463
464 break;
465
466 case SHAPE_T::BEZIER:
467 TEST_PT( curr_shape.GetStart(), ref_shape.GetStart(), "" );
468 TEST_PT( curr_shape.GetEnd(), ref_shape.GetEnd(), "" );
469 TEST_PT( curr_shape.GetBezierC1(), ref_shape.GetBezierC1(), "" );
470 TEST_PT( curr_shape.GetBezierC2(), ref_shape.GetBezierC2(), "" );
471 break;
472
473 case SHAPE_T::POLY:
474 TEST( curr_shape.GetPolyShape().TotalVertices(), ref_shape.GetPolyShape().TotalVertices(), "" );
475
476 for( int ii = 0; ii < curr_shape.GetPolyShape().TotalVertices(); ++ii )
477 TEST_PT( curr_shape.GetPolyShape().CVertex( ii ), ref_shape.GetPolyShape().CVertex( ii ), "" );
478
479 break;
480
481 default:
482 UNIMPLEMENTED_FOR( curr_shape.SHAPE_T_asString() );
483 }
484
485 if( curr_shape.IsOnCopperLayer() )
486 TEST( curr_shape.GetStroke(), ref_shape.GetStroke(), "" );
487
488 TEST( curr_shape.GetFillMode(), ref_shape.GetFillMode(), "" );
489
490 TEST( curr_shape.GetLayer(), ref_shape.GetLayer(), "" );
491
492 return diff;
493}
494
495
496bool zoneNeedsUpdate( const ZONE* a, const ZONE* b, REPORTER* aReporter )
497{
498 bool diff = false;
499
501 wxString::Format( _( "%s corner smoothing setting differs." ), ITEM_DESC( a ) ) );
503 wxString::Format( _( "%s corner smoothing radius differs." ), ITEM_DESC( a ) ) );
504 TEST( a->GetZoneName(), b->GetZoneName(),
505 wxString::Format( _( "%s name differs." ), ITEM_DESC( a ) ) );
507 wxString::Format( _( "%s priority differs." ), ITEM_DESC( a ) ) );
508
509 TEST( a->GetIsRuleArea(), b->GetIsRuleArea(),
510 wxString::Format( _( "%s keep-out property differs." ), ITEM_DESC( a ) ) );
512 wxString::Format( _( "%s keep out zone fill setting differs." ), ITEM_DESC( a ) ) );
514 wxString::Format( _( "%s keep out footprints setting differs." ), ITEM_DESC( a ) ) );
516 wxString::Format( _( "%s keep out pads setting differs." ), ITEM_DESC( a ) ) );
518 wxString::Format( _( "%s keep out tracks setting differs." ), ITEM_DESC( a ) ) );
520 wxString::Format( _( "%s keep out vias setting differs." ), ITEM_DESC( a ) ) );
521
523 wxString::Format( _( "%s layers differ." ), ITEM_DESC( a ) ) );
524
526 wxString::Format( _( "%s pad connection property differs." ), ITEM_DESC( a ) ) );
528 wxString::Format( _( "%s local clearance differs." ), ITEM_DESC( a ) ) );
530 wxString::Format( _( "%s thermal relief gap differs." ), ITEM_DESC( a ) ) );
532 wxString::Format( _( "%s thermal relief spoke width differs." ), ITEM_DESC( a ) ) );
533
535 wxString::Format( _( "%s min thickness differs." ), ITEM_DESC( a ) ) );
536
538 wxString::Format( _( "%s remove islands setting differs." ), ITEM_DESC( a ) ) );
540 wxString::Format( _( "%s minimum island size setting differs." ), ITEM_DESC( a ) ) );
541
542 TEST( a->GetFillMode(), b->GetFillMode(),
543 wxString::Format( _( "%s fill type differs." ), ITEM_DESC( a ) ) );
545 wxString::Format( _( "%s hatch width differs." ), ITEM_DESC( a ) ) );
546 TEST( a->GetHatchGap(), b->GetHatchGap(),
547 wxString::Format( _( "%s hatch gap differs." ), ITEM_DESC( a ) ) );
549 wxString::Format( _( "%s hatch orientation differs." ), ITEM_DESC( a ) ) );
551 wxString::Format( _( "%s hatch smoothing level differs." ), ITEM_DESC( a ) ) );
553 wxString::Format( _( "%s hatch smoothing amount differs." ), ITEM_DESC( a ) ) );
555 wxString::Format( _( "%s minimum hatch hole setting differs." ), ITEM_DESC( a ) ) );
556
557 // This is just a display property
558 // TEST( a->GetHatchBorderAlgorithm(), b->GetHatchBorderAlgorithm() );
559
561 wxString::Format( _( "%s outline corner count differs." ), ITEM_DESC( a ) ) );
562
563 bool cornersDiffer = false;
564
565 for( int ii = 0; ii < a->Outline()->TotalVertices(); ++ii )
566 {
567 if( a->Outline()->CVertex( ii ) != b->Outline()->CVertex( ii ) )
568 {
569 diff = true;
570 cornersDiffer = true;
571 break;
572 }
573 }
574
575 if( cornersDiffer && aReporter )
576 aReporter->Report( wxString::Format( _( "%s corners differ." ), ITEM_DESC( a ) ) );
577
578 return diff;
579}
580
581
582bool FOOTPRINT::FootprintNeedsUpdate( const FOOTPRINT* aLibFP, int aCompareFlags,
583 REPORTER* aReporter )
584{
585 wxASSERT( aLibFP );
586 bool diff = false;
587
588 // To avoid issues when comparing the footprint on board and the footprint in library
589 // use the footprint from lib flipped, rotated and at same position as this.
590 // And using the footprint from lib with same changes as this minimize the issues
591 // due to rounding and shape modifications
592
593 std::unique_ptr<FOOTPRINT> temp( static_cast<FOOTPRINT*>( aLibFP->Clone() ) );
594
595 temp->SetParent( GetBoard() ); // Needed to know the copper layer count;
596
597 if( !( aCompareFlags & COMPARE_FLAGS::INSTANCE_TO_INSTANCE ) )
598 {
599 if( IsFlipped() != temp->IsFlipped() )
600 temp->Flip( { 0, 0 }, FLIP_DIRECTION::TOP_BOTTOM );
601
602 if( GetOrientation() != temp->GetOrientation() )
603 temp->SetOrientation( GetOrientation() );
604
605 if( GetPosition() != temp->GetPosition() )
606 temp->SetPosition( GetPosition() );
607 }
608
609 for( BOARD_ITEM* item : temp->GraphicalItems() )
610 item->NormalizeForCompare();
611
612 // This temporary footprint must not have a parent when it goes out of scope because it
613 // must not trigger the IncrementTimestamp call in ~FOOTPRINT.
614 temp->SetParent( nullptr );
615
616 aLibFP = temp.get();
617
618#define TEST_ATTR( a, b, attr, msg ) TEST( ( a & attr ), ( b & attr ), msg )
619
621 _( "Footprint types differ." ) );
622
624 wxString::Format( _( "'%s' settings differ." ),
625 _( "Allow bridged solder mask apertures between pads" ) ) );
626
627 if( !( aCompareFlags & COMPARE_FLAGS::DRC ) )
628 {
629 // These tests are skipped for DRC: they are presumed to relate to a given design.
631 wxString::Format( _( "'%s' settings differ." ),
632 _( "Not in schematic" ) ) );
633
635 wxString::Format( _( "'%s' settings differ." ),
636 _( "Exclude from position files" ) ) );
637
639 wxString::Format( _( "'%s' settings differ." ),
640 _( "Exclude from bill of materials" ) ) );
641
643 wxString::Format( _( "'%s' settings differ." ),
644 _( "Do not populate" ) ) );
645 }
646
647#define REPORT( msg ) { if( aReporter ) aReporter->Report( msg ); }
648#define CHECKPOINT { if( diff && !aReporter ) return diff; }
649
650 // Clearance and zone connection overrides are as likely to be set at the board level as in
651 // the library.
652 //
653 // If we ignore them and someone *does* change one of them in the library, then stale
654 // footprints won't be caught.
655 //
656 // On the other hand, if we report them then boards that override at the board level are
657 // going to be VERY noisy.
658 //
659 // For report them as different, but we DON'T generate DRC errors on them.
660 if( !( aCompareFlags & COMPARE_FLAGS::DRC ) )
661 {
662 if( GetLocalClearance().has_value() && GetLocalClearance() != aLibFP->GetLocalClearance() )
663 {
664 diff = true;
665 REPORT( _( "Pad clearance overridden." ) );
666 }
667
668 if( GetLocalSolderMaskMargin().has_value()
670 {
671 diff = true;
672 REPORT( _( "Solder mask expansion overridden." ) );
673 }
674
675
676 if( GetLocalSolderPasteMargin().has_value()
678 {
679 diff = true;
680 REPORT( _( "Solder paste absolute clearance overridden." ) );
681 }
682
685 {
686 diff = true;
687 REPORT( _( "Solder paste relative clearance overridden." ) );
688 }
689
690 if( GetLocalZoneConnection() != ZONE_CONNECTION::INHERITED
692 {
693 diff = true;
694 REPORT( _( "Zone connection overridden." ) );
695 }
696 }
697
698 TEST( GetNetTiePadGroups().size(), aLibFP->GetNetTiePadGroups().size(),
699 _( "Net tie pad groups differ." ) );
700
701 for( size_t ii = 0; ii < GetNetTiePadGroups().size(); ++ii )
702 {
703 TEST( GetNetTiePadGroups()[ii], aLibFP->GetNetTiePadGroups()[ii],
704 _( "Net tie pad groups differ." ) );
705 }
706
707 // Text items are really problematic. We don't want to test the reference, but after that
708 // it gets messy.
709 //
710 // What about the value? Depends on whether or not it's a singleton part.
711 //
712 // And what about other texts? They might be added only to instances on the board, or even
713 // changed for instances on the board. Or they might want to be tested for equality.
714 //
715 // Currently we punt and ignore all the text items.
716
717 // Drawings and pads are also somewhat problematic as there's no guarantee that they'll be
718 // in the same order in the two footprints. Rather than building some sophisticated hashing
719 // algorithm we use the footprint sorting functions to attempt to sort them in the same
720 // order.
721
722 // However FOOTPRINT::cmp_drawings uses PCB_SHAPE coordinates and other infos, so we have
723 // already normalized graphic items in model footprint from library, so we need to normalize
724 // graphic items in the footprint to test (*this). So normalize them using a copy of this
725 FOOTPRINT dummy( *this );
726 dummy.SetParentGroup( nullptr );
727 dummy.SetParent( nullptr );
728
729 for( BOARD_ITEM* item : dummy.GraphicalItems() )
730 item->NormalizeForCompare();
731
732 std::set<BOARD_ITEM*, FOOTPRINT::cmp_drawings> aShapes;
733 std::copy_if( dummy.GraphicalItems().begin(), dummy.GraphicalItems().end(),
734 std::inserter( aShapes, aShapes.begin() ),
735 []( BOARD_ITEM* item )
736 {
737 return item->Type() == PCB_SHAPE_T;
738 } );
739
740 std::set<BOARD_ITEM*, FOOTPRINT::cmp_drawings> bShapes;
741 std::copy_if( aLibFP->GraphicalItems().begin(), aLibFP->GraphicalItems().end(),
742 std::inserter( bShapes, bShapes.begin() ),
743 []( BOARD_ITEM* item )
744 {
745 return item->Type() == PCB_SHAPE_T;
746 } );
747
748 if( aShapes.size() != bShapes.size() )
749 {
750 diff = true;
751 REPORT( _( "Graphic item count differs." ) );
752 }
753 else
754 {
755 for( auto aIt = aShapes.begin(), bIt = bShapes.begin(); aIt != aShapes.end(); aIt++, bIt++ )
756 {
757 // aShapes and bShapes are the tested footprint PCB_SHAPE and the model PCB_SHAPE.
758 // These shapes are already normalized.
759 PCB_SHAPE* curr_shape = static_cast<PCB_SHAPE*>( *aIt );
760 PCB_SHAPE* test_shape = static_cast<PCB_SHAPE*>( *bIt );
761
762 if( shapeNeedsUpdate( *curr_shape, *test_shape ) )
763 {
764 diff = true;
765 REPORT( wxString::Format( _( "%s differs." ), ITEM_DESC( *aIt ) ) );
766 }
767 }
768 }
769
771
772 std::set<PAD*, FOOTPRINT::cmp_pads> aPads( Pads().begin(), Pads().end() );
773 std::set<PAD*, FOOTPRINT::cmp_pads> bPads( aLibFP->Pads().begin(), aLibFP->Pads().end() );
774
775 if( aPads.size() != bPads.size() )
776 {
777 diff = true;
778 REPORT( _( "Pad count differs." ) );
779 }
780 else
781 {
782 for( auto aIt = aPads.begin(), bIt = bPads.begin(); aIt != aPads.end(); aIt++, bIt++ )
783 {
784 if( padNeedsUpdate( *aIt, *bIt, aReporter ) )
785 diff = true;
786 else if( aReporter && padHasOverrides( *aIt, *bIt, *aReporter ) )
787 diff = true;
788 }
789 }
790
792
793 std::set<ZONE*, FOOTPRINT::cmp_zones> aZones( Zones().begin(), Zones().end() );
794 std::set<ZONE*, FOOTPRINT::cmp_zones> bZones( aLibFP->Zones().begin(), aLibFP->Zones().end() );
795
796 if( aZones.size() != bZones.size() )
797 {
798 diff = true;
799 REPORT( _( "Rule area count differs." ) );
800 }
801 else
802 {
803 for( auto aIt = aZones.begin(), bIt = bZones.begin(); aIt != aZones.end(); aIt++, bIt++ )
804 diff |= zoneNeedsUpdate( *aIt, *bIt, aReporter );
805 }
806
807 return diff;
808}
809
810
812{
813 BOARD* board = m_drcEngine->GetBoard();
814 PROJECT* project = board->GetProject();
815
816 if( !project )
817 {
818 REPORT_AUX( _( "No project loaded, skipping library parity tests." ) );
819 return true; // Continue with other tests
820 }
821
822 if( !reportPhase( _( "Loading footprint library table..." ) ) )
823 return false; // DRC cancelled
824
825 std::map<LIB_ID, std::shared_ptr<FOOTPRINT>> libFootprintCache;
826
828 wxString msg;
829 int ii = 0;
830 const int progressDelta = 250;
831
832 if( !reportPhase( _( "Checking board footprints against library..." ) ) )
833 return false;
834
835 for( FOOTPRINT* footprint : board->Footprints() )
836 {
839 {
840 return true; // Continue with other tests
841 }
842
843 if( !reportProgress( ii++, (int) board->Footprints().size(), progressDelta ) )
844 return false; // DRC cancelled
845
846 LIB_ID fpID = footprint->GetFPID();
847 wxString libName = fpID.GetLibNickname();
848 wxString fpName = fpID.GetLibItemName();
849 const LIB_TABLE_ROW* libTableRow = nullptr;
850
851 if( libName.IsEmpty() )
852 {
853 // Not much we can do here
854 continue;
855 }
856
857 try
858 {
859 libTableRow = libTable->FindRow( libName );
860 }
861 catch( const IO_ERROR& )
862 {
863 }
864
865 if( !libTableRow )
866 {
868 {
869 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
870 msg.Printf( _( "The current configuration does not include the footprint library '%s'." ),
871 UnescapeString( libName ) );
872 drcItem->SetErrorMessage( msg );
873 drcItem->SetItems( footprint );
874 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
875 }
876
877 continue;
878 }
879 else if( !libTable->HasLibrary( libName, true ) )
880 {
882 {
883 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
884 msg.Printf( _( "The footprint library '%s' is not enabled in the current configuration." ),
885 UnescapeString( libName ) );
886 drcItem->SetErrorMessage( msg );
887 drcItem->SetItems( footprint );
888 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
889 }
890
891 continue;
892 }
893 else if( !libTableRow->LibraryExists() )
894 {
896 {
897 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
898 msg.Printf( _( "The footprint library '%s' was not found at '%s'." ),
899 UnescapeString( libName ),
900 libTableRow->GetFullURI( true ) );
901 drcItem->SetErrorMessage( msg );
902 drcItem->SetItems( footprint );
903 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
904 }
905
906 continue;
907 }
908
909 auto cacheIt = libFootprintCache.find( fpID );
910 std::shared_ptr<FOOTPRINT> libFootprint;
911
912 if( cacheIt != libFootprintCache.end() )
913 {
914 libFootprint = cacheIt->second;
915 }
916 else
917 {
918 try
919 {
920 libFootprint.reset( libTable->FootprintLoad( libName, fpName, true ) );
921
922 if( libFootprint )
923 libFootprintCache[ fpID ] = libFootprint;
924 }
925 catch( const IO_ERROR& )
926 {
927 }
928 }
929
930 if( !libFootprint )
931 {
933 {
934 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
935 msg.Printf( _( "Footprint '%s' not found in library '%s'." ),
936 fpName,
937 libName );
938 drcItem->SetErrorMessage( msg );
939 drcItem->SetItems( footprint );
940 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
941 }
942 }
943 else if( footprint->FootprintNeedsUpdate( libFootprint.get(), BOARD_ITEM::COMPARE_FLAGS::DRC ) )
944 {
946 {
947 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_MISMATCH );
948 msg.Printf( _( "Footprint '%s' does not match copy in library '%s'." ),
949 fpName,
950 libName );
951 drcItem->SetErrorMessage( msg );
952 drcItem->SetItems( footprint );
953 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
954 }
955 }
956 }
957
958 return true;
959}
960
961
962namespace detail
963{
965}
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:112
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:79
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
Definition: board_item.cpp:79
VECTOR2I GetFPRelativePosition() const
Definition: board_item.cpp:346
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition: board_item.h:252
@ INSTANCE_TO_INSTANCE
Definition: board_item.h:436
virtual bool IsOnCopperLayer() const
Definition: board_item.h:151
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:317
const FOOTPRINTS & Footprints() const
Definition: board.h:358
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition: board.cpp:680
PROJECT * GetProject() const
Definition: board.h:538
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition: board.cpp:907
constexpr const Vec GetEnd() const
Definition: box2.h:212
constexpr BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition: box2.h:146
constexpr const Vec & GetOrigin() const
Definition: box2.h:210
BOARD * GetBoard() const
Definition: drc_engine.h:100
bool IsErrorLimitExceeded(int error_code)
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition: drc_item.cpp:393
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
Represent a DRC "provider" which runs some DRC functions over a BOARD and spits out DRC_ITEM and posi...
virtual bool reportPhase(const wxString &aStageName)
virtual void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, DRC_CUSTOM_MARKER_HANDLER *aCustomHandler=nullptr)
DRC_ENGINE * m_drcEngine
virtual bool reportProgress(size_t aCount, size_t aSize, size_t aDelta=1)
EDA_ANGLE Normalize()
Definition: eda_angle.h:229
double AsDegrees() const
Definition: eda_angle.h:116
const VECTOR2I & GetBezierC2() const
Definition: eda_shape.h:258
FILL_T GetFillMode() const
Definition: eda_shape.h:142
SHAPE_POLY_SET & GetPolyShape()
Definition: eda_shape.h:337
SHAPE_T GetShape() const
Definition: eda_shape.h:168
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition: eda_shape.h:215
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition: eda_shape.h:173
wxString SHAPE_T_asString() const
Definition: eda_shape.cpp:343
const VECTOR2I & GetBezierC1() const
Definition: eda_shape.h:255
VECTOR2I GetArcMid() const
Definition: eda_shape.cpp:975
bool AllowSolderMaskBridges() const
Definition: footprint.h:299
ZONE_CONNECTION GetLocalZoneConnection() const
Definition: footprint.h:291
EDA_ANGLE GetOrientation() const
Definition: footprint.h:230
ZONES & Zones()
Definition: footprint.h:215
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:284
EDA_ITEM * Clone() const override
Invoke a function on all children.
Definition: footprint.cpp:2250
std::optional< int > GetLocalClearance() const
Definition: footprint.h:278
std::deque< PAD * > & Pads()
Definition: footprint.h:209
int GetAttributes() const
Definition: footprint.h:293
bool IsFlipped() const
Definition: footprint.h:400
const std::vector< wxString > & GetNetTiePadGroups() const
Definition: footprint.h:348
std::optional< double > GetLocalSolderPasteMarginRatio() const
Definition: footprint.h:287
std::optional< int > GetLocalSolderMaskMargin() const
Definition: footprint.h:281
VECTOR2I GetPosition() const override
Definition: footprint.h:227
DRAWINGS & GraphicalItems()
Definition: footprint.h:212
const FP_LIB_TABLE_ROW * FindRow(const wxString &aNickName, bool aCheckIfEnabled=false)
Return an FP_LIB_TABLE_ROW if aNickName is found in this table or in any chained fall back table frag...
FOOTPRINT * FootprintLoad(const wxString &aNickname, const wxString &aFootprintName, bool aKeepUUID=false)
Load a footprint having aFootprintName from the library given by aNickname.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
Definition: ki_exception.h:77
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:49
const UTF8 & GetLibItemName() const
Definition: lib_id.h:102
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition: lib_id.h:87
Hold a record identifying a library accessed by the appropriate plug in object in the LIB_TABLE.
virtual bool LibraryExists() const =0
const wxString GetFullURI(bool aSubstituted=false) const
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
bool HasLibrary(const wxString &aNickname, bool aCheckEnabled=false) const
Test for the existence of aNickname in the library table.
LSET is a set of PCB_LAYER_IDs.
Definition: lset.h:37
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::...
Definition: padstack.cpp:882
std::vector< PCB_LAYER_ID > UniqueLayers() const
Definition: padstack.cpp:909
Definition: pad.h:54
PAD_PROP GetProperty() const
Definition: pad.h:443
bool GetRemoveUnconnected() const
Definition: pad.h:734
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:365
std::optional< double > GetLocalSolderPasteMarginRatio() const
Definition: pad.h:475
const VECTOR2I & GetDrillSize() const
Definition: pad.h:305
PAD_ATTRIB GetAttribute() const
Definition: pad.h:440
const wxString & GetNumber() const
Definition: pad.h:136
const VECTOR2I & GetDelta(PCB_LAYER_ID aLayer) const
Definition: pad.h:299
EDA_ANGLE GetThermalSpokeAngle() const
Definition: pad.h:625
double GetRoundRectRadiusRatio(PCB_LAYER_ID aLayer) const
Definition: pad.h:671
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition: pad.h:195
bool GetKeepTopBottom() const
Definition: pad.h:750
std::optional< int > GetLocalClearance() const override
Return any local clearances set in the "classic" (ie: pre-rule) system.
Definition: pad.h:458
const PADSTACK & Padstack() const
Definition: pad.h:321
const VECTOR2I & GetOffset(PCB_LAYER_ID aLayer) const
Definition: pad.h:317
PADSTACK::CUSTOM_SHAPE_ZONE_MODE GetCustomShapeInZoneOpt() const
Definition: pad.h:221
PAD_DRILL_SHAPE GetDrillShape() const
Definition: pad.h:422
int GetChamferPositions(PCB_LAYER_ID aLayer) const
Definition: pad.h:711
std::optional< int > GetLocalSolderPasteMargin() const
Definition: pad.h:468
std::optional< int > GetLocalSolderMaskMargin() const
Definition: pad.h:461
EDA_ANGLE GetFPRelativeOrientation() const
Definition: pad.cpp:974
double GetChamferRectRatio(PCB_LAYER_ID aLayer) const
Definition: pad.h:694
std::optional< int > GetLocalThermalSpokeWidthOverride() const
Definition: pad.h:609
ZONE_CONNECTION GetLocalZoneConnection() const
Definition: pad.h:486
int GetThermalGap() const
Definition: pad.h:641
int GetLocalThermalGapOverride(wxString *aSource) const
Definition: pad.cpp:1333
int GetPadToDieLength() const
Definition: pad.h:453
const VECTOR2I & GetSize(PCB_LAYER_ID aLayer) const
Definition: pad.h:264
STROKE_PARAMS GetStroke() const override
Definition: pcb_shape.h:91
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition: pcb_shape.h:71
static FP_LIB_TABLE * PcbFootprintLibs(PROJECT *aProject)
Return the table of footprint libraries without Kiway.
Definition: project_pcb.cpp:37
Container for project specific data.
Definition: project.h:65
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
int TotalVertices() const
Return total number of vertices stored in the set.
const VECTOR2I & CVertex(int aIndex, int aOutline, int aHole) const
Return the index-th vertex in a given hole outline within a given outline.
Handle a list of polygons defining a copper zone.
Definition: zone.h:74
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition: zone.h:704
std::optional< int > GetLocalClearance() const override
Definition: zone.cpp:814
bool GetDoNotAllowVias() const
Definition: zone.h:720
bool GetDoNotAllowPads() const
Definition: zone.h:722
bool GetDoNotAllowTracks() const
Definition: zone.h:721
ISLAND_REMOVAL_MODE GetIslandRemovalMode() const
Definition: zone.h:731
SHAPE_POLY_SET * Outline()
Definition: zone.h:335
long long int GetMinIslandArea() const
Definition: zone.h:734
const wxString & GetZoneName() const
Definition: zone.h:163
int GetMinThickness() const
Definition: zone.h:301
ZONE_CONNECTION GetPadConnection() const
Definition: zone.h:298
int GetHatchThickness() const
Definition: zone.h:310
double GetHatchHoleMinArea() const
Definition: zone.h:325
int GetThermalReliefSpokeWidth() const
Definition: zone.h:245
EDA_ANGLE GetHatchOrientation() const
Definition: zone.h:316
bool GetDoNotAllowFootprints() const
Definition: zone.h:723
ZONE_FILL_MODE GetFillMode() const
Definition: zone.h:224
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition: zone.h:136
int GetHatchGap() const
Definition: zone.h:313
double GetHatchSmoothingValue() const
Definition: zone.h:322
bool GetDoNotAllowZoneFills() const
Definition: zone.h:719
int GetHatchSmoothingLevel() const
Definition: zone.h:319
unsigned int GetCornerRadius() const
Definition: zone.h:650
int GetCornerSmoothingType() const
Definition: zone.h:646
int GetThermalReliefGap() const
Definition: zone.h:234
unsigned GetAssignedPriority() const
Definition: zone.h:126
@ DRCE_LIB_FOOTPRINT_ISSUES
Definition: drc_item.h:82
@ DRCE_LIB_FOOTPRINT_MISMATCH
Definition: drc_item.h:83
#define REPORT_AUX(s)
UNITS_PROVIDER g_unitsProvider(pcbIUScale, EDA_UNITS::MM)
#define TEST_PT(a, b, msg)
#define PAD_DESC(pad)
bool primitiveNeedsUpdate(const std::shared_ptr< PCB_SHAPE > &a, const std::shared_ptr< PCB_SHAPE > &b)
#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)
#define TEST_ATTR(a, b, attr, msg)
#define TEST_D(a, b, msg)
bool padNeedsUpdate(const PAD *a, const PAD *b, REPORTER *aReporter)
#define CHECKPOINT
LSET getBoardNormalizedLayerSet(const BOARD_ITEM *aLibItem, const BOARD *aBoard)
#define REPORT_MSG(s, p)
#define ITEM_DESC(item)
#define _(s)
#define TEST(a, b)
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
@ FP_SMD
Definition: footprint.h:81
@ FP_DNP
Definition: footprint.h:86
@ FP_EXCLUDE_FROM_POS_FILES
Definition: footprint.h:82
@ FP_BOARD_ONLY
Definition: footprint.h:84
@ FP_EXCLUDE_FROM_BOM
Definition: footprint.h:83
@ FP_THROUGH_HOLE
Definition: footprint.h:80
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:60
@ UNDEFINED_LAYER
Definition: layer_ids.h:61
#define REPORT(msg)
#define ITEM_DESC(item)
This file contains miscellaneous commonly used macros and functions.
#define UNIMPLEMENTED_FOR(type)
Definition: macros.h:96
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
std::vector< FAB_LAYER_COLOR > dummy
wxString UnescapeString(const wxString &aSource)
constexpr int mmToIU(double mm) const
Definition: base_units.h:92
VECTOR2I end