KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_kicad_sexpr.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
20#include <filesystem>
21#include <fstream>
22#include <memory>
23#include <sstream>
24#include <string>
25
29
31#include <richio.h>
32
33#include <board.h>
37#include <footprint.h>
38#include <netinfo.h>
39#include <pad.h>
40#include <pcb_shape.h>
41#include <pcb_track.h>
42#include <zone.h>
43
44
51
52
56BOOST_FIXTURE_TEST_SUITE( KiCadSexprIO, KICAD_SEXPR_FIXTURE )
57
58
59
62BOOST_AUTO_TEST_CASE( Issue19775_ZoneLayerWildcards )
63{
64 std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + "plugins/kicad_sexpr/Issue19775_ZoneLayers/";
65
66 BOOST_TEST_CONTEXT( "Zone layers with wildcards" )
67 {
68 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
69
70 kicadPlugin.LoadBoard( dataPath + "LayerWildcard.kicad_pcb", testBoard.get() );
71
72 // One zone in the file
73 BOOST_CHECK( testBoard->Zones().size() == 1 );
74
75 ZONE* z = testBoard->Zones()[0];
76
77 // On both front and back layers, with zone fill on both
78 BOOST_CHECK( z->GetLayerSet().Contains( F_Cu ) && z->GetLayerSet().Contains( B_Cu ) );
79 BOOST_CHECK( z->GetFilledPolysList( F_Cu )->TotalVertices() > 0 );
80 BOOST_CHECK( z->GetFilledPolysList( B_Cu )->TotalVertices() > 0 );
81 }
82
83 BOOST_TEST_CONTEXT( "Round trip layers" )
84 {
85 auto tmpBoard = std::filesystem::temp_directory_path() / "Issue19775_RoundTrip.kicad_pcb";
86
87 // Load and save the board from above to test how we write the zones into it
88 {
89 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
90 kicadPlugin.LoadBoard( dataPath + "LayerEnumerate.kicad_pcb", testBoard.get() );
91 kicadPlugin.SaveBoard( tmpBoard.string(), testBoard.get() );
92 }
93
94 // Read the new board
95 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
96 kicadPlugin.LoadBoard( tmpBoard.string(), testBoard.get() );
97
98 // One zone in the file
99 BOOST_CHECK( testBoard->Zones().size() == 1 );
100
101 ZONE* z = testBoard->Zones()[0];
102
103 // On both front and back layers, with zone fill on both
104 BOOST_CHECK( z->GetLayerSet().Contains( F_Cu ) && z->GetLayerSet().Contains( B_Cu ) );
105 BOOST_CHECK( z->GetFilledPolysList( F_Cu )->TotalVertices() > 0 );
106 BOOST_CHECK( z->GetFilledPolysList( B_Cu )->TotalVertices() > 0 );
107 BOOST_CHECK( z->LayerProperties().contains( F_Cu ) );
108 }
109}
110
111
119BOOST_AUTO_TEST_CASE( Issue23125_EmptyZoneDiscarded )
120{
121 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
122 + "plugins/kicad_sexpr/Issue23125_EmptyZone/";
123
124 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
125
126 kicadPlugin.LoadBoard( dataPath + "EmptyZone.kicad_pcb", testBoard.get() );
127
128 // The file contains 3 zones: 1 valid (with polygon) and 2 empty (no polygon).
129 // The 2 empty zones should have been discarded during loading.
130 BOOST_CHECK_EQUAL( testBoard->Zones().size(), 1 );
131
132 // The surviving zone should have a valid position
133 ZONE* z = testBoard->Zones()[0];
134 BOOST_CHECK_NO_THROW( z->GetPosition() );
135 BOOST_CHECK( z->GetNumCorners() > 0 );
136}
137
138
144BOOST_AUTO_TEST_CASE( ScientificNotationLoading )
145{
146 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
147 + "plugins/kicad_sexpr/";
148
149 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
150
151 kicadPlugin.LoadBoard( dataPath + "ScientificNotation.kicad_pcb", testBoard.get() );
152
153 // The file contains 1 arc with scientific notation in its coordinates
154 BOOST_CHECK_EQUAL( testBoard->Drawings().size(), 1 );
155
156 PCB_SHAPE* arc = dynamic_cast<PCB_SHAPE*>( testBoard->Drawings().front() );
157
158 // The arc's midpoint should be located at (4.17, -4.5e-05) in file units
159 BOOST_REQUIRE( arc );
160 BOOST_TEST( arc->GetArcMid().x == 4170000 );
161 BOOST_TEST( arc->GetArcMid().y == -45 );
162}
163
164
171BOOST_AUTO_TEST_CASE( Issue23625_CorruptedStackupCapped )
172{
173 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
174 + "plugins/kicad_sexpr/Issue23625_CorruptedStackup/";
175
176 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
177
178 BOOST_CHECK_NO_THROW( kicadPlugin.LoadBoard( dataPath + "corrupted_stackup.kicad_pcb",
179 testBoard.get() ) );
180
181 const BOARD_STACKUP& stackup =
182 testBoard->GetDesignSettings().GetStackupDescriptor();
183
184 // The test file has 200 dielectric layers (plus copper, silk, mask), well
185 // beyond the parser's 128-item cap. After loading, the count must be
186 // clamped and the board must still be usable.
187 BOOST_CHECK_LE( stackup.GetCount(), 128 );
188 BOOST_CHECK_GT( stackup.GetCount(), 0 );
189}
190
191
199BOOST_AUTO_TEST_CASE( Issue24133_DuplicatedStackupNotInflated )
200{
201 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
202 + "plugins/kicad_sexpr/Issue24133_DuplicatedStackup/";
203
204 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
205
206 BOOST_CHECK_NO_THROW( kicadPlugin.LoadBoard( dataPath + "duplicated_stackup.kicad_pcb",
207 testBoard.get() ) );
208
209 const BOARD_STACKUP& stackup = testBoard->GetDesignSettings().GetStackupDescriptor();
210
211 // The file repeats the 9-item layer sequence three times. Only the first copy must be kept.
212 BOOST_CHECK_EQUAL( stackup.GetCount(), 9 );
213
214 // A single copy is a standard 1.6 mm two-layer board. Without dedup the three copies would
215 // sum to 4.8 mm.
217}
218
219
226BOOST_AUTO_TEST_CASE( Issue23752_AppendBoardPreservesStackupAndGrowsToSixCopperLayers )
227{
228 std::string dataPath = KI_TEST::GetPcbnewTestDataDir();
229
230 // four layer board
231 std::string destinationPath = dataPath + "issue3812.kicad_pcb";
232 // six layer board
233 std::string sourcePath = dataPath + "issue18142.kicad_pcb";
234 std::map<std::string, UTF8> props;
235
236 // basically, make sure that we start with a four layer board, append a six layer
237 // board like this is a design block apply-layout with six layers, and make
238 // sure we end up with a six layer board and a six layer stackup and not a
239 // ten layer board
240 //
241 // we also need to test the stackup properties are preserved, because the increase
242 // in copper layers should cause a stackup growth from from four to six layers,
243 // but not replace the stackup with the source board's stackup, which has different properties (e.g. finish type)
245
246 auto countCopperLayers = []( const BOARD_STACKUP& aStackup )
247 {
248 int copperLayerCount = 0;
249
250 for( BOARD_STACKUP_ITEM* item : aStackup.GetList() )
251 {
252 if( item->GetType() == BS_ITEM_TYPE_COPPER )
253 ++copperLayerCount;
254 }
255
256 return copperLayerCount;
257 };
258
259 auto findFirstDielectric = []( const BOARD_STACKUP& aStackup ) -> const BOARD_STACKUP_ITEM*
260 {
261 for( BOARD_STACKUP_ITEM* item : aStackup.GetList() )
262 {
263 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
264 return item;
265 }
266
267 return nullptr;
268 };
269
270 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
271
272 kicadPlugin.LoadBoard( destinationPath, testBoard.get() );
273
274 const BOARD_STACKUP& initialStackup = testBoard->GetDesignSettings().GetStackupDescriptor();
275 const BOARD_STACKUP_ITEM* initialFirstDielectric = findFirstDielectric( initialStackup );
276 const int initialCopperLayerCount = testBoard->GetCopperLayerCount();
277 const LSET initialEnabledLayers = testBoard->GetEnabledLayers();
278 const wxString initialFinishType = initialStackup.m_FinishType;
279
280 BOOST_REQUIRE_EQUAL( initialCopperLayerCount, 4 );
281 BOOST_REQUIRE_EQUAL( countCopperLayers( initialStackup ), 4 );
282 BOOST_REQUIRE( initialFirstDielectric );
283 BOOST_REQUIRE_EQUAL( initialFinishType, wxS( "ENIG" ) );
284 const wxString initialFirstDielectricMaterial = initialFirstDielectric->GetMaterial();
285 const int initialFirstDielectricThickness = initialFirstDielectric->GetThickness();
286
287 kicadPlugin.LoadBoard( sourcePath, testBoard.get(), &props );
288
289 const int appendedCopperLayerCount = testBoard->GetCopperLayerCount();
290
291 if( appendedCopperLayerCount > initialCopperLayerCount )
292 testBoard->SetCopperLayerCount( appendedCopperLayerCount );
293
294 LSET enabledLayers = testBoard->GetEnabledLayers();
295 enabledLayers |= initialEnabledLayers;
296 testBoard->SetEnabledLayers( enabledLayers );
297 testBoard->GetDesignSettings().GetStackupDescriptor().SynchronizeWithBoard( &testBoard->GetDesignSettings() );
298
299 const BOARD_STACKUP& finalStackup = testBoard->GetDesignSettings().GetStackupDescriptor();
300 const BOARD_STACKUP_ITEM* finalFirstDielectric = findFirstDielectric( finalStackup );
301
302 BOOST_CHECK_EQUAL( testBoard->GetCopperLayerCount(), 6 );
303 BOOST_CHECK_EQUAL( countCopperLayers( finalStackup ), 6 );
304 BOOST_REQUIRE( finalFirstDielectric );
305 BOOST_CHECK_EQUAL( finalStackup.m_FinishType, initialFinishType );
306 BOOST_CHECK_EQUAL( finalFirstDielectric->GetMaterial(), initialFirstDielectricMaterial );
307 BOOST_CHECK_EQUAL( finalFirstDielectric->GetThickness(), initialFirstDielectricThickness );
308}
309
310
318BOOST_AUTO_TEST_CASE( Issue24642_AppendBoardPreservesDestinationLayerNames )
319{
320 std::string dataPath = KI_TEST::GetPcbnewTestDataDir();
321
322 // four layer board with custom copper layer names
323 std::string destinationPath = dataPath + "issue3812.kicad_pcb";
324 // six layer board using the standard copper layer names
325 std::string sourcePath = dataPath + "issue18142.kicad_pcb";
326
327 std::map<std::string, UTF8> props;
329
330 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
331
332 kicadPlugin.LoadBoard( destinationPath, testBoard.get() );
333
334 BOOST_REQUIRE_EQUAL( testBoard->GetLayerName( F_Cu ), wxS( "Top_layer" ) );
335 BOOST_REQUIRE_EQUAL( testBoard->GetLayerName( In1_Cu ), wxS( "GND_layer" ) );
336 BOOST_REQUIRE_EQUAL( testBoard->GetLayerName( In2_Cu ), wxS( "VDD_layer" ) );
337 BOOST_REQUIRE_EQUAL( testBoard->GetLayerName( B_Cu ), wxS( "Bottom_layer" ) );
338
339 kicadPlugin.LoadBoard( sourcePath, testBoard.get(), &props );
340
341 // The destination layer names must survive the append
342 BOOST_CHECK_EQUAL( testBoard->GetLayerName( F_Cu ), wxS( "Top_layer" ) );
343 BOOST_CHECK_EQUAL( testBoard->GetLayerName( In1_Cu ), wxS( "GND_layer" ) );
344 BOOST_CHECK_EQUAL( testBoard->GetLayerName( In2_Cu ), wxS( "VDD_layer" ) );
345 BOOST_CHECK_EQUAL( testBoard->GetLayerName( B_Cu ), wxS( "Bottom_layer" ) );
346
347 // The two extra copper layers from the source board are still added
348 BOOST_CHECK( testBoard->IsLayerEnabled( In3_Cu ) );
349 BOOST_CHECK( testBoard->IsLayerEnabled( In4_Cu ) );
350 BOOST_CHECK_EQUAL( testBoard->GetCopperLayerCount(), 6 );
351}
352
353
360BOOST_AUTO_TEST_CASE( Issue24642_AppendBoardRemapsLayersViaHandler )
361{
362 std::string dataPath = KI_TEST::GetPcbnewTestDataDir();
363 // four layer board with custom copper names and tracks on In2.Cu
364 std::string sourcePath = dataPath + "issue3812.kicad_pcb";
365
366 std::map<std::string, UTF8> props;
368
369 auto countTracksOn = []( BOARD* aBoard, PCB_LAYER_ID aLayer )
370 {
371 int n = 0;
372
373 for( PCB_TRACK* track : aBoard->Tracks() )
374 {
375 if( track->GetLayer() == aLayer )
376 n++;
377 }
378
379 return n;
380 };
381
382 // Source copper user names as the handler sees them
383 const wxString fName = wxS( "Top_layer" );
384 const wxString in1Name = wxS( "GND_layer" );
385 const wxString in2Name = wxS( "VDD_layer" );
386 const wxString bName = wxS( "Bottom_layer" );
387
388 // Identity mapping: appended layers stay where they are
389 std::vector<wxString> seenNames;
390
391 kicadPlugin.RegisterCallback(
392 [&]( const std::vector<INPUT_LAYER_DESC>& aDescs ) -> std::map<wxString, PCB_LAYER_ID>
393 {
394 for( const INPUT_LAYER_DESC& desc : aDescs )
395 seenNames.push_back( desc.Name );
396
397 return { { fName, F_Cu }, { in1Name, In1_Cu }, { in2Name, In2_Cu }, { bName, B_Cu } };
398 } );
399
400 std::unique_ptr<BOARD> identityBoard = std::make_unique<BOARD>();
401 kicadPlugin.LoadBoard( sourcePath, identityBoard.get(), &props );
402
403 // The dialog must see the source board's own layer names, not the canonical defaults
404 BOOST_CHECK( std::find( seenNames.begin(), seenNames.end(), in1Name ) != seenNames.end() );
405 BOOST_CHECK( std::find( seenNames.begin(), seenNames.end(), in2Name ) != seenNames.end() );
406
407 const int idIn1 = countTracksOn( identityBoard.get(), In1_Cu );
408 const int idIn2 = countTracksOn( identityBoard.get(), In2_Cu );
409
410 BOOST_REQUIRE_GT( idIn2, 0 ); // the source has tracks on In2.Cu
411
412 // Swap mapping: appended In1 <-> In2
413 kicadPlugin.RegisterCallback(
414 [&]( const std::vector<INPUT_LAYER_DESC>& ) -> std::map<wxString, PCB_LAYER_ID>
415 {
416 return { { fName, F_Cu }, { in1Name, In2_Cu }, { in2Name, In1_Cu }, { bName, B_Cu } };
417 } );
418
419 std::unique_ptr<BOARD> swapBoard = std::make_unique<BOARD>();
420 kicadPlugin.LoadBoard( sourcePath, swapBoard.get(), &props );
421
422 const int swIn1 = countTracksOn( swapBoard.get(), In1_Cu );
423 const int swIn2 = countTracksOn( swapBoard.get(), In2_Cu );
424
425 // The inner-copper track counts must have swapped
426 BOOST_CHECK_EQUAL( swIn1, idIn2 );
427 BOOST_CHECK_EQUAL( swIn2, idIn1 );
428}
429
430
436BOOST_AUTO_TEST_CASE( Issue24642_AppendBoardPromptsOnlyOnNameMismatch )
437{
438 std::string dataPath = KI_TEST::GetPcbnewTestDataDir();
439 std::string standardNames = dataPath + "issue18142.kicad_pcb"; // six layer, default names
440 std::string customNames = dataPath + "issue3812.kicad_pcb"; // four layer, custom copper names
441
442 std::map<std::string, UTF8> props;
444
445 bool handlerCalled = false;
446
447 kicadPlugin.RegisterCallback(
448 [&]( const std::vector<INPUT_LAYER_DESC>& ) -> std::map<wxString, PCB_LAYER_ID>
449 {
450 handlerCalled = true;
451 return {};
452 } );
453
454 // Appending onto a board with identical layer names must not prompt
455 std::unique_ptr<BOARD> matchBoard = std::make_unique<BOARD>();
456 kicadPlugin.LoadBoard( standardNames, matchBoard.get() );
457 handlerCalled = false;
458 kicadPlugin.LoadBoard( standardNames, matchBoard.get(), &props );
459 BOOST_CHECK( !handlerCalled );
460
461 // Appending a board with differently-named layers must prompt
462 std::unique_ptr<BOARD> mismatchBoard = std::make_unique<BOARD>();
463 kicadPlugin.LoadBoard( standardNames, mismatchBoard.get() );
464 handlerCalled = false;
465 kicadPlugin.LoadBoard( customNames, mismatchBoard.get(), &props );
466 BOOST_CHECK( handlerCalled );
467}
468
469
487BOOST_AUTO_TEST_CASE( FootprintSave_OmitsNetsOnAllBoardConnectedItems )
488{
489 auto tmpLib = std::filesystem::temp_directory_path() / "qa_fp_save_netinfo.pretty";
490 std::filesystem::remove_all( tmpLib );
491 std::filesystem::create_directories( tmpLib );
492
493 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
494
495 NETINFO_ITEM* net = new NETINFO_ITEM( board.get(), wxT( "TestNet" ), 1 );
496 board->Add( net );
497
498 FOOTPRINT* fp = new FOOTPRINT( board.get() );
499 board->Add( fp );
500 fp->SetFPID( LIB_ID( wxT( "scratch" ), wxT( "test_fp_save_netinfo" ) ) );
501
502 PAD* pad = new PAD( fp );
504 pad->SetSize( PADSTACK::ALL_LAYERS,
505 VECTOR2I( pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) ) );
506 pad->SetNet( net );
507 fp->Add( pad );
508
509 PCB_SHAPE* shape = new PCB_SHAPE( fp, SHAPE_T::SEGMENT );
510 shape->SetLayer( F_Cu );
511 shape->SetStart( VECTOR2I( 0, 0 ) );
512 shape->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 5 ), 0 ) );
513 shape->SetNet( net );
514 fp->Add( shape );
515
516 ZONE* zone = new ZONE( fp );
517 zone->SetLayer( F_Cu );
518 zone->SetNet( net );
519 fp->Add( zone );
520
521 BOOST_REQUIRE_EQUAL( pad->GetNet(), net );
522 BOOST_REQUIRE_EQUAL( shape->GetNet(), net );
523 BOOST_REQUIRE_EQUAL( zone->GetNet(), net );
524
525 // Save into the scratch library via the public library-save API. Must not throw.
526 BOOST_REQUIRE_NO_THROW( kicadPlugin.FootprintSave( tmpLib.string(), fp ) );
527
528 auto savedFile = tmpLib / "test_fp_save_netinfo.kicad_mod";
529 BOOST_REQUIRE( std::filesystem::exists( savedFile ) );
530
531 std::ifstream in( savedFile );
532 BOOST_REQUIRE_MESSAGE( in.is_open(),
533 "Failed to open serialized footprint: " << savedFile.string() );
534
535 std::stringstream ss;
536 ss << in.rdbuf();
537 BOOST_REQUIRE( !in.bad() );
538
539 const std::string contents = ss.str();
540 BOOST_REQUIRE( !contents.empty() );
541
542 BOOST_CHECK_MESSAGE( contents.find( "(net " ) == std::string::npos,
543 "Saved footprint library file must not contain (net ...) tokens:\n"
544 << contents );
545
546 // Verify the second defense directly on the save-path's transient state: clone the
547 // footprint, detach it from its parent board exactly as FootprintSave does, invoke
548 // ClearAllNets(), and assert every BOARD_CONNECTED_ITEM descendant is orphaned. This
549 // catches a regression where FootprintSave stops calling ClearAllNets() even if the
550 // serializer guards keep the file contents looking correct.
551 std::unique_ptr<FOOTPRINT> detached( static_cast<FOOTPRINT*>( fp->Clone() ) );
552 detached->SetParent( nullptr );
553 detached->SetParentGroup( nullptr );
554 detached->ClearAllNets();
555
556 BOARD_CONNECTED_ITEM* detachedPad = nullptr;
557 BOARD_CONNECTED_ITEM* detachedShape = nullptr;
558 BOARD_CONNECTED_ITEM* detachedZone = nullptr;
559
560 detached->RunOnChildren(
561 [&]( BOARD_ITEM* aItem )
562 {
563 switch( aItem->Type() )
564 {
565 case PCB_PAD_T: detachedPad = static_cast<BOARD_CONNECTED_ITEM*>( aItem ); break;
566 case PCB_SHAPE_T: detachedShape = static_cast<BOARD_CONNECTED_ITEM*>( aItem ); break;
567 case PCB_ZONE_T: detachedZone = static_cast<BOARD_CONNECTED_ITEM*>( aItem ); break;
568 default: break;
569 }
570 },
572
573 BOOST_REQUIRE( detachedPad );
574 BOOST_REQUIRE( detachedShape );
575 BOOST_REQUIRE( detachedZone );
579
580 std::filesystem::remove_all( tmpLib );
581}
582
583
592BOOST_AUTO_TEST_CASE( CopperThievingZone_RoundTrip )
593{
594 std::unique_ptr<BOARD> writeBoard = std::make_unique<BOARD>();
595
596 ZONE* zone = new ZONE( writeBoard.get() );
597 zone->SetLayer( F_Cu );
598 zone->AppendCorner( VECTOR2I( 0, 0 ), -1 );
599 zone->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 10 ), 0 ), -1 );
600 zone->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), -1 );
601 zone->AppendCorner( VECTOR2I( 0, pcbIUScale.mmToIU( 10 ) ), -1 );
603
604 THIEVING_SETTINGS thieving;
606 thieving.element_size = pcbIUScale.mmToIU( 0.42 );
607 thieving.gap = pcbIUScale.mmToIU( 1.27 );
608 thieving.line_width = pcbIUScale.mmToIU( 0.35 );
609 thieving.stagger = true;
610 thieving.orientation = EDA_ANGLE( 30.0, DEGREES_T );
611 zone->SetThievingSettings( thieving );
612
613 writeBoard->Add( zone );
614
615 std::filesystem::path tmpPath = std::filesystem::temp_directory_path()
616 / "copper_thieving_roundtrip.kicad_pcb";
617
618 PCB_IO_KICAD_SEXPR writer;
619 writer.SaveBoard( tmpPath.string(), writeBoard.get() );
620
621 std::unique_ptr<BOARD> readBoard = std::make_unique<BOARD>();
622 PCB_IO_KICAD_SEXPR reader;
623 reader.LoadBoard( tmpPath.string(), readBoard.get() );
624
625 BOOST_REQUIRE_EQUAL( readBoard->Zones().size(), 1u );
626
627 ZONE* loaded = readBoard->Zones()[0];
628
629 BOOST_CHECK( loaded->GetFillMode() == ZONE_FILL_MODE::COPPER_THIEVING );
630 BOOST_CHECK( loaded->IsCopperThieving() );
631 BOOST_CHECK_EQUAL( loaded->GetNetCode(), 0 );
632
633 const THIEVING_SETTINGS& loadedSettings = loaded->GetThievingSettings();
634 BOOST_CHECK( loadedSettings.pattern == THIEVING_PATTERN::HATCH );
635 BOOST_CHECK_EQUAL( loadedSettings.element_size, thieving.element_size );
636 BOOST_CHECK_EQUAL( loadedSettings.gap, thieving.gap );
637 BOOST_CHECK_EQUAL( loadedSettings.line_width, thieving.line_width );
638 BOOST_CHECK_EQUAL( loadedSettings.stagger, true );
639 BOOST_CHECK( loadedSettings.orientation == EDA_ANGLE( 30.0, DEGREES_T ) );
640
641 std::filesystem::remove( tmpPath );
642}
643
644
649BOOST_AUTO_TEST_CASE( CopperThievingZone_AllPatternsRoundTrip )
650{
651 const std::array<THIEVING_PATTERN, 3> patterns = {
655 };
656
657 for( THIEVING_PATTERN pattern : patterns )
658 {
659 BOOST_TEST_CONTEXT( "pattern enum value " << static_cast<int>( pattern ) )
660 {
661 std::unique_ptr<BOARD> writeBoard = std::make_unique<BOARD>();
662
663 ZONE* zone = new ZONE( writeBoard.get() );
664 zone->SetLayer( F_Cu );
665 zone->AppendCorner( VECTOR2I( 0, 0 ), -1 );
666 zone->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 5 ), 0 ), -1 );
667 zone->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) ), -1 );
668 zone->AppendCorner( VECTOR2I( 0, pcbIUScale.mmToIU( 5 ) ), -1 );
670
671 THIEVING_SETTINGS thieving = zone->GetThievingSettings();
672 thieving.pattern = pattern;
673 zone->SetThievingSettings( thieving );
674
675 writeBoard->Add( zone );
676
677 std::filesystem::path tmpPath = std::filesystem::temp_directory_path()
678 / "copper_thieving_pattern.kicad_pcb";
679 PCB_IO_KICAD_SEXPR writer;
680 writer.SaveBoard( tmpPath.string(), writeBoard.get() );
681
682 std::unique_ptr<BOARD> readBoard = std::make_unique<BOARD>();
683 PCB_IO_KICAD_SEXPR reader;
684 reader.LoadBoard( tmpPath.string(), readBoard.get() );
685
686 BOOST_REQUIRE_EQUAL( readBoard->Zones().size(), 1u );
687 BOOST_CHECK( readBoard->Zones()[0]->GetThievingSettings().pattern == pattern );
688
689 std::filesystem::remove( tmpPath );
690 }
691 }
692}
693
694
701BOOST_AUTO_TEST_CASE( CopperThievingZone_RejectedInOldFileVersion )
702{
703 std::filesystem::path tmpPath = std::filesystem::temp_directory_path()
704 / "copper_thieving_old_version.kicad_pcb";
705 std::ofstream out( tmpPath );
706 // 20260512 is the Net Chains release; thieving needs >= 20260513. Using the
707 // immediately-prior version exercises the boundary check.
708 out << "(kicad_pcb (version 20260512) (generator \"test\") (generator_version \"test\")"
709 << " (general (thickness 1.6)) (paper \"A4\")"
710 << " (layers (0 \"F.Cu\" signal) (31 \"B.Cu\" signal))"
711 << " (zone (net 0) (net_name \"\") (layer \"F.Cu\") (uuid \"00000000-0000-0000-0000-000000000001\")"
712 << " (hatch edge 0.5)"
713 << " (connect_pads (clearance 0))"
714 << " (min_thickness 0.25) (filled_areas_thickness no)"
715 << " (fill yes (mode thieving) (thermal_gap 0.5) (thermal_bridge_width 0.5)"
716 << " (island_removal_mode 0))"
717 << " (polygon (pts (xy 0 0) (xy 5 0) (xy 5 5) (xy 0 5))))"
718 << ")";
719 out.close();
720
721 std::unique_ptr<BOARD> readBoard = std::make_unique<BOARD>();
722 PCB_IO_KICAD_SEXPR reader;
723 BOOST_CHECK_THROW( reader.LoadBoard( tmpPath.string(), readBoard.get() ), IO_ERROR );
724
725 std::filesystem::remove( tmpPath );
726}
727
728
735BOOST_AUTO_TEST_CASE( CopperThievingZone_RejectsMalformedGeometry )
736{
737 std::filesystem::path tmpPath = std::filesystem::temp_directory_path()
738 / "copper_thieving_malformed.kicad_pcb";
739 std::ofstream out( tmpPath );
740 out << "(kicad_pcb (version 20260513) (generator \"test\") (generator_version \"test\")"
741 << " (general (thickness 1.6)) (paper \"A4\")"
742 << " (layers (0 \"F.Cu\" signal) (31 \"B.Cu\" signal))"
743 << " (zone (net 0) (net_name \"\") (layer \"F.Cu\") (uuid \"00000000-0000-0000-0000-000000000002\")"
744 << " (hatch edge 0.5)"
745 << " (connect_pads (clearance 0))"
746 << " (min_thickness 0.25) (filled_areas_thickness no)"
747 << " (fill yes (mode thieving)"
748 << " (thermal_gap 0.5) (thermal_bridge_width 0.5)"
749 << " (island_removal_mode 0)"
750 << " (thieving (type dots) (size -1) (gap 0)"
751 << " (width -5) (stagger no) (orientation 0)))"
752 << " (polygon (pts (xy 0 0) (xy 5 0) (xy 5 5) (xy 0 5))))"
753 << ")";
754 out.close();
755
756 std::unique_ptr<BOARD> readBoard = std::make_unique<BOARD>();
757 PCB_IO_KICAD_SEXPR reader;
758 reader.LoadBoard( tmpPath.string(), readBoard.get() );
759
760 BOOST_REQUIRE_EQUAL( readBoard->Zones().size(), 1u );
761
762 const THIEVING_SETTINGS& loaded = readBoard->Zones()[0]->GetThievingSettings();
763 BOOST_CHECK_GT( loaded.element_size, 0 );
764 BOOST_CHECK_GT( loaded.gap, 0 );
765 BOOST_CHECK_GT( loaded.line_width, 0 );
766
767 std::filesystem::remove( tmpPath );
768}
769
770
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
General utilities for PCB file IO for QA programs.
@ BS_ITEM_TYPE_COPPER
@ BS_ITEM_TYPE_DIELECTRIC
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
virtual void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
NETINFO_ITEM * GetNet() const
Return #NET_INFO object for a given item.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
Manage one layer needed to make a physical board.
int GetThickness(int aDielectricSubLayer=0) const
wxString GetMaterial(int aDielectricSubLayer=0) const
Manage layers needed to make a physical board.
int GetCount() const
int BuildBoardThicknessFromStackup() const
wxString m_FinishType
The name of external copper finish.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
const TRACKS & Tracks() const
Definition board.h:418
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
VECTOR2I GetArcMid() const
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:442
EDA_ITEM * Clone() const override
Invoke a function on all children.
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition lset.h:63
Handle the data for a net.
Definition netinfo.h:46
static NETINFO_ITEM * OrphanedItem()
NETINFO_ITEM meaning that there was no net assigned for an item, as there was no board storing net li...
Definition netinfo.h:264
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:61
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 ...
void SaveBoard(const wxString &aFileName, BOARD *aBoard, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aBoard to a storage file in a format that this PCB_IO implementation knows about or it can be u...
void SetEnd(const VECTOR2I &aEnd) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
int TotalVertices() const
Return total number of vertices stored in the set.
Handle a list of polygons defining a copper zone.
Definition zone.h:70
const THIEVING_SETTINGS & GetThievingSettings() const
Definition zone.h:351
ZONE_LAYER_PROPERTIES & LayerProperties(PCB_LAYER_ID aLayer)
Definition zone.h:146
std::shared_ptr< SHAPE_POLY_SET > GetFilledPolysList(PCB_LAYER_ID aLayer) const
Definition zone.h:697
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:599
bool IsCopperThieving() const
Definition zone.h:349
void SetFillMode(ZONE_FILL_MODE aFillMode)
Definition zone.cpp:605
void SetThievingSettings(const THIEVING_SETTINGS &aSettings)
Definition zone.h:352
VECTOR2I GetPosition() const override
Definition zone.cpp:523
void SetNet(NETINFO_ITEM *aNetInfo) override
Override that drops aNetInfo when this zone is in copper-thieving fill mode.
Definition zone.cpp:590
ZONE_FILL_MODE GetFillMode() const
Definition zone.h:238
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:133
bool AppendCorner(VECTOR2I aPosition, int aHoleIdx, bool aAllowDuplication=false)
Add a new corner to the zone outline (to the main outline or a hole)
Definition zone.cpp:1367
int GetNumCorners(void) const
Access to m_Poly parameters.
Definition zone.h:615
@ DEGREES_T
Definition eda_angle.h:31
@ RECURSE
Definition eda_item.h:49
@ SEGMENT
Definition eda_shape.h:46
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ B_Cu
Definition layer_ids.h:61
@ In2_Cu
Definition layer_ids.h:63
@ In4_Cu
Definition layer_ids.h:65
@ In1_Cu
Definition layer_ids.h:62
@ In3_Cu
Definition layer_ids.h:64
@ F_Cu
Definition layer_ids.h:60
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
constexpr char APPEND_PRESERVE_DESTINATION_STACKUP[]
Definition pcb_io.h:43
Describes an imported layer and how it could be mapped to KiCad Layers.
PCB_IO_KICAD_SEXPR kicadPlugin
Parameters that drive copper-thieving fill generation.
EDA_ANGLE orientation
THIEVING_PATTERN pattern
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST(netlist.find("R_G1 ARM_OUT1 DIE_B R='0.001 / ((SW_STATE)") !=std::string::npos)
BOOST_AUTO_TEST_CASE(Issue19775_ZoneLayerWildcards)
Declares the struct as the Boost test fixture.
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:81
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:101
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:80
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
THIEVING_PATTERN
Shape stamped onto the grid for a copper-thieving fill.