KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_erc_footprint_paths.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
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
22
23#include <algorithm>
24#include <set>
25#include <advanced_config.h>
26#include <api/schematic/schematic_rules.pb.h>
28#include <erc/erc.h>
29#include <erc/erc_exclusion.h>
30#include <json_common.h>
31#include <kiface_ids.h>
32#include <kiway.h>
33#include <lib_symbol.h>
34#include <locale_io.h>
35#include <sch_marker.h>
36#include <sch_sheet.h>
37#include <sch_symbol.h>
38#include <schematic.h>
40#include <scoped_set_reset.h>
41
42BOOST_AUTO_TEST_CASE( ERCDuplicateUnitsDoNotHideMissingUnits )
43{
44 LOCALE_IO locale;
45 auto& enabled = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() ).m_ConnectivityEngine;
46 SCOPED_SET_RESET restore( enabled, enabled );
47 SETTINGS_MANAGER settings;
48 std::unique_ptr<SCHEMATIC> schematic;
49 KI_TEST::LoadSchematic( settings, "issue1768/issue1768", schematic );
50 const SCH_SHEET_PATH path = schematic->Hierarchy().front();
51 SCH_SCREEN& screen = *path.LastScreen();
52 std::vector<SCH_SYMBOL*> symbols;
53
54 for( SCH_ITEM* item : screen.Items().OfType( SCH_SYMBOL_T ) )
55 {
56 auto* symbol = static_cast<SCH_SYMBOL*>( item );
57
58 if( symbol->IsMultiUnit() )
59 {
60 symbol->SetRef( &path, "ERC_DUPLICATE_UNITS" );
61 symbol->SetUnitSelection( &path, 1 );
62 symbols.push_back( symbol );
63 }
64 }
65
66 BOOST_REQUIRE( !symbols.empty() );
67 BOOST_REQUIRE_EQUAL( symbols.size(), symbols.front()->GetLibSymbolRef()->GetUnitCount() );
68
69 for( const SCH_SYMBOL* symbol : symbols )
70 BOOST_REQUIRE( symbol->GetLibId() == symbols.front()->GetLibId() );
71
72 schematic->ErcSettings().SetSeverity( ERCE_MISSING_UNIT, RPT_SEVERITY_WARNING );
73
74 for( bool backend : { true, false } )
75 {
76 BOOST_TEST_CONTEXT( "backend=" << backend )
77 {
78 enabled = backend;
79 schematic->RebuildConnectivity();
80 ERC_TESTER tester( schematic.get() );
81 tester.TestMissingUnits();
82 size_t missing = 0;
83 std::vector<SCH_MARKER*> markers;
84
85 for( SCH_ITEM* item : screen.Items().OfType( SCH_MARKER_T ) )
86 {
87 auto* marker = static_cast<SCH_MARKER*>( item );
88
89 if( marker->GetRCItem()->GetErrorCode() == ERCE_MISSING_UNIT )
90 ++missing;
91
92 markers.push_back( marker );
93 }
94
95 BOOST_CHECK_EQUAL( missing, 1 );
96
97 for( SCH_MARKER* marker : markers )
98 screen.DeleteItem( marker );
99 }
100 }
101}
102
103
104BOOST_AUTO_TEST_CASE( ERCMultiUnitFootprintsBindSharedInstancePaths )
105{
106 LOCALE_IO locale;
107 auto& enabled = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() ).m_ConnectivityEngine;
108 SCOPED_SET_RESET restore( enabled, enabled );
109 SETTINGS_MANAGER settings;
110 std::unique_ptr<SCHEMATIC> schematic;
111 KI_TEST::LoadSchematic( settings, "issue23840/BusAndVectors", schematic );
112 std::vector<SCH_SHEET_PATH> paths;
113
114 for( const SCH_SHEET_PATH& path : schematic->Hierarchy() )
115 {
116 if( path.LastScreen()->GetFileName().EndsWith( "LEDs.kicad_sch" ) )
117 paths.push_back( path );
118 }
119
120 BOOST_REQUIRE_EQUAL( paths.size(), 2 );
121 SCH_SCREEN* screen = paths[0].LastScreen();
122 BOOST_REQUIRE( screen == paths[1].LastScreen() );
123 SETTINGS_MANAGER sourceSettings;
124 std::unique_ptr<SCHEMATIC> source;
125 KI_TEST::LoadSchematic( sourceSettings, "issue1768/issue1768", source );
126 SCH_SYMBOL* first = nullptr;
127 SCH_SYMBOL* second = nullptr;
128 SCH_SHEET_PATH root = schematic->Hierarchy().front();
129
130 for( SCH_ITEM* item : source->RootScreen()->Items().OfType( SCH_SYMBOL_T ) )
131 {
132 auto* symbol = static_cast<SCH_SYMBOL*>( item );
133
134 if( !symbol->IsMultiUnit() )
135 continue;
136
137 if( !first && symbol->GetUnit() == 1 )
138 {
139 first = static_cast<SCH_SYMBOL*>( symbol->Duplicate( false ) );
140 root.LastScreen()->Append( first );
141 }
142 else if( !second && symbol->GetUnit() == 2 )
143 {
144 second = static_cast<SCH_SYMBOL*>( symbol->Duplicate( false ) );
145 screen->Append( second );
146 }
147 }
148
149 BOOST_REQUIRE( first );
150 BOOST_REQUIRE( second );
151 BOOST_REQUIRE( first->m_Uuid != second->m_Uuid );
152 first->SetRef( &root, "ERC_FP" );
153 first->SetUnitSelection( &root, 1 );
154 first->SetFootprintFieldText( "Acceptance:First" );
155 second->SetFootprintFieldText( "Acceptance:Second" );
156
157 for( const SCH_SHEET_PATH& path : paths )
158 {
159 second->SetRef( &path, "ERC_FP" );
160 second->SetUnitSelection( &path, 2 );
161 }
162
163 for( bool backend : { true, false } )
164 {
165 enabled = backend;
166 schematic->RebuildConnectivity();
167
168 for( const SCH_SHEET_PATH& displayed : paths )
169 {
170 BOOST_TEST_CONTEXT( "backend=" << backend << "; displayed=" << displayed.GetPageNumber() )
171 {
172 schematic->SetCurrentSheet( displayed );
173 ERC_TESTER tester( schematic.get() );
175 std::vector<SCH_MARKER*> markers;
176 std::set<KIID_PATH> seen;
177
178 for( SCH_ITEM* item : screen->Items().OfType( SCH_MARKER_T ) )
179 {
180 auto* marker = static_cast<SCH_MARKER*>( item );
181 const auto error = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
182
183 if( error->GetErrorCode() != ERCE_DIFFERENT_UNIT_FP
184 || error->GetMainItemID() != first->m_Uuid || error->GetAuxItemID() != second->m_Uuid )
185 {
186 continue;
187 }
188
189 markers.push_back( marker );
190 BOOST_CHECK( marker->GetPosition() == second->GetPosition() );
191 BOOST_CHECK( error->IsSheetSpecific() );
192 BOOST_CHECK( error->MainItemHasSheetPath() );
193 BOOST_CHECK( error->AuxItemHasSheetPath() );
194
195 if( error->MainItemHasSheetPath() )
196 BOOST_CHECK( error->GetMainItemSheetPath().PathRef() == root.PathRef() );
197
198 if( error->IsSheetSpecific() && error->AuxItemHasSheetPath() )
199 {
200 BOOST_CHECK( error->GetAuxItemSheetPath().PathRef()
201 == error->GetSpecificSheetPath().PathRef() );
202 BOOST_CHECK( seen.insert( error->GetSpecificSheetPath().PathRef() ).second );
203 }
204 }
205
206 BOOST_CHECK_EQUAL( markers.size(), 2 );
207 const std::set<KIID_PATH> expected{ paths[0].PathRef(), paths[1].PathRef() };
208 BOOST_CHECK( seen == expected );
209
210 for( SCH_MARKER* marker : markers )
211 screen->DeleteItem( marker );
212 }
213 }
214 }
215
216 const auto targetMarkers = [&]
217 {
218 std::vector<SCH_MARKER*> result;
219
220 for( SCH_ITEM* item : screen->Items().OfType( SCH_MARKER_T ) )
221 {
222 auto* marker = static_cast<SCH_MARKER*>( item );
223 const auto error = marker->GetRCItem();
224
225 if( error->GetErrorCode() == ERCE_DIFFERENT_UNIT_FP
226 && error->GetMainItemID() == first->m_Uuid && error->GetAuxItemID() == second->m_Uuid )
227 {
228 result.push_back( marker );
229 }
230 }
231
232 return result;
233 };
234 auto& exclusions = schematic->ErcSettings().m_ErcExclusions;
235
236 for( bool legacy : { false, true } )
237 {
238 exclusions.clear();
239 ERC_TESTER tester( schematic.get() );
241 BOOST_REQUIRE_EQUAL( targetMarkers().size(), 2 );
242
243 for( SCH_MARKER* marker : targetMarkers() )
244 {
245 const auto error = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
246 BOOST_REQUIRE( error->IsSheetSpecific() );
247
248 if( error->GetSpecificSheetPath().PathRef() != paths[0].PathRef() )
249 continue;
250
251 auto proto = ERC_EXCLUSION::FromMarker( *marker ).ToProto();
252
253 if( legacy )
254 {
255 proto.mutable_marker()->clear_sheet_specific_path();
256 proto.mutable_marker()->clear_main_item_sheet_path();
257 proto.mutable_marker()->clear_aux_item_sheet_path();
258 }
259
260 auto exclusion = ERC_EXCLUSION::FromProto( proto );
261 exclusion.SetComment( "Retained multi-unit footprint exclusion" );
262 const nlohmann::json saved = exclusion;
263 exclusions.insert( saved.get<ERC_EXCLUSION>() );
264 }
265
266 BOOST_REQUIRE_EQUAL( exclusions.size(), 1 );
267 const auto savedExclusions = exclusions;
268
269 for( SCH_MARKER* marker : targetMarkers() )
270 screen->DeleteItem( marker );
271
272 for( bool backend : { true, false } )
273 {
274 BOOST_TEST_CONTEXT( "legacy exclusion=" << legacy << "; backend=" << backend )
275 {
276 enabled = backend;
277 exclusions = savedExclusions;
278 schematic->RebuildConnectivity();
279 ERC_TESTER rescan( schematic.get() );
281 schematic->ResolveERCExclusionsPostUpdate();
282 std::vector<SCH_MARKER*> rootMarkers;
283
284 for( SCH_ITEM* item : root.LastScreen()->Items().OfType( SCH_MARKER_T ) )
285 {
286 auto* marker = static_cast<SCH_MARKER*>( item );
287 const auto error = marker->GetRCItem();
288
289 if( error->GetErrorCode() == ERCE_DIFFERENT_UNIT_FP
290 && error->GetMainItemID() == first->m_Uuid && error->GetAuxItemID() == second->m_Uuid )
291 {
292 rootMarkers.push_back( marker );
293 }
294 }
295
296 BOOST_CHECK( rootMarkers.empty() );
297
298 for( SCH_MARKER* marker : rootMarkers )
299 root.LastScreen()->DeleteItem( marker );
300
301 BOOST_CHECK_EQUAL( targetMarkers().size(), 2 );
302 size_t excluded = 0;
303
304 for( SCH_MARKER* marker : targetMarkers() )
305 {
306 const auto error = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
307 BOOST_CHECK( error->IsSheetSpecific() );
308
309 if( !error->IsSheetSpecific() )
310 continue;
311
312 const bool expected = legacy || error->GetSpecificSheetPath().PathRef() == paths[0].PathRef();
313 BOOST_CHECK_EQUAL( marker->IsExcluded(), expected );
314 BOOST_CHECK_EQUAL( marker->GetComment(), expected
315 ? wxString( "Retained multi-unit footprint exclusion" ) : wxString() );
316 excluded += marker->IsExcluded();
317 }
318
319 BOOST_CHECK_EQUAL( excluded, legacy ? 2 : 1 );
320
321 for( SCH_MARKER* marker : targetMarkers() )
322 screen->DeleteItem( marker );
323 }
324 }
325 }
326
327 BOOST_REQUIRE( root.LastScreen()->Remove( first, false ) );
328 BOOST_REQUIRE( screen->Remove( second, false ) );
329 screen->Append( first, false );
330 root.LastScreen()->Append( second, false );
331 second->SetRef( &root, "ERC_FP" );
332 second->SetUnitSelection( &root, 2 );
333 root.SetPageNumber( "100" );
334
335 for( const SCH_SHEET_PATH& path : paths )
336 {
337 first->SetRef( &path, "ERC_FP" );
338 first->SetUnitSelection( &path, 1 );
339 }
340
341 const KIID_PATH canonical = std::min( paths[0].PathRef(), paths[1].PathRef() );
342
343 for( bool variable : { false, true } )
344 {
345 first->SetFootprintFieldText( variable ? "Acceptance:${#}" : "Acceptance:First" );
346
347 for( bool backend : { true, false } )
348 {
349 enabled = backend;
350 exclusions.clear();
351 nlohmann::json saved;
352
353 for( bool reverse : { false, true } )
354 {
355 BOOST_TEST_CONTEXT( "variable=" << variable << "; backend=" << backend << "; reverse=" << reverse )
356 {
357 paths[0].SetPageNumber( reverse ? "3" : "2" );
358 paths[1].SetPageNumber( reverse ? "2" : "3" );
359
360 if( backend && reverse )
361 schematic->Connectivity().Recalculate( *schematic );
362 else
363 schematic->RebuildConnectivity();
364
365 ERC_TESTER tester( schematic.get() );
367
368 if( !variable && reverse )
369 {
370 exclusions.insert( saved.get<ERC_EXCLUSION>() );
371 schematic->ResolveERCExclusionsPostUpdate();
372 }
373
374 size_t count = 0;
375
376 for( SCH_ITEM* item : root.LastScreen()->Items().OfType( SCH_MARKER_T ) )
377 {
378 auto* marker = static_cast<SCH_MARKER*>( item );
379 const auto error = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
380
381 if( error->GetErrorCode() != ERCE_DIFFERENT_UNIT_FP
382 || error->GetMainItemID() != first->m_Uuid || error->GetAuxItemID() != second->m_Uuid )
383 {
384 continue;
385 }
386
387 ++count;
388 BOOST_REQUIRE( error->MainItemHasSheetPath() );
389 const SCH_SHEET_PATH& witness = error->GetMainItemSheetPath();
390
391 if( variable )
392 {
393 BOOST_CHECK( first->GetFootprintFieldText( &witness, RESOLVED )
394 == "Acceptance:2" );
395 }
396 else
397 {
398 BOOST_CHECK( witness.PathRef() == canonical );
399
400 if( reverse )
401 {
402 BOOST_CHECK( marker->IsExcluded() );
403 BOOST_CHECK_EQUAL( marker->GetComment(), wxString( "Page order retained" ) );
404 }
405 else
406 {
407 auto exclusion = ERC_EXCLUSION::FromMarker( *marker );
408 exclusion.SetComment( "Page order retained" );
409 saved = exclusion;
410 }
411 }
412 }
413
414 BOOST_CHECK_EQUAL( count, 1 );
415
416 for( SCH_SCREEN* current : { root.LastScreen(), screen } )
417 {
418 std::vector<SCH_MARKER*> markers;
419
420 for( SCH_ITEM* item : current->Items().OfType( SCH_MARKER_T ) )
421 markers.push_back( static_cast<SCH_MARKER*>( item ) );
422
423 for( SCH_MARKER* marker : markers )
424 current->DeleteItem( marker );
425 }
426 }
427 }
428 }
429 }
430}
431
432
433BOOST_AUTO_TEST_CASE( ERCFootprintLinksBindSharedInstancePaths )
434{
435 struct FOOTPRINT_FACE : KIFACE
436 {
437 bool OnKifaceStart( PGM_BASE*, int, KIWAY* ) override { return true; }
438 void OnKifaceEnd() override {}
439 void Reset() override {}
440 wxWindow* CreateKiWindow( wxWindow*, int, KIWAY*, int ) override { return nullptr; }
441 void GetActions( std::vector<TOOL_ACTION*>& ) const override {}
442
443 static int Check( const wxString& aFootprint, PROJECT* )
444 {
445 if( aFootprint == "NoLibrary:2" )
447
448 if( aFootprint == "Disabled:2" )
450
451 if( aFootprint == "Missing:2" )
453
454 return 0;
455 }
456
457 void* IfaceOrAddress( int aId ) override
458 {
459 BOOST_REQUIRE_EQUAL( aId, KIFACE_TEST_FOOTPRINT_LINK );
460 return reinterpret_cast<void*>( &Check );
461 }
462 } face;
463
464 LOCALE_IO locale;
465 auto& enabled = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() ).m_ConnectivityEngine;
466 SCOPED_SET_RESET restore( enabled, enabled );
467 SETTINGS_MANAGER settings;
468 std::unique_ptr<SCHEMATIC> schematic;
469 KI_TEST::LoadSchematic( settings, "legacy_hierarchy/legacy_hierarchy", schematic );
470 std::vector<SCH_SHEET_PATH> paths;
471
472 for( const SCH_SHEET_PATH& path : schematic->Hierarchy() )
473 {
474 if( path.LastScreen()->GetFileName().EndsWith( "ampli_ht.kicad_sch" ) )
475 paths.push_back( path );
476 }
477
478 BOOST_REQUIRE_EQUAL( paths.size(), 2 );
479 SCH_SCREEN* screen = paths[0].LastScreen();
480 BOOST_REQUIRE( screen == paths[1].LastScreen() );
481 paths[0].SetPageNumber( "2" );
482 paths[1].SetPageNumber( "3" );
483 SCH_SYMBOL* symbol = nullptr;
484
485 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
486 {
487 auto* candidate = static_cast<SCH_SYMBOL*>( item );
488
489 if( candidate->GetLibSymbolRef() && !candidate->GetLibSymbolRef()->IsPower() )
490 {
491 symbol = candidate;
492 break;
493 }
494 }
495
496 BOOST_REQUIRE( symbol );
497
498 for( const wxString& footprint : { wxString( "NoLibrary:2" ), wxString( "NoLibrary:${#}" ),
499 wxString( "Disabled:${#}" ), wxString( "Missing:${#}" ),
500 wxString( "Present:${#}" ) } )
501 {
502 symbol->SetFootprintFieldText( footprint );
503 const bool bothInstances = footprint == "NoLibrary:2";
504 const size_t expected = bothInstances ? 2 : footprint == "Present:${#}" ? 0 : 1;
505
506 for( bool backend : { true, false } )
507 {
508 enabled = backend;
509 schematic->RebuildConnectivity();
510
511 for( const SCH_SHEET_PATH& displayed : paths )
512 {
513 BOOST_TEST_CONTEXT( "footprint=" << footprint << "; backend=" << backend
514 << "; displayed=" << displayed.GetPageNumber() )
515 {
516 schematic->SetCurrentSheet( displayed );
517 ERC_TESTER tester( schematic.get() );
518 BOOST_CHECK_EQUAL( tester.TestFootprintLinkIssues( &face, &schematic->Project() ), expected );
519 size_t count = 0;
520 std::set<KIID_PATH> seen;
521 std::vector<SCH_MARKER*> markers;
522
523 for( SCH_ITEM* item : screen->Items().OfType( SCH_MARKER_T ) )
524 {
525 auto* marker = static_cast<SCH_MARKER*>( item );
526 markers.push_back( marker );
527 const auto error = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
528
529 if( error->GetErrorCode() != ERCE_FOOTPRINT_LINK_ISSUES
530 || error->GetMainItemID() != symbol->m_Uuid )
531 {
532 continue;
533 }
534
535 ++count;
536 BOOST_CHECK( marker->GetPosition() == symbol->GetPosition() );
537 BOOST_CHECK( error->IsSheetSpecific() );
538 BOOST_CHECK( error->MainItemHasSheetPath() );
539 BOOST_CHECK( !error->AuxItemHasSheetPath() );
540
541 if( error->IsSheetSpecific() && error->MainItemHasSheetPath() )
542 {
543 const KIID_PATH& path = error->GetSpecificSheetPath().PathRef();
544 BOOST_CHECK( error->GetMainItemSheetPath().PathRef() == path );
545 BOOST_CHECK( seen.insert( path ).second );
546 BOOST_CHECK( path == paths[0].PathRef()
547 || ( bothInstances && path == paths[1].PathRef() ) );
548 }
549 }
550
551 BOOST_CHECK_EQUAL( count, expected );
552 BOOST_CHECK_EQUAL( seen.size(), expected );
553
554 for( SCH_MARKER* marker : markers )
555 screen->DeleteItem( marker );
556 }
557 }
558 }
559 }
560
561 symbol->SetFootprintFieldText( "NoLibrary:2" );
562 const auto targetMarkers = [&]
563 {
564 std::vector<SCH_MARKER*> result;
565
566 for( SCH_ITEM* item : screen->Items().OfType( SCH_MARKER_T ) )
567 {
568 auto* marker = static_cast<SCH_MARKER*>( item );
569
570 if( marker->GetRCItem()->GetErrorCode() == ERCE_FOOTPRINT_LINK_ISSUES
571 && marker->GetRCItem()->GetMainItemID() == symbol->m_Uuid )
572 {
573 result.push_back( marker );
574 }
575 }
576
577 return result;
578 };
579 auto& exclusions = schematic->ErcSettings().m_ErcExclusions;
580
581 for( const SCH_SHEET_PATH& target : paths )
582 {
583 for( bool pathlessExclusion : { false, true } )
584 {
585 enabled = false;
586 exclusions.clear();
587 schematic->RebuildConnectivity();
588 ERC_TESTER tester( schematic.get() );
589 tester.TestFootprintLinkIssues( &face, &schematic->Project() );
590 BOOST_REQUIRE_EQUAL( targetMarkers().size(), 2 );
591
592 for( SCH_MARKER* marker : targetMarkers() )
593 {
594 const auto error = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
595 BOOST_REQUIRE( error->IsSheetSpecific() );
596
597 if( error->GetSpecificSheetPath().PathRef() != target.PathRef() )
598 continue;
599
600 auto proto = ERC_EXCLUSION::FromMarker( *marker ).ToProto();
601
602 if( pathlessExclusion )
603 {
604 proto.mutable_marker()->clear_sheet_specific_path();
605 proto.mutable_marker()->clear_main_item_sheet_path();
606 }
607
608 auto exclusion = ERC_EXCLUSION::FromProto( proto );
609 exclusion.SetComment( "Retained footprint link exclusion" );
610 const nlohmann::json saved = exclusion;
611 exclusions.insert( saved.get<ERC_EXCLUSION>() );
612 }
613
614 BOOST_REQUIRE_EQUAL( exclusions.size(), 1 );
615 const auto savedExclusions = exclusions;
616
617 for( SCH_MARKER* marker : targetMarkers() )
618 screen->DeleteItem( marker );
619
620 for( bool backend : { true, false } )
621 {
622 BOOST_TEST_CONTEXT( "pathless exclusion=" << pathlessExclusion << "; backend=" << backend
623 << "; target=" << target.GetPageNumber() )
624 {
625 enabled = backend;
626 exclusions = savedExclusions;
627 schematic->RebuildConnectivity();
628 ERC_TESTER rescan( schematic.get() );
629 rescan.TestFootprintLinkIssues( &face, &schematic->Project() );
630 schematic->ResolveERCExclusionsPostUpdate();
631 BOOST_CHECK_EQUAL( targetMarkers().size(), 2 );
632 size_t excluded = 0;
633
634 for( SCH_MARKER* marker : targetMarkers() )
635 {
636 const auto error = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
637 BOOST_CHECK( error->IsSheetSpecific() );
638 BOOST_CHECK( error->MainItemHasSheetPath() );
639
640 if( !error->IsSheetSpecific() )
641 continue;
642
643 const bool expected = pathlessExclusion
644 || error->GetSpecificSheetPath().PathRef() == target.PathRef();
645 BOOST_CHECK_EQUAL( marker->IsExcluded(), expected );
646 BOOST_CHECK_EQUAL( marker->GetComment(), expected
647 ? wxString( "Retained footprint link exclusion" ) : wxString() );
648 excluded += marker->IsExcluded();
649 }
650
651 BOOST_CHECK_EQUAL( excluded, pathlessExclusion ? 2 : 1 );
652
653 for( SCH_MARKER* marker : targetMarkers() )
654 screen->DeleteItem( marker );
655 }
656 }
657 }
658 }
659
660 symbol->SetFootprintFieldText( "NoLibrary:${#}" );
661
662 for( const auto& [backend, legacyString] : { std::pair{ true, false }, std::pair{ false, false },
663 std::pair{ true, true }, std::pair{ false, true } } )
664 {
665 BOOST_TEST_CONTEXT( "deleted exclusion instance; backend=" << backend << "; legacy=" << legacyString )
666 {
667 enabled = backend;
668 exclusions.clear();
669 schematic->SetCurrentSheet( schematic->Hierarchy().front() );
670 schematic->RebuildConnectivity();
671 ERC_TESTER tester( schematic.get() );
672 tester.TestFootprintLinkIssues( &face, &schematic->Project() );
673 BOOST_REQUIRE_EQUAL( targetMarkers().size(), 1 );
674 SCH_MARKER* original = targetMarkers().front();
675 const auto error = std::static_pointer_cast<ERC_ITEM>( original->GetRCItem() );
676 BOOST_REQUIRE( error->IsSheetSpecific() );
677 BOOST_REQUIRE( error->GetSpecificSheetPath().PathRef() == paths[0].PathRef() );
678 original->SetExcluded( true, "Keep the removed instance exclusion" );
679 schematic->RecordERCExclusions();
680 BOOST_REQUIRE_EQUAL( exclusions.size(), 1 );
681 const nlohmann::json saved = *exclusions.begin();
682 const wxString originalComment = original->GetComment();
683 const wxString legacyData = wxString::Format( wxS( "%s|%d|%d|%s|%s|%s|%s|" ),
684 error->GetSettingsKey(), original->GetPosition().x, original->GetPosition().y,
685 error->GetMainItemID().AsString(), niluuid.AsString(),
686 error->GetSpecificSheetPath().PathRef().AsString(),
687 error->GetMainItemSheetPath().PathRef().AsString() );
688 screen->DeleteItem( original );
689 exclusions.clear();
690
691 if( legacyString )
692 schematic->ErcSettings().m_ErcExclusionsLegacy.emplace( legacyData, originalComment );
693 else
694 exclusions.insert( saved.get<ERC_EXCLUSION>() );
695
696 SCH_SHEET* removed = paths[0].Last();
697 schematic->RootScreen()->Remove( removed );
698 std::unique_ptr<SCH_SHEET> undoOwnedSheet( removed );
699 schematic->RefreshHierarchy();
700 BOOST_REQUIRE( !schematic->Hierarchy().GetSheetPathByKIIDPath( paths[0].PathRef() ) );
701 BOOST_REQUIRE( schematic->Hierarchy().GetSheetPathByKIIDPath( paths[1].PathRef() ) );
702 BOOST_REQUIRE( schematic->ResolveItem( symbol->m_Uuid, nullptr, true ) == symbol );
703
704 schematic->SetCurrentSheet( paths[1] );
705 schematic->ResolveERCExclusionsPostUpdate();
706 BOOST_CHECK( schematic->ErcSettings().m_ErcExclusionsLegacy.empty() );
707
708 for( SCH_MARKER* marker : targetMarkers() )
709 {
710 const auto retained = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
711 BOOST_CHECK( retained->IsSheetSpecific() );
712
713 if( retained->IsSheetSpecific() )
714 BOOST_CHECK( retained->GetSpecificSheetPath().PathRef() == paths[0].PathRef() );
715 }
716
717 BOOST_CHECK_EQUAL( exclusions.size(), 1 );
718
719 if( exclusions.size() == 1 )
720 BOOST_CHECK( nlohmann::json( *exclusions.begin() ) == saved );
721
722 BOOST_CHECK_EQUAL( schematic->GetUnresolvedERCExclusionCount(), 1 );
723
724 if( !legacyString )
725 {
726 schematic->ClearUnresolvedERCExclusions( ERCE_PIN_NOT_CONNECTED );
727 BOOST_CHECK_EQUAL( schematic->GetUnresolvedERCExclusionCount(), 1 );
728 schematic->ClearUnresolvedERCExclusions();
729 schematic->RecordERCExclusions();
730 schematic->ResolveERCExclusionsPostUpdate();
731 BOOST_CHECK( exclusions.empty() );
732 BOOST_CHECK_EQUAL( schematic->GetUnresolvedERCExclusionCount(), 0 );
733 exclusions.insert( saved.get<ERC_EXCLUSION>() );
734 schematic->ResolveERCExclusionsPostUpdate();
735 }
736
737 schematic->RootScreen()->Append( undoOwnedSheet.release() );
738 schematic->RefreshHierarchy();
739 schematic->SetCurrentSheet( schematic->Hierarchy().front() );
740 schematic->RecordERCExclusions();
741 BOOST_CHECK_EQUAL( exclusions.size(), 1 );
742
743 if( exclusions.size() == 1 )
744 BOOST_CHECK( nlohmann::json( *exclusions.begin() ) == saved );
745
746 for( SCH_MARKER* marker : targetMarkers() )
747 screen->DeleteItem( marker );
748
749 schematic->RebuildConnectivity();
750 ERC_TESTER restored( schematic.get() );
751 restored.TestFootprintLinkIssues( &face, &schematic->Project() );
752 exclusions.clear();
753 schematic->ResolveERCExclusionsPostUpdate();
754 BOOST_CHECK_EQUAL( targetMarkers().size(), 1 );
755 BOOST_CHECK_EQUAL( schematic->GetUnresolvedERCExclusionCount(), 0 );
756
757 for( SCH_MARKER* marker : targetMarkers() )
758 {
759 const auto restoredError = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
760 BOOST_CHECK( marker->IsExcluded() );
761 BOOST_CHECK_EQUAL( marker->GetComment(), wxString( "Keep the removed instance exclusion" ) );
762 BOOST_CHECK( restoredError->IsSheetSpecific() );
763
764 if( restoredError->IsSheetSpecific() )
765 BOOST_CHECK( restoredError->GetSpecificSheetPath().PathRef() == paths[0].PathRef() );
766
767 marker->SetExcluded( false );
768 schematic->RecordERCExclusions();
769 BOOST_CHECK( exclusions.empty() );
770 screen->DeleteItem( marker );
771 }
772 }
773 }
774}
775
776
777BOOST_AUTO_TEST_CASE( ERCFootprintFiltersBindSharedInstancePaths )
778{
779 LOCALE_IO locale;
780 auto& enabled = const_cast<ADVANCED_CFG&>( ADVANCED_CFG::GetCfg() ).m_ConnectivityEngine;
781 SCOPED_SET_RESET restore( enabled, true );
782
783 SETTINGS_MANAGER settings;
784 std::unique_ptr<SCHEMATIC> schematic;
785 KI_TEST::LoadSchematic( settings, "legacy_hierarchy/legacy_hierarchy", schematic );
786 std::vector<SCH_SHEET_PATH> paths;
787
788 for( const SCH_SHEET_PATH& path : schematic->Hierarchy() )
789 {
790 if( path.LastScreen()->GetFileName().EndsWith( "ampli_ht.kicad_sch" ) )
791 paths.push_back( path );
792 }
793
794 BOOST_REQUIRE_EQUAL( paths.size(), 2 );
795 SCH_SCREEN* screen = paths[0].LastScreen();
796 BOOST_REQUIRE( screen == paths[1].LastScreen() );
797 BOOST_REQUIRE( paths[0].GetPageNumber() != paths[1].GetPageNumber() );
798 SCH_SYMBOL* symbol = nullptr;
799
800 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
801 {
802 auto* candidate = static_cast<SCH_SYMBOL*>( item );
803
804 if( candidate->GetLibSymbolRef() && !candidate->GetLibSymbolRef()->IsPower() )
805 {
806 symbol = candidate;
807 break;
808 }
809 }
810
811 BOOST_REQUIRE( symbol );
812 symbol->SetFootprintFieldText( "Acceptance:${#}" );
813
814 const wxString firstPage = paths[0].GetPageNumber();
815 const wxString secondPage = paths[1].GetPageNumber();
816 wxArrayString pageFilters;
817 pageFilters.Add( wxString( "Acceptance:" ) + firstPage );
818 symbol->GetLibSymbolRef()->SetFPFilters( pageFilters );
819 auto collectPaths = [&]()
820 {
821 ERC_TESTER tester( schematic.get() );
822 tester.TestFootprintFilters();
823 std::set<KIID_PATH> found;
824 std::vector<SCH_MARKER*> markers;
825
826 for( SCH_ITEM* item : screen->Items().OfType( SCH_MARKER_T ) )
827 {
828 auto* marker = static_cast<SCH_MARKER*>( item );
829 const auto error = std::static_pointer_cast<ERC_ITEM>( marker->GetRCItem() );
830
831 if( error->GetErrorCode() == ERCE_FOOTPRINT_FILTERS && error->GetMainItemID() == symbol->m_Uuid )
832 {
833 BOOST_REQUIRE( error->IsSheetSpecific() );
834 BOOST_CHECK( error->MainItemHasSheetPath() );
835 BOOST_CHECK( error->GetMainItemSheetPath().PathRef() == error->GetSpecificSheetPath().PathRef() );
836 BOOST_CHECK( found.insert( error->GetSpecificSheetPath().PathRef() ).second );
837 }
838
839 markers.push_back( marker );
840 }
841
842 for( SCH_MARKER* marker : markers )
843 screen->DeleteItem( marker );
844
845 return found;
846 };
847
848 for( bool backend : { false, true } )
849 {
850 BOOST_TEST_CONTEXT( "backend=" << backend )
851 {
852 enabled = backend;
853 schematic->RebuildConnectivity();
854 const auto beforePages = collectPaths();
855 BOOST_REQUIRE( beforePages == std::set<KIID_PATH>{ paths[1].PathRef() } );
856 paths[0].SetPageNumber( secondPage );
857 paths[1].SetPageNumber( firstPage );
858
859 // Only the engine refreshes incrementally, and that refresh must see page renumbering
860 if( backend )
861 schematic->Connectivity().Recalculate( *schematic );
862
863 const auto afterPages = collectPaths();
864 BOOST_REQUIRE( afterPages == std::set<KIID_PATH>{ paths[0].PathRef() } );
865 schematic->RebuildConnectivity();
866 BOOST_CHECK( collectPaths() == afterPages );
867 paths[0].SetPageNumber( firstPage );
868 paths[1].SetPageNumber( secondPage );
869
870 if( backend )
871 schematic->Connectivity().Recalculate( *schematic );
872
873 BOOST_CHECK( collectPaths() == beforePages );
874 schematic->SetCurrentSheet( schematic->Hierarchy().front() );
875 SCH_SHEET* removed = paths[1].Last();
876 schematic->RootScreen()->Remove( removed );
877 std::unique_ptr<SCH_SHEET> undoOwnedSheet( removed );
878 schematic->RefreshHierarchy();
879 BOOST_CHECK( collectPaths().empty() );
880 schematic->RootScreen()->Append( undoOwnedSheet.release() );
881 schematic->RefreshHierarchy();
882 BOOST_CHECK( collectPaths() == beforePages );
883 }
884 }
885}
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
const KIID m_Uuid
Definition eda_item.h:597
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
Container for an ERC exclusion, which is a SCH_MARKER plus an optional comment.
const kiapi::schematic::ErcExclusion & ToProto() const
static ERC_EXCLUSION FromMarker(const SCH_MARKER &aMarker)
static ERC_EXCLUSION FromProto(const kiapi::schematic::ErcExclusion &aMessage)
Runs the electrical rules checks and adds a SCH_MARKER for each violation.
Definition erc.h:60
int TestFootprintLinkIssues(KIFACE *aCvPcb, PROJECT *aProject)
Test footprint links against the current footprint libraries.
Definition erc.cpp:2883
int TestFootprintFilters()
Test symbols to ensure that assigned footprint passes any given footprint filters.
Definition erc.cpp:2944
int TestMissingUnits()
Test for uninstantiated units of multi unit symbols.
Definition erc.cpp:970
int TestMultiunitFootprints()
Test if all units of each multiunit symbol have the same footprint assigned.
Definition erc.cpp:923
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:340
void SetFPFilters(const wxArrayString &aFilters)
Definition lib_symbol.h:245
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
std::shared_ptr< RC_ITEM > GetRCItem() const
void SetExcluded(bool aExcluded, const wxString &aComment=wxEmptyString)
Definition marker_base.h:90
wxString GetComment() const
Definition marker_base.h:96
Container for data for KiCad programs.
Definition pgm_base.h:101
Container for project specific data.
Definition project.h:63
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:170
int GetUnit() const
Definition sch_item.h:242
VECTOR2I GetPosition() const override
Definition sch_marker.h:104
void Append(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
bool Remove(SCH_ITEM *aItem, bool aUpdateLibSymbol=true)
Remove aItem from the schematic associated with this screen.
void DeleteItem(SCH_ITEM *aItem)
Remove aItem from the linked list and deletes the object.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
SCH_SCREEN * LastScreen()
void SetPageNumber(const wxString &aPageNumber)
Set the sheet instance user definable page number.
const KIID_PATH & PathRef() const
Borrow the cached path until this sheet path is modified or destroyed.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
Schematic symbol object.
Definition sch_symbol.h:74
const wxString GetFootprintFieldText(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString) const
void SetRef(const SCH_SHEET_PATH *aSheet, const wxString &aReference)
Set the reference for the given sheet path for this symbol.
void SetFootprintFieldText(const wxString &aFootprint)
VECTOR2I GetPosition() const override
Definition sch_symbol.h:896
void SetUnitSelection(const SCH_SHEET_PATH *aSheet, int aUnitSelection)
Set the selected unit of this symbol on one sheet.
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:182
RAII class that sets an value at construction and resets it to the original value at destruction.
@ RESOLVED
Definition common.h:93
static bool empty(const wxTextEntryBase *aCtrl)
@ ERCE_FOOTPRINT_LINK_ISSUES
The footprint link is invalid, or points to a missing (or inactive) footprint or library.
@ ERCE_PIN_NOT_CONNECTED
Pin not connected and not no connect symbol.
@ ERCE_DIFFERENT_UNIT_FP
Different units of the same symbol have different footprints assigned.
@ ERCE_MISSING_UNIT
Symbol has units that are not placed on the schematic.
@ ERCE_FOOTPRINT_FILTERS
The assigned footprint doesn't match the footprint filters.
@ KIFACE_TEST_FOOTPRINT_LINK_LIBRARY_NOT_ENABLED
Definition kiface_ids.h:56
@ KIFACE_TEST_FOOTPRINT_LINK
Definition kiface_ids.h:54
@ KIFACE_TEST_FOOTPRINT_LINK_NO_LIBRARY
Definition kiface_ids.h:55
@ KIFACE_TEST_FOOTPRINT_LINK_NO_FOOTPRINT
Definition kiface_ids.h:57
KIID niluuid(0)
void LoadSchematic(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< SCHEMATIC > &aSchematic)
@ RPT_SEVERITY_WARNING
Implement a participant in the KIWAY alchemy.
Definition kiway.h:153
virtual wxWindow * CreateKiWindow(wxWindow *aParent, int aClassId, KIWAY *aKIWAY, int aCtlBits=0)=0
Create a wxWindow for the current project.
virtual bool OnKifaceStart(PGM_BASE *aProgram, int aCtlBits, KIWAY *aKiway)=0
Called just once shortly after the DSO is loaded.
virtual void OnKifaceEnd()=0
Called just once just before the DSO is to be unloaded.
virtual void * IfaceOrAddress(int aDataId)=0
Return pointer to the requested object.
virtual void Reset()=0
Reloads global state.
virtual void GetActions(std::vector< TOOL_ACTION * > &aActions) const =0
Append this Kiface's registered actions to the given list.
BOOST_AUTO_TEST_CASE(ERCDuplicateUnitsDoNotHideMissingUnits)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
std::string path
VECTOR3I expected(15, 30, 45)
BOOST_TEST_CONTEXT("Test Clearance")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_MARKER_T
Definition typeinfo.h:154