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.SetIsDryRun( false );
1408 updater.SetWillDrag( false );
1409 updater.SetReplaceFootprints( false );
1410 updater.SetDeleteUnusedFootprints( false );
1411
1412 BOOST_REQUIRE( updater.UpdateNetlist( netlist ) );
1413
1414 // Default variant: base FP visible, variant FP hidden.
1415 BOOST_CHECK_MESSAGE( !baseFp->GetDNPForVariant( wxEmptyString ),
1416 "Base FP must be visible in the default variant" );
1417 BOOST_CHECK_MESSAGE( variantFp->GetDNPForVariant( wxEmptyString ),
1418 "Variant A FP must be hidden in the default variant (globally DNP)" );
1419
1420 // Variant A active: variant FP visible, base FP hidden. The base-FP check is the #23298 fix;
1421 // the variant-FP check guards the single-pass ordering.
1422 BOOST_CHECK_MESSAGE( !variantFp->GetDNPForVariant( wxT( "Variant A" ) ),
1423 "Variant A FP must be visible when Variant A is active" );
1424 BOOST_CHECK_MESSAGE( baseFp->GetDNPForVariant( wxT( "Variant A" ) ),
1425 "Base FP must be hidden when Variant A is active (issue #23298)" );
1426}
1427
1428
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 SetWillDrag(bool aWillDrag)
void SetIsDryRun(bool aEnabled)
Enable dry run mode (just report, no changes to PCB).
void SetDeleteUnusedFootprints(bool aEnabled)
void SetReplaceFootprints(bool aEnabled)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
void SetCurrentVariant(const wxString &aVariant)
Definition board.cpp:2969
const std::vector< wxString > & GetVariantNames() const
Definition board.h:533
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1520
wxArrayString GetVariantNamesForUI() const
Return the variant names for UI display.
Definition board.cpp:3161
void DeleteVariant(const wxString &aVariantName)
Definition board.cpp:3014
bool ResolveTextVar(wxString *token, int aDepth) const
Definition board.cpp:680
bool HasVariant(const wxString &aVariantName) const
Definition board.cpp:2995
void AddVariant(const wxString &aVariantName)
Definition board.cpp:3001
wxString GetVariantDescription(const wxString &aVariantName) const
Definition board.cpp:3125
wxString GetCurrentVariant() const
Definition board.h:530
void RenameVariant(const wxString &aOldName, const wxString &aNewName)
Definition board.cpp:3042
void SetVariantDescription(const wxString &aVariantName, const wxString &aDescription)
Definition board.cpp:3144
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:178
wxString GetName() const
Definition footprint.h:189
bool HasFieldValue(const wxString &aFieldName) const
Definition footprint.h:237
void SetExcludedFromPosFiles(bool aExclude)
Definition footprint.h:202
wxString GetFieldValue(const wxString &aFieldName) const
Get a field value override for this variant.
Definition footprint.h:209
const std::map< wxString, wxString > & GetFields() const
Definition footprint.h:242
bool GetExcludedFromSim() const
Definition footprint.h:198
bool GetExcludedFromBOM() const
Definition footprint.h:195
void SetExcludedFromSim(bool aExclude)
Definition footprint.h:199
void SetDNP(bool aDNP)
Definition footprint.h:193
bool GetExcludedFromPosFiles() const
Definition footprint.h:201
bool GetDNP() const
Definition footprint.h:192
void SetFieldValue(const wxString &aFieldName, const wxString &aValue)
Set a field value override for this variant.
Definition footprint.h:224
void SetExcludedFromBOM(bool aExclude)
Definition footprint.h:196
const CASE_INSENSITIVE_MAP< FOOTPRINT_VARIANT > & GetVariants() const
Get all variants.
Definition footprint.h:1011
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:418
bool IsDNP() const
Definition footprint.h:947
bool IsExcludedFromBOM() const
Definition footprint.h:929
void SetDNP(bool aDNP=true)
Definition footprint.h:948
bool GetExcludedFromSimForVariant(const wxString &aVariantName) const
Get the exclude-from-simulation status for a specific variant.
void SetExcludedFromSim(bool aExclude=true)
Definition footprint.h:939
void SetExcludedFromBOM(bool aExclude=true)
Definition footprint.h:930
void SetAttributes(int aAttributes)
Definition footprint.h:510
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:831
void SetExcludedFromPosFiles(bool aExclude=true)
Definition footprint.h:921
const FOOTPRINT_VARIANT * GetVariant(const wxString &aVariantName) const
Get a variant by name.
bool ResolveTextVar(wxString *aToken, int aDepth=0) const
Resolve any references to system tokens supported by the component.
void DeleteVariant(const wxString &aVariantName)
Delete a variant by name.
void SetReference(const wxString &aReference)
Definition footprint.h:799
bool IsExcludedFromPosFiles() const
Definition footprint.h:920
bool IsExcludedFromSim() const
Definition footprint.h:938
void SetValue(const wxString &aValue)
Definition footprint.h:822
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 PCB_IO 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:96
#define _(s)
@ FP_DNP
Definition footprint.h:92
@ FP_EXCLUDE_FROM_POS_FILES
Definition footprint.h:88
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:89
@ FP_EXCLUDE_FROM_SIM
Definition footprint.h:93
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)