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