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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
29
31#include <boost/test/unit_test.hpp>
32
33#include <board.h>
34#include <footprint.h>
40#include <richio.h>
41
42
43BOOST_AUTO_TEST_SUITE( Variant )
44
45
46
49BOOST_AUTO_TEST_CASE( FootprintVariantBasics )
50{
51 FOOTPRINT_VARIANT variant( "TestVariant" );
52
53 // Check default values
54 BOOST_CHECK_EQUAL( variant.GetName(), "TestVariant" );
55 BOOST_CHECK( !variant.GetDNP() );
56 BOOST_CHECK( !variant.GetExcludedFromBOM() );
57 BOOST_CHECK( !variant.GetExcludedFromPosFiles() );
58 BOOST_CHECK( variant.GetFields().empty() );
59
60 // Test setting values
61 variant.SetDNP( true );
62 variant.SetExcludedFromBOM( true );
63 variant.SetExcludedFromPosFiles( true );
64 variant.SetFieldValue( "Value", "100R" );
65
66 BOOST_CHECK( variant.GetDNP() );
67 BOOST_CHECK( variant.GetExcludedFromBOM() );
68 BOOST_CHECK( variant.GetExcludedFromPosFiles() );
69 BOOST_CHECK( variant.HasFieldValue( "Value" ) );
70 BOOST_CHECK_EQUAL( variant.GetFieldValue( "Value" ), "100R" );
71}
72
73
77BOOST_AUTO_TEST_CASE( BoardVariantRegistry )
78{
79 BOARD board;
80
81 // Initially no variants
82 BOOST_CHECK( board.GetVariantNames().empty() );
83 BOOST_CHECK( board.GetCurrentVariant().IsEmpty() );
84
85 // Add a variant
86 board.AddVariant( "Production" );
87 BOOST_CHECK( board.HasVariant( "Production" ) );
88 BOOST_CHECK_EQUAL( board.GetVariantNames().size(), 1 );
89
90 // Add another variant
91 board.AddVariant( "Debug" );
92 BOOST_CHECK( board.HasVariant( "Debug" ) );
93 BOOST_CHECK_EQUAL( board.GetVariantNames().size(), 2 );
94
95 // Test case insensitivity
96 BOOST_CHECK( board.HasVariant( "production" ) );
97 BOOST_CHECK( board.HasVariant( "PRODUCTION" ) );
98 BOOST_CHECK( board.HasVariant( "PrOdUcTiOn" ) );
99
100 // Set current variant
101 board.SetCurrentVariant( "Production" );
102 BOOST_CHECK_EQUAL( board.GetCurrentVariant(), "Production" );
103
104 // Set variant description
105 board.SetVariantDescription( "Production", "Standard production build" );
106 BOOST_CHECK_EQUAL( board.GetVariantDescription( "Production" ), "Standard production build" );
107
108 // Delete a variant
109 board.DeleteVariant( "Debug" );
110 BOOST_CHECK( !board.HasVariant( "Debug" ) );
111 BOOST_CHECK_EQUAL( board.GetVariantNames().size(), 1 );
112}
113
114
118BOOST_AUTO_TEST_CASE( FootprintDNPForVariant )
119{
120 BOARD board;
121 FOOTPRINT fp( &board );
122
123 // Add variants to board
124 board.AddVariant( "Production" );
125 board.AddVariant( "Debug" );
126
127 // Set base DNP to false
128 fp.SetAttributes( 0 );
129 BOOST_CHECK( !fp.GetDNPForVariant( wxEmptyString ) );
130 BOOST_CHECK( !fp.GetDNPForVariant( "Production" ) );
131
132 // Set base DNP to true
133 fp.SetAttributes( FP_DNP );
134 BOOST_CHECK( fp.GetDNPForVariant( wxEmptyString ) );
135 BOOST_CHECK( fp.GetDNPForVariant( "Production" ) );
136
137 // Now set Production variant to override DNP
138 FOOTPRINT_VARIANT prodVariant( "Production" );
139 prodVariant.SetDNP( false );
140 fp.SetVariant( prodVariant );
141
142 // Base is still DNP, but Production overrides to not DNP
143 BOOST_CHECK( fp.GetDNPForVariant( wxEmptyString ) );
144 BOOST_CHECK( !fp.GetDNPForVariant( "Production" ) );
145 BOOST_CHECK( !fp.GetDNPForVariant( "production" ) ); // Case insensitive
146 BOOST_CHECK( fp.GetDNPForVariant( "Debug" ) ); // No override, uses base
147
148 // Set Debug variant to also override
149 FOOTPRINT_VARIANT debugVariant( "Debug" );
150 debugVariant.SetDNP( true );
151 fp.SetVariant( debugVariant );
152
153 BOOST_CHECK( fp.GetDNPForVariant( "Debug" ) );
154}
155
156
160BOOST_AUTO_TEST_CASE( FootprintBOMExclusionForVariant )
161{
162 BOARD board;
163 FOOTPRINT fp( &board );
164
165 board.AddVariant( "Production" );
166
167 // Set base exclude from BOM to false
168 fp.SetAttributes( 0 );
169 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( wxEmptyString ) );
170 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( "Production" ) );
171
172 // Set base exclude from BOM to true
174 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( wxEmptyString ) );
175 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( "Production" ) );
176
177 // Override for Production variant
178 FOOTPRINT_VARIANT prodVariant( "Production" );
179 prodVariant.SetExcludedFromBOM( false );
180 fp.SetVariant( prodVariant );
181
182 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( wxEmptyString ) );
183 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( "Production" ) );
184}
185
186
190BOOST_AUTO_TEST_CASE( FootprintPosFileExclusionForVariant )
191{
192 BOARD board;
193 FOOTPRINT fp( &board );
194
195 board.AddVariant( "Production" );
196
197 // Set base exclude from position files to false
198 fp.SetAttributes( 0 );
199 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( wxEmptyString ) );
200 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "Production" ) );
201
202 // Set base exclude from position files to true
204 BOOST_CHECK( fp.GetExcludedFromPosFilesForVariant( wxEmptyString ) );
205 BOOST_CHECK( fp.GetExcludedFromPosFilesForVariant( "Production" ) );
206
207 // Override for Production variant
208 FOOTPRINT_VARIANT prodVariant( "Production" );
209 prodVariant.SetExcludedFromPosFiles( false );
210 fp.SetVariant( prodVariant );
211
212 BOOST_CHECK( fp.GetExcludedFromPosFilesForVariant( wxEmptyString ) );
213 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "Production" ) );
214}
215
216
220BOOST_AUTO_TEST_CASE( VariantCaseInsensitivity )
221{
222 BOARD board;
223 FOOTPRINT fp( &board );
224
225 board.AddVariant( "Production" );
226
227 // Set variant override with specific case
228 FOOTPRINT_VARIANT prodVariant( "Production" );
229 prodVariant.SetDNP( true );
230 fp.SetVariant( prodVariant );
231
232 // Access with different cases - all should find the same variant
233 BOOST_CHECK( fp.GetDNPForVariant( "Production" ) );
234 BOOST_CHECK( fp.GetDNPForVariant( "production" ) );
235 BOOST_CHECK( fp.GetDNPForVariant( "PRODUCTION" ) );
236 BOOST_CHECK( fp.GetDNPForVariant( "PrOdUcTiOn" ) );
237
238 // Board operations should also be case insensitive
239 BOOST_CHECK( board.HasVariant( "production" ) );
240 BOOST_CHECK( board.HasVariant( "PRODUCTION" ) );
241}
242
243
247BOOST_AUTO_TEST_CASE( EmptyVariantReturnsBase )
248{
249 BOARD board;
250 FOOTPRINT fp( &board );
251
252 // Set base DNP
253 fp.SetAttributes( FP_DNP );
254
255 // Empty variant should return base
256 BOOST_CHECK( fp.GetDNPForVariant( wxEmptyString ) );
257
258 // Add a variant but still check empty
259 board.AddVariant( "Production" );
260
261 FOOTPRINT_VARIANT prodVariant( "Production" );
262 prodVariant.SetDNP( false );
263 fp.SetVariant( prodVariant );
264
265 // Empty still returns base
266 BOOST_CHECK( fp.GetDNPForVariant( wxEmptyString ) );
267 BOOST_CHECK( !fp.GetDNPForVariant( "Production" ) );
268}
269
270
274BOOST_AUTO_TEST_CASE( UnknownVariantReturnsBase )
275{
276 BOARD board;
277 FOOTPRINT fp( &board );
278
279 // Set base DNP
280 fp.SetAttributes( FP_DNP );
281
282 board.AddVariant( "Production" );
283
284 FOOTPRINT_VARIANT prodVariant( "Production" );
285 prodVariant.SetDNP( false );
286 fp.SetVariant( prodVariant );
287
288 // Unknown variant should return base
289 BOOST_CHECK( fp.GetDNPForVariant( "NonExistentVariant" ) );
290 BOOST_CHECK( fp.GetDNPForVariant( "Debug" ) );
291}
292
293
294BOOST_AUTO_TEST_CASE( FootprintVariantCopyAssignment )
295{
296 BOARD board;
297 FOOTPRINT original( &board );
298
299 original.SetReference( "R1" );
300 original.SetValue( "10K" );
301
302 FOOTPRINT_VARIANT prodVariant( "Production" );
303 prodVariant.SetDNP( true );
304 prodVariant.SetExcludedFromBOM( true );
305 prodVariant.SetFieldValue( original.Value().GetName(), "22K" );
306 original.SetVariant( prodVariant );
307
308 FOOTPRINT copy( original );
309 const FOOTPRINT_VARIANT* copyVariant = copy.GetVariant( "Production" );
310 BOOST_REQUIRE( copyVariant );
311 BOOST_CHECK( copyVariant->GetDNP() );
312 BOOST_CHECK( copyVariant->GetExcludedFromBOM() );
313 BOOST_CHECK_EQUAL( copyVariant->GetFieldValue( original.Value().GetName() ), "22K" );
314
315 FOOTPRINT assigned( &board );
316 assigned = original;
317
318 const FOOTPRINT_VARIANT* assignedVariant = assigned.GetVariant( "Production" );
319 BOOST_REQUIRE( assignedVariant );
320 BOOST_CHECK( assignedVariant->GetDNP() );
321 BOOST_CHECK( assignedVariant->GetExcludedFromBOM() );
322 BOOST_CHECK_EQUAL( assignedVariant->GetFieldValue( original.Value().GetName() ), "22K" );
323}
324
325
326BOOST_AUTO_TEST_CASE( FootprintFieldShownTextForVariant )
327{
328 BOARD board;
329 FOOTPRINT fp( &board );
330
331 board.AddVariant( "Production" );
332 board.SetCurrentVariant( "Production" );
333
334 fp.SetValue( "10K" );
335
336 FOOTPRINT_VARIANT prodVariant( "Production" );
337 prodVariant.SetFieldValue( fp.Value().GetName(), "22K" );
338 fp.SetVariant( prodVariant );
339
340 BOOST_CHECK_EQUAL( fp.Value().GetShownText( false ), "22K" );
341
342 board.SetCurrentVariant( wxEmptyString );
343 BOOST_CHECK_EQUAL( fp.Value().GetShownText( false ), "10K" );
344}
345
346
347BOOST_AUTO_TEST_CASE( BoardVariantTextVars )
348{
349 BOARD board;
350
351 board.AddVariant( "Production" );
352 board.SetVariantDescription( "Production", "Production build" );
353 board.SetCurrentVariant( "Production" );
354
355 wxString variantToken = wxT( "VARIANT" );
356 BOOST_CHECK( board.ResolveTextVar( &variantToken, 0 ) );
357 BOOST_CHECK_EQUAL( variantToken, "Production" );
358
359 wxString descToken = wxT( "VARIANT_DESC" );
360 BOOST_CHECK( board.ResolveTextVar( &descToken, 0 ) );
361 BOOST_CHECK_EQUAL( descToken, "Production build" );
362
363 board.SetCurrentVariant( wxEmptyString );
364 wxString defaultToken = wxT( "VARIANT" );
365 BOOST_CHECK( board.ResolveTextVar( &defaultToken, 0 ) );
366 BOOST_CHECK( defaultToken.IsEmpty() );
367}
368
369
370BOOST_AUTO_TEST_CASE( NetlistComponentVariantsParsing )
371{
372 const std::string netlist =
373 "(export (version 1)\n"
374 " (components\n"
375 " (comp (ref R1)\n"
376 " (value 10K)\n"
377 " (footprint Resistor_SMD:R_0603_1608Metric)\n"
378 " (libsource (lib Device) (part R))\n"
379 " (variants\n"
380 " (variant (name Alt)\n"
381 " (property (name dnp) (value 1))\n"
382 " (property (name exclude_from_bom) (value 0))\n"
383 " (fields\n"
384 " (field (name Value) \"22K\")\n"
385 " (field (name Footprint) \"Resistor_SMD:R_0805_2012Metric\")\n"
386 " )\n"
387 " )\n"
388 " )\n"
389 " )\n"
390 " )\n"
391 ")\n";
392
393 STRING_LINE_READER reader( netlist, wxT( "variant_netlist" ) );
394 NETLIST parsedNetlist;
395 KICAD_NETLIST_PARSER parser( &reader, &parsedNetlist );
396
397 parser.Parse();
398
399 BOOST_REQUIRE_EQUAL( parsedNetlist.GetCount(), 1 );
400
401 COMPONENT* component = parsedNetlist.GetComponent( 0 );
402 const COMPONENT_VARIANT* variant = component->GetVariant( "Alt" );
403
404 BOOST_REQUIRE( variant );
405 BOOST_CHECK( variant->m_hasDnp );
406 BOOST_CHECK( variant->m_dnp );
407 BOOST_CHECK( variant->m_hasExcludedFromBOM );
408 BOOST_CHECK( !variant->m_excludedFromBOM );
409
410 auto valueIt = variant->m_fields.find( "Value" );
411 BOOST_CHECK( valueIt != variant->m_fields.end() );
412 BOOST_CHECK_EQUAL( valueIt->second, "22K" );
413
414 auto fpIt = variant->m_fields.find( "Footprint" );
415 BOOST_CHECK( fpIt != variant->m_fields.end() );
416 BOOST_CHECK_EQUAL( fpIt->second, "Resistor_SMD:R_0805_2012Metric" );
417}
418
419
423BOOST_AUTO_TEST_CASE( MultipleVariantsIndependent )
424{
425 BOARD board;
426
427 // Add multiple variants
428 board.AddVariant( "Variant1" );
429 board.AddVariant( "Variant2" );
430 board.SetVariantDescription( "Variant1", "First variant" );
431 board.SetVariantDescription( "Variant2", "Second variant" );
432
433 // Create footprint and set variant-specific properties
434 FOOTPRINT fp( &board );
435 fp.SetReference( "R1" );
436 fp.SetValue( "10K" );
437
438 FOOTPRINT_VARIANT variant1( "Variant1" );
439 variant1.SetDNP( true );
440 variant1.SetFieldValue( fp.Value().GetName(), "22K" );
441 fp.SetVariant( variant1 );
442
443 FOOTPRINT_VARIANT variant2( "Variant2" );
444 variant2.SetDNP( false );
445 variant2.SetFieldValue( fp.Value().GetName(), "47K" );
446 fp.SetVariant( variant2 );
447
448 // Verify both variants are independent
449 BOOST_CHECK( fp.GetDNPForVariant( "Variant1" ) );
450 BOOST_CHECK( !fp.GetDNPForVariant( "Variant2" ) );
451
452 const FOOTPRINT_VARIANT* v1 = fp.GetVariant( "Variant1" );
453 const FOOTPRINT_VARIANT* v2 = fp.GetVariant( "Variant2" );
454
455 BOOST_REQUIRE( v1 );
456 BOOST_REQUIRE( v2 );
457
458 BOOST_CHECK_EQUAL( v1->GetFieldValue( fp.Value().GetName() ), "22K" );
459 BOOST_CHECK_EQUAL( v2->GetFieldValue( fp.Value().GetName() ), "47K" );
460
461 // Verify descriptions are independent
462 BOOST_CHECK_EQUAL( board.GetVariantDescription( "Variant1" ), "First variant" );
463 BOOST_CHECK_EQUAL( board.GetVariantDescription( "Variant2" ), "Second variant" );
464}
465
466
470BOOST_AUTO_TEST_CASE( VariantFieldUnicodeAndSpecialChars )
471{
472 BOARD board;
473 FOOTPRINT fp( &board );
474
475 board.AddVariant( "UnicodeTest" );
476
477 fp.SetValue( "Default" );
478
479 // Unicode characters
480 FOOTPRINT_VARIANT unicodeVariant( "UnicodeTest" );
481 wxString unicodeValue = wxT( "1kΩ ±5% 日本語" );
482 unicodeVariant.SetFieldValue( fp.Value().GetName(), unicodeValue );
483 fp.SetVariant( unicodeVariant );
484
485 const FOOTPRINT_VARIANT* retrieved = fp.GetVariant( "UnicodeTest" );
486 BOOST_REQUIRE( retrieved );
487 BOOST_CHECK_EQUAL( retrieved->GetFieldValue( fp.Value().GetName() ), unicodeValue );
488
489 // Special characters
490 FOOTPRINT_VARIANT specialVariant( "SpecialChars" );
491 wxString specialChars = wxT( "R<1K>\"test\"'value'" );
492 specialVariant.SetFieldValue( fp.Value().GetName(), specialChars );
493 fp.SetVariant( specialVariant );
494
495 const FOOTPRINT_VARIANT* retrievedSpecial = fp.GetVariant( "SpecialChars" );
496 BOOST_REQUIRE( retrievedSpecial );
497 BOOST_CHECK_EQUAL( retrievedSpecial->GetFieldValue( fp.Value().GetName() ), specialChars );
498
499 // Unicode in variant description
500 wxString unicodeDesc = wxT( "Variante für Produktion — 测试" );
501 board.SetVariantDescription( "UnicodeTest", unicodeDesc );
502 BOOST_CHECK_EQUAL( board.GetVariantDescription( "UnicodeTest" ), unicodeDesc );
503}
504
505
509BOOST_AUTO_TEST_CASE( VariantDeletionClearsRegistry )
510{
511 BOARD board;
512
513 board.AddVariant( "Variant1" );
514 board.AddVariant( "Variant2" );
515 board.SetVariantDescription( "Variant1", "Description 1" );
516 board.SetCurrentVariant( "Variant1" );
517
518 BOOST_CHECK_EQUAL( board.GetVariantNames().size(), 2 );
519 BOOST_CHECK( board.HasVariant( "Variant1" ) );
520
521 // Delete the variant
522 board.DeleteVariant( "Variant1" );
523
524 // Verify it's gone
525 BOOST_CHECK_EQUAL( board.GetVariantNames().size(), 1 );
526 BOOST_CHECK( !board.HasVariant( "Variant1" ) );
527 BOOST_CHECK( board.HasVariant( "Variant2" ) );
528
529 // Current variant should be cleared since we deleted the current one
530 BOOST_CHECK( board.GetCurrentVariant().IsEmpty() || board.GetCurrentVariant() != "Variant1" );
531}
532
533
537BOOST_AUTO_TEST_CASE( RenameVariantPreservesData )
538{
539 BOARD board;
540
541 board.AddVariant( "OldName" );
542 board.SetVariantDescription( "OldName", "Test description" );
543 board.SetCurrentVariant( "OldName" );
544
545 // Rename the variant
546 board.RenameVariant( "OldName", "NewName" );
547
548 // Old name should be gone
549 BOOST_CHECK( !board.HasVariant( "OldName" ) );
550
551 // New name should exist with same properties
552 BOOST_CHECK( board.HasVariant( "NewName" ) );
553 BOOST_CHECK_EQUAL( board.GetVariantDescription( "NewName" ), "Test description" );
554
555 // Current variant should be updated if it was the renamed one
556 BOOST_CHECK_EQUAL( board.GetCurrentVariant(), "NewName" );
557}
558
559
563BOOST_AUTO_TEST_CASE( GetVariantNamesForUIFormat )
564{
565 BOARD board;
566
567 board.AddVariant( "Zebra" );
568 board.AddVariant( "Alpha" );
569 board.AddVariant( "Beta" );
570
571 wxArrayString names = board.GetVariantNamesForUI();
572
573 // Should have 4 entries (default + 3 variants)
574 BOOST_CHECK( names.GetCount() >= 4 );
575
576 // First should be the default placeholder
577 BOOST_CHECK( !names[0].IsEmpty() );
578
579 // Remaining should be sorted alphabetically
580 bool foundAlpha = false;
581 bool foundBeta = false;
582 bool foundZebra = false;
583
584 for( size_t i = 1; i < names.GetCount(); i++ )
585 {
586 if( names[i] == wxT( "Alpha" ) )
587 foundAlpha = true;
588 else if( names[i] == wxT( "Beta" ) )
589 foundBeta = true;
590 else if( names[i] == wxT( "Zebra" ) )
591 foundZebra = true;
592 }
593
594 BOOST_CHECK( foundAlpha );
595 BOOST_CHECK( foundBeta );
596 BOOST_CHECK( foundZebra );
597}
598
599
603BOOST_AUTO_TEST_CASE( VariantMultipleFlagsCombinations )
604{
605 BOARD board;
606 FOOTPRINT fp( &board );
607
608 board.AddVariant( "DNPOnly" );
609 board.AddVariant( "BOMOnly" );
610 board.AddVariant( "AllFlags" );
611 board.AddVariant( "NoFlags" );
612
613 // Set various flag combinations
614 FOOTPRINT_VARIANT dnpOnly( "DNPOnly" );
615 dnpOnly.SetDNP( true );
616 dnpOnly.SetExcludedFromBOM( false );
617 dnpOnly.SetExcludedFromPosFiles( false );
618 fp.SetVariant( dnpOnly );
619
620 FOOTPRINT_VARIANT bomOnly( "BOMOnly" );
621 bomOnly.SetDNP( false );
622 bomOnly.SetExcludedFromBOM( true );
623 bomOnly.SetExcludedFromPosFiles( false );
624 fp.SetVariant( bomOnly );
625
626 FOOTPRINT_VARIANT allFlags( "AllFlags" );
627 allFlags.SetDNP( true );
628 allFlags.SetExcludedFromBOM( true );
629 allFlags.SetExcludedFromPosFiles( true );
630 fp.SetVariant( allFlags );
631
632 FOOTPRINT_VARIANT noFlags( "NoFlags" );
633 noFlags.SetDNP( false );
634 noFlags.SetExcludedFromBOM( false );
635 noFlags.SetExcludedFromPosFiles( false );
636 fp.SetVariant( noFlags );
637
638 // Verify each variant has correct flags
639 BOOST_CHECK( fp.GetDNPForVariant( "DNPOnly" ) );
640 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( "DNPOnly" ) );
641 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "DNPOnly" ) );
642
643 BOOST_CHECK( !fp.GetDNPForVariant( "BOMOnly" ) );
644 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( "BOMOnly" ) );
645 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "BOMOnly" ) );
646
647 BOOST_CHECK( fp.GetDNPForVariant( "AllFlags" ) );
648 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( "AllFlags" ) );
649 BOOST_CHECK( fp.GetExcludedFromPosFilesForVariant( "AllFlags" ) );
650
651 BOOST_CHECK( !fp.GetDNPForVariant( "NoFlags" ) );
652 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( "NoFlags" ) );
653 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "NoFlags" ) );
654}
655
656
661BOOST_AUTO_TEST_CASE( ComponentVariantToFootprintTransfer )
662{
663 BOARD board;
664 FOOTPRINT fp( &board );
665
666 fp.SetReference( "R1" );
667 fp.SetValue( "10K" );
668
669 board.AddVariant( "Variant A" );
670 board.AddVariant( "Variant B" );
671
672 // Create a COMPONENT with variant data (simulating schematic data)
673 LIB_ID fpid( wxT( "Resistor_SMD" ), wxT( "R_0805_2012Metric" ) );
674 wxString reference = wxT( "R1" );
675 wxString value = wxT( "10K" );
677 std::vector<KIID> kiids;
678
679 COMPONENT component( fpid, reference, value, path, kiids );
680
681 // Add variant "Variant A" with DNP=true, ExcludedFromBOM=false, ExcludedFromPosFiles=true
682 // and a field override for Datasheet
683 COMPONENT_VARIANT variantA( "Variant A" );
684 variantA.m_dnp = true;
685 variantA.m_hasDnp = true;
686 variantA.m_excludedFromBOM = false;
687 variantA.m_hasExcludedFromBOM = true;
688 variantA.m_excludedFromPosFiles = true;
689 variantA.m_hasExcludedFromPosFiles = true;
690 variantA.m_fields[wxT( "Datasheet" )] = wxT( "https://example.com/datasheet.pdf" );
691 component.AddVariant( variantA );
692
693 // Add variant "Variant B" with DNP=false, ExcludedFromBOM=true
694 COMPONENT_VARIANT variantB( "Variant B" );
695 variantB.m_dnp = false;
696 variantB.m_hasDnp = true;
697 variantB.m_excludedFromBOM = true;
698 variantB.m_hasExcludedFromBOM = true;
699 variantB.m_excludedFromPosFiles = false;
700 variantB.m_hasExcludedFromPosFiles = true;
701 variantB.m_fields[wxT( "Value" )] = wxT( "22K" );
702 component.AddVariant( variantB );
703
704 // Transfer variant data from COMPONENT to FOOTPRINT (simulating applyComponentVariants)
705 for( const auto& [variantName, componentVariant] : component.GetVariants() )
706 {
707 FOOTPRINT_VARIANT* fpVariant = fp.AddVariant( variantName );
708 BOOST_REQUIRE( fpVariant );
709
710 if( componentVariant.m_hasDnp )
711 fpVariant->SetDNP( componentVariant.m_dnp );
712
713 if( componentVariant.m_hasExcludedFromBOM )
714 fpVariant->SetExcludedFromBOM( componentVariant.m_excludedFromBOM );
715
716 if( componentVariant.m_hasExcludedFromPosFiles )
717 fpVariant->SetExcludedFromPosFiles( componentVariant.m_excludedFromPosFiles );
718
719 for( const auto& [fieldName, fieldValue] : componentVariant.m_fields )
720 fpVariant->SetFieldValue( fieldName, fieldValue );
721 }
722
723 // Verify Variant A properties
724 const FOOTPRINT_VARIANT* fpVariantA = fp.GetVariant( "Variant A" );
725 BOOST_REQUIRE( fpVariantA );
726 BOOST_CHECK( fpVariantA->GetDNP() );
727 BOOST_CHECK( !fpVariantA->GetExcludedFromBOM() );
728 BOOST_CHECK( fpVariantA->GetExcludedFromPosFiles() );
729 BOOST_CHECK( fpVariantA->HasFieldValue( wxT( "Datasheet" ) ) );
730 BOOST_CHECK_EQUAL( fpVariantA->GetFieldValue( wxT( "Datasheet" ) ),
731 wxT( "https://example.com/datasheet.pdf" ) );
732
733 // Verify Variant B properties
734 const FOOTPRINT_VARIANT* fpVariantB = fp.GetVariant( "Variant B" );
735 BOOST_REQUIRE( fpVariantB );
736 BOOST_CHECK( !fpVariantB->GetDNP() );
737 BOOST_CHECK( fpVariantB->GetExcludedFromBOM() );
738 BOOST_CHECK( !fpVariantB->GetExcludedFromPosFiles() );
739 BOOST_CHECK( fpVariantB->HasFieldValue( wxT( "Value" ) ) );
740 BOOST_CHECK_EQUAL( fpVariantB->GetFieldValue( wxT( "Value" ) ), wxT( "22K" ) );
741
742 // Verify variant-aware getters work
743 BOOST_CHECK( fp.GetDNPForVariant( "Variant A" ) );
744 BOOST_CHECK( !fp.GetDNPForVariant( "Variant B" ) );
745 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( "Variant A" ) );
746 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( "Variant B" ) );
747 BOOST_CHECK( fp.GetExcludedFromPosFilesForVariant( "Variant A" ) );
748 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "Variant B" ) );
749}
750
751
757BOOST_AUTO_TEST_CASE( ComponentVariantPartialOverride )
758{
759 BOARD board;
760 FOOTPRINT fp( &board );
761
762 fp.SetReference( "R1" );
763
764 // Set base footprint to have all attributes false
765 fp.SetDNP( false );
766 fp.SetExcludedFromBOM( false );
767 fp.SetExcludedFromPosFiles( false );
768
769 board.AddVariant( "TestVariant" );
770
771 // Pre-populate the footprint variant with all true values (simulating old state)
772 FOOTPRINT_VARIANT initialVariant( "TestVariant" );
773 initialVariant.SetDNP( true );
774 initialVariant.SetExcludedFromBOM( true );
775 initialVariant.SetExcludedFromPosFiles( true );
776 fp.SetVariant( initialVariant );
777
778 // Create a component variant that only has explicit DNP override set
779 LIB_ID fpid( wxT( "Resistor_SMD" ), wxT( "R_0805_2012Metric" ) );
781 std::vector<KIID> kiids;
782 COMPONENT component( fpid, wxT( "R1" ), wxT( "10K" ), path, kiids );
783
784 COMPONENT_VARIANT partialVariant( "TestVariant" );
785 partialVariant.m_dnp = true;
786 partialVariant.m_hasDnp = true;
787 // m_hasExcludedFromBOM and m_hasExcludedFromPosFiles are false (no explicit override)
788 component.AddVariant( partialVariant );
789
790 // Transfer properties, resetting non-overridden ones to base footprint values
791 for( const auto& [variantName, componentVariant] : component.GetVariants() )
792 {
793 FOOTPRINT_VARIANT* fpVariant = fp.GetVariant( variantName );
794 BOOST_REQUIRE( fpVariant );
795
796 // Apply explicit override or reset to base footprint value
797 bool targetDnp = componentVariant.m_hasDnp ? componentVariant.m_dnp : fp.IsDNP();
798 fpVariant->SetDNP( targetDnp );
799
800 bool targetBOM = componentVariant.m_hasExcludedFromBOM
801 ? componentVariant.m_excludedFromBOM
802 : fp.IsExcludedFromBOM();
803 fpVariant->SetExcludedFromBOM( targetBOM );
804
805 bool targetPos = componentVariant.m_hasExcludedFromPosFiles
806 ? componentVariant.m_excludedFromPosFiles
808 fpVariant->SetExcludedFromPosFiles( targetPos );
809 }
810
811 // Verify: DNP should be true (m_hasDnp was true with value true)
812 BOOST_CHECK( fp.GetDNPForVariant( "TestVariant" ) );
813
814 // Verify: ExcludedFromBOM should be reset to base (false)
815 BOOST_CHECK( !fp.GetExcludedFromBOMForVariant( "TestVariant" ) );
816
817 // Verify: ExcludedFromPosFiles should be reset to base (false)
818 BOOST_CHECK( !fp.GetExcludedFromPosFilesForVariant( "TestVariant" ) );
819}
820
821
826BOOST_AUTO_TEST_CASE( NetlistVariantAttributeParsing )
827{
828 // Create component variants with all attribute types
829 COMPONENT_VARIANT variantWithAll( "WithAll" );
830 variantWithAll.m_dnp = true;
831 variantWithAll.m_hasDnp = true;
832 variantWithAll.m_excludedFromBOM = true;
833 variantWithAll.m_hasExcludedFromBOM = true;
834 variantWithAll.m_excludedFromPosFiles = true;
835 variantWithAll.m_hasExcludedFromPosFiles = true;
836
837 // Verify all flags are set correctly
838 BOOST_CHECK( variantWithAll.m_hasDnp );
839 BOOST_CHECK( variantWithAll.m_dnp );
840 BOOST_CHECK( variantWithAll.m_hasExcludedFromBOM );
841 BOOST_CHECK( variantWithAll.m_excludedFromBOM );
842 BOOST_CHECK( variantWithAll.m_hasExcludedFromPosFiles );
843 BOOST_CHECK( variantWithAll.m_excludedFromPosFiles );
844
845 // Create a component variant with only some attributes
846 COMPONENT_VARIANT variantPartial( "Partial" );
847 variantPartial.m_dnp = true;
848 variantPartial.m_hasDnp = true;
849 // ExcludedFromBOM and ExcludedFromPosFiles have no explicit override
850
851 BOOST_CHECK( variantPartial.m_hasDnp );
852 BOOST_CHECK( !variantPartial.m_hasExcludedFromBOM );
853 BOOST_CHECK( !variantPartial.m_hasExcludedFromPosFiles );
854
855 // Create a variant with false values (explicit override to false)
856 COMPONENT_VARIANT variantAllFalse( "AllFalse" );
857 variantAllFalse.m_dnp = false;
858 variantAllFalse.m_hasDnp = true;
859 variantAllFalse.m_excludedFromBOM = false;
860 variantAllFalse.m_hasExcludedFromBOM = true;
861 variantAllFalse.m_excludedFromPosFiles = false;
862 variantAllFalse.m_hasExcludedFromPosFiles = true;
863
864 BOOST_CHECK( variantAllFalse.m_hasDnp );
865 BOOST_CHECK( !variantAllFalse.m_dnp );
866 BOOST_CHECK( variantAllFalse.m_hasExcludedFromBOM );
867 BOOST_CHECK( !variantAllFalse.m_excludedFromBOM );
868 BOOST_CHECK( variantAllFalse.m_hasExcludedFromPosFiles );
869 BOOST_CHECK( !variantAllFalse.m_excludedFromPosFiles );
870}
871
872
877BOOST_AUTO_TEST_CASE( VariantAttributeTransferWithReset )
878{
879 BOARD board;
880 FOOTPRINT fp( &board );
881 fp.SetReference( "R1" );
882 fp.SetFPID( LIB_ID( wxT( "Resistor_SMD" ), wxT( "R_0805" ) ) );
883
884 // Base footprint has no flags set
885 fp.SetDNP( false );
886 fp.SetExcludedFromBOM( false );
887 fp.SetExcludedFromPosFiles( false );
888
889 board.AddVariant( "Variant A" );
890
891 // Step 1: Initial state - variant has all attributes set (simulating previous netlist update)
892 FOOTPRINT_VARIANT* fpVariant = fp.AddVariant( "Variant A" );
893 BOOST_REQUIRE( fpVariant );
894 fpVariant->SetDNP( true );
895 fpVariant->SetExcludedFromBOM( true );
896 fpVariant->SetExcludedFromPosFiles( true );
897
898 // Verify initial state
899 BOOST_CHECK( fp.GetDNPForVariant( "Variant A" ) );
900 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( "Variant A" ) );
901 BOOST_CHECK( fp.GetExcludedFromPosFilesForVariant( "Variant A" ) );
902
903 // Step 2: New netlist has variant with NO explicit attribute overrides
904 // This simulates the user removing all variant attribute overrides from schematic
905 COMPONENT_VARIANT componentVariant( "Variant A" );
906 // All m_has* flags are false by default (no explicit overrides)
907
908 // Step 3: Apply the fixed transfer logic (same as board_netlist_updater::applyComponentVariants)
909 // For isActive = true case with no explicit overrides, attributes should reset to base
910 bool targetDnp = componentVariant.m_hasDnp ? componentVariant.m_dnp : fp.IsDNP();
911 bool targetBOM = componentVariant.m_hasExcludedFromBOM ? componentVariant.m_excludedFromBOM
912 : fp.IsExcludedFromBOM();
913 bool targetPos = componentVariant.m_hasExcludedFromPosFiles
914 ? componentVariant.m_excludedFromPosFiles
916
917 fpVariant->SetDNP( targetDnp );
918 fpVariant->SetExcludedFromBOM( targetBOM );
919 fpVariant->SetExcludedFromPosFiles( targetPos );
920
921 // Step 4: Verify all attributes were reset to base footprint values (false)
922 BOOST_CHECK_MESSAGE( !fp.GetDNPForVariant( "Variant A" ),
923 "DNP should be reset to base value (false) when no explicit override" );
925 "ExcludedFromBOM should be reset to base value (false) when no override" );
927 "ExcludedFromPosFiles should be reset to base (false) when no override" );
928}
929
930
942BOOST_AUTO_TEST_CASE( VariantTestR2FootprintAttributeVerification )
943{
944 wxString dataPath = KI_TEST::GetPcbnewTestDataDir() + wxString( "variant_test/variant_test.kicad_pcb" );
945
946 PCB_IO_KICAD_SEXPR pcbIo;
947 std::unique_ptr<BOARD> board( pcbIo.LoadBoard( dataPath, nullptr ) );
948
949 BOOST_REQUIRE( board );
950 BOOST_REQUIRE( board->HasVariant( "Variant A" ) );
951
952 // Find both R2 footprints and verify their variant data
953 FOOTPRINT* r2_c1210 = nullptr; // Variant A's footprint (C_1210_3225Metric)
954 FOOTPRINT* r2_c3640 = nullptr; // Base footprint (C_3640_9110Metric)
955
956 for( FOOTPRINT* fp : board->Footprints() )
957 {
958 if( fp->GetReference() == wxT( "R2" ) )
959 {
960 wxString fpName = fp->GetFPID().GetLibItemName();
961
962 if( fpName.Contains( wxT( "C_1210" ) ) )
963 r2_c1210 = fp;
964 else if( fpName.Contains( wxT( "C_3640" ) ) )
965 r2_c3640 = fp;
966 }
967 }
968
969 // Verify we found both R2 footprints
970 BOOST_TEST_MESSAGE( "Looking for R2 footprints in test data" );
971 BOOST_REQUIRE_MESSAGE( r2_c1210, "Should find R2 with C_1210 footprint (variant footprint)" );
972 BOOST_REQUIRE_MESSAGE( r2_c3640, "Should find R2 with C_3640 footprint (base footprint)" );
973
974 // Check C_1210 (Variant A's footprint) - this IS the active footprint for Variant A
975 // The schematic has NO attribute overrides, so the PCB variant should also have no overrides
976 // (or equivalently, values should match base footprint)
977 const FOOTPRINT_VARIANT* c1210_variantA = r2_c1210->GetVariant( "Variant A" );
978
979 BOOST_TEST_MESSAGE( "R2 C_1210 (variant footprint) base attributes: DNP="
980 << r2_c1210->IsDNP() << " ExcludedFromBOM=" << r2_c1210->IsExcludedFromBOM()
981 << " ExcludedFromPosFiles=" << r2_c1210->IsExcludedFromPosFiles() );
982
983 if( c1210_variantA )
984 {
985 BOOST_TEST_MESSAGE( "R2 C_1210 Variant A attributes: DNP=" << c1210_variantA->GetDNP()
986 << " ExcludedFromBOM=" << c1210_variantA->GetExcludedFromBOM()
987 << " ExcludedFromPosFiles=" << c1210_variantA->GetExcludedFromPosFiles() );
988
989 // For the variant footprint, since schematic has NO attribute overrides,
990 // variant attributes should match base footprint values (all false)
991 BOOST_CHECK_MESSAGE( !c1210_variantA->GetDNP(),
992 "C_1210 Variant A DNP should be false (no schematic override)" );
993 BOOST_CHECK_MESSAGE( !c1210_variantA->GetExcludedFromBOM(),
994 "C_1210 Variant A ExcludedFromBOM should be false (no override)" );
996 "C_1210 Variant A ExcludedFromPosFiles should be false (no override)" );
997 }
998 else
999 {
1000 BOOST_TEST_MESSAGE( "R2 C_1210 has no Variant A data" );
1001 }
1002
1003 // Check C_3640 (base footprint) - this is NOT the active footprint for Variant A
1004 // Since schematic R2:Variant A has NO explicit attribute overrides, the PCB variant
1005 // should also have no attribute overrides (attributes reset to base footprint values).
1006 // This matches the schematic inheritance model where unset attributes inherit from base.
1007 const FOOTPRINT_VARIANT* c3640_variantA = r2_c3640->GetVariant( "Variant A" );
1008
1009 BOOST_TEST_MESSAGE( "R2 C_3640 (base footprint) base attributes: DNP="
1010 << r2_c3640->IsDNP() << " ExcludedFromBOM=" << r2_c3640->IsExcludedFromBOM()
1011 << " ExcludedFromPosFiles=" << r2_c3640->IsExcludedFromPosFiles() );
1012
1013 if( c3640_variantA )
1014 {
1015 BOOST_TEST_MESSAGE( "R2 C_3640 Variant A attributes: DNP=" << c3640_variantA->GetDNP()
1016 << " ExcludedFromBOM=" << c3640_variantA->GetExcludedFromBOM()
1017 << " ExcludedFromPosFiles=" << c3640_variantA->GetExcludedFromPosFiles() );
1018
1019 // For the base footprint, since schematic has NO explicit attribute overrides,
1020 // variant attributes should match base footprint values (all false).
1021 // The PCB should mirror the schematic's inheritance model.
1022 BOOST_CHECK_MESSAGE( !c3640_variantA->GetDNP(),
1023 "C_3640 Variant A DNP should be false (no schematic override)" );
1024 BOOST_CHECK_MESSAGE( !c3640_variantA->GetExcludedFromBOM(),
1025 "C_3640 Variant A ExcludedFromBOM should be false (no override)" );
1026 BOOST_CHECK_MESSAGE( !c3640_variantA->GetExcludedFromPosFiles(),
1027 "C_3640 Variant A ExcludedFromPosFiles should be false (no override)" );
1028 }
1029 else
1030 {
1031 // If there's no variant data stored, that's also acceptable - implies no overrides
1032 BOOST_TEST_MESSAGE( "R2 C_3640 has no Variant A data (implies no overrides)" );
1033 }
1034}
1035
1036
1040BOOST_AUTO_TEST_CASE( VariantTestProjectLoad )
1041{
1042 wxString dataPath = KI_TEST::GetPcbnewTestDataDir() + wxString( "variant_test/variant_test.kicad_pcb" );
1043
1044 PCB_IO_KICAD_SEXPR pcbIo;
1045 std::unique_ptr<BOARD> board( pcbIo.LoadBoard( dataPath, nullptr ) );
1046
1047 BOOST_REQUIRE( board );
1048
1049 // Verify the board has the "Variant A" variant registered
1050 BOOST_CHECK( board->HasVariant( "Variant A" ) );
1051
1052 // Find footprints and verify their variant properties
1053 // Based on the variant_test.kicad_pcb and schematic:
1054 // - R1 with Variant A having field override for Datasheet
1055 // - R2 (C_3640 base footprint) with Variant A having NO attribute overrides
1056 // (schematic has no explicit overrides, so PCB mirrors base values)
1057 // - R3 with Variant A having DNP (explicit schematic override)
1058
1059 for( FOOTPRINT* fp : board->Footprints() )
1060 {
1061 const wxString& ref = fp->GetReference();
1062
1063 if( ref == wxT( "R1" ) )
1064 {
1065 const FOOTPRINT_VARIANT* variantA = fp->GetVariant( "Variant A" );
1066
1067 if( variantA )
1068 {
1069 // R1 has a Datasheet field override in Variant A
1070 BOOST_CHECK( variantA->HasFieldValue( wxT( "Datasheet" ) ) );
1071 BOOST_CHECK_EQUAL( variantA->GetFieldValue( wxT( "Datasheet" ) ), wxT( "test" ) );
1072 }
1073 }
1074 else if( ref == wxT( "R2" ) )
1075 {
1076 // R2's C_3640 footprint (base) should have NO attribute overrides
1077 // because schematic R2:Variant A has no explicit attribute overrides.
1078 // PCB mirrors the schematic inheritance model.
1079 wxString fpName = fp->GetFPID().GetLibItemName();
1080
1081 if( fpName.Contains( wxT( "C_3640" ) ) )
1082 {
1083 const FOOTPRINT_VARIANT* variantA = fp->GetVariant( "Variant A" );
1084
1085 if( variantA )
1086 {
1087 BOOST_CHECK( !variantA->GetDNP() );
1088 BOOST_CHECK( !variantA->GetExcludedFromBOM() );
1089 BOOST_CHECK( !variantA->GetExcludedFromPosFiles() );
1090 }
1091 }
1092 }
1093 else if( ref == wxT( "R3" ) )
1094 {
1095 const FOOTPRINT_VARIANT* variantA = fp->GetVariant( "Variant A" );
1096
1097 if( variantA )
1098 {
1099 // R3 has DNP in Variant A (explicit schematic override)
1100 BOOST_CHECK( variantA->GetDNP() );
1101 }
1102 }
1103 }
1104}
1105
1106
1113BOOST_AUTO_TEST_CASE( PosExportVariantValue )
1114{
1115 BOARD board;
1116
1117 board.AddVariant( "AltPop" );
1118
1119 FOOTPRINT* fp = new FOOTPRINT( &board );
1120 fp->SetReference( "R1" );
1121 fp->SetValue( "10K" );
1122
1123 FOOTPRINT_VARIANT altPopVariant( "AltPop" );
1124 altPopVariant.SetFieldValue( fp->Value().GetName(), "22K" );
1125 fp->SetVariant( altPopVariant );
1126
1127 board.Add( fp, ADD_MODE::INSERT );
1128
1129 auto runExport = [&]( const wxString& aVariant, bool aCsv ) -> std::string
1130 {
1131 PLACE_FILE_EXPORTER exporter( &board,
1132 true, // mm
1133 false, // all footprints
1134 false, // include TH
1135 false, // don't exclude DNP
1136 false, // don't exclude BOM
1137 true, // front
1138 true, // back
1139 aCsv, // format
1140 false, // no aux origin
1141 false ); // don't negate X
1142 exporter.SetVariant( aVariant );
1143 return exporter.GenPositionData();
1144 };
1145
1146 // ASCII format
1147 std::string defaultAscii = runExport( wxEmptyString, false );
1148 BOOST_CHECK( defaultAscii.find( "10K" ) != std::string::npos );
1149 BOOST_CHECK( defaultAscii.find( "22K" ) == std::string::npos );
1150
1151 std::string altPopAscii = runExport( wxS( "AltPop" ), false );
1152 BOOST_CHECK( altPopAscii.find( "22K" ) != std::string::npos );
1153 BOOST_CHECK( altPopAscii.find( "10K" ) == std::string::npos );
1154
1155 // CSV format
1156 std::string defaultCsv = runExport( wxEmptyString, true );
1157 BOOST_CHECK( defaultCsv.find( "10K" ) != std::string::npos );
1158 BOOST_CHECK( defaultCsv.find( "22K" ) == std::string::npos );
1159
1160 std::string altPopCsv = runExport( wxS( "AltPop" ), true );
1161 BOOST_CHECK( altPopCsv.find( "22K" ) != std::string::npos );
1162 BOOST_CHECK( altPopCsv.find( "10K" ) == std::string::npos );
1163
1164 // GenReportData should also respect the variant
1165 auto runReport = [&]( const wxString& aVariant ) -> std::string
1166 {
1167 PLACE_FILE_EXPORTER exporter( &board, true, false, false, false, false,
1168 true, true, false, false, false );
1169 exporter.SetVariant( aVariant );
1170 return exporter.GenReportData();
1171 };
1172
1173 std::string defaultReport = runReport( wxEmptyString );
1174 BOOST_CHECK( defaultReport.find( "10K" ) != std::string::npos );
1175 BOOST_CHECK( defaultReport.find( "22K" ) == std::string::npos );
1176
1177 std::string altPopReport = runReport( wxS( "AltPop" ) );
1178 BOOST_CHECK( altPopReport.find( "22K" ) != std::string::npos );
1179 BOOST_CHECK( altPopReport.find( "10K" ) == std::string::npos );
1180}
1181
1182
1192BOOST_AUTO_TEST_CASE( ExcessVariantsCleanedWhenNetlistEmpty )
1193{
1194 BOARD board;
1195 FOOTPRINT fp( &board );
1196 fp.SetReference( "C1" );
1197 fp.SetFPID( LIB_ID( wxT( "Capacitor_SMD" ), wxT( "C_0805" ) ) );
1198 fp.SetDNP( false );
1199 fp.SetExcludedFromBOM( false );
1200 fp.SetExcludedFromPosFiles( false );
1201
1202 board.AddVariant( "TestVariant" );
1203
1204 // Simulate a previous netlist update that applied DNP=true for this variant
1205 FOOTPRINT_VARIANT* fpVariant = fp.AddVariant( "TestVariant" );
1206 BOOST_REQUIRE( fpVariant );
1207 fpVariant->SetDNP( true );
1208 fpVariant->SetExcludedFromBOM( true );
1209
1210 BOOST_CHECK( fp.GetVariant( "TestVariant" ) != nullptr );
1211 BOOST_CHECK( fp.GetDNPForVariant( "TestVariant" ) );
1212 BOOST_CHECK( fp.GetExcludedFromBOMForVariant( "TestVariant" ) );
1213
1214 // Simulate what applyComponentVariants does when the netlist has no variant
1215 // data for this component (all variant properties now match the base).
1216 // With the fix, the function no longer returns early on empty variants,
1217 // so the excess-variants cleanup runs.
1218
1219 std::set<wxString> excessVariants;
1220
1221 for( const auto& [variantName, _] : fp.GetVariants() )
1222 excessVariants.insert( variantName );
1223
1224 // No netlist variants to process, so nothing is erased from excessVariants.
1225 // All footprint variants are excess.
1226 BOOST_CHECK_EQUAL( excessVariants.size(), 1 );
1227 BOOST_CHECK( excessVariants.count( "TestVariant" ) == 1 );
1228
1229 for( const wxString& excess : excessVariants )
1230 fp.DeleteVariant( excess );
1231
1232 BOOST_CHECK_MESSAGE( fp.GetVariant( "TestVariant" ) == nullptr,
1233 "Stale variant must be removed when netlist has no variant data" );
1234 BOOST_CHECK_MESSAGE( fp.GetVariants().empty(),
1235 "All variants should be cleaned up" );
1236}
1237
1238
1243BOOST_AUTO_TEST_CASE( ExcessVariantsSelectiveCleanup )
1244{
1245 BOARD board;
1246 FOOTPRINT fp( &board );
1247 fp.SetReference( "U1" );
1248 fp.SetFPID( LIB_ID( wxT( "Package_SO" ), wxT( "SOIC-8" ) ) );
1249 fp.SetDNP( false );
1250
1251 board.AddVariant( "Production" );
1252 board.AddVariant( "Debug" );
1253
1254 // Both variants were previously applied to the footprint
1255 FOOTPRINT_VARIANT* prodVariant = fp.AddVariant( "Production" );
1256 BOOST_REQUIRE( prodVariant );
1257 prodVariant->SetDNP( true );
1258
1259 FOOTPRINT_VARIANT* debugVariant = fp.AddVariant( "Debug" );
1260 BOOST_REQUIRE( debugVariant );
1261 debugVariant->SetExcludedFromBOM( true );
1262
1263 BOOST_CHECK_EQUAL( fp.GetVariants().size(), 2 );
1264
1265 // Simulate netlist update where only "Production" has variant data.
1266 // "Debug" variant properties now match the base so it was omitted from the netlist.
1267 std::set<wxString> excessVariants;
1268
1269 for( const auto& [variantName, _] : fp.GetVariants() )
1270 excessVariants.insert( variantName );
1271
1272 // Erase variants that ARE in the netlist
1273 excessVariants.erase( "Production" );
1274
1275 BOOST_CHECK_EQUAL( excessVariants.size(), 1 );
1276 BOOST_CHECK( excessVariants.count( "Debug" ) == 1 );
1277
1278 for( const wxString& excess : excessVariants )
1279 fp.DeleteVariant( excess );
1280
1281 BOOST_CHECK_MESSAGE( fp.GetVariant( "Production" ) != nullptr,
1282 "Production variant should be preserved (in netlist)" );
1283 BOOST_CHECK_MESSAGE( fp.GetVariant( "Debug" ) == nullptr,
1284 "Debug variant should be removed (not in netlist)" );
1285 BOOST_CHECK_EQUAL( fp.GetVariants().size(), 1 );
1286}
1287
1288
General utilities for PCB file IO for QA programs.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:323
void SetCurrentVariant(const wxString &aVariant)
Definition board.cpp:2721
const std::vector< wxString > & GetVariantNames() const
Definition board.h:401
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1237
wxArrayString GetVariantNamesForUI() const
Return the variant names for UI display.
Definition board.cpp:2867
void DeleteVariant(const wxString &aVariantName)
Definition board.cpp:2755
bool ResolveTextVar(wxString *token, int aDepth) const
Definition board.cpp:510
bool HasVariant(const wxString &aVariantName) const
Definition board.cpp:2738
void AddVariant(const wxString &aVariantName)
Definition board.cpp:2744
wxString GetVariantDescription(const wxString &aVariantName) const
Definition board.cpp:2831
wxString GetCurrentVariant() const
Definition board.h:398
void RenameVariant(const wxString &aOldName, const wxString &aNewName)
Definition board.cpp:2783
void SetVariantDescription(const wxString &aVariantName, const wxString &aDescription)
Definition board.cpp:2850
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:166
wxString GetName() const
Definition footprint.h:176
bool HasFieldValue(const wxString &aFieldName) const
Definition footprint.h:213
void SetExcludedFromPosFiles(bool aExclude)
Definition footprint.h:186
wxString GetFieldValue(const wxString &aFieldName) const
Get a field value override for this variant.
Definition footprint.h:193
const std::map< wxString, wxString > & GetFields() const
Definition footprint.h:218
bool GetExcludedFromBOM() const
Definition footprint.h:182
void SetDNP(bool aDNP)
Definition footprint.h:180
bool GetExcludedFromPosFiles() const
Definition footprint.h:185
bool GetDNP() const
Definition footprint.h:179
void SetFieldValue(const wxString &aFieldName, const wxString &aValue)
Set a field value override for this variant.
Definition footprint.h:208
void SetExcludedFromBOM(bool aExclude)
Definition footprint.h:183
const CASE_INSENSITIVE_MAP< FOOTPRINT_VARIANT > & GetVariants() const
Get all variants.
Definition footprint.h:963
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:372
bool IsDNP() const
Definition footprint.h:899
bool IsExcludedFromBOM() const
Definition footprint.h:890
void SetDNP(bool aDNP=true)
Definition footprint.h:900
void SetExcludedFromBOM(bool aExclude=true)
Definition footprint.h:891
void SetAttributes(int aAttributes)
Definition footprint.h:438
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:807
void SetExcludedFromPosFiles(bool aExclude=true)
Definition footprint.h:882
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:777
bool IsExcludedFromPosFiles() const
Definition footprint.h:881
void SetValue(const wxString &aValue)
Definition footprint.h:798
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.
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:49
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(bool aAllowExtraText, 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.
BOARD * LoadBoard(const wxString &aFileName, BOARD *aAppendToMe, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr) override
Load information from some input file format that this PCB_IO implementation knows about into either ...
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,...
Is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition richio.h:226
#define _(s)
@ FP_DNP
Definition footprint.h:90
@ FP_EXCLUDE_FROM_POS_FILES
Definition footprint.h:86
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:87
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
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 path
VECTOR3I v1(5, 5, 5)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
BOOST_CHECK_EQUAL(result, "25.4")
BOOST_AUTO_TEST_CASE(FootprintVariantBasics)
Test FOOTPRINT_VARIANT class basic operations.
VECTOR2I v2(1, 0)