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