KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_multichannel.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 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
26#include <board.h>
28#include <pad.h>
29#include <pcb_group.h>
30#include <pcb_shape.h>
31#include <pcb_track.h>
32#include <pcb_text.h>
33#include <pcb_field.h>
34#include <footprint.h>
35#include <zone.h>
36#include <drc/drc_item.h>
42#include <lib_id.h>
43#include <atomic>
44
52
54{
55public:
58
59 virtual wxWindow* GetToolCanvas() const override { return nullptr; }
60};
61
62BOOST_FIXTURE_TEST_SUITE( MultichannelTool, MULTICHANNEL_TEST_FIXTURE )
63
64RULE_AREA* findRuleAreaByPartialName( MULTICHANNEL_TOOL* aTool, const wxString& aName )
65{
66 for( RULE_AREA& ra : aTool->GetData()->m_areas )
67 {
68 if( ra.m_ruleName.Contains( ( aName ) ) )
69 return &ra;
70 }
71
72 return nullptr;
73}
74
75RULE_AREA* findRuleAreaByPlacementGroup( MULTICHANNEL_TOOL* aTool, const wxString& aGroupName )
76{
77 for( RULE_AREA& ra : aTool->GetData()->m_areas )
78 {
79 if( ra.m_zone && ra.m_zone->GetPlacementAreaSource() == aGroupName )
80 return &ra;
81 }
82
83 return nullptr;
84}
85
86int countZonesByNameInRuleArea( BOARD* aBoard, const wxString& aZoneName, const RULE_AREA& aRuleArea )
87{
88 int count = 0;
89
90 for( const ZONE* zone : aBoard->Zones() )
91 {
92 if( zone == aRuleArea.m_zone )
93 continue;
94
95 if( zone->GetZoneName() != aZoneName )
96 continue;
97
98 if( aRuleArea.m_zone->Outline()->Contains( zone->Outline()->COutline( 0 ).Centre() ) )
99 count++;
100 }
101
102 return count;
103}
104
105
107{
109
110 std::vector<wxString> tests = { "vme-wren" };
111
112 for( const wxString& relPath : tests )
113 {
114 KI_TEST::LoadBoard( m_settingsManager, relPath, m_board );
115
116 TOOL_MANAGER toolMgr;
117 MOCK_TOOLS_HOLDER* toolsHolder = new MOCK_TOOLS_HOLDER;
118
119 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, toolsHolder );
120
121 MULTICHANNEL_TOOL* mtTool = new MULTICHANNEL_TOOL; // TOOL_MANAGER owns the tools
122 toolMgr.RegisterTool( mtTool );
123
124 //RULE_AREAS_DATA* raData = m_parentTool->GetData();
125
127
128 auto ruleData = mtTool->GetData();
129
130 BOOST_TEST_MESSAGE( wxString::Format( "RA multichannel sheets = %d",
131 static_cast<int>( ruleData->m_areas.size() ) ) );
132
133 BOOST_CHECK_EQUAL( ruleData->m_areas.size(), 72 );
134
135 int cnt = 0;
136
137 ruleData->m_replaceExisting = true;
138
139 for( RULE_AREA& ra : ruleData->m_areas )
140 {
141 if( ra.m_sheetName == wxT( "io_driver.kicad_sch" )
142 || ra.m_sheetName == wxT( "pp_driver_2x.kicad_sch" ) )
143 {
144 ra.m_generateEnabled = true;
145 cnt++;
146 }
147 }
148
149 BOOST_TEST_MESSAGE( wxString::Format( "Autogenerating %d RAs", cnt ) );
150
151 TOOL_EVENT dummyEvent;
152
153 mtTool->AutogenerateRuleAreas( dummyEvent );
154 mtTool->FindExistingRuleAreas();
155
156 int n_areas_io = 0, n_areas_pp = 0, n_areas_other = 0;
157
158 BOOST_TEST_MESSAGE( wxString::Format( "Found %d RAs after commit",
159 static_cast<int>(ruleData->m_areas.size() ) ) );
160
161 for( const RULE_AREA& ra : ruleData->m_areas )
162 {
163 BOOST_TEST_MESSAGE( wxString::Format( "SN '%s'", ra.m_ruleName ) );
164
165 if( ra.m_ruleName.Contains( wxT( "io_drivers_fp" ) ) )
166 {
167 n_areas_io++;
168 BOOST_CHECK_EQUAL( ra.m_components.size(), 31 );
169 }
170 else if( ra.m_ruleName.Contains( wxT( "io_drivers_pp" ) ) )
171 {
172 n_areas_pp++;
173 BOOST_CHECK_EQUAL( ra.m_components.size(), 11 );
174 }
175 else
176 {
177 n_areas_other++;
178 }
179 }
180
181 BOOST_TEST_MESSAGE( wxString::Format( "IO areas=%d, PP areas=%d, others=%d",
182 n_areas_io, n_areas_pp, n_areas_other ) );
183
184 BOOST_CHECK_EQUAL( n_areas_io, 16 );
185 BOOST_CHECK_EQUAL( n_areas_pp, 16 );
186 BOOST_CHECK_EQUAL( n_areas_other, 0 );
187
188 const std::vector<wxString> rulesToTest = { wxT( "io_drivers_fp" ),
189 wxT( "io_drivers_pp" ) };
190
191 for( const wxString& ruleName : rulesToTest )
192 {
193 for( const RULE_AREA& refArea : ruleData->m_areas )
194 {
195 if( !refArea.m_ruleName.Contains( ruleName ) )
196 continue;
197
198 BOOST_TEST_MESSAGE( wxString::Format( "REF AREA: '%s'", refArea.m_ruleName ) );
199
200 for( const RULE_AREA& targetArea : ruleData->m_areas )
201 {
202 if( targetArea.m_zone == refArea.m_zone )
203 continue;
204
205 if( !targetArea.m_ruleName.Contains( ruleName ) )
206 continue;
207
208 auto cgRef = CONNECTION_GRAPH::BuildFromFootprintSet( refArea.m_components,
209 targetArea.m_components );
210 auto cgTarget =
211 CONNECTION_GRAPH::BuildFromFootprintSet( targetArea.m_components,
212 refArea.m_components );
213
215
216 std::vector<TMATCH::TOPOLOGY_MISMATCH_REASON> details;
217 bool status = cgRef->FindIsomorphism( cgTarget.get(), result, details );
218
219 BOOST_TEST_MESSAGE( wxString::Format(
220 "topo match: '%s' [%d] -> '%s' [%d] result %d", refArea.m_ruleName.c_str().AsChar(),
221 static_cast<int>( refArea.m_components.size() ), targetArea.m_ruleName.c_str().AsChar(),
222 static_cast<int>( targetArea.m_components.size() ), status ? 1 : 0 ) );
223
224 for( const auto& iter : result )
225 {
226 BOOST_TEST_MESSAGE( wxString::Format( "%s : %s",
227 iter.second->GetReference(),
228 iter.first->GetReference() ) );
229 }
230
231 BOOST_CHECK( status );
232 BOOST_CHECK( details.empty() );
233 }
234 }
235 }
236
237 auto refArea = findRuleAreaByPartialName( mtTool, wxT( "io_drivers_fp/bank3/io78/" ) );
238
239 BOOST_ASSERT( refArea );
240
241 const std::vector<wxString> targetAreaNames( { wxT( "io_drivers_fp/bank2/io78/" ),
242 wxT( "io_drivers_fp/bank1/io78/" ),
243 wxT( "io_drivers_fp/bank0/io01/" ) } );
244
245 for( const wxString& targetRaName : targetAreaNames )
246 {
247 auto targetRA = findRuleAreaByPartialName( mtTool, targetRaName );
248
249 BOOST_ASSERT( targetRA != nullptr );
250
251 BOOST_TEST_MESSAGE( wxString::Format( "Clone to: %s", targetRA->m_ruleName ) );
252
253 ruleData->m_compatMap[targetRA].m_doCopy = true;
254 }
255
256 int result = mtTool->RepeatLayout( TOOL_EVENT(), refArea->m_zone );
257
258 BOOST_ASSERT( result >= 0 );
259 }
260}
261
262
267BOOST_FIXTURE_TEST_CASE( RepeatLayoutCopiesFootprintProperties, MULTICHANNEL_TEST_FIXTURE )
268{
269 KI_TEST::LoadBoard( m_settingsManager, "issue22548/issue22548", m_board );
270
271 TOOL_MANAGER toolMgr;
272 MOCK_TOOLS_HOLDER* toolsHolder = new MOCK_TOOLS_HOLDER;
273
274 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, toolsHolder );
275
277 toolMgr.RegisterTool( mtTool );
278
279 mtTool->FindExistingRuleAreas();
280
281 auto ruleData = mtTool->GetData();
282
283 BOOST_TEST_MESSAGE( wxString::Format( "Found %d rule areas",
284 static_cast<int>( ruleData->m_areas.size() ) ) );
285
286 BOOST_CHECK( ruleData->m_areas.size() >= 2 );
287
288 if( ruleData->m_areas.size() < 2 )
289 return;
290
291 RULE_AREA* refArea = nullptr;
292 RULE_AREA* targetArea = nullptr;
293
294 for( RULE_AREA& ra : ruleData->m_areas )
295 {
296 if( ra.m_ruleName.Contains( wxT( "Untitled Sheet/" ) ) )
297 refArea = &ra;
298 else if( ra.m_ruleName.Contains( wxT( "Untitled Sheet1/" ) ) )
299 targetArea = &ra;
300 }
301
302 if( !refArea || !targetArea )
303 {
304 BOOST_TEST_MESSAGE( "Could not find Untitled Sheet and Untitled Sheet1 rule areas, skipping test" );
305 return;
306 }
307
308 BOOST_TEST_MESSAGE( wxString::Format( "Reference area: %s, Target area: %s",
309 refArea->m_ruleName, targetArea->m_ruleName ) );
310
311 FOOTPRINT* refFP = nullptr;
312 FOOTPRINT* targetFP = nullptr;
313
314 for( FOOTPRINT* fp : refArea->m_components )
315 {
316 if( fp->GetReference().StartsWith( wxT( "U1" ) ) )
317 {
318 refFP = fp;
319 break;
320 }
321 }
322
323 for( FOOTPRINT* fp : targetArea->m_components )
324 {
325 if( fp->GetReference().StartsWith( wxT( "U2" ) ) )
326 {
327 targetFP = fp;
328 break;
329 }
330 }
331
332 if( !refFP || !targetFP )
333 {
334 BOOST_TEST_MESSAGE( "Could not find matching footprints in the rule areas, skipping test" );
335 return;
336 }
337
338 PCB_FIELD* refValueField = refFP->GetField( FIELD_T::VALUE );
339 bool refValueVisible = refValueField ? refValueField->IsVisible() : true;
340
341 std::vector<FP_3DMODEL> refModels = refFP->Models();
342
343 mtTool->CheckRACompatibility( refArea->m_zone );
344
345 ruleData->m_compatMap[targetArea].m_doCopy = true;
346 ruleData->m_options.m_copyPlacement = true;
347
348 int result = mtTool->RepeatLayout( TOOL_EVENT(), refArea->m_zone );
349
350 BOOST_CHECK( result >= 0 );
351
352 PCB_FIELD* targetValueField = targetFP->GetField( FIELD_T::VALUE );
353
354 if( targetValueField && refValueField )
355 {
356 BOOST_CHECK_EQUAL( targetValueField->IsVisible(), refValueVisible );
357 BOOST_TEST_MESSAGE( wxString::Format( "Value field visibility: ref=%d, target=%d",
358 refValueVisible, targetValueField->IsVisible() ) );
359 }
360
361 BOOST_CHECK_EQUAL( targetFP->Models().size(), refModels.size() );
362
363 if( !refModels.empty() )
364 {
365 BOOST_TEST_MESSAGE( wxString::Format( "3D models: ref=%d, target=%d",
366 static_cast<int>( refModels.size() ),
367 static_cast<int>( targetFP->Models().size() ) ) );
368 }
369}
370
371
379BOOST_FIXTURE_TEST_CASE( RepeatLayoutDoesNotRemoveReferenceVias, MULTICHANNEL_TEST_FIXTURE )
380{
381 KI_TEST::LoadBoard( m_settingsManager, "issue21184/issue21184", m_board );
382
383 TOOL_MANAGER toolMgr;
384 MOCK_TOOLS_HOLDER* toolsHolder = new MOCK_TOOLS_HOLDER;
385
386 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, toolsHolder );
387
389 toolMgr.RegisterTool( mtTool );
390
391 mtTool->FindExistingRuleAreas();
392
393 auto ruleData = mtTool->GetData();
394
395 BOOST_TEST_MESSAGE( wxString::Format( "Found %d rule areas",
396 static_cast<int>( ruleData->m_areas.size() ) ) );
397
398 BOOST_CHECK_EQUAL( ruleData->m_areas.size(), 2 );
399
400 if( ruleData->m_areas.size() < 2 )
401 return;
402
403 RULE_AREA* refArea = nullptr;
404 RULE_AREA* targetArea = nullptr;
405
406 for( RULE_AREA& ra : ruleData->m_areas )
407 {
408 if( ra.m_ruleName == wxT( "Test1" ) )
409 refArea = &ra;
410 else if( ra.m_ruleName == wxT( "Test2" ) )
411 targetArea = &ra;
412 }
413
414 BOOST_REQUIRE( refArea != nullptr );
415 BOOST_REQUIRE( targetArea != nullptr );
416
417 int refViaCountBefore = 0;
418
419 for( PCB_TRACK* track : m_board->Tracks() )
420 {
421 if( track->Type() == PCB_VIA_T )
422 {
423 PCB_VIA* via = static_cast<PCB_VIA*>( track );
424 VECTOR2I viaPos = via->GetPosition();
425
426 if( refArea->m_zone->Outline()->Contains( viaPos ) )
427 refViaCountBefore++;
428 }
429 }
430
431 BOOST_TEST_MESSAGE( wxString::Format( "Reference area vias before repeat: %d", refViaCountBefore ) );
432 BOOST_CHECK( refViaCountBefore > 0 );
433
434 mtTool->CheckRACompatibility( refArea->m_zone );
435
436 ruleData->m_compatMap[targetArea].m_doCopy = true;
437 ruleData->m_options.m_copyPlacement = true;
438 ruleData->m_options.m_copyRouting = true;
439
440 int result = mtTool->RepeatLayout( TOOL_EVENT(), refArea->m_zone );
441
442 BOOST_CHECK( result >= 0 );
443
444 int refViaCountAfter = 0;
445
446 for( PCB_TRACK* track : m_board->Tracks() )
447 {
448 if( track->Type() == PCB_VIA_T )
449 {
450 PCB_VIA* via = static_cast<PCB_VIA*>( track );
451 VECTOR2I viaPos = via->GetPosition();
452
453 if( refArea->m_zone->Outline()->Contains( viaPos ) )
454 refViaCountAfter++;
455 }
456 }
457
458 BOOST_TEST_MESSAGE( wxString::Format( "Reference area vias after repeat: %d", refViaCountAfter ) );
459
460 BOOST_CHECK_EQUAL( refViaCountAfter, refViaCountBefore );
461}
462
463
468BOOST_FIXTURE_TEST_CASE( RepeatLayoutRespectsZoneLayerSetsForOtherItems, MULTICHANNEL_TEST_FIXTURE )
469{
470 KI_TEST::LoadBoard( m_settingsManager, "issue22983/issue22983", m_board );
471
472 TOOL_MANAGER toolMgr;
473 MOCK_TOOLS_HOLDER* toolsHolder = new MOCK_TOOLS_HOLDER;
474
475 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, toolsHolder );
476
478 toolMgr.RegisterTool( mtTool );
479
480 mtTool->FindExistingRuleAreas();
481
482 RULE_AREA* sourceA = findRuleAreaByPlacementGroup( mtTool, wxT( "SourceA" ) );
483 RULE_AREA* destA = findRuleAreaByPlacementGroup( mtTool, wxT( "DestA" ) );
484 RULE_AREA* sourceB = findRuleAreaByPlacementGroup( mtTool, wxT( "SourceB" ) );
485 RULE_AREA* destB = findRuleAreaByPlacementGroup( mtTool, wxT( "DestB" ) );
486
487 BOOST_REQUIRE( sourceA != nullptr );
488 BOOST_REQUIRE( destA != nullptr );
489 BOOST_REQUIRE( sourceB != nullptr );
490 BOOST_REQUIRE( destB != nullptr );
491
492 BOOST_CHECK_EQUAL( countZonesByNameInRuleArea( m_board.get(), wxT( "MultilayerZoneFrontAndOne" ), *sourceA ), 1 );
493 BOOST_CHECK_EQUAL( countZonesByNameInRuleArea( m_board.get(), wxT( "MultilayerZoneFrontAndOne" ), *destA ), 0 );
495 countZonesByNameInRuleArea( m_board.get(), wxT( "MultilayerZoneSourceBLayerMismatch" ), *sourceB ), 1 );
496 BOOST_CHECK_EQUAL( countZonesByNameInRuleArea( m_board.get(), wxT( "MultilayerZoneSourceBLayerMismatch" ), *destB ),
497 0 );
498 BOOST_CHECK_EQUAL( countZonesByNameInRuleArea( m_board.get(), wxT( "BottomZoneDontCopyMe" ), *sourceB ), 1 );
499 BOOST_CHECK_EQUAL( countZonesByNameInRuleArea( m_board.get(), wxT( "BottomZoneDontCopyMe" ), *destB ), 0 );
500
501 REPEAT_LAYOUT_OPTIONS options;
502 options.m_copyPlacement = false;
503 options.m_copyRouting = false;
504 options.m_copyOtherItems = true;
505 options.m_includeLockedItems = true;
506
507 int copyAStatus = mtTool->RepeatLayout( TOOL_EVENT(), *sourceA, *destA, options );
508 BOOST_REQUIRE( copyAStatus >= 0 );
509
510 int copyBStatus = mtTool->RepeatLayout( TOOL_EVENT(), *sourceB, *destB, options );
511 BOOST_REQUIRE( copyBStatus >= 0 );
512
513 // SourceA and DestA both include F.Cu+B.Cu, so this multilayer zone should copy.
514 BOOST_CHECK_EQUAL( countZonesByNameInRuleArea( m_board.get(), wxT( "MultilayerZoneFrontAndOne" ), *destA ), 1 );
515
516 // SourceB only includes F.Cu, so this F.Cu+B.Cu zone should not copy to DestB.
517 BOOST_CHECK_EQUAL( countZonesByNameInRuleArea( m_board.get(), wxT( "MultilayerZoneSourceBLayerMismatch" ), *destB ),
518 0 );
519
520 // SourceB excludes B.Cu, so this B.Cu-only zone should not copy either.
521 BOOST_CHECK_EQUAL( countZonesByNameInRuleArea( m_board.get(), wxT( "BottomZoneDontCopyMe" ), *destB ), 0 );
522}
523
524
533{
535 using TMATCH::COMPONENT;
536
537 // Create two connection graphs with components that have dotted reference designators
538 auto cgRef = std::make_unique<CONNECTION_GRAPH>();
539 auto cgTarget = std::make_unique<CONNECTION_GRAPH>();
540
541 // Create mock footprints with the same FPID
542 LIB_ID fpid( wxT( "Package_SO" ), wxT( "SOIC-8_3.9x4.9mm_P1.27mm" ) );
543
544 // Create reference footprint TRIM_1.1 and target footprint TRIM_2.1
545 FOOTPRINT fpRef( nullptr );
546 fpRef.SetFPID( fpid );
547 fpRef.SetReference( wxT( "TRIM_1.1" ) );
548
549 FOOTPRINT fpTarget( nullptr );
550 fpTarget.SetFPID( fpid );
551 fpTarget.SetReference( wxT( "TRIM_2.1" ) );
552
553 // Create matching pad structures
554 PAD padRef1( &fpRef );
555 padRef1.SetNumber( wxT( "1" ) );
556 padRef1.SetNetCode( 1 );
557 fpRef.Add( &padRef1 );
558
559 PAD padRef2( &fpRef );
560 padRef2.SetNumber( wxT( "2" ) );
561 padRef2.SetNetCode( 2 );
562 fpRef.Add( &padRef2 );
563
564 PAD padTarget1( &fpTarget );
565 padTarget1.SetNumber( wxT( "1" ) );
566 padTarget1.SetNetCode( 3 );
567 fpTarget.Add( &padTarget1 );
568
569 PAD padTarget2( &fpTarget );
570 padTarget2.SetNumber( wxT( "2" ) );
571 padTarget2.SetNetCode( 4 );
572 fpTarget.Add( &padTarget2 );
573
574 // Build connection graphs
575 cgRef->AddFootprint( &fpRef, VECTOR2I( 0, 0 ) );
576 cgTarget->AddFootprint( &fpTarget, VECTOR2I( 0, 0 ) );
577
578 cgRef->BuildConnectivity();
579 cgTarget->BuildConnectivity();
580
581 // Check that the components are considered the same kind
582 BOOST_CHECK_EQUAL( cgRef->Components().size(), 1 );
583 BOOST_CHECK_EQUAL( cgTarget->Components().size(), 1 );
584
585 COMPONENT* cmpRef = cgRef->Components()[0];
586 COMPONENT* cmpTarget = cgTarget->Components()[0];
587
588 bool sameKind = cmpRef->IsSameKind( *cmpTarget );
589
590 BOOST_TEST_MESSAGE( wxString::Format( "TRIM_1.1 and TRIM_2.1 IsSameKind: %d", sameKind ? 1 : 0 ) );
591 BOOST_CHECK( sameKind );
592
593 // Test topology matching
595 std::vector<TMATCH::TOPOLOGY_MISMATCH_REASON> details;
596 bool status = cgRef->FindIsomorphism( cgTarget.get(), result, details );
597
598 BOOST_TEST_MESSAGE( wxString::Format( "Topology match result: %d", status ? 1 : 0 ) );
599
600 if( !status && !details.empty() )
601 {
602 for( const auto& reason : details )
603 {
604 BOOST_TEST_MESSAGE( wxString::Format( "Mismatch: %s <-> %s: %s",
605 reason.m_reference, reason.m_candidate, reason.m_reason ) );
606 }
607 }
608
609 BOOST_CHECK( status );
610 BOOST_CHECK( details.empty() );
611
612 // Cleanup: remove pads before footprints go out of scope
613 fpRef.Pads().clear();
614 fpTarget.Pads().clear();
615}
616
617
626BOOST_FIXTURE_TEST_CASE( GenerateRuleAreasIncludesChildSheets, MULTICHANNEL_TEST_FIXTURE )
627{
628 KI_TEST::LoadBoard( m_settingsManager, "vme-wren", m_board );
629
630 TOOL_MANAGER toolMgr;
631 MOCK_TOOLS_HOLDER* toolsHolder = new MOCK_TOOLS_HOLDER;
632
633 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, toolsHolder );
634
636 toolMgr.RegisterTool( mtTool );
637
639
640 auto ruleData = mtTool->GetData();
641
642 RULE_AREA* leafArea = nullptr;
643 RULE_AREA* midArea = nullptr;
644 RULE_AREA* topArea = nullptr;
645
646 for( RULE_AREA& ra : ruleData->m_areas )
647 {
648 if( ra.m_sheetPath == wxT( "/io_drivers_fp/bank0/io01/" ) )
649 leafArea = &ra;
650 else if( ra.m_sheetPath == wxT( "/io_drivers_fp/bank0/" ) )
651 midArea = &ra;
652 else if( ra.m_sheetPath == wxT( "/io_drivers_fp/" ) )
653 topArea = &ra;
654 }
655
656 BOOST_REQUIRE( leafArea != nullptr );
657 BOOST_REQUIRE( midArea != nullptr );
658 BOOST_REQUIRE( topArea != nullptr );
659
660 BOOST_TEST_MESSAGE( wxString::Format( "Leaf /io_drivers_fp/bank0/io01/ components: %d",
661 static_cast<int>( leafArea->m_components.size() ) ) );
662 BOOST_TEST_MESSAGE( wxString::Format( "Mid /io_drivers_fp/bank0/ components: %d",
663 static_cast<int>( midArea->m_components.size() ) ) );
664 BOOST_TEST_MESSAGE( wxString::Format( "Top /io_drivers_fp/ components: %d",
665 static_cast<int>( topArea->m_components.size() ) ) );
666
667 // Leaf sheet has 31 direct components and no children
668 BOOST_CHECK_EQUAL( leafArea->m_components.size(), 31 );
669
670 // Mid-level sheet has 7 direct + 4 child sheets * 31 each = 131
671 BOOST_CHECK_EQUAL( midArea->m_components.size(), 131 );
672
673 // Top-level sheet has 3 direct + 4 banks * 131 each = 527
674 BOOST_CHECK_EQUAL( topArea->m_components.size(), 527 );
675
676 // Mid-level components must be a superset of leaf components
677 for( FOOTPRINT* fp : leafArea->m_components )
678 BOOST_CHECK( midArea->m_components.count( fp ) > 0 );
679
680 // Top-level components must be a superset of mid-level components
681 for( FOOTPRINT* fp : midArea->m_components )
682 BOOST_CHECK( topArea->m_components.count( fp ) > 0 );
683}
684
685
691{
693
694 KI_TEST::LoadBoard( m_settingsManager, "vme-wren", m_board );
695
696 TOOL_MANAGER toolMgr;
697 MOCK_TOOLS_HOLDER* toolsHolder = new MOCK_TOOLS_HOLDER;
698
699 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, toolsHolder );
700
702 toolMgr.RegisterTool( mtTool );
703
705
706 auto ruleData = mtTool->GetData();
707
708 ruleData->m_replaceExisting = true;
709
710 for( RULE_AREA& ra : ruleData->m_areas )
711 {
712 if( ra.m_sheetName == wxT( "io_driver.kicad_sch" ) )
713 ra.m_generateEnabled = true;
714 }
715
716 TOOL_EVENT dummyEvent;
717 mtTool->AutogenerateRuleAreas( dummyEvent );
718 mtTool->FindExistingRuleAreas();
719
720 RULE_AREA* refArea = findRuleAreaByPartialName( mtTool, wxT( "io_drivers_fp/bank3/io78/" ) );
721 RULE_AREA* targetArea = findRuleAreaByPartialName( mtTool, wxT( "io_drivers_fp/bank2/io78/" ) );
722
723 BOOST_REQUIRE( refArea != nullptr );
724 BOOST_REQUIRE( targetArea != nullptr );
725
726 auto cgRef = CONNECTION_GRAPH::BuildFromFootprintSet( refArea->m_components,
727 targetArea->m_components );
728 auto cgTarget = CONNECTION_GRAPH::BuildFromFootprintSet( targetArea->m_components,
729 refArea->m_components );
730
731 // Pre-cancelled: should return false immediately with empty result
732 {
733 std::atomic<bool> cancelled( true );
734 std::atomic<int> matched( 0 );
735 std::atomic<int> total( 0 );
736
738 params.m_cancelled = &cancelled;
739 params.m_matchedComponents = &matched;
740 params.m_totalComponents = &total;
741
743 std::vector<TMATCH::TOPOLOGY_MISMATCH_REASON> details;
744
745 bool status = cgRef->FindIsomorphism( cgTarget.get(), result, details, params );
746
747 BOOST_CHECK( !status );
748 BOOST_CHECK( result.empty() );
749
750 BOOST_TEST_MESSAGE( "Pre-cancelled FindIsomorphism correctly returned false" );
751 }
752
753 // Normal run with progress reporting
754 {
755 std::atomic<bool> cancelled( false );
756 std::atomic<int> matched( 0 );
757 std::atomic<int> total( 0 );
758
760 params.m_cancelled = &cancelled;
761 params.m_matchedComponents = &matched;
762 params.m_totalComponents = &total;
763
765 std::vector<TMATCH::TOPOLOGY_MISMATCH_REASON> details;
766
767 bool status = cgRef->FindIsomorphism( cgTarget.get(), result, details, params );
768
769 BOOST_CHECK( status );
770
771 int finalMatched = matched.load();
772 int finalTotal = total.load();
773
774 BOOST_TEST_MESSAGE( wxString::Format( "Progress: matched=%d, total=%d", finalMatched, finalTotal ) );
775
776 BOOST_CHECK( finalTotal > 0 );
777 BOOST_CHECK_EQUAL( finalMatched, finalTotal );
778 }
779
780 // Sanity check: same graphs without params still succeed
781 {
783 std::vector<TMATCH::TOPOLOGY_MISMATCH_REASON> details;
784
785 bool status = cgRef->FindIsomorphism( cgTarget.get(), result, details );
786
787 BOOST_CHECK( status );
788 BOOST_CHECK( !result.empty() );
789
790 BOOST_TEST_MESSAGE( "Default params FindIsomorphism still succeeds" );
791 }
792}
793
794
805BOOST_FIXTURE_TEST_CASE( TopoMatchGlobalNetHierarchicalPins, MULTICHANNEL_TEST_FIXTURE )
806{
808
809 KI_TEST::LoadBoard( m_settingsManager, "issue21739/topology_mismatch", m_board );
810
811 TOOL_MANAGER toolMgr;
812 MOCK_TOOLS_HOLDER* toolsHolder = new MOCK_TOOLS_HOLDER;
813
814 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, toolsHolder );
815
817 toolMgr.RegisterTool( mtTool );
818
819 mtTool->FindExistingRuleAreas();
820
821 auto ruleData = mtTool->GetData();
822
823 BOOST_TEST_MESSAGE( wxString::Format( "Found %d rule areas",
824 static_cast<int>( ruleData->m_areas.size() ) ) );
825
826 BOOST_REQUIRE( ruleData->m_areas.size() >= 2 );
827
828 RULE_AREA* ch0Area = nullptr;
829 RULE_AREA* ch1Area = nullptr;
830
831 for( RULE_AREA& ra : ruleData->m_areas )
832 {
833 if( !ra.m_zone )
834 continue;
835
836 wxString source = ra.m_zone->GetPlacementAreaSource();
837
838 if( source == wxT( "/i2c_thingy_ch0/" ) )
839 ch0Area = &ra;
840 else if( source == wxT( "/i2c_thingy_ch1/" ) )
841 ch1Area = &ra;
842 }
843
844 BOOST_REQUIRE_MESSAGE( ch0Area != nullptr, "Could not find i2c_thingy_ch0 rule area" );
845 BOOST_REQUIRE_MESSAGE( ch1Area != nullptr, "Could not find i2c_thingy_ch1 rule area" );
846
847 BOOST_TEST_MESSAGE( wxString::Format( "ch0 components: %d, ch1 components: %d",
848 static_cast<int>( ch0Area->m_components.size() ),
849 static_cast<int>( ch1Area->m_components.size() ) ) );
850
851 BOOST_CHECK_EQUAL( ch0Area->m_components.size(), ch1Area->m_components.size() );
852
853 auto cgRef = CONNECTION_GRAPH::BuildFromFootprintSet( ch0Area->m_components,
854 ch1Area->m_components );
855 auto cgTarget = CONNECTION_GRAPH::BuildFromFootprintSet( ch1Area->m_components,
856 ch0Area->m_components );
857
859 std::vector<TMATCH::TOPOLOGY_MISMATCH_REASON> details;
860 bool status = cgRef->FindIsomorphism( cgTarget.get(), result, details );
861
862 if( !status )
863 {
864 for( const auto& reason : details )
865 {
866 BOOST_TEST_MESSAGE( wxString::Format( "Mismatch: %s <-> %s: %s",
867 reason.m_reference, reason.m_candidate,
868 reason.m_reason ) );
869 }
870 }
871
872 BOOST_CHECK_MESSAGE( status,
873 "Topology match failed for channels with hierarchical pins "
874 "tied to global nets (issue 21739)" );
875}
876
877
887BOOST_FIXTURE_TEST_CASE( ApplyDesignBlockLayoutCopiesSilkscreen, MULTICHANNEL_TEST_FIXTURE )
888{
889 m_board = std::make_unique<BOARD>();
890 m_board->SetEnabledLayers( LSET::AllCuMask() | LSET::AllTechMask() );
891
892 // Net for the single connection between the two footprints in each block.
893 NETINFO_ITEM* net = new NETINFO_ITEM( m_board.get(), wxT( "NET1" ), 1 );
894 m_board->Add( net );
895
896 auto makeFootprint =
897 [&]( const wxString& aRef, const VECTOR2I& aPos ) -> FOOTPRINT*
898 {
899 FOOTPRINT* fp = new FOOTPRINT( m_board.get() );
900 fp->SetFPID( LIB_ID( wxT( "TestLib" ), wxT( "R" ) ) );
901 fp->SetReference( aRef );
902 fp->SetPosition( aPos );
903
904 PAD* pad = new PAD( fp );
905 pad->SetNumber( wxT( "1" ) );
906 pad->SetNet( net );
907 pad->SetPosition( aPos );
908 pad->SetSize( F_Cu,
909 VECTOR2I( pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) ) );
910 pad->SetLayerSet( LSET( { F_Cu } ) );
911 fp->Add( pad );
912
913 m_board->Add( fp );
914 return fp;
915 };
916
917 // Source: a "design block" with two matched footprints and a silkscreen rectangle that
918 // is not associated with any footprint.
919 FOOTPRINT* refFp1 = makeFootprint( wxT( "R1" ),
920 VECTOR2I( pcbIUScale.mmToIU( 0 ), pcbIUScale.mmToIU( 0 ) ) );
921 FOOTPRINT* refFp2 = makeFootprint( wxT( "R2" ),
922 VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 0 ) ) );
923
924 PCB_SHAPE* silkRect = new PCB_SHAPE( m_board.get(), SHAPE_T::RECTANGLE );
925 silkRect->SetStart( VECTOR2I( pcbIUScale.mmToIU( -2 ), pcbIUScale.mmToIU( -2 ) ) );
926 silkRect->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 12 ), pcbIUScale.mmToIU( 2 ) ) );
927 silkRect->SetLayer( F_SilkS );
928 silkRect->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.15 ), LINE_STYLE::SOLID ) );
929 m_board->Add( silkRect );
930
931 // Destination: a group containing the matched pair so RepeatLayout can find a parent group.
932 FOOTPRINT* destFp1 = makeFootprint( wxT( "R3" ),
933 VECTOR2I( pcbIUScale.mmToIU( 50 ), pcbIUScale.mmToIU( 50 ) ) );
934 FOOTPRINT* destFp2 = makeFootprint( wxT( "R4" ),
935 VECTOR2I( pcbIUScale.mmToIU( 60 ), pcbIUScale.mmToIU( 50 ) ) );
936
937 PCB_GROUP* destGroup = new PCB_GROUP( m_board.get() );
938 destGroup->SetName( wxT( "design-block-dest" ) );
939 destGroup->AddItem( destFp1 );
940 destGroup->AddItem( destFp2 );
941 m_board->Add( destGroup );
942
943 // Unrelated silkscreen drawing inside the destination bounding box but not part of the
944 // destination group. Apply Design Block Layout must not delete or claim this item.
945 PCB_SHAPE* unrelatedSilk = new PCB_SHAPE( m_board.get(), SHAPE_T::SEGMENT );
946 unrelatedSilk->SetStart( VECTOR2I( pcbIUScale.mmToIU( 52 ), pcbIUScale.mmToIU( 52 ) ) );
947 unrelatedSilk->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 58 ), pcbIUScale.mmToIU( 52 ) ) );
948 unrelatedSilk->SetLayer( F_SilkS );
949 unrelatedSilk->SetStroke( STROKE_PARAMS( pcbIUScale.mmToIU( 0.15 ), LINE_STYLE::SOLID ) );
950 m_board->Add( unrelatedSilk );
951
952 int silkBefore = 0;
953
954 for( BOARD_ITEM* item : m_board->Drawings() )
955 {
956 if( item->Type() == PCB_SHAPE_T && item->GetLayer() == F_SilkS )
957 silkBefore++;
958 }
959
960 BOOST_REQUIRE_EQUAL( silkBefore, 2 );
961
962 RULE_AREA dbRA;
964 dbRA.m_components.insert( refFp1 );
965 dbRA.m_components.insert( refFp2 );
966 dbRA.m_designBlockItems.insert( refFp1 );
967 dbRA.m_designBlockItems.insert( refFp2 );
968 dbRA.m_designBlockItems.insert( silkRect );
969
970 // The Apply Design Block flow uses a synthetic copper-only rule area zone. The destination
971 // zone is a temporary zone never added to the board.
972 dbRA.m_zone = new ZONE( m_board.get() );
973 dbRA.m_zone->SetIsRuleArea( true );
976 BOX2I::ByCorners( VECTOR2I( pcbIUScale.mmToIU( -5 ), pcbIUScale.mmToIU( -5 ) ),
977 VECTOR2I( pcbIUScale.mmToIU( 15 ), pcbIUScale.mmToIU( 5 ) ) ) ) );
978
979 RULE_AREA destRA;
981 destRA.m_components.insert( destFp1 );
982 destRA.m_components.insert( destFp2 );
983
984 destRA.m_zone = new ZONE( m_board.get() );
985 destRA.m_zone->SetIsRuleArea( true );
988 BOX2I::ByCorners( VECTOR2I( pcbIUScale.mmToIU( 45 ), pcbIUScale.mmToIU( 45 ) ),
989 VECTOR2I( pcbIUScale.mmToIU( 65 ), pcbIUScale.mmToIU( 55 ) ) ) ) );
990
991 TOOL_MANAGER toolMgr;
992 MOCK_TOOLS_HOLDER* toolsHolder = new MOCK_TOOLS_HOLDER;
993 toolMgr.SetEnvironment( m_board.get(), nullptr, nullptr, nullptr, toolsHolder );
994
996 toolMgr.RegisterTool( mtTool );
997
998 REPEAT_LAYOUT_OPTIONS opts = { .m_copyRouting = true,
999 .m_connectedRoutingOnly = false,
1000 .m_copyPlacement = true,
1001 .m_copyOtherItems = true,
1002 .m_groupItems = false,
1003 .m_includeLockedItems = true,
1004 .m_anchorFp = nullptr };
1005
1006 int result = mtTool->RepeatLayout( TOOL_EVENT(), dbRA, destRA, opts );
1007 BOOST_CHECK_MESSAGE( result >= 0, "RepeatLayout failed" );
1008
1009 delete dbRA.m_zone;
1010 delete destRA.m_zone;
1011
1012 // Verify a third silkscreen shape (the duplicated rectangle) was added and grouped under
1013 // the destination group, that the unrelated silk segment was preserved, and that the
1014 // duplicated rectangle landed inside the destination region.
1015 int silkAfter = 0;
1016 int silkInGroup = 0;
1017 bool unrelatedSurvived = false;
1018 PCB_SHAPE* copiedRect = nullptr;
1019
1020 for( BOARD_ITEM* item : m_board->Drawings() )
1021 {
1022 if( item->Type() != PCB_SHAPE_T || item->GetLayer() != F_SilkS )
1023 continue;
1024
1025 silkAfter++;
1026
1027 if( item == unrelatedSilk )
1028 unrelatedSurvived = true;
1029
1030 if( item->GetParentGroup() == destGroup )
1031 {
1032 silkInGroup++;
1033
1034 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
1035
1036 if( shape->GetShape() == SHAPE_T::RECTANGLE )
1037 copiedRect = shape;
1038 }
1039 }
1040
1041 BOOST_CHECK_MESSAGE( silkAfter == 3,
1042 wxString::Format( "Expected 3 silkscreen shapes after Apply Design Block "
1043 "Layout (2 original + 1 copy), found %d (issue 24372)",
1044 silkAfter ) );
1045 BOOST_CHECK_MESSAGE( silkInGroup == 1,
1046 wxString::Format( "Expected 1 silkscreen shape in destination group, "
1047 "found %d (issue 24372)",
1048 silkInGroup ) );
1049 BOOST_CHECK_MESSAGE( unrelatedSurvived,
1050 "Unrelated silkscreen drawing outside the design block group was "
1051 "deleted by Apply Design Block Layout (issue 24372)" );
1052 BOOST_REQUIRE( copiedRect != nullptr );
1053
1054 // The copied rectangle should sit near the destination footprints (offset by ~50mm from
1055 // the source position), not at the original source location.
1056 BOOST_CHECK_GT( copiedRect->GetStart().x, pcbIUScale.mmToIU( 30 ) );
1057}
1058
1059
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:125
virtual bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:323
const ZONES & Zones() const
Definition board.h:368
static constexpr BOX2< VECTOR2I > ByCorners(const VECTOR2I &aCorner1, const VECTOR2I &aCorner2)
Definition box2.h:70
Store all of the related component information found in a netlist.
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:62
void SetName(const wxString &aName)
Definition eda_group.h:52
SHAPE_T GetShape() const
Definition eda_shape.h:189
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:198
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:194
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:240
virtual bool IsVisible() const
Definition eda_text.h:212
void SetPosition(const VECTOR2I &aPos) override
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:430
PCB_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this footprint.
std::deque< PAD * > & Pads()
Definition footprint.h:377
void SetReference(const wxString &aReference)
Definition footprint.h:835
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
std::vector< FP_3DMODEL > & Models()
Definition footprint.h:394
const wxString & GetReference() const
Definition footprint.h:829
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:49
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllTechMask()
Return a mask holding all technical layers (no CU layer) on both side.
Definition lset.cpp:676
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:599
virtual wxWindow * GetToolCanvas() const override
Canvas access.
int CheckRACompatibility(ZONE *aRefZone)
RULE_AREAS_DATA * GetData()
int RepeatLayout(const TOOL_EVENT &aEvent, ZONE *aRefZone)
int AutogenerateRuleAreas(const TOOL_EVENT &aEvent)
Handle the data for a net.
Definition netinfo.h:50
Definition pad.h:65
void SetNumber(const wxString &aNumber)
Set the pad number (note that it can be alphanumeric, such as the array reference "AA12").
Definition pad.h:146
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:53
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition pcb_shape.h:98
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
Simple container to manage line stroke parameters.
Generic, UI-independent tool event.
Definition tool_event.h:171
Master controller class:
void RegisterTool(TOOL_BASE *aTool)
Add a tool to the manager set and sets it up.
void SetEnvironment(EDA_ITEM *aModel, KIGFX::VIEW *aView, KIGFX::VIEW_CONTROLS *aViewControls, APP_SETTINGS_BASE *aSettings, TOOLS_HOLDER *aFrame)
Set the work environment (model, view, view controls and the parent window).
Handle a list of polygons defining a copper zone.
Definition zone.h:74
void AddPolygon(std::vector< VECTOR2I > &aPolygon)
Add a polygon to the zone outline.
Definition zone.cpp:1263
wxString GetPlacementAreaSource() const
Definition zone.h:807
SHAPE_POLY_SET * Outline()
Definition zone.h:422
void SetIsRuleArea(bool aEnable)
Definition zone.h:803
void SetLayerSet(const LSET &aLayerSet) override
Definition zone.cpp:628
@ SEGMENT
Definition eda_shape.h:50
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:51
@ F_SilkS
Definition layer_ids.h:100
@ F_Cu
Definition layer_ids.h:64
SHAPE_LINE_CHAIN BoxToLineChain(const BOX2I &aBox)
void LoadBoard(SETTINGS_MANAGER &aSettingsManager, const wxString &aRelPath, std::unique_ptr< BOARD > &aBoard)
std::map< FOOTPRINT *, FOOTPRINT * > COMPONENT_MATCHES
Definition topo_match.h:184
Class to handle a set of BOARD_ITEMs.
Utility functions for working with shapes.
std::unique_ptr< BOARD > m_board
std::vector< RULE_AREA > m_areas
std::unordered_set< EDA_ITEM * > m_designBlockItems
wxString m_sheetName
PLACEMENT_SOURCE_T m_sourceType
std::set< FOOTPRINT * > m_components
wxString m_ruleName
wxString m_sheetPath
std::atomic< bool > * m_cancelled
Definition topo_match.h:49
std::atomic< int > * m_matchedComponents
Definition topo_match.h:50
std::atomic< int > * m_totalComponents
Definition topo_match.h:51
@ VALUE
Field Value of part, i.e. "3.3K".
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
RULE_AREA * findRuleAreaByPartialName(MULTICHANNEL_TOOL *aTool, const wxString &aName)
BOOST_FIXTURE_TEST_CASE(MultichannelToolRegressions, MULTICHANNEL_TEST_FIXTURE)
int countZonesByNameInRuleArea(BOARD *aBoard, const wxString &aZoneName, const RULE_AREA &aRuleArea)
RULE_AREA * findRuleAreaByPlacementGroup(MULTICHANNEL_TOOL *aTool, const wxString &aGroupName)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:85
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:94
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687