KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_variant.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, see <https://www.gnu.org/licenses/>.
18 */
19
25
27#include <boost/test/unit_test.hpp>
28
29#include <board.h>
30#include <footprint.h>
31#include <lib_id.h>
39#include <richio.h>
41#include <template_fieldnames.h>
42#include <tool/tool_manager.h>
43
44
45BOOST_AUTO_TEST_SUITE( Variant )
46
47
48
51BOOST_AUTO_TEST_CASE( FootprintVariantBasics )
52{
53 FOOTPRINT_VARIANT variant( "TestVariant" );
54
55 // Check default values
56 BOOST_CHECK_EQUAL( variant.GetName(), "TestVariant" );
57 BOOST_CHECK( !variant.GetDNP() );
58 BOOST_CHECK( !variant.GetExcludedFromBOM() );
59 BOOST_CHECK( !variant.GetExcludedFromSim() );
60 BOOST_CHECK( !variant.GetExcludedFromPosFiles() );
61 BOOST_CHECK( variant.GetFields().empty() );
62
63 // Test setting values
64 variant.SetDNP( true );
65 variant.SetExcludedFromBOM( true );
66 variant.SetExcludedFromSim( true );
67 variant.SetExcludedFromPosFiles( true );
68 variant.SetFieldValue( "Value", "100R" );
69
70 BOOST_CHECK( variant.GetDNP() );
71 BOOST_CHECK( variant.GetExcludedFromBOM() );
72 BOOST_CHECK( variant.GetExcludedFromSim() );
73 BOOST_CHECK( variant.GetExcludedFromPosFiles() );
74 BOOST_CHECK( variant.HasFieldValue( "Value" ) );
75 BOOST_CHECK_EQUAL( variant.GetFieldValue( "Value" ), "100R" );
76}
77
78
82BOOST_AUTO_TEST_CASE( BoardVariantRegistry )
83{
84 BOARD board;
85
86 // Initially no variants
87 BOOST_CHECK( board.GetVariantNames().empty() );
88 BOOST_CHECK( board.GetCurrentVariant().IsEmpty() );
89
90 // Add a variant
91 board.AddVariant( "Production" );
92 BOOST_CHECK( board.HasVariant( "Production" ) );
93 BOOST_CHECK_EQUAL( board.GetVariantNames().size(), 1 );
94
95 // Add another variant
96 board.AddVariant( "Debug" );
97 BOOST_CHECK( board.HasVariant( "Debug" ) );
98 BOOST_CHECK_EQUAL( board.GetVariantNames().size(), 2 );
99
100 // Test case insensitivity
101 BOOST_CHECK( board.HasVariant( "production" ) );
102 BOOST_CHECK( board.HasVariant( "PRODUCTION" ) );
103 BOOST_CHECK( board.HasVariant( "PrOdUcTiOn" ) );
104
105 // Set current variant
106 board.SetCurrentVariant( "Production" );
107 BOOST_CHECK_EQUAL( board.GetCurrentVariant(), "Production" );
108
109 // Set variant description
110 board.SetVariantDescription( "Production", "Standard production build" );
111 BOOST_CHECK_EQUAL( board.GetVariantDescription( "Production" ), "Standard production build" );
112
113 // Delete a variant
114 board.DeleteVariant( "Debug" );
115 BOOST_CHECK( !board.HasVariant( "Debug" ) );
116 BOOST_CHECK_EQUAL( board.GetVariantNames().size(), 1 );
117}
118
119
123BOOST_AUTO_TEST_CASE( FootprintDNPForVariant )
124{
125 BOARD board;
126 FOOTPRINT fp( &board );
127
128 // Add variants to board
129 board.AddVariant( "Production" );
130 board.AddVariant( "Debug" );
131
132 // Set base DNP to false
133 fp.SetAttributes( 0 );
134 BOOST_CHECK( !fp.GetDNPForVariant( wxEmptyString ) );
135 BOOST_CHECK( !fp.GetDNPForVariant( "Production" ) );
136
137 // Set base DNP to true
138 fp.SetAttributes( FP_DNP );
139 BOOST_CHECK( fp.GetDNPForVariant( wxEmptyString ) );
140 BOOST_CHECK( fp.GetDNPForVariant( "Production" ) );
141
142 // Now set Production variant to override DNP
143 FOOTPRINT_VARIANT prodVariant( "Production" );
144 prodVariant.SetDNP( false );
145 fp.SetVariant( prodVariant );
146
147 // Base is still DNP, but Production overrides to not DNP
148 BOOST_CHECK( fp.GetDNPForVariant( wxEmptyString ) );
149 BOOST_CHECK( !fp.GetDNPForVariant( "Production" ) );
150 BOOST_CHECK( !fp.GetDNPForVariant( "production" ) ); // Case insensitive
151 BOOST_CHECK( fp.GetDNPForVariant( "Debug" ) ); // No override, uses base
152
153 // Set Debug variant to also override
154 FOOTPRINT_VARIANT debugVariant( "Debug" );
155 debugVariant.SetDNP( true );
156 fp.SetVariant( debugVariant );
157
158 BOOST_CHECK( fp.GetDNPForVariant( "Debug" ) );
159}
160
161
165BOOST_AUTO_TEST_CASE( FootprintVariantTextVarResolution )
166{
167 BOARD board;
168 FOOTPRINT fp( &board );
169
170 board.AddVariant( "Production" );
171 board.SetCurrentVariant( "Production" );
172
173 fp.SetAttributes( 0 );
174
175 FOOTPRINT_VARIANT prodVariant( "Production" );
176 prodVariant.SetDNP( true );
177 prodVariant.SetExcludedFromSim( true );
178 fp.SetVariant( prodVariant );
179
180 wxString token = wxS( "DNP" );
181 BOOST_CHECK( fp.ResolveTextVar( &token ) );
182 BOOST_CHECK_EQUAL( token, wxS( "DNP" ) );
183
184 token = wxS( "DNP" );
185 BOOST_CHECK( fp.ResolveTextVar( &token, wxEmptyString ) );
186 BOOST_CHECK_EQUAL( token, wxEmptyString );
187
188 token = wxS( "EXCLUDE_FROM_SIM" );
189 BOOST_CHECK( fp.ResolveTextVar( &token ) );
190 BOOST_CHECK_EQUAL( token, wxS( "Excluded from simulation" ) );
191
192 token = wxS( "EXCLUDE_FROM_SIM" );
193 BOOST_CHECK( fp.ResolveTextVar( &token, wxEmptyString ) );
194 BOOST_CHECK_EQUAL( token, wxEmptyString );
195}
196
197
201BOOST_AUTO_TEST_CASE( FootprintBOMExclusionForVariant )
202{
203 BOARD board;
204 FOOTPRINT fp( &board );
205
206 board.AddVariant( "Production" );
207
208 // Set base exclude from BOM to false
209 fp.SetAttributes( 0 );
210 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( wxEmptyString ) );
211 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( "Production" ) );
212
213 // Set base exclude from BOM to true
215 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( wxEmptyString ) );
216 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( "Production" ) );
217
218 // Override for Production variant
219 FOOTPRINT_VARIANT prodVariant( "Production" );
220 prodVariant.SetExcludedFromBOM( false );
221 fp.SetVariant( prodVariant );
222
223 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( wxEmptyString ) );
224 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( "Production" ) );
225}
226
227
231BOOST_AUTO_TEST_CASE( FootprintSimExclusionForVariant )
232{
233 BOARD board;
234 FOOTPRINT fp( &board );
235
236 board.AddVariant( "Production" );
237
238 // Set base exclude from simulation to false
239 fp.SetAttributes( 0 );
240 BOOST_CHECK( !fp.GetExcludedFromSimForVariant( wxEmptyString ) );
241 BOOST_CHECK( !fp.GetExcludedFromSimForVariant( "Production" ) );
242
243 // Set base exclude from simulation to true
245 BOOST_CHECK( fp.GetExcludedFromSimForVariant( wxEmptyString ) );
246 BOOST_CHECK( fp.GetExcludedFromSimForVariant( "Production" ) );
247
248 // Override for Production variant
249 FOOTPRINT_VARIANT prodVariant( "Production" );
250 prodVariant.SetExcludedFromSim( false );
251 fp.SetVariant( prodVariant );
252
253 BOOST_CHECK( fp.GetExcludedFromSimForVariant( wxEmptyString ) );
254 BOOST_CHECK( !fp.GetExcludedFromSimForVariant( "Production" ) );
255}
256
257
261BOOST_AUTO_TEST_CASE( FootprintPosFileExclusionForVariant )
262{
263 BOARD board;
264 FOOTPRINT fp( &board );
265
266 board.AddVariant( "Production" );
267
268 // Set base exclude from position files to false
269 fp.SetAttributes( 0 );
270 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( wxEmptyString ) );
271 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "Production" ) );
272
273 // Set base exclude from position files to true
275 BOOST_CHECK( fp.GetExcludedFromPosFilesForVariant( wxEmptyString ) );
276 BOOST_CHECK( fp.GetExcludedFromPosFilesForVariant( "Production" ) );
277
278 // Override for Production variant
279 FOOTPRINT_VARIANT prodVariant( "Production" );
280 prodVariant.SetExcludedFromPosFiles( false );
281 fp.SetVariant( prodVariant );
282
283 BOOST_CHECK( fp.GetExcludedFromPosFilesForVariant( wxEmptyString ) );
284 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "Production" ) );
285}
286
287
291BOOST_AUTO_TEST_CASE( VariantCaseInsensitivity )
292{
293 BOARD board;
294 FOOTPRINT fp( &board );
295
296 board.AddVariant( "Production" );
297
298 // Set variant override with specific case
299 FOOTPRINT_VARIANT prodVariant( "Production" );
300 prodVariant.SetDNP( true );
301 fp.SetVariant( prodVariant );
302
303 // Access with different cases - all should find the same variant
304 BOOST_CHECK( fp.GetDNPForVariant( "Production" ) );
305 BOOST_CHECK( fp.GetDNPForVariant( "production" ) );
306 BOOST_CHECK( fp.GetDNPForVariant( "PRODUCTION" ) );
307 BOOST_CHECK( fp.GetDNPForVariant( "PrOdUcTiOn" ) );
308
309 // Board operations should also be case insensitive
310 BOOST_CHECK( board.HasVariant( "production" ) );
311 BOOST_CHECK( board.HasVariant( "PRODUCTION" ) );
312}
313
314
318BOOST_AUTO_TEST_CASE( EmptyVariantReturnsBase )
319{
320 BOARD board;
321 FOOTPRINT fp( &board );
322
323 // Set base DNP
324 fp.SetAttributes( FP_DNP );
325
326 // Empty variant should return base
327 BOOST_CHECK( fp.GetDNPForVariant( wxEmptyString ) );
328
329 // Add a variant but still check empty
330 board.AddVariant( "Production" );
331
332 FOOTPRINT_VARIANT prodVariant( "Production" );
333 prodVariant.SetDNP( false );
334 fp.SetVariant( prodVariant );
335
336 // Empty still returns base
337 BOOST_CHECK( fp.GetDNPForVariant( wxEmptyString ) );
338 BOOST_CHECK( !fp.GetDNPForVariant( "Production" ) );
339}
340
341
345BOOST_AUTO_TEST_CASE( UnknownVariantReturnsBase )
346{
347 BOARD board;
348 FOOTPRINT fp( &board );
349
350 // Set base DNP
351 fp.SetAttributes( FP_DNP );
352
353 board.AddVariant( "Production" );
354
355 FOOTPRINT_VARIANT prodVariant( "Production" );
356 prodVariant.SetDNP( false );
357 fp.SetVariant( prodVariant );
358
359 // Unknown variant should return base
360 BOOST_CHECK( fp.GetDNPForVariant( "NonExistentVariant" ) );
361 BOOST_CHECK( fp.GetDNPForVariant( "Debug" ) );
362}
363
364
365BOOST_AUTO_TEST_CASE( FootprintVariantCopyAssignment )
366{
367 BOARD board;
368 FOOTPRINT original( &board );
369
370 original.SetReference( "R1" );
371 original.SetValue( "10K" );
372
373 FOOTPRINT_VARIANT prodVariant( "Production" );
374 prodVariant.SetDNP( true );
375 prodVariant.SetExcludedFromBOM( true );
376 prodVariant.SetExcludedFromSim( true );
377 prodVariant.SetFieldValue( original.Value().GetName(), "22K" );
378 original.SetVariant( prodVariant );
379
380 FOOTPRINT copy( original );
381 const FOOTPRINT_VARIANT* copyVariant = copy.GetVariant( "Production" );
382 BOOST_REQUIRE( copyVariant );
383 BOOST_CHECK( copyVariant->GetDNP() );
384 BOOST_CHECK( copyVariant->GetExcludedFromBOM() );
385 BOOST_CHECK( copyVariant->GetExcludedFromSim() );
386 BOOST_CHECK_EQUAL( copyVariant->GetFieldValue( original.Value().GetName() ), "22K" );
387
388 FOOTPRINT assigned( &board );
389 assigned = original;
390
391 const FOOTPRINT_VARIANT* assignedVariant = assigned.GetVariant( "Production" );
392 BOOST_REQUIRE( assignedVariant );
393 BOOST_CHECK( assignedVariant->GetDNP() );
394 BOOST_CHECK( assignedVariant->GetExcludedFromBOM() );
395 BOOST_CHECK( assignedVariant->GetExcludedFromSim() );
396 BOOST_CHECK_EQUAL( assignedVariant->GetFieldValue( original.Value().GetName() ), "22K" );
397}
398
399
400BOOST_AUTO_TEST_CASE( FootprintFieldShownTextForVariant )
401{
402 BOARD board;
403 FOOTPRINT fp( &board );
404
405 board.AddVariant( "Production" );
406 board.SetCurrentVariant( "Production" );
407
408 fp.SetValue( "10K" );
409
410 FOOTPRINT_VARIANT prodVariant( "Production" );
411 prodVariant.SetFieldValue( fp.Value().GetName(), "22K" );
412 fp.SetVariant( prodVariant );
413
415
416 board.SetCurrentVariant( wxEmptyString );
418}
419
420
421BOOST_AUTO_TEST_CASE( BoardVariantTextVars )
422{
423 BOARD board;
424
425 board.AddVariant( "Production" );
426 board.SetVariantDescription( "Production", "Production build" );
427 board.SetCurrentVariant( "Production" );
428
429 wxString variantToken = wxT( "VARIANT" );
430 BOOST_CHECK( board.ResolveTextVar( &variantToken, 0 ) );
431 BOOST_CHECK_EQUAL( variantToken, "Production" );
432
433 wxString descToken = wxT( "VARIANT_DESC" );
434 BOOST_CHECK( board.ResolveTextVar( &descToken, 0 ) );
435 BOOST_CHECK_EQUAL( descToken, "Production build" );
436
437 board.SetCurrentVariant( wxEmptyString );
438 wxString defaultToken = wxT( "VARIANT" );
439 BOOST_CHECK( board.ResolveTextVar( &defaultToken, 0 ) );
440 BOOST_CHECK( defaultToken.IsEmpty() );
441}
442
443
444BOOST_AUTO_TEST_CASE( NetlistComponentVariantsParsing )
445{
446 const std::string netlist =
447 "(export (version 1)\n"
448 " (components\n"
449 " (comp (ref R1)\n"
450 " (value 10K)\n"
451 " (footprint Resistor_SMD:R_0603_1608Metric)\n"
452 " (libsource (lib Device) (part R))\n"
453 " (variants\n"
454 " (variant (name Alt)\n"
455 " (property (name dnp) (value 1))\n"
456 " (property (name exclude_from_bom) (value 0))\n"
457 " (property (name exclude_from_sim) (value 1))\n"
458 " (fields\n"
459 " (field (name Value) \"22K\")\n"
460 " (field (name Footprint) \"Resistor_SMD:R_0805_2012Metric\")\n"
461 " )\n"
462 " )\n"
463 " )\n"
464 " )\n"
465 " )\n"
466 ")\n";
467
468 STRING_LINE_READER reader( netlist, wxT( "variant_netlist" ) );
469 NETLIST parsedNetlist;
470 KICAD_NETLIST_PARSER parser( &reader, &parsedNetlist );
471
472 parser.Parse();
473
474 BOOST_REQUIRE_EQUAL( parsedNetlist.GetCount(), 1 );
475
476 COMPONENT* component = parsedNetlist.GetComponent( 0 );
477 const COMPONENT_VARIANT* variant = component->GetVariant( "Alt" );
478
479 BOOST_REQUIRE( variant );
480 BOOST_CHECK( variant->m_hasDnp );
481 BOOST_CHECK( variant->m_dnp );
482 BOOST_CHECK( variant->m_hasExcludedFromBOM );
483 BOOST_CHECK( !variant->m_excludedFromBOM );
484 BOOST_CHECK( variant->m_hasExcludedFromSim );
485 BOOST_CHECK( variant->m_excludedFromSim );
486
487 auto valueIt = variant->m_fields.find( "Value" );
488 BOOST_CHECK( valueIt != variant->m_fields.end() );
489 BOOST_CHECK_EQUAL( valueIt->second, "22K" );
490
491 auto fpIt = variant->m_fields.find( "Footprint" );
492 BOOST_CHECK( fpIt != variant->m_fields.end() );
493 BOOST_CHECK_EQUAL( fpIt->second, "Resistor_SMD:R_0805_2012Metric" );
494}
495
496
500BOOST_AUTO_TEST_CASE( MultipleVariantsIndependent )
501{
502 BOARD board;
503
504 // Add multiple variants
505 board.AddVariant( "Variant1" );
506 board.AddVariant( "Variant2" );
507 board.SetVariantDescription( "Variant1", "First variant" );
508 board.SetVariantDescription( "Variant2", "Second variant" );
509
510 // Create footprint and set variant-specific properties
511 FOOTPRINT fp( &board );
512 fp.SetReference( "R1" );
513 fp.SetValue( "10K" );
514
515 FOOTPRINT_VARIANT variant1( "Variant1" );
516 variant1.SetDNP( true );
517 variant1.SetFieldValue( fp.Value().GetName(), "22K" );
518 fp.SetVariant( variant1 );
519
520 FOOTPRINT_VARIANT variant2( "Variant2" );
521 variant2.SetDNP( false );
522 variant2.SetFieldValue( fp.Value().GetName(), "47K" );
523 fp.SetVariant( variant2 );
524
525 // Verify both variants are independent
526 BOOST_CHECK( fp.GetDNPForVariant( "Variant1" ) );
527 BOOST_CHECK( !fp.GetDNPForVariant( "Variant2" ) );
528
529 const FOOTPRINT_VARIANT* v1 = fp.GetVariant( "Variant1" );
530 const FOOTPRINT_VARIANT* v2 = fp.GetVariant( "Variant2" );
531
532 BOOST_REQUIRE( v1 );
533 BOOST_REQUIRE( v2 );
534
535 BOOST_CHECK_EQUAL( v1->GetFieldValue( fp.Value().GetName() ), "22K" );
536 BOOST_CHECK_EQUAL( v2->GetFieldValue( fp.Value().GetName() ), "47K" );
537
538 // Verify descriptions are independent
539 BOOST_CHECK_EQUAL( board.GetVariantDescription( "Variant1" ), "First variant" );
540 BOOST_CHECK_EQUAL( board.GetVariantDescription( "Variant2" ), "Second variant" );
541}
542
543
547BOOST_AUTO_TEST_CASE( VariantFieldUnicodeAndSpecialChars )
548{
549 BOARD board;
550 FOOTPRINT fp( &board );
551
552 board.AddVariant( "UnicodeTest" );
553
554 fp.SetValue( "Default" );
555
556 // Unicode characters
557 FOOTPRINT_VARIANT unicodeVariant( "UnicodeTest" );
558 wxString unicodeValue = wxT( "1kΩ ±5% 日本語" );
559 unicodeVariant.SetFieldValue( fp.Value().GetName(), unicodeValue );
560 fp.SetVariant( unicodeVariant );
561
562 const FOOTPRINT_VARIANT* retrieved = fp.GetVariant( "UnicodeTest" );
563 BOOST_REQUIRE( retrieved );
564 BOOST_CHECK_EQUAL( retrieved->GetFieldValue( fp.Value().GetName() ), unicodeValue );
565
566 // Special characters
567 FOOTPRINT_VARIANT specialVariant( "SpecialChars" );
568 wxString specialChars = wxT( "R<1K>\"test\"'value'" );
569 specialVariant.SetFieldValue( fp.Value().GetName(), specialChars );
570 fp.SetVariant( specialVariant );
571
572 const FOOTPRINT_VARIANT* retrievedSpecial = fp.GetVariant( "SpecialChars" );
573 BOOST_REQUIRE( retrievedSpecial );
574 BOOST_CHECK_EQUAL( retrievedSpecial->GetFieldValue( fp.Value().GetName() ), specialChars );
575
576 // Unicode in variant description
577 wxString unicodeDesc = wxT( "Variante für Produktion — 测试" );
578 board.SetVariantDescription( "UnicodeTest", unicodeDesc );
579 BOOST_CHECK_EQUAL( board.GetVariantDescription( "UnicodeTest" ), unicodeDesc );
580}
581
582
586BOOST_AUTO_TEST_CASE( VariantDeletionClearsRegistry )
587{
588 BOARD board;
589
590 board.AddVariant( "Variant1" );
591 board.AddVariant( "Variant2" );
592 board.SetVariantDescription( "Variant1", "Description 1" );
593 board.SetCurrentVariant( "Variant1" );
594
595 BOOST_CHECK_EQUAL( board.GetVariantNames().size(), 2 );
596 BOOST_CHECK( board.HasVariant( "Variant1" ) );
597
598 // Delete the variant
599 board.DeleteVariant( "Variant1" );
600
601 // Verify it's gone
602 BOOST_CHECK_EQUAL( board.GetVariantNames().size(), 1 );
603 BOOST_CHECK( !board.HasVariant( "Variant1" ) );
604 BOOST_CHECK( board.HasVariant( "Variant2" ) );
605
606 // Current variant should be cleared since we deleted the current one
607 BOOST_CHECK( board.GetCurrentVariant().IsEmpty() || board.GetCurrentVariant() != "Variant1" );
608}
609
610
614BOOST_AUTO_TEST_CASE( RenameVariantPreservesData )
615{
616 BOARD board;
617
618 board.AddVariant( "OldName" );
619 board.SetVariantDescription( "OldName", "Test description" );
620 board.SetCurrentVariant( "OldName" );
621
622 // Rename the variant
623 board.RenameVariant( "OldName", "NewName" );
624
625 // Old name should be gone
626 BOOST_CHECK( !board.HasVariant( "OldName" ) );
627
628 // New name should exist with same properties
629 BOOST_CHECK( board.HasVariant( "NewName" ) );
630 BOOST_CHECK_EQUAL( board.GetVariantDescription( "NewName" ), "Test description" );
631
632 // Current variant should be updated if it was the renamed one
633 BOOST_CHECK_EQUAL( board.GetCurrentVariant(), "NewName" );
634}
635
636
640BOOST_AUTO_TEST_CASE( GetVariantNamesForUIFormat )
641{
642 BOARD board;
643
644 board.AddVariant( "Zebra" );
645 board.AddVariant( "Alpha" );
646 board.AddVariant( "Beta" );
647
648 wxArrayString names = board.GetVariantNamesForUI();
649
650 // Should have 4 entries (default + 3 variants)
651 BOOST_CHECK( names.GetCount() >= 4 );
652
653 // First should be the default placeholder
654 BOOST_CHECK( !names[0].IsEmpty() );
655
656 // Remaining should be sorted alphabetically
657 bool foundAlpha = false;
658 bool foundBeta = false;
659 bool foundZebra = false;
660
661 for( size_t i = 1; i < names.GetCount(); i++ )
662 {
663 if( names[i] == wxT( "Alpha" ) )
664 foundAlpha = true;
665 else if( names[i] == wxT( "Beta" ) )
666 foundBeta = true;
667 else if( names[i] == wxT( "Zebra" ) )
668 foundZebra = true;
669 }
670
671 BOOST_CHECK( foundAlpha );
672 BOOST_CHECK( foundBeta );
673 BOOST_CHECK( foundZebra );
674}
675
676
680BOOST_AUTO_TEST_CASE( VariantMultipleFlagsCombinations )
681{
682 BOARD board;
683 FOOTPRINT fp( &board );
684
685 board.AddVariant( "DNPOnly" );
686 board.AddVariant( "BOMOnly" );
687 board.AddVariant( "AllFlags" );
688 board.AddVariant( "NoFlags" );
689
690 // Set various flag combinations
691 FOOTPRINT_VARIANT dnpOnly( "DNPOnly" );
692 dnpOnly.SetDNP( true );
693 dnpOnly.SetExcludedFromBOM( false );
694 dnpOnly.SetExcludedFromSim( false );
695 dnpOnly.SetExcludedFromPosFiles( false );
696 fp.SetVariant( dnpOnly );
697
698 FOOTPRINT_VARIANT bomOnly( "BOMOnly" );
699 bomOnly.SetDNP( false );
700 bomOnly.SetExcludedFromBOM( true );
701 bomOnly.SetExcludedFromSim( false );
702 bomOnly.SetExcludedFromPosFiles( false );
703 fp.SetVariant( bomOnly );
704
705 FOOTPRINT_VARIANT allFlags( "AllFlags" );
706 allFlags.SetDNP( true );
707 allFlags.SetExcludedFromBOM( true );
708 allFlags.SetExcludedFromSim( true );
709 allFlags.SetExcludedFromPosFiles( true );
710 fp.SetVariant( allFlags );
711
712 FOOTPRINT_VARIANT noFlags( "NoFlags" );
713 noFlags.SetDNP( false );
714 noFlags.SetExcludedFromBOM( false );
715 noFlags.SetExcludedFromSim( false );
716 noFlags.SetExcludedFromPosFiles( false );
717 fp.SetVariant( noFlags );
718
719 // Verify each variant has correct flags
720 BOOST_CHECK( fp.GetDNPForVariant( "DNPOnly" ) );
721 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( "DNPOnly" ) );
722 BOOST_CHECK( !fp.GetExcludedFromSimForVariant( "DNPOnly" ) );
723 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "DNPOnly" ) );
724
725 BOOST_CHECK( !fp.GetDNPForVariant( "BOMOnly" ) );
726 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( "BOMOnly" ) );
727 BOOST_CHECK( !fp.GetExcludedFromSimForVariant( "BOMOnly" ) );
728 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "BOMOnly" ) );
729
730 BOOST_CHECK( fp.GetDNPForVariant( "AllFlags" ) );
731 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( "AllFlags" ) );
732 BOOST_CHECK( fp.GetExcludedFromSimForVariant( "AllFlags" ) );
733 BOOST_CHECK( fp.GetExcludedFromPosFilesForVariant( "AllFlags" ) );
734
735 BOOST_CHECK( !fp.GetDNPForVariant( "NoFlags" ) );
736 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( "NoFlags" ) );
737 BOOST_CHECK( !fp.GetExcludedFromSimForVariant( "NoFlags" ) );
738 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "NoFlags" ) );
739}
740
741
746BOOST_AUTO_TEST_CASE( ComponentVariantToFootprintTransfer )
747{
748 BOARD board;
749 FOOTPRINT fp( &board );
750
751 fp.SetReference( "R1" );
752 fp.SetValue( "10K" );
753
754 board.AddVariant( "Variant A" );
755 board.AddVariant( "Variant B" );
756
757 // Create a COMPONENT with variant data (simulating schematic data)
758 LIB_ID fpid( wxT( "Resistor_SMD" ), wxT( "R_0805_2012Metric" ) );
759 wxString reference = wxT( "R1" );
760 wxString value = wxT( "10K" );
762 std::vector<KIID> kiids;
763
764 COMPONENT component( fpid, reference, value, path, kiids );
765
766 // Add variant "Variant A" with DNP=true, ExcludedFromBOM=false, ExcludedFromPosFiles=true
767 // and a field override for Datasheet
768 COMPONENT_VARIANT variantA( "Variant A" );
769 variantA.m_dnp = true;
770 variantA.m_hasDnp = true;
771 variantA.m_excludedFromBOM = false;
772 variantA.m_hasExcludedFromBOM = true;
773 variantA.m_excludedFromSim = true;
774 variantA.m_hasExcludedFromSim = true;
775 variantA.m_excludedFromPosFiles = true;
776 variantA.m_hasExcludedFromPosFiles = true;
777 variantA.m_fields[wxT( "Datasheet" )] = wxT( "https://example.com/datasheet.pdf" );
778 component.AddVariant( variantA );
779
780 // Add variant "Variant B" with DNP=false, ExcludedFromBOM=true
781 COMPONENT_VARIANT variantB( "Variant B" );
782 variantB.m_dnp = false;
783 variantB.m_hasDnp = true;
784 variantB.m_excludedFromBOM = true;
785 variantB.m_hasExcludedFromBOM = true;
786 variantB.m_excludedFromSim = false;
787 variantB.m_hasExcludedFromSim = true;
788 variantB.m_excludedFromPosFiles = false;
789 variantB.m_hasExcludedFromPosFiles = true;
790 variantB.m_fields[wxT( "Value" )] = wxT( "22K" );
791 component.AddVariant( variantB );
792
793 // Transfer variant data from COMPONENT to FOOTPRINT (simulating applyComponentVariants)
794 for( const auto& [variantName, componentVariant] : component.GetVariants() )
795 {
796 FOOTPRINT_VARIANT* fpVariant = fp.AddVariant( variantName );
797 BOOST_REQUIRE( fpVariant );
798
799 if( componentVariant.m_hasDnp )
800 fpVariant->SetDNP( componentVariant.m_dnp );
801
802 if( componentVariant.m_hasExcludedFromBOM )
803 fpVariant->SetExcludedFromBOM( componentVariant.m_excludedFromBOM );
804
805 if( componentVariant.m_hasExcludedFromSim )
806 fpVariant->SetExcludedFromSim( componentVariant.m_excludedFromSim );
807
808 if( componentVariant.m_hasExcludedFromPosFiles )
809 fpVariant->SetExcludedFromPosFiles( componentVariant.m_excludedFromPosFiles );
810
811 for( const auto& [fieldName, fieldValue] : componentVariant.m_fields )
812 fpVariant->SetFieldValue( fieldName, fieldValue );
813 }
814
815 // Verify Variant A properties
816 const FOOTPRINT_VARIANT* fpVariantA = fp.GetVariant( "Variant A" );
817 BOOST_REQUIRE( fpVariantA );
818 BOOST_CHECK( fpVariantA->GetDNP() );
819 BOOST_CHECK( !fpVariantA->GetExcludedFromBOM() );
820 BOOST_CHECK( fpVariantA->GetExcludedFromSim() );
821 BOOST_CHECK( fpVariantA->GetExcludedFromPosFiles() );
822 BOOST_CHECK( fpVariantA->HasFieldValue( wxT( "Datasheet" ) ) );
823 BOOST_CHECK_EQUAL( fpVariantA->GetFieldValue( wxT( "Datasheet" ) ),
824 wxT( "https://example.com/datasheet.pdf" ) );
825
826 // Verify Variant B properties
827 const FOOTPRINT_VARIANT* fpVariantB = fp.GetVariant( "Variant B" );
828 BOOST_REQUIRE( fpVariantB );
829 BOOST_CHECK( !fpVariantB->GetDNP() );
830 BOOST_CHECK( fpVariantB->GetExcludedFromBOM() );
831 BOOST_CHECK( !fpVariantB->GetExcludedFromSim() );
832 BOOST_CHECK( !fpVariantB->GetExcludedFromPosFiles() );
833 BOOST_CHECK( fpVariantB->HasFieldValue( wxT( "Value" ) ) );
834 BOOST_CHECK_EQUAL( fpVariantB->GetFieldValue( wxT( "Value" ) ), wxT( "22K" ) );
835
836 // Verify variant-aware getters work
837 BOOST_CHECK( fp.GetDNPForVariant( "Variant A" ) );
838 BOOST_CHECK( !fp.GetDNPForVariant( "Variant B" ) );
839 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( "Variant A" ) );
840 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( "Variant B" ) );
841 BOOST_CHECK( fp.GetExcludedFromSimForVariant( "Variant A" ) );
842 BOOST_CHECK( !fp.GetExcludedFromSimForVariant( "Variant B" ) );
843 BOOST_CHECK( fp.GetExcludedFromPosFilesForVariant( "Variant A" ) );
844 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "Variant B" ) );
845}
846
847
853BOOST_AUTO_TEST_CASE( ComponentVariantPartialOverride )
854{
855 BOARD board;
856 FOOTPRINT fp( &board );
857
858 fp.SetReference( "R1" );
859
860 // Set base footprint to have all attributes false
861 fp.SetDNP( false );
862 fp.SetExcludedFromBOM( false );
863 fp.SetExcludedFromSim( false );
864 fp.SetExcludedFromPosFiles( false );
865
866 board.AddVariant( "TestVariant" );
867
868 // Pre-populate the footprint variant with all true values (simulating old state)
869 FOOTPRINT_VARIANT initialVariant( "TestVariant" );
870 initialVariant.SetDNP( true );
871 initialVariant.SetExcludedFromBOM( true );
872 initialVariant.SetExcludedFromSim( true );
873 initialVariant.SetExcludedFromPosFiles( true );
874 fp.SetVariant( initialVariant );
875
876 // Create a component variant that only has explicit DNP override set
877 LIB_ID fpid( wxT( "Resistor_SMD" ), wxT( "R_0805_2012Metric" ) );
879 std::vector<KIID> kiids;
880 COMPONENT component( fpid, wxT( "R1" ), wxT( "10K" ), path, kiids );
881
882 COMPONENT_VARIANT partialVariant( "TestVariant" );
883 partialVariant.m_dnp = true;
884 partialVariant.m_hasDnp = true;
885 // The remaining m_has* flags are false (no explicit override)
886 component.AddVariant( partialVariant );
887
888 // Transfer properties, resetting non-overridden ones to base footprint values
889 for( const auto& [variantName, componentVariant] : component.GetVariants() )
890 {
891 FOOTPRINT_VARIANT* fpVariant = fp.GetVariant( variantName );
892 BOOST_REQUIRE( fpVariant );
893
894 // Apply explicit override or reset to base footprint value
895 bool targetDnp = componentVariant.m_hasDnp ? componentVariant.m_dnp : fp.IsDNP();
896 fpVariant->SetDNP( targetDnp );
897
898 bool targetBOM = componentVariant.m_hasExcludedFromBOM
899 ? componentVariant.m_excludedFromBOM
900 : fp.IsExcludedFromBOM();
901 fpVariant->SetExcludedFromBOM( targetBOM );
902
903 bool targetSim = componentVariant.m_hasExcludedFromSim
904 ? componentVariant.m_excludedFromSim
905 : fp.IsExcludedFromSim();
906 fpVariant->SetExcludedFromSim( targetSim );
907
908 bool targetPos = componentVariant.m_hasExcludedFromPosFiles
909 ? componentVariant.m_excludedFromPosFiles
911 fpVariant->SetExcludedFromPosFiles( targetPos );
912 }
913
914 // Verify: DNP should be true (m_hasDnp was true with value true)
915 BOOST_CHECK( fp.GetDNPForVariant( "TestVariant" ) );
916
917 // Verify: ExcludedFromBOM should be reset to base (false)
918 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( "TestVariant" ) );
919
920 // Verify: ExcludedFromSim should be reset to base (false)
921 BOOST_CHECK( !fp.GetExcludedFromSimForVariant( "TestVariant" ) );
922
923 // Verify: ExcludedFromPosFiles should be reset to base (false)
924 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "TestVariant" ) );
925}
926
927
932BOOST_AUTO_TEST_CASE( VariantAttributeTransferWithReset )
933{
934 BOARD board;
935 FOOTPRINT fp( &board );
936 fp.SetReference( "R1" );
937 fp.SetFPID( LIB_ID( wxT( "Resistor_SMD" ), wxT( "R_0805" ) ) );
938
939 // Base footprint has no flags set
940 fp.SetDNP( false );
941 fp.SetExcludedFromBOM( false );
942 fp.SetExcludedFromSim( false );
943 fp.SetExcludedFromPosFiles( false );
944
945 board.AddVariant( "Variant A" );
946
947 // Step 1: Initial state - variant has all attributes set (simulating previous netlist update)
948 FOOTPRINT_VARIANT* fpVariant = fp.AddVariant( "Variant A" );
949 BOOST_REQUIRE( fpVariant );
950 fpVariant->SetDNP( true );
951 fpVariant->SetExcludedFromBOM( true );
952 fpVariant->SetExcludedFromSim( true );
953 fpVariant->SetExcludedFromPosFiles( true );
954
955 // Verify initial state
956 BOOST_CHECK( fp.GetDNPForVariant( "Variant A" ) );
957 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( "Variant A" ) );
958 BOOST_CHECK( fp.GetExcludedFromSimForVariant( "Variant A" ) );
959 BOOST_CHECK( fp.GetExcludedFromPosFilesForVariant( "Variant A" ) );
960
961 // Step 2: New netlist has variant with NO explicit attribute overrides
962 // This simulates the user removing all variant attribute overrides from schematic
963 COMPONENT_VARIANT componentVariant( "Variant A" );
964 // All m_has* flags are false by default (no explicit overrides)
965
966 // Step 3: Apply the fixed transfer logic (same as board_netlist_updater::applyComponentVariants)
967 // For isActive = true case with no explicit overrides, attributes should reset to base
968 bool targetDnp = componentVariant.m_hasDnp ? componentVariant.m_dnp : fp.IsDNP();
969 bool targetBOM = componentVariant.m_hasExcludedFromBOM ? componentVariant.m_excludedFromBOM
970 : fp.IsExcludedFromBOM();
971 bool targetSim = componentVariant.m_hasExcludedFromSim ? componentVariant.m_excludedFromSim
972 : fp.IsExcludedFromSim();
973 bool targetPos = componentVariant.m_hasExcludedFromPosFiles
974 ? componentVariant.m_excludedFromPosFiles
976
977 fpVariant->SetDNP( targetDnp );
978 fpVariant->SetExcludedFromBOM( targetBOM );
979 fpVariant->SetExcludedFromSim( targetSim );
980 fpVariant->SetExcludedFromPosFiles( targetPos );
981
982 // Step 4: Verify all attributes were reset to base footprint values (false)
983 BOOST_CHECK_MESSAGE( !fp.GetDNPForVariant( "Variant A" ),
984 "DNP should be reset to base value (false) when no explicit override" );
985 BOOST_CHECK_MESSAGE( !fp.GetExcludedFromBOMForVariant( "Variant A" ),
986 "ExcludedFromBOM should be reset to base value (false) when no override" );
987 BOOST_CHECK_MESSAGE( !fp.GetExcludedFromSimForVariant( "Variant A" ),
988 "ExcludedFromSim should be reset to base value (false) when no override" );
989 BOOST_CHECK_MESSAGE( !fp.GetExcludedFromPosFilesForVariant( "Variant A" ),
990 "ExcludedFromPosFiles should be reset to base (false) when no override" );
991}
992
993
1005BOOST_AUTO_TEST_CASE( VariantTestR2FootprintAttributeVerification )
1006{
1007 wxString dataPath = KI_TEST::GetPcbnewTestDataDir() + wxString( "variant_test/variant_test.kicad_pcb" );
1008
1009 PCB_IO_KICAD_SEXPR pcbIo;
1010 std::unique_ptr<BOARD> board = pcbIo.LoadBoard( dataPath );
1011
1012 BOOST_REQUIRE( board );
1013 BOOST_REQUIRE( board->HasVariant( "Variant A" ) );
1014
1015 // Find both R2 footprints and verify their variant data
1016 FOOTPRINT* r2_c1210 = nullptr; // Variant A's footprint (C_1210_3225Metric)
1017 FOOTPRINT* r2_c3640 = nullptr; // Base footprint (C_3640_9110Metric)
1018
1019 for( FOOTPRINT* fp : board->Footprints() )
1020 {
1021 if( fp->GetReference() == wxT( "R2" ) )
1022 {
1023 wxString fpName = fp->GetFPID().GetLibItemName();
1024
1025 if( fpName.Contains( wxT( "C_1210" ) ) )
1026 r2_c1210 = fp;
1027 else if( fpName.Contains( wxT( "C_3640" ) ) )
1028 r2_c3640 = fp;
1029 }
1030 }
1031
1032 // Verify we found both R2 footprints
1033 BOOST_TEST_MESSAGE( "Looking for R2 footprints in test data" );
1034 BOOST_REQUIRE_MESSAGE( r2_c1210, "Should find R2 with C_1210 footprint (variant footprint)" );
1035 BOOST_REQUIRE_MESSAGE( r2_c3640, "Should find R2 with C_3640 footprint (base footprint)" );
1036
1037 // Check C_1210 (Variant A's footprint) - this IS the active footprint for Variant A
1038 // The schematic has NO attribute overrides, so the PCB variant should also have no overrides
1039 // (or equivalently, values should match base footprint)
1040 const FOOTPRINT_VARIANT* c1210_variantA = r2_c1210->GetVariant( "Variant A" );
1041
1042 BOOST_TEST_MESSAGE( "R2 C_1210 (variant footprint) base attributes: DNP="
1043 << r2_c1210->IsDNP() << " ExcludedFromBOM=" << r2_c1210->IsExcludedFromBOM()
1044 << " ExcludedFromPosFiles=" << r2_c1210->IsExcludedFromPosFiles() );
1045
1046 if( c1210_variantA )
1047 {
1048 BOOST_TEST_MESSAGE( "R2 C_1210 Variant A attributes: DNP=" << c1210_variantA->GetDNP()
1049 << " ExcludedFromBOM=" << c1210_variantA->GetExcludedFromBOM()
1050 << " ExcludedFromPosFiles=" << c1210_variantA->GetExcludedFromPosFiles() );
1051
1052 // For the variant footprint, since schematic has NO attribute overrides,
1053 // variant attributes should match base footprint values (all false)
1054 BOOST_CHECK_MESSAGE( !c1210_variantA->GetDNP(),
1055 "C_1210 Variant A DNP should be false (no schematic override)" );
1056 BOOST_CHECK_MESSAGE( !c1210_variantA->GetExcludedFromBOM(),
1057 "C_1210 Variant A ExcludedFromBOM should be false (no override)" );
1058 BOOST_CHECK_MESSAGE( !c1210_variantA->GetExcludedFromPosFiles(),
1059 "C_1210 Variant A ExcludedFromPosFiles should be false (no override)" );
1060 }
1061 else
1062 {
1063 BOOST_TEST_MESSAGE( "R2 C_1210 has no Variant A data" );
1064 }
1065
1066 // Check C_3640 (base footprint) - this is NOT the active footprint for Variant A.
1067 // The netlist updater marks non-associated footprints as DNP for each variant where
1068 // they are not the active choice, so C_3640 must have DNP=true for Variant A.
1069 const FOOTPRINT_VARIANT* c3640_variantA = r2_c3640->GetVariant( "Variant A" );
1070
1071 BOOST_TEST_MESSAGE( "R2 C_3640 (base footprint) base attributes: DNP="
1072 << r2_c3640->IsDNP() << " ExcludedFromBOM=" << r2_c3640->IsExcludedFromBOM()
1073 << " ExcludedFromPosFiles=" << r2_c3640->IsExcludedFromPosFiles() );
1074
1075 BOOST_REQUIRE_MESSAGE( c3640_variantA,
1076 "C_3640 must have Variant A data to hide it when Variant A is active" );
1077
1078 BOOST_TEST_MESSAGE( "R2 C_3640 Variant A attributes: DNP=" << c3640_variantA->GetDNP()
1079 << " ExcludedFromBOM=" << c3640_variantA->GetExcludedFromBOM()
1080 << " ExcludedFromPosFiles=" << c3640_variantA->GetExcludedFromPosFiles() );
1081
1082 // C_3640 is NOT the active footprint for Variant A (C_1210 is), so it must be
1083 // marked DNP for Variant A so the 3D viewer and other consumers hide it correctly.
1084 BOOST_CHECK_MESSAGE( c3640_variantA->GetDNP(),
1085 "C_3640 Variant A DNP should be true (it is not active for Variant A)" );
1086}
1087
1088
1092BOOST_AUTO_TEST_CASE( VariantTestProjectLoad )
1093{
1094 wxString dataPath = KI_TEST::GetPcbnewTestDataDir() + wxString( "variant_test/variant_test.kicad_pcb" );
1095
1096 PCB_IO_KICAD_SEXPR pcbIo;
1097 std::unique_ptr<BOARD> board = pcbIo.LoadBoard( dataPath );
1098
1099 BOOST_REQUIRE( board );
1100
1101 // Verify the board has the "Variant A" variant registered
1102 BOOST_CHECK( board->HasVariant( "Variant A" ) );
1103
1104 // Find footprints and verify their variant properties
1105 // Based on the variant_test.kicad_pcb and schematic:
1106 // - R1 with Variant A having field override for Datasheet
1107 // - R2 (C_3640 base footprint) with Variant A having NO attribute overrides
1108 // (schematic has no explicit overrides, so PCB mirrors base values)
1109 // - R3 with Variant A having DNP (explicit schematic override)
1110
1111 for( FOOTPRINT* fp : board->Footprints() )
1112 {
1113 const wxString& ref = fp->GetReference();
1114
1115 if( ref == wxT( "R1" ) )
1116 {
1117 const FOOTPRINT_VARIANT* variantA = fp->GetVariant( "Variant A" );
1118
1119 if( variantA )
1120 {
1121 // R1 has a Datasheet field override in Variant A
1122 BOOST_CHECK( variantA->HasFieldValue( wxT( "Datasheet" ) ) );
1123 BOOST_CHECK_EQUAL( variantA->GetFieldValue( wxT( "Datasheet" ) ), wxT( "test" ) );
1124 }
1125 }
1126 else if( ref == wxT( "R2" ) )
1127 {
1128 wxString fpName = fp->GetFPID().GetLibItemName();
1129
1130 if( fpName.Contains( wxT( "C_3640" ) ) )
1131 {
1132 // C_3640 is the base (default) footprint for R2. When Variant A is active,
1133 // C_1210 is used instead, so C_3640 must be DNP for Variant A.
1134 const FOOTPRINT_VARIANT* variantA = fp->GetVariant( "Variant A" );
1135 BOOST_REQUIRE( variantA );
1136 BOOST_CHECK( variantA->GetDNP() );
1137 }
1138 else if( fpName.Contains( wxT( "C_1210" ) ) )
1139 {
1140 // C_1210 is the Variant A footprint for R2. It must be globally DNP
1141 // (so it's hidden in the default variant) and its Variant A entry must
1142 // be non-DNP (so it's visible when Variant A is active).
1143 BOOST_CHECK( fp->IsDNP() );
1144 const FOOTPRINT_VARIANT* variantA = fp->GetVariant( "Variant A" );
1145 BOOST_REQUIRE( variantA );
1146 BOOST_CHECK( !variantA->GetDNP() );
1147 }
1148 }
1149 else if( ref == wxT( "R3" ) )
1150 {
1151 const FOOTPRINT_VARIANT* variantA = fp->GetVariant( "Variant A" );
1152
1153 if( variantA )
1154 {
1155 // R3 has DNP in Variant A (explicit schematic override)
1156 BOOST_CHECK( variantA->GetDNP() );
1157 }
1158 }
1159 }
1160}
1161
1162
1169BOOST_AUTO_TEST_CASE( PosExportVariantValue )
1170{
1171 BOARD board;
1172
1173 board.AddVariant( "AltPop" );
1174
1175 FOOTPRINT* fp = new FOOTPRINT( &board );
1176 fp->SetReference( "R1" );
1177 fp->SetValue( "10K" );
1178
1179 FOOTPRINT_VARIANT altPopVariant( "AltPop" );
1180 altPopVariant.SetFieldValue( fp->Value().GetName(), "22K" );
1181 fp->SetVariant( altPopVariant );
1182
1183 board.Add( fp, ADD_MODE::INSERT );
1184
1185 auto runExport = [&]( const wxString& aVariant, bool aCsv ) -> std::string
1186 {
1187 PLACE_FILE_EXPORTER exporter( &board,
1188 true, // mm
1189 false, // all footprints
1190 false, // include TH
1191 false, // don't exclude DNP
1192 false, // don't exclude BOM
1193 true, // front
1194 true, // back
1195 aCsv, // format
1196 false, // no aux origin
1197 false ); // don't negate X
1198 exporter.SetVariant( aVariant );
1199 return exporter.GenPositionData();
1200 };
1201
1202 // ASCII format
1203 std::string defaultAscii = runExport( wxEmptyString, false );
1204 BOOST_CHECK( defaultAscii.find( "10K" ) != std::string::npos );
1205 BOOST_CHECK( defaultAscii.find( "22K" ) == std::string::npos );
1206
1207 std::string altPopAscii = runExport( wxS( "AltPop" ), false );
1208 BOOST_CHECK( altPopAscii.find( "22K" ) != std::string::npos );
1209 BOOST_CHECK( altPopAscii.find( "10K" ) == std::string::npos );
1210
1211 // CSV format
1212 std::string defaultCsv = runExport( wxEmptyString, true );
1213 BOOST_CHECK( defaultCsv.find( "10K" ) != std::string::npos );
1214 BOOST_CHECK( defaultCsv.find( "22K" ) == std::string::npos );
1215
1216 std::string altPopCsv = runExport( wxS( "AltPop" ), true );
1217 BOOST_CHECK( altPopCsv.find( "22K" ) != std::string::npos );
1218 BOOST_CHECK( altPopCsv.find( "10K" ) == std::string::npos );
1219
1220 // GenReportData should also respect the variant
1221 auto runReport = [&]( const wxString& aVariant ) -> std::string
1222 {
1223 PLACE_FILE_EXPORTER exporter( &board, true, false, false, false, false,
1224 true, true, false, false, false );
1225 exporter.SetVariant( aVariant );
1226 return exporter.GenReportData();
1227 };
1228
1229 std::string defaultReport = runReport( wxEmptyString );
1230 BOOST_CHECK( defaultReport.find( "10K" ) != std::string::npos );
1231 BOOST_CHECK( defaultReport.find( "22K" ) == std::string::npos );
1232
1233 std::string altPopReport = runReport( wxS( "AltPop" ) );
1234 BOOST_CHECK( altPopReport.find( "22K" ) != std::string::npos );
1235 BOOST_CHECK( altPopReport.find( "10K" ) == std::string::npos );
1236}
1237
1238
1248BOOST_AUTO_TEST_CASE( ExcessVariantsCleanedWhenNetlistEmpty )
1249{
1250 BOARD board;
1251 FOOTPRINT fp( &board );
1252 fp.SetReference( "C1" );
1253 fp.SetFPID( LIB_ID( wxT( "Capacitor_SMD" ), wxT( "C_0805" ) ) );
1254 fp.SetDNP( false );
1255 fp.SetExcludedFromBOM( false );
1256 fp.SetExcludedFromPosFiles( false );
1257
1258 board.AddVariant( "TestVariant" );
1259
1260 // Simulate a previous netlist update that applied DNP=true for this variant
1261 FOOTPRINT_VARIANT* fpVariant = fp.AddVariant( "TestVariant" );
1262 BOOST_REQUIRE( fpVariant );
1263 fpVariant->SetDNP( true );
1264 fpVariant->SetExcludedFromBOM( true );
1265
1266 BOOST_CHECK( fp.GetVariant( "TestVariant" ) != nullptr );
1267 BOOST_CHECK( fp.GetDNPForVariant( "TestVariant" ) );
1268 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( "TestVariant" ) );
1269
1270 // Simulate what applyComponentVariants does when the netlist has no variant
1271 // data for this component (all variant properties now match the base).
1272 // With the fix, the function no longer returns early on empty variants,
1273 // so the excess-variants cleanup runs.
1274
1275 std::set<wxString> excessVariants;
1276
1277 for( const auto& [variantName, _] : fp.GetVariants() )
1278 excessVariants.insert( variantName );
1279
1280 // No netlist variants to process, so nothing is erased from excessVariants.
1281 // All footprint variants are excess.
1282 BOOST_CHECK_EQUAL( excessVariants.size(), 1 );
1283 BOOST_CHECK( excessVariants.count( "TestVariant" ) == 1 );
1284
1285 for( const wxString& excess : excessVariants )
1286 fp.DeleteVariant( excess );
1287
1288 BOOST_CHECK_MESSAGE( fp.GetVariant( "TestVariant" ) == nullptr,
1289 "Stale variant must be removed when netlist has no variant data" );
1290 BOOST_CHECK_MESSAGE( fp.GetVariants().empty(),
1291 "All variants should be cleaned up" );
1292}
1293
1294
1299BOOST_AUTO_TEST_CASE( ExcessVariantsSelectiveCleanup )
1300{
1301 BOARD board;
1302 FOOTPRINT fp( &board );
1303 fp.SetReference( "U1" );
1304 fp.SetFPID( LIB_ID( wxT( "Package_SO" ), wxT( "SOIC-8" ) ) );
1305 fp.SetDNP( false );
1306
1307 board.AddVariant( "Production" );
1308 board.AddVariant( "Debug" );
1309
1310 // Both variants were previously applied to the footprint
1311 FOOTPRINT_VARIANT* prodVariant = fp.AddVariant( "Production" );
1312 BOOST_REQUIRE( prodVariant );
1313 prodVariant->SetDNP( true );
1314
1315 FOOTPRINT_VARIANT* debugVariant = fp.AddVariant( "Debug" );
1316 BOOST_REQUIRE( debugVariant );
1317 debugVariant->SetExcludedFromBOM( true );
1318
1319 BOOST_CHECK_EQUAL( fp.GetVariants().size(), 2 );
1320
1321 // Simulate netlist update where only "Production" has variant data.
1322 // "Debug" variant properties now match the base so it was omitted from the netlist.
1323 std::set<wxString> excessVariants;
1324
1325 for( const auto& [variantName, _] : fp.GetVariants() )
1326 excessVariants.insert( variantName );
1327
1328 // Erase variants that ARE in the netlist
1329 excessVariants.erase( "Production" );
1330
1331 BOOST_CHECK_EQUAL( excessVariants.size(), 1 );
1332 BOOST_CHECK( excessVariants.count( "Debug" ) == 1 );
1333
1334 for( const wxString& excess : excessVariants )
1335 fp.DeleteVariant( excess );
1336
1337 BOOST_CHECK_MESSAGE( fp.GetVariant( "Production" ) != nullptr,
1338 "Production variant should be preserved (in netlist)" );
1339 BOOST_CHECK_MESSAGE( fp.GetVariant( "Debug" ) == nullptr,
1340 "Debug variant should be removed (not in netlist)" );
1341 BOOST_CHECK_EQUAL( fp.GetVariants().size(), 1 );
1342}
1343
1344
1358BOOST_AUTO_TEST_CASE( Issue23298_BaseFootprintHiddenInNonDefaultVariant )
1359{
1360 // The updater dereferences the board's project, so the board needs one.
1361 SETTINGS_MANAGER settingsManager;
1362 settingsManager.LoadProject( "" );
1363
1364 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
1365 board->SetProject( &settingsManager.Prj() );
1366 board->AddVariant( "Variant A" );
1367
1368 LIB_ID baseFpid;
1369 BOOST_REQUIRE_EQUAL( baseFpid.Parse( wxS( "TestLib:C_3640" ) ), -1 );
1370
1371 LIB_ID variantFpid;
1372 BOOST_REQUIRE_EQUAL( variantFpid.Parse( wxS( "TestLib:C_1210" ) ), -1 );
1373
1374 // R2 has one footprint per variant sharing the RefDes: C_3640 for the default variant and
1375 // C_1210 for Variant A. Reproduce the board state the netlist updater starts from.
1376 FOOTPRINT* baseFp = new FOOTPRINT( board.get() );
1377 baseFp->SetReference( "R2" );
1378 baseFp->SetFPID( baseFpid );
1379 board->Add( baseFp );
1380
1381 FOOTPRINT* variantFp = new FOOTPRINT( board.get() );
1382 variantFp->SetReference( "R2" );
1383 variantFp->SetFPID( variantFpid );
1384 board->Add( variantFp );
1385
1386 // Schematic side: R2 with base footprint C_3640 and a Variant A that reassigns C_1210 and keeps
1387 // it populated (the non-base footprint is otherwise flagged globally DNP by the updater). The
1388 // netlist must also declare the variant, otherwise the updater reconciles it away.
1390 netlist.AddVariant( "Variant A" );
1391
1392 COMPONENT* component = new COMPONENT( baseFpid, "R2", "10uF", KIID_PATH(),
1393 std::vector<KIID>{ KIID() } );
1394
1395 COMPONENT_VARIANT variantA( "Variant A" );
1396 variantA.m_fields[GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED )] = variantFpid.Format().wx_str();
1397 variantA.m_dnp = false;
1398 variantA.m_hasDnp = true;
1399 component->AddVariant( variantA );
1400 netlist.AddComponent( component );
1401
1402 TOOL_MANAGER toolMgr;
1403 toolMgr.SetEnvironment( board.get(), nullptr, nullptr, nullptr, nullptr );
1404 toolMgr.RegisterTool( new KI_TEST::DUMMY_TOOL() );
1405
1406 BOARD_NETLIST_UPDATER updater( &toolMgr, board.get() );
1407 updater.SetReplaceFootprints( false );
1408 updater.SetDeleteUnusedFootprints( false );
1409
1410 BOOST_REQUIRE( updater.UpdateNetlist( netlist ) );
1411
1412 // Default variant: base FP visible, variant FP hidden.
1413 BOOST_CHECK_MESSAGE( !baseFp->GetDNPForVariant( wxEmptyString ),
1414 "Base FP must be visible in the default variant" );
1415 BOOST_CHECK_MESSAGE( variantFp->GetDNPForVariant( wxEmptyString ),
1416 "Variant A FP must be hidden in the default variant (globally DNP)" );
1417
1418 // Variant A active: variant FP visible, base FP hidden. The base-FP check is the #23298 fix;
1419 // the variant-FP check guards the single-pass ordering.
1420 BOOST_CHECK_MESSAGE( !variantFp->GetDNPForVariant( wxT( "Variant A" ) ),
1421 "Variant A FP must be visible when Variant A is active" );
1422 BOOST_CHECK_MESSAGE( baseFp->GetDNPForVariant( wxT( "Variant A" ) ),
1423 "Base FP must be hidden when Variant A is active (issue #23298)" );
1424}
1425
1426
General utilities for PCB file IO for QA programs.
Update the BOARD with a new netlist.
bool UpdateNetlist(NETLIST &aNetlist)
Update the board's components according to the new netlist.
void SetDeleteUnusedFootprints(bool aEnabled)
void SetReplaceFootprints(bool aEnabled)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
void SetCurrentVariant(const wxString &aVariant)
Definition board.cpp:3152
const std::vector< wxString > & GetVariantNames() const
Definition board.h:524
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1497
wxArrayString GetVariantNamesForUI() const
Return the variant names for UI display.
Definition board.cpp:3342
void DeleteVariant(const wxString &aVariantName)
Definition board.cpp:3195
bool ResolveTextVar(wxString *token, int aDepth) const
Definition board.cpp:686
bool HasVariant(const wxString &aVariantName) const
Definition board.cpp:3178
void AddVariant(const wxString &aVariantName)
Definition board.cpp:3184
wxString GetVariantDescription(const wxString &aVariantName) const
Definition board.cpp:3306
wxString GetCurrentVariant() const
Definition board.h:521
void RenameVariant(const wxString &aOldName, const wxString &aNewName)
Definition board.cpp:3223
void SetVariantDescription(const wxString &aVariantName, const wxString &aDescription)
Definition board.cpp:3325
Store all of the related component information found in a netlist.
void AddVariant(const COMPONENT_VARIANT &aVariant)
const COMPONENT_VARIANT * GetVariant(const wxString &aVariantName) const
const CASE_INSENSITIVE_MAP< COMPONENT_VARIANT > & GetVariants() const
Variant information for a footprint.
Definition footprint.h:227
wxString GetName() const
Definition footprint.h:238
bool HasFieldValue(const wxString &aFieldName) const
Definition footprint.h:286
void SetExcludedFromPosFiles(bool aExclude)
Definition footprint.h:251
wxString GetFieldValue(const wxString &aFieldName) const
Get a field value override for this variant.
Definition footprint.h:258
const std::map< wxString, wxString > & GetFields() const
Definition footprint.h:291
bool GetExcludedFromSim() const
Definition footprint.h:247
bool GetExcludedFromBOM() const
Definition footprint.h:244
void SetExcludedFromSim(bool aExclude)
Definition footprint.h:248
void SetDNP(bool aDNP)
Definition footprint.h:242
bool GetExcludedFromPosFiles() const
Definition footprint.h:250
bool GetDNP() const
Definition footprint.h:241
void SetFieldValue(const wxString &aFieldName, const wxString &aValue)
Set a field value override for this variant.
Definition footprint.h:273
void SetExcludedFromBOM(bool aExclude)
Definition footprint.h:245
const CASE_INSENSITIVE_MAP< FOOTPRINT_VARIANT > & GetVariants() const
Get all variants.
Definition footprint.h:1119
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:474
bool IsDNP() const
Definition footprint.h:1055
bool ResolveTextVar(wxString *token, int aDepth=0) const
Resolve any references to system tokens supported by the component.
bool IsExcludedFromBOM() const
Definition footprint.h:1037
void SetDNP(bool aDNP=true)
Definition footprint.h:1056
bool GetExcludedFromSimForVariant(const wxString &aVariantName) const
Get the exclude-from-simulation status for a specific variant.
void SetExcludedFromSim(bool aExclude=true)
Definition footprint.h:1047
void SetExcludedFromBOM(bool aExclude=true)
Definition footprint.h:1038
void SetAttributes(int aAttributes)
Definition footprint.h:551
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:939
void SetExcludedFromPosFiles(bool aExclude=true)
Definition footprint.h:1029
const FOOTPRINT_VARIANT * GetVariant(const wxString &aVariantName) const
Get a variant by name.
void DeleteVariant(const wxString &aVariantName)
Delete a variant by name.
void SetReference(const wxString &aReference)
Definition footprint.h:907
bool IsExcludedFromPosFiles() const
Definition footprint.h:1028
bool IsExcludedFromSim() const
Definition footprint.h:1046
void SetValue(const wxString &aValue)
Definition footprint.h:930
bool GetDNPForVariant(const wxString &aVariantName) const
Get the DNP status for a specific variant.
void SetVariant(const FOOTPRINT_VARIANT &aVariant)
Add or update a variant.
bool GetExcludedFromPosFilesForVariant(const wxString &aVariantName) const
Get the exclude-from-position-files status for a specific variant.
FOOTPRINT_VARIANT * AddVariant(const wxString &aVariantName)
Add a new variant with the given name.
bool GetExcludedFromBOMForVariant(const wxString &aVariantName) const
Get the exclude-from-BOM status for a specific variant.
The parser for reading the KiCad s-expression netlist format.
void Parse()
Function Parse parse the full netlist.
Definition kiid.h:46
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:65
UTF8 Format() const
Definition lib_id.cpp:132
Store information read from a netlist along with the flags used to update the NETLIST in the BOARD.
unsigned GetCount() const
COMPONENT * GetComponent(unsigned aIndex)
Return the COMPONENT at aIndex.
wxString GetShownText(RESOLUTION_CONTEXT aContext, int aDepth=0) const override
Return the string actually shown after processing of the base text.
wxString GetName(bool aUseDefaultName=true) const
Return the field name (not translated).
A #PLUGIN derivation for saving and loading Pcbnew s-expression formatted files.
std::unique_ptr< BOARD > LoadBoard(const wxString &aFileName, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr)
Load information from some input file format that this PCB_IO implementation knows about into new BOA...
Definition pcb_io.cpp:72
The ASCII format of the kicad place file is:
std::string GenPositionData()
build a string filled with the position data
void SetVariant(const wxString &aVariant)
Set the variant name for variant-aware export.
std::string GenReportData()
build a string filled with the pad report data This report does not used options aForceSmdItems,...
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
Is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition richio.h:225
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).
wxString wx_str() const
Definition utf8.cpp:41
@ INTERNAL
Definition common.h:92
#define _(s)
@ FP_DNP
Definition footprint.h:91
@ FP_EXCLUDE_FROM_POS_FILES
Definition footprint.h:87
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:88
@ FP_EXCLUDE_FROM_SIM
Definition footprint.h:92
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
bool m_hasExcludedFromPosFiles
nlohmann::ordered_map< wxString, wxString > m_fields
wxString GetDefaultFieldName(FIELD_T aFieldId, TRANSLATION aTranslation)
Return a default symbol field name for a mandatory field type.
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ UNTRANSLATED
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string netlist
std::string path
VECTOR3I v1(5, 5, 5)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")
BOOST_AUTO_TEST_CASE(FootprintVariantBasics)
Test FOOTPRINT_VARIANT class basic operations.
VECTOR2I v2(1, 0)