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 (C) 2021-2024 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 <kiway.h>
25#include <macros.h>
27#include <fp_lib_table.h>
28#include <board.h>
29#include <pcb_shape.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
38/*
39 Library parity test.
40
41 Errors generated:
42 - DRCE_LIB_FOOTPRINT_ISSUES
43 - DRCE_LIB_FOOTPRINT_MISMATCH
44*/
45
47{
48public:
50 {
51 m_isRuleDriven = false;
52 }
53
55 {
56 }
57
58 virtual bool Run() override;
59
60 virtual const wxString GetName() const override
61 {
62 return wxT( "library_parity" );
63 };
64
65 virtual const wxString GetDescription() const override
66 {
67 return wxT( "Performs board footprint vs library integity checks" );
68 }
69};
70
71
72//
73// The TEST*() macros have two modes:
74// In "Report" mode (aReporter != nullptr) all properties are checked and reported on.
75// In "DRC" mode (aReporter == nulltpr) properties are only checked until a difference is found.
76//
77#define TEST( a, b, msg ) \
78 do { \
79 if( a != b ) \
80 { \
81 diff = true; \
82 \
83 if( aReporter && wxString( msg ).length() ) \
84 aReporter->Report( msg ); \
85 } \
86 \
87 if( diff && !aReporter ) \
88 return diff; \
89 } while (0)
90
91#define EPSILON 2
92#define TEST_PT( a, b, msg ) \
93 do { \
94 if( abs( a.x - b.x ) > EPSILON \
95 || abs( a.y - b.y ) > EPSILON ) \
96 { \
97 diff = true; \
98 \
99 if( aReporter && wxString( msg ).length() ) \
100 aReporter->Report( msg ); \
101 } \
102 \
103 if( diff && !aReporter ) \
104 return diff; \
105 } while (0)
106
107#define EPSILON_D 0.000002
108#define TEST_D( a, b, msg ) \
109 do { \
110 if( abs( a - b ) > EPSILON_D ) \
111 { \
112 diff = true; \
113 \
114 if( aReporter && wxString( msg ).length() ) \
115 aReporter->Report( msg ); \
116 } \
117 \
118 if( diff && !aReporter ) \
119 return diff; \
120 } while (0)
121
122#define ITEM_DESC( item ) ( item )->GetItemDescription( &g_unitsProvider, true )
123#define PAD_DESC( pad ) wxString::Format( _( "Pad %s" ), ( pad )->GetNumber() )
124
125
127
128
129bool primitiveNeedsUpdate( const std::shared_ptr<PCB_SHAPE>& a,
130 const std::shared_ptr<PCB_SHAPE>& b )
131{
132 REPORTER* aReporter = nullptr;
133 bool diff = false;
134
135 TEST( a->GetShape(), b->GetShape(), "" );
136
137 switch( a->GetShape() )
138 {
140 {
141 BOX2I aRect( a->GetStart(), a->GetEnd() - a->GetStart() );
142 BOX2I bRect( b->GetStart(), b->GetEnd() - b->GetStart() );
143
144 aRect.Normalize();
145 bRect.Normalize();
146
147 TEST_PT( aRect.GetOrigin(), bRect.GetOrigin(), "" );
148 TEST_PT( aRect.GetEnd(), bRect.GetEnd(), "" );
149 break;
150 }
151
152 case SHAPE_T::SEGMENT:
153 case SHAPE_T::CIRCLE:
154 TEST_PT( a->GetStart(), b->GetStart(), "" );
155 TEST_PT( a->GetEnd(), b->GetEnd(), "" );
156 break;
157
158 case SHAPE_T::ARC:
159 TEST_PT( a->GetStart(), b->GetStart(), "" );
160 TEST_PT( a->GetEnd(), b->GetEnd(), "" );
161
162 // Arc center is calculated and so may have round-off errors when parents are
163 // differentially rotated.
164 if( ( a->GetCenter() - b->GetCenter() ).EuclideanNorm() > pcbIUScale.mmToIU( 0.0005 ) )
165 return true;
166
167 break;
168
169 case SHAPE_T::BEZIER:
170 TEST_PT( a->GetStart(), b->GetStart(), "" );
171 TEST_PT( a->GetEnd(), b->GetEnd(), "" );
172 TEST_PT( a->GetBezierC1(), b->GetBezierC1(), "" );
173 TEST_PT( a->GetBezierC2(), b->GetBezierC2(), "" );
174 break;
175
176 case SHAPE_T::POLY:
177 TEST( a->GetPolyShape().TotalVertices(), b->GetPolyShape().TotalVertices(), "" );
178
179 for( int ii = 0; ii < a->GetPolyShape().TotalVertices(); ++ii )
180 TEST_PT( a->GetPolyShape().CVertex( ii ), b->GetPolyShape().CVertex( ii ), "" );
181
182 break;
183
184 default:
185 UNIMPLEMENTED_FOR( a->SHAPE_T_asString() );
186 }
187
188 TEST( a->GetStroke(), b->GetStroke(), "" );
189 TEST( a->IsFilled(), b->IsFilled(), "" );
190
191 return diff;
192}
193
194
195bool padHasOverrides( const PAD* a, const PAD* b, REPORTER& aReporter )
196{
197 bool diff = false;
198
199#define REPORT_MSG( s, p ) aReporter.Report( wxString::Format( s, p ) )
200
201 if( a->GetLocalClearance().has_value() && a->GetLocalClearance() != b->GetLocalClearance() )
202 {
203 diff = true;
204 REPORT_MSG( _( "%s has clearance override." ), PAD_DESC( a ) );
205 }
206
207 if( a->GetLocalSolderMaskMargin().has_value()
209 {
210 diff = true;
211 REPORT_MSG( _( "%s has solder mask expansion override." ), PAD_DESC( a ) );
212 }
213
214
215 if( a->GetLocalSolderPasteMargin().has_value()
217 {
218 diff = true;
219 REPORT_MSG( _( "%s has solder paste clearance override." ), PAD_DESC( a ) );
220 }
221
224 {
225 diff = true;
226 REPORT_MSG( _( "%s has solder paste clearance override." ), PAD_DESC( a ) );
227 }
228
231 {
232 diff = true;
233 REPORT_MSG( _( "%s has zone connection override." ), PAD_DESC( a ) );
234 }
235
236 if( a->GetThermalGap() != b->GetThermalGap() )
237 {
238 diff = true;
239 REPORT_MSG( _( "%s has thermal relief gap override." ), PAD_DESC( a ) );
240 }
241
243 {
244 diff = true;
245 REPORT_MSG( _( "%s has thermal relief spoke width override." ), PAD_DESC( a ) );
246 }
247
249 {
250 diff = true;
251 REPORT_MSG( _( "%s has thermal relief spoke angle override." ), PAD_DESC( a ) );
252 }
253
255 {
256 diff = true;
257 REPORT_MSG( _( "%s has zone knockout setting override." ), PAD_DESC( a ) );
258 }
259
260 return diff;
261}
262
263
264bool padNeedsUpdate( const PAD* a, const PAD* b, REPORTER* aReporter )
265{
266 bool diff = false;
267
269 wxString::Format( _( "%s pad to die length differs." ), PAD_DESC( a ) ) );
271 wxString::Format( _( "%s position differs." ), PAD_DESC( a ) ) );
272
273 TEST( a->GetNumber(), b->GetNumber(),
274 wxString::Format( _( "%s has different numbers." ), PAD_DESC( a ) ) );
275
276 // These are assigned from the schematic and not from the library
277 // TEST( a->GetPinFunction(), b->GetPinFunction() );
278 // TEST( a->GetPinType(), b->GetPinType() );
279
280 bool layerSettingsDiffer = a->GetRemoveUnconnected() != b->GetRemoveUnconnected();
281
282 // NB: KeepTopBottom is undefined if RemoveUnconnected is NOT set.
283 if( a->GetRemoveUnconnected() )
284 layerSettingsDiffer |= a->GetKeepTopBottom() != b->GetKeepTopBottom();
285
286 // Trim layersets to the current board before comparing
287 LSET enabledLayers = a->GetBoard() ? a->GetBoard()->GetEnabledLayers() : LSET::AllLayersMask();
288 LSET aLayers = a->GetLayerSet() & enabledLayers;
289 LSET bLayers = b->GetLayerSet() & enabledLayers;
290
291 if( layerSettingsDiffer || aLayers != bLayers )
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." ), PAD_DESC( a ),
321 layerName ) );
322
323 TEST( a->GetSize( layer ), b->GetSize( layer ),
324 wxString::Format( _( "%s size differs on layer %s." ), PAD_DESC( a ), layerName ) );
325
326 TEST( a->GetDelta( layer ), b->GetDelta( layer ),
327 wxString::Format( _( "%s trapezoid delta differs on layer %s." ), PAD_DESC( a ),
328 layerName ) );
329
330 TEST_D( a->GetRoundRectRadiusRatio( layer ),
331 b->GetRoundRectRadiusRatio( layer ),
332 wxString::Format( _( "%s rounded corners differ on layer %s." ), PAD_DESC( a ),
333 layerName ) );
334
335 TEST_D( a->GetChamferRectRatio( layer ),
336 b->GetChamferRectRatio( layer ),
337 wxString::Format( _( "%s chamfered corner sizes differ on layer %s." ),
338 PAD_DESC( a ), layerName ) );
339
340 TEST( a->GetChamferPositions( layer ),
341 b->GetChamferPositions( layer ),
342 wxString::Format( _( "%s chamfered corners differ on layer %s." ), PAD_DESC( a ),
343 layerName ) );
344
345 TEST_PT( a->GetOffset( layer ), b->GetOffset( layer ),
346 wxString::Format( _( "%s shape offset from hole differs on layer %s." ),
347 PAD_DESC( a ), layerName ) );
348 }
349
350 TEST( a->GetDrillShape(), b->GetDrillShape(),
351 wxString::Format( _( "%s drill shape differs." ), PAD_DESC( a ) ) );
352 TEST( a->GetDrillSize(), b->GetDrillSize(),
353 wxString::Format( _( "%s drill size differs." ), PAD_DESC( a ) ) );
354
355 // Clearance and zone connection overrides are as likely to be set at the board level as in
356 // the library.
357 //
358 // If we ignore them and someone *does* change one of them in the library, then stale
359 // footprints won't be caught.
360 //
361 // On the other hand, if we report them then boards that override at the board level are
362 // going to be VERY noisy.
363 //
364 // So we just do it when we have a reporter.
365 if( aReporter && padHasOverrides( a, b, *aReporter ) )
366 diff = true;
367
368 bool primitivesDiffer = false;
369 PCB_LAYER_ID firstDifferingLayer = UNDEFINED_LAYER;
370
372 [&]( PCB_LAYER_ID aLayer )
373 {
374 if( a->GetPrimitives( aLayer ).size() !=
375 b->GetPrimitives( aLayer ).size() )
376 {
377 primitivesDiffer = true;
378 }
379 else
380 {
381 for( size_t ii = 0; ii < a->GetPrimitives( aLayer ).size(); ++ii )
382 {
383 if( primitiveNeedsUpdate( a->GetPrimitives( aLayer )[ii],
384 b->GetPrimitives( aLayer )[ii] ) )
385 {
386 primitivesDiffer = true;
387 break;
388 }
389 }
390 }
391
392 if( primitivesDiffer && firstDifferingLayer == UNDEFINED_LAYER )
393 firstDifferingLayer = aLayer;
394 } );
395
396
397 if( primitivesDiffer )
398 {
399 diff = true;
400 layerName = board ? board->GetLayerName( firstDifferingLayer )
401 : LayerName( firstDifferingLayer );
402
403 if( aReporter )
404 aReporter->Report( wxString::Format( _( "%s shape primitives differ on layer %s." ),
405 PAD_DESC( a ), layerName ) );
406 else
407 return true;
408 }
409
410 return diff;
411}
412
413
414bool shapeNeedsUpdate( const PCB_SHAPE& curr_shape, const PCB_SHAPE& ref_shape )
415{
416 // curr_shape and ref_shape are expected to be normalized, for a more reliable test.
417 REPORTER* aReporter = nullptr;
418 bool diff = false;
419
420 TEST( curr_shape.GetShape(), ref_shape.GetShape(), "" );
421
422 switch( curr_shape.GetShape() )
423 {
425 {
426 BOX2I aRect( curr_shape.GetStart(), curr_shape.GetEnd() - curr_shape.GetStart() );
427 BOX2I bRect( ref_shape.GetStart(), ref_shape.GetEnd() - ref_shape.GetStart() );
428
429 aRect.Normalize();
430 bRect.Normalize();
431
432 TEST_PT( aRect.GetOrigin(), bRect.GetOrigin(), "" );
433 TEST_PT( aRect.GetEnd(), bRect.GetEnd(), "" );
434 break;
435 }
436
437 case SHAPE_T::SEGMENT:
438 case SHAPE_T::CIRCLE:
439 TEST_PT( curr_shape.GetStart(), ref_shape.GetStart(), "" );
440 TEST_PT( curr_shape.GetEnd(), ref_shape.GetEnd(), "" );
441 break;
442
443 case SHAPE_T::ARC:
444 TEST_PT( curr_shape.GetStart(), ref_shape.GetStart(), "" );
445 TEST_PT( curr_shape.GetEnd(), ref_shape.GetEnd(), "" );
446
447 // Arc center is calculated and so may have round-off errors when parents are
448 // differentially rotated.
449 if( ( curr_shape.GetCenter() - ref_shape.GetCenter() ).EuclideanNorm() > pcbIUScale.mmToIU( 0.0005 ) )
450 return true;
451
452 break;
453
454 case SHAPE_T::BEZIER:
455 TEST_PT( curr_shape.GetStart(), ref_shape.GetStart(), "" );
456 TEST_PT( curr_shape.GetEnd(), ref_shape.GetEnd(), "" );
457 TEST_PT( curr_shape.GetBezierC1(), ref_shape.GetBezierC1(), "" );
458 TEST_PT( curr_shape.GetBezierC2(), ref_shape.GetBezierC2(), "" );
459 break;
460
461 case SHAPE_T::POLY:
462 TEST( curr_shape.GetPolyShape().TotalVertices(), ref_shape.GetPolyShape().TotalVertices(), "" );
463
464 for( int ii = 0; ii < curr_shape.GetPolyShape().TotalVertices(); ++ii )
465 TEST_PT( curr_shape.GetPolyShape().CVertex( ii ), ref_shape.GetPolyShape().CVertex( ii ), "" );
466
467 break;
468
469 default:
470 UNIMPLEMENTED_FOR( curr_shape.SHAPE_T_asString() );
471 }
472
473 if( curr_shape.IsOnCopperLayer() )
474 TEST( curr_shape.GetStroke(), ref_shape.GetStroke(), "" );
475
476 TEST( curr_shape.IsFilled(), ref_shape.IsFilled(), "" );
477
478 TEST( curr_shape.GetLayer(), ref_shape.GetLayer(), "" );
479
480 return diff;
481}
482
483
484bool zoneNeedsUpdate( const ZONE* a, const ZONE* b, REPORTER* aReporter )
485{
486 bool diff = false;
487
489 wxString::Format( _( "%s corner smoothing setting differs." ), ITEM_DESC( a ) ) );
491 wxString::Format( _( "%s corner smoothing radius differs." ), ITEM_DESC( a ) ) );
492 TEST( a->GetZoneName(), b->GetZoneName(),
493 wxString::Format( _( "%s name differs." ), ITEM_DESC( a ) ) );
495 wxString::Format( _( "%s priority differs." ), ITEM_DESC( a ) ) );
496
497 TEST( a->GetIsRuleArea(), b->GetIsRuleArea(),
498 wxString::Format( _( "%s keep-out property differs." ), ITEM_DESC( a ) ) );
500 wxString::Format( _( "%s keep out copper fill setting differs." ), ITEM_DESC( a ) ) );
502 wxString::Format( _( "%s keep out footprints setting differs." ), ITEM_DESC( a ) ) );
504 wxString::Format( _( "%s keep out pads setting differs." ), ITEM_DESC( a ) ) );
506 wxString::Format( _( "%s keep out tracks setting differs." ), ITEM_DESC( a ) ) );
508 wxString::Format( _( "%s keep out vias setting differs." ), ITEM_DESC( a ) ) );
509
510 TEST( a->GetLayerSet(), b->GetLayerSet(),
511 wxString::Format( _( "%s layers differ." ), ITEM_DESC( a ) ) );
512
514 wxString::Format( _( "%s pad connection property differs." ), ITEM_DESC( a ) ) );
516 wxString::Format( _( "%s local clearance differs." ), ITEM_DESC( a ) ) );
518 wxString::Format( _( "%s thermal relief gap differs." ), ITEM_DESC( a ) ) );
520 wxString::Format( _( "%s thermal relief spoke width differs." ), ITEM_DESC( a ) ) );
521
523 wxString::Format( _( "%s min thickness differs." ), ITEM_DESC( a ) ) );
524
526 wxString::Format( _( "%s remove islands setting differs." ), ITEM_DESC( a ) ) );
528 wxString::Format( _( "%s minimum island size setting differs." ), ITEM_DESC( a ) ) );
529
530 TEST( a->GetFillMode(), b->GetFillMode(),
531 wxString::Format( _( "%s fill type differs." ), ITEM_DESC( a ) ) );
533 wxString::Format( _( "%s hatch width differs." ), ITEM_DESC( a ) ) );
534 TEST( a->GetHatchGap(), b->GetHatchGap(),
535 wxString::Format( _( "%s hatch gap differs." ), ITEM_DESC( a ) ) );
537 wxString::Format( _( "%s hatch orientation differs." ), ITEM_DESC( a ) ) );
539 wxString::Format( _( "%s hatch smoothing level differs." ), ITEM_DESC( a ) ) );
541 wxString::Format( _( "%s hatch smoothing amount differs." ), ITEM_DESC( a ) ) );
543 wxString::Format( _( "%s minimum hatch hole setting differs." ), ITEM_DESC( a ) ) );
544
545 // This is just a display property
546 // TEST( a->GetHatchBorderAlgorithm(), b->GetHatchBorderAlgorithm() );
547
549 wxString::Format( _( "%s outline corner count differs." ), ITEM_DESC( a ) ) );
550
551 bool cornersDiffer = false;
552
553 for( int ii = 0; ii < a->Outline()->TotalVertices(); ++ii )
554 {
555 if( a->Outline()->CVertex( ii ) != b->Outline()->CVertex( ii ) )
556 {
557 diff = true;
558 cornersDiffer = true;
559 break;
560 }
561 }
562
563 if( cornersDiffer && aReporter )
564 aReporter->Report( wxString::Format( _( "%s corners differ." ), ITEM_DESC( a ) ) );
565
566 return diff;
567}
568
569
570bool FOOTPRINT::FootprintNeedsUpdate( const FOOTPRINT* aLibFP, int aCompareFlags,
571 REPORTER* aReporter )
572{
573 UNITS_PROVIDER unitsProvider( pcbIUScale, EDA_UNITS::MILLIMETRES );
574
575 wxASSERT( aLibFP );
576 bool diff = false;
577
578 // To avoid issues when comparing the footprint on board and the footprint in library
579 // use the footprint from lib flipped, rotated and at same position as this.
580 // And using the footprint from lib with same changes as this minimize the issues
581 // due to rounding and shape modifications
582
583 std::unique_ptr<FOOTPRINT> temp( static_cast<FOOTPRINT*>( aLibFP->Clone() ) );
584 temp->SetParentGroup( nullptr );
585
586 temp->SetParent( GetBoard() ); // Needed to know the copper layer count;
587
588 if( IsFlipped() != temp->IsFlipped() )
589 temp->Flip( { 0, 0 }, FLIP_DIRECTION::TOP_BOTTOM );
590
591 if( GetOrientation() != temp->GetOrientation() )
592 temp->SetOrientation( GetOrientation() );
593
594 if( GetPosition() != temp->GetPosition() )
595 temp->SetPosition( GetPosition() );
596
597 for( BOARD_ITEM* item : temp->GraphicalItems() )
598 item->NormalizeForCompare();
599
600 // This temporary footprint must not have a parent when it goes out of scope because it
601 // must not trigger the IncrementTimestamp call in ~FOOTPRINT.
602 temp->SetParent( nullptr );
603
604 aLibFP = temp.get();
605
606 TEST( GetLibDescription(), aLibFP->GetLibDescription(), _( "Footprint descriptions differ." ) );
607 TEST( GetKeywords(), aLibFP->GetKeywords(), _( "Footprint keywords differ." ) );
608
609#define TEST_ATTR( a, b, attr, msg ) TEST( ( a & attr ), ( b & attr ), msg )
610
612 _( "Footprint types differ." ) );
613
615 wxString::Format( _( "'%s' settings differ." ),
616 _( "Allow bridged solder mask apertures between pads" ) ) );
617
619 wxString::Format( _( "'%s' settings differ." ),
620 _( "Exempt From Courtyard Requirement" ) ) );
621
622 if( !( aCompareFlags & COMPARE_FLAGS::DRC ) )
623 {
624 // These tests are skipped for DRC: they are presumed to relate to a given design.
626 wxString::Format( _( "'%s' settings differ." ),
627 _( "Not in schematic" ) ) );
628
630 wxString::Format( _( "'%s' settings differ." ),
631 _( "Exclude from position files" ) ) );
632
634 wxString::Format( _( "'%s' settings differ." ),
635 _( "Exclude from bill of materials" ) ) );
636
638 wxString::Format( _( "'%s' settings differ." ),
639 _( "Do not populate" ) ) );
640 }
641
642 // Clearance and zone connection overrides are as likely to be set at the board level as in
643 // the library.
644 //
645 // If we ignore them and someone *does* change one of them in the library, then stale
646 // footprints won't be caught.
647 //
648 // On the other hand, if we report them then boards that override at the board level are
649 // going to be VERY noisy.
650 //
651 // For now we report them if there's a reporter, but we DON'T generate DRC errors on them.
652 if( aReporter )
653 {
654 if( GetLocalClearance().has_value() && GetLocalClearance() != aLibFP->GetLocalClearance() )
655 {
656 diff = true;
657 aReporter->Report( _( "Pad clearance overridden." ) );
658 }
659
660 if( GetLocalSolderMaskMargin().has_value()
662 {
663 diff = true;
664 aReporter->Report( _( "Solder mask expansion overridden." ) );
665 }
666
667
668 if( GetLocalSolderPasteMargin().has_value()
670 {
671 diff = true;
672 aReporter->Report( _( "Solder paste absolute clearance overridden." ) );
673 }
674
677 {
678 diff = true;
679 aReporter->Report( _( "Solder paste relative clearance overridden." ) );
680 }
681
682 if( GetLocalZoneConnection() != ZONE_CONNECTION::INHERITED
684 {
685 diff = true;
686 aReporter->Report( _( "Zone connection overridden." ) );
687 }
688 }
689
690 TEST( GetNetTiePadGroups().size(), aLibFP->GetNetTiePadGroups().size(),
691 _( "Net tie pad groups differ." ) );
692
693 for( size_t ii = 0; ii < GetNetTiePadGroups().size(); ++ii )
694 {
695 TEST( GetNetTiePadGroups()[ii], aLibFP->GetNetTiePadGroups()[ii],
696 _( "Net tie pad groups differ." ) );
697 }
698
699#define REPORT( msg ) { if( aReporter ) aReporter->Report( msg ); }
700#define CHECKPOINT { if( diff && !aReporter ) return diff; }
701
702 // Text items are really problematic. We don't want to test the reference, but after that
703 // it gets messy.
704 //
705 // What about the value? Depends on whether or not it's a singleton part.
706 //
707 // And what about other texts? They might be added only to instances on the board, or even
708 // changed for instances on the board. Or they might want to be tested for equality.
709 //
710 // Currently we punt and ignore all the text items.
711
712 // Drawings and pads are also somewhat problematic as there's no guarantee that they'll be
713 // in the same order in the two footprints. Rather than building some sophisticated hashing
714 // algorithm we use the footprint sorting functions to attempt to sort them in the same
715 // order.
716
717 // However FOOTPRINT::cmp_drawings uses PCB_SHAPE coordinates and other infos, so we have
718 // already normalized graphic items in model footprint from library, so we need to normalize
719 // graphic items in the footprint to test (*this). So normalize them using a copy of this
720 FOOTPRINT dummy( *this );
721 dummy.SetParentGroup( nullptr );
722 dummy.SetParent( nullptr );
723
724 for( BOARD_ITEM* item : dummy.GraphicalItems() )
725 item->NormalizeForCompare();
726
727 std::set<BOARD_ITEM*, FOOTPRINT::cmp_drawings> aShapes;
728 std::copy_if( dummy.GraphicalItems().begin(), dummy.GraphicalItems().end(),
729 std::inserter( aShapes, aShapes.begin() ),
730 []( BOARD_ITEM* item )
731 {
732 return item->Type() == PCB_SHAPE_T;
733 } );
734
735 std::set<BOARD_ITEM*, FOOTPRINT::cmp_drawings> bShapes;
736 std::copy_if( aLibFP->GraphicalItems().begin(), aLibFP->GraphicalItems().end(),
737 std::inserter( bShapes, bShapes.begin() ),
738 []( BOARD_ITEM* item )
739 {
740 return item->Type() == PCB_SHAPE_T;
741 } );
742
743 if( aShapes.size() != bShapes.size() )
744 {
745 diff = true;
746 REPORT( _( "Graphic item count differs." ) );
747 }
748 else
749 {
750 for( auto aIt = aShapes.begin(), bIt = bShapes.begin(); aIt != aShapes.end(); aIt++, bIt++ )
751 {
752 // aShapes and bShapes are the tested footprint PCB_SHAPE and the model PCB_SHAPE.
753 // These shapes are already normalized.
754 PCB_SHAPE* curr_shape = static_cast<PCB_SHAPE*>( *aIt );
755 PCB_SHAPE* test_shape = static_cast<PCB_SHAPE*>( *bIt );
756
757 if( shapeNeedsUpdate( *curr_shape, *test_shape ) )
758 {
759 diff = true;
760 REPORT( wxString::Format( _( "%s differs." ), ITEM_DESC( *aIt ) ) );
761 }
762 }
763 }
764
766
767 std::set<PAD*, FOOTPRINT::cmp_pads> aPads( Pads().begin(), Pads().end() );
768 std::set<PAD*, FOOTPRINT::cmp_pads> bPads( aLibFP->Pads().begin(), aLibFP->Pads().end() );
769
770 if( aPads.size() != bPads.size() )
771 {
772 diff = true;
773 REPORT( _( "Pad count differs." ) );
774 }
775 else
776 {
777 for( auto aIt = aPads.begin(), bIt = bPads.begin(); aIt != aPads.end(); aIt++, bIt++ )
778 {
779 if( padNeedsUpdate( *aIt, *bIt, aReporter ) )
780 diff = true;
781 else if( aReporter && padHasOverrides( *aIt, *bIt, *aReporter ) )
782 diff = true;
783 }
784 }
785
787
788 std::set<ZONE*, FOOTPRINT::cmp_zones> aZones( Zones().begin(), Zones().end() );
789 std::set<ZONE*, FOOTPRINT::cmp_zones> bZones( aLibFP->Zones().begin(), aLibFP->Zones().end() );
790
791 if( aZones.size() != bZones.size() )
792 {
793 diff = true;
794 REPORT( _( "Rule area count differs." ) );
795 }
796 else
797 {
798 for( auto aIt = aZones.begin(), bIt = bZones.begin(); aIt != aZones.end(); aIt++, bIt++ )
799 diff |= zoneNeedsUpdate( *aIt, *bIt, aReporter );
800 }
801
802 return diff;
803}
804
805
807{
808 BOARD* board = m_drcEngine->GetBoard();
809 PROJECT* project = board->GetProject();
810
811 if( !project )
812 {
813 reportAux( _( "No project loaded, skipping library parity tests." ) );
814 return true; // Continue with other tests
815 }
816
817 if( !reportPhase( _( "Loading footprint library table..." ) ) )
818 return false; // DRC cancelled
819
820 std::map<LIB_ID, std::shared_ptr<FOOTPRINT>> libFootprintCache;
821
823 wxString msg;
824 int ii = 0;
825 const int progressDelta = 250;
826
827 if( !reportPhase( _( "Checking board footprints against library..." ) ) )
828 return false;
829
830 for( FOOTPRINT* footprint : board->Footprints() )
831 {
834 {
835 return true; // Continue with other tests
836 }
837
838 if( !reportProgress( ii++, (int) board->Footprints().size(), progressDelta ) )
839 return false; // DRC cancelled
840
841 LIB_ID fpID = footprint->GetFPID();
842 wxString libName = fpID.GetLibNickname();
843 wxString fpName = fpID.GetLibItemName();
844 const LIB_TABLE_ROW* libTableRow = nullptr;
845
846 if( libName.IsEmpty() )
847 {
848 // Not much we can do here
849 continue;
850 }
851
852 try
853 {
854 libTableRow = libTable->FindRow( libName );
855 }
856 catch( const IO_ERROR& )
857 {
858 }
859
860 if( !libTableRow )
861 {
863 {
864 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
865 msg.Printf( _( "The current configuration does not include the footprint library '%s'." ),
866 libName );
867 drcItem->SetErrorMessage( msg );
868 drcItem->SetItems( footprint );
869 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
870 }
871
872 continue;
873 }
874 else if( !libTable->HasLibrary( libName, true ) )
875 {
877 {
878 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
879 msg.Printf( _( "The footprint library '%s' is not enabled in the current configuration." ),
880 libName );
881 drcItem->SetErrorMessage( msg );
882 drcItem->SetItems( footprint );
883 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
884 }
885
886 continue;
887 }
888
889 auto cacheIt = libFootprintCache.find( fpID );
890 std::shared_ptr<FOOTPRINT> libFootprint;
891
892 if( cacheIt != libFootprintCache.end() )
893 {
894 libFootprint = cacheIt->second;
895 }
896 else
897 {
898 try
899 {
900 libFootprint.reset( libTable->FootprintLoad( libName, fpName, true ) );
901
902 if( libFootprint )
903 libFootprintCache[ fpID ] = libFootprint;
904 }
905 catch( const IO_ERROR& )
906 {
907 }
908 }
909
910 if( !libFootprint )
911 {
913 {
914 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_ISSUES );
915 msg.Printf( _( "Footprint '%s' not found in library '%s'." ),
916 fpName,
917 libName );
918 drcItem->SetErrorMessage( msg );
919 drcItem->SetItems( footprint );
920 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
921 }
922 }
923 else if( footprint->FootprintNeedsUpdate( libFootprint.get(), BOARD_ITEM::COMPARE_FLAGS::DRC ) )
924 {
926 {
927 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_LIB_FOOTPRINT_MISMATCH );
928 msg.Printf( _( "Footprint '%s' does not match copy in library '%s'." ),
929 fpName,
930 libName );
931 drcItem->SetErrorMessage( msg );
932 drcItem->SetItems( footprint );
933 reportViolation( drcItem, footprint->GetCenter(), UNDEFINED_LAYER );
934 }
935 }
936 }
937
938 return true;
939}
940
941
942namespace detail
943{
945}
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:108
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:47
VECTOR2I GetFPRelativePosition() const
Definition: board_item.cpp:328
virtual bool IsOnCopperLayer() const
Definition: board_item.h:150
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:290
LSET GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition: board.cpp:775
const FOOTPRINTS & Footprints() const
Definition: board.h:331
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition: board.cpp:579
PROJECT * GetProject() const
Definition: board.h:491
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:99
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:372
virtual const wxString GetDescription() const override
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_ENGINE * m_drcEngine
void reportAux(const wxString &aMsg)
virtual bool reportProgress(size_t aCount, size_t aSize, size_t aDelta=1)
EDA_ANGLE Normalize()
Definition: eda_angle.h:221
double AsDegrees() const
Definition: eda_angle.h:113
const VECTOR2I & GetBezierC2() const
Definition: eda_shape.h:206
SHAPE_POLY_SET & GetPolyShape()
Definition: eda_shape.h:279
bool IsFilled() const
Definition: eda_shape.h:91
SHAPE_T GetShape() const
Definition: eda_shape.h:125
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition: eda_shape.h:167
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition: eda_shape.h:130
wxString SHAPE_T_asString() const
Definition: eda_shape.cpp:89
const VECTOR2I & GetBezierC1() const
Definition: eda_shape.h:203
wxString GetLibDescription() const
Definition: footprint.h:257
ZONE_CONNECTION GetLocalZoneConnection() const
Definition: footprint.h:288
EDA_ANGLE GetOrientation() const
Definition: footprint.h:227
ZONES & Zones()
Definition: footprint.h:212
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:281
EDA_ITEM * Clone() const override
Invoke a function on all children.
Definition: footprint.cpp:2032
std::optional< int > GetLocalClearance() const
Definition: footprint.h:275
std::deque< PAD * > & Pads()
Definition: footprint.h:206
int GetAttributes() const
Definition: footprint.h:290
bool IsFlipped() const
Definition: footprint.h:391
const std::vector< wxString > & GetNetTiePadGroups() const
Definition: footprint.h:339
std::optional< double > GetLocalSolderPasteMarginRatio() const
Definition: footprint.h:284
std::optional< int > GetLocalSolderMaskMargin() const
Definition: footprint.h:278
wxString GetKeywords() const
Definition: footprint.h:260
VECTOR2I GetPosition() const override
Definition: footprint.h:224
DRAWINGS & GraphicalItems()
Definition: footprint.h:209
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.
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:36
static LSET AllLayersMask()
Definition: lset.cpp:701
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:870
std::vector< PCB_LAYER_ID > UniqueLayers() const
Definition: padstack.cpp:897
Definition: pad.h:54
PAD_PROP GetProperty() const
Definition: pad.h:445
bool GetRemoveUnconnected() const
Definition: pad.h:711
LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition: pad.h:439
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:367
std::optional< double > GetLocalSolderPasteMarginRatio() const
Definition: pad.h:472
const VECTOR2I & GetDrillSize() const
Definition: pad.h:307
PAD_ATTRIB GetAttribute() const
Definition: pad.h:442
const wxString & GetNumber() const
Definition: pad.h:134
const VECTOR2I & GetDelta(PCB_LAYER_ID aLayer) const
Definition: pad.h:301
EDA_ANGLE GetThermalSpokeAngle() const
Definition: pad.h:615
double GetRoundRectRadiusRatio(PCB_LAYER_ID aLayer) const
Definition: pad.h:652
PAD_SHAPE GetShape(PCB_LAYER_ID aLayer) const
Definition: pad.h:193
bool GetKeepTopBottom() const
Definition: pad.h:727
std::optional< int > GetLocalClearance() const override
Return any local clearances set in the "classic" (ie: pre-rule) system.
Definition: pad.h:457
const PADSTACK & Padstack() const
Definition: pad.h:323
const VECTOR2I & GetOffset(PCB_LAYER_ID aLayer) const
Definition: pad.h:319
PADSTACK::CUSTOM_SHAPE_ZONE_MODE GetCustomShapeInZoneOpt() const
Definition: pad.h:223
PAD_DRILL_SHAPE GetDrillShape() const
Definition: pad.h:424
int GetChamferPositions(PCB_LAYER_ID aLayer) const
Definition: pad.h:688
std::optional< int > GetLocalSolderPasteMargin() const
Definition: pad.h:466
std::optional< int > GetLocalSolderMaskMargin() const
Definition: pad.h:460
int GetThermalSpokeWidth() const
Definition: pad.h:602
EDA_ANGLE GetFPRelativeOrientation() const
Definition: pad.cpp:883
double GetChamferRectRatio(PCB_LAYER_ID aLayer) const
Definition: pad.h:671
ZONE_CONNECTION GetLocalZoneConnection() const
Definition: pad.h:482
int GetThermalGap() const
Definition: pad.h:631
int GetPadToDieLength() const
Definition: pad.h:455
const VECTOR2I & GetSize(PCB_LAYER_ID aLayer) const
Definition: pad.h:266
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition: pcb_shape.h:79
STROKE_PARAMS GetStroke() const override
Definition: pcb_shape.h:89
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition: pcb_shape.h:69
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:64
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:72
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
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:73
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition: zone.h:721
std::optional< int > GetLocalClearance() const override
Definition: zone.cpp:528
bool GetDoNotAllowVias() const
Definition: zone.h:729
bool GetDoNotAllowPads() const
Definition: zone.h:731
bool GetDoNotAllowTracks() const
Definition: zone.h:730
ISLAND_REMOVAL_MODE GetIslandRemovalMode() const
Definition: zone.h:750
SHAPE_POLY_SET * Outline()
Definition: zone.h:337
long long int GetMinIslandArea() const
Definition: zone.h:753
const wxString & GetZoneName() const
Definition: zone.h:132
int GetMinThickness() const
Definition: zone.h:270
ZONE_CONNECTION GetPadConnection() const
Definition: zone.h:267
int GetHatchThickness() const
Definition: zone.h:285
double GetHatchHoleMinArea() const
Definition: zone.h:300
int GetThermalReliefSpokeWidth() const
Definition: zone.h:214
EDA_ANGLE GetHatchOrientation() const
Definition: zone.h:291
bool GetDoNotAllowFootprints() const
Definition: zone.h:732
ZONE_FILL_MODE GetFillMode() const
Definition: zone.h:193
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition: zone.h:130
bool GetDoNotAllowCopperPour() const
Definition: zone.h:728
int GetHatchGap() const
Definition: zone.h:288
double GetHatchSmoothingValue() const
Definition: zone.h:297
int GetHatchSmoothingLevel() const
Definition: zone.h:294
unsigned int GetCornerRadius() const
Definition: zone.h:667
int GetCornerSmoothingType() const
Definition: zone.h:663
int GetThermalReliefGap() const
Definition: zone.h:203
unsigned GetAssignedPriority() const
Definition: zone.h:120
@ DRCE_LIB_FOOTPRINT_ISSUES
Definition: drc_item.h:82
@ DRCE_LIB_FOOTPRINT_MISMATCH
Definition: drc_item.h:83
#define TEST_PT(a, b, msg)
#define PAD_DESC(pad)
UNITS_PROVIDER g_unitsProvider(pcbIUScale, EDA_UNITS::MILLIMETRES)
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
#define REPORT_MSG(s, p)
#define ITEM_DESC(item)
#define _(s)
#define TEST(a, b)
@ ARC
use RECTANGLE instead of RECT to avoid collision in a Windows header
@ FP_SMD
Definition: footprint.h:76
@ FP_DNP
Definition: footprint.h:83
@ FP_ALLOW_MISSING_COURTYARD
Definition: footprint.h:82
@ FP_EXCLUDE_FROM_POS_FILES
Definition: footprint.h:77
@ FP_BOARD_ONLY
Definition: footprint.h:79
@ FP_EXCLUDE_FROM_BOM
Definition: footprint.h:78
@ FP_THROUGH_HOLE
Definition: footprint.h:75
@ FP_ALLOW_SOLDERMASK_BRIDGES
Definition: footprint.h:81
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
constexpr int mmToIU(double mm) const
Definition: base_units.h:88