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 <algorithm>
21#include <chrono>
22#include <filesystem>
23#include <fstream>
24#include <memory>
25#include <sstream>
26#include <string>
27#include <thread>
28
32
35#include <progress_reporter.h>
36#include <richio.h>
37
38#include <board.h>
42#include <footprint.h>
43#include <netinfo.h>
44#include <pad.h>
45#include <pcb_shape.h>
46#include <pcb_track.h>
47#include <zone.h>
48
49
56
57
61BOOST_FIXTURE_TEST_SUITE( KiCadSexprIO, KICAD_SEXPR_FIXTURE )
62
63
64
67BOOST_AUTO_TEST_CASE( Issue19775_ZoneLayerWildcards )
68{
69 std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + "plugins/kicad_sexpr/Issue19775_ZoneLayers/";
70
71 BOOST_TEST_CONTEXT( "Zone layers with wildcards" )
72 {
73 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
74
75 kicadPlugin.LoadBoard( dataPath + "LayerWildcard.kicad_pcb", testBoard.get() );
76
77 // One zone in the file
78 BOOST_CHECK( testBoard->Zones().size() == 1 );
79
80 ZONE* z = testBoard->Zones()[0];
81
82 // On both front and back layers, with zone fill on both
83 BOOST_CHECK( z->GetLayerSet().Contains( F_Cu ) && z->GetLayerSet().Contains( B_Cu ) );
84 BOOST_CHECK( z->GetFilledPolysList( F_Cu )->TotalVertices() > 0 );
85 BOOST_CHECK( z->GetFilledPolysList( B_Cu )->TotalVertices() > 0 );
86 }
87
88 BOOST_TEST_CONTEXT( "Round trip layers" )
89 {
90 auto tmpBoard = std::filesystem::temp_directory_path() / "Issue19775_RoundTrip.kicad_pcb";
91
92 // Load and save the board from above to test how we write the zones into it
93 {
94 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
95 kicadPlugin.LoadBoard( dataPath + "LayerEnumerate.kicad_pcb", testBoard.get() );
96 kicadPlugin.SaveBoard( tmpBoard.string(), testBoard.get() );
97 }
98
99 // Read the new board
100 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
101 kicadPlugin.LoadBoard( tmpBoard.string(), testBoard.get() );
102
103 // One zone in the file
104 BOOST_CHECK( testBoard->Zones().size() == 1 );
105
106 ZONE* z = testBoard->Zones()[0];
107
108 // On both front and back layers, with zone fill on both
109 BOOST_CHECK( z->GetLayerSet().Contains( F_Cu ) && z->GetLayerSet().Contains( B_Cu ) );
110 BOOST_CHECK( z->GetFilledPolysList( F_Cu )->TotalVertices() > 0 );
111 BOOST_CHECK( z->GetFilledPolysList( B_Cu )->TotalVertices() > 0 );
112 BOOST_CHECK( z->LayerProperties().contains( F_Cu ) );
113 }
114}
115
116
124BOOST_AUTO_TEST_CASE( Issue23125_EmptyZoneDiscarded )
125{
126 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
127 + "plugins/kicad_sexpr/Issue23125_EmptyZone/";
128
129 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
130
131 kicadPlugin.LoadBoard( dataPath + "EmptyZone.kicad_pcb", testBoard.get() );
132
133 // The file contains 3 zones: 1 valid (with polygon) and 2 empty (no polygon).
134 // The 2 empty zones should have been discarded during loading.
135 BOOST_CHECK_EQUAL( testBoard->Zones().size(), 1 );
136
137 // The surviving zone should have a valid position
138 ZONE* z = testBoard->Zones()[0];
139 BOOST_CHECK_NO_THROW( z->GetPosition() );
140 BOOST_CHECK( z->GetNumCorners() > 0 );
141}
142
143
149BOOST_AUTO_TEST_CASE( ScientificNotationLoading )
150{
151 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
152 + "plugins/kicad_sexpr/";
153
154 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
155
156 kicadPlugin.LoadBoard( dataPath + "ScientificNotation.kicad_pcb", testBoard.get() );
157
158 // The file contains 1 arc with scientific notation in its coordinates
159 BOOST_CHECK_EQUAL( testBoard->Drawings().size(), 1 );
160
161 PCB_SHAPE* arc = dynamic_cast<PCB_SHAPE*>( testBoard->Drawings().front() );
162
163 // The arc's midpoint should be located at (4.17, -4.5e-05) in file units
164 BOOST_REQUIRE( arc );
165 BOOST_TEST( arc->GetArcMid().x == 4170000 );
166 BOOST_TEST( arc->GetArcMid().y == -45 );
167}
168
169
176BOOST_AUTO_TEST_CASE( Issue23625_CorruptedStackupCapped )
177{
178 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
179 + "plugins/kicad_sexpr/Issue23625_CorruptedStackup/";
180
181 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
182
183 BOOST_CHECK_NO_THROW( kicadPlugin.LoadBoard( dataPath + "corrupted_stackup.kicad_pcb",
184 testBoard.get() ) );
185
186 const BOARD_STACKUP& stackup =
187 testBoard->GetDesignSettings().GetStackupDescriptor();
188
189 // The test file has 200 dielectric layers (plus copper, silk, mask), well
190 // beyond the parser's 128-item cap. After loading, the count must be
191 // clamped and the board must still be usable.
192 BOOST_CHECK_LE( stackup.GetCount(), 128 );
193 BOOST_CHECK_GT( stackup.GetCount(), 0 );
194}
195
196
204BOOST_AUTO_TEST_CASE( Issue24133_DuplicatedStackupNotInflated )
205{
206 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
207 + "plugins/kicad_sexpr/Issue24133_DuplicatedStackup/";
208
209 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
210
211 BOOST_CHECK_NO_THROW( kicadPlugin.LoadBoard( dataPath + "duplicated_stackup.kicad_pcb",
212 testBoard.get() ) );
213
214 const BOARD_STACKUP& stackup = testBoard->GetDesignSettings().GetStackupDescriptor();
215
216 // The file repeats the 9-item layer sequence three times. Only the first copy must be kept.
217 BOOST_CHECK_EQUAL( stackup.GetCount(), 9 );
218
219 // A single copy is a standard 1.6 mm two-layer board. Without dedup the three copies would
220 // sum to 4.8 mm.
222}
223
224
231BOOST_AUTO_TEST_CASE( Issue23752_AppendBoardPreservesStackupAndGrowsToSixCopperLayers )
232{
233 std::string dataPath = KI_TEST::GetPcbnewTestDataDir();
234
235 // four layer board
236 std::string destinationPath = dataPath + "issue3812.kicad_pcb";
237 // six layer board
238 std::string sourcePath = dataPath + "issue18142.kicad_pcb";
239 std::map<std::string, UTF8> props;
240
241 // basically, make sure that we start with a four layer board, append a six layer
242 // board like this is a design block apply-layout with six layers, and make
243 // sure we end up with a six layer board and a six layer stackup and not a
244 // ten layer board
245 //
246 // we also need to test the stackup properties are preserved, because the increase
247 // in copper layers should cause a stackup growth from from four to six layers,
248 // but not replace the stackup with the source board's stackup, which has different properties (e.g. finish type)
250
251 auto countCopperLayers = []( const BOARD_STACKUP& aStackup )
252 {
253 int copperLayerCount = 0;
254
255 for( BOARD_STACKUP_ITEM* item : aStackup.GetList() )
256 {
257 if( item->GetType() == BS_ITEM_TYPE_COPPER )
258 ++copperLayerCount;
259 }
260
261 return copperLayerCount;
262 };
263
264 auto findFirstDielectric = []( const BOARD_STACKUP& aStackup ) -> const BOARD_STACKUP_ITEM*
265 {
266 for( BOARD_STACKUP_ITEM* item : aStackup.GetList() )
267 {
268 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
269 return item;
270 }
271
272 return nullptr;
273 };
274
275 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
276
277 kicadPlugin.LoadBoard( destinationPath, testBoard.get() );
278
279 const BOARD_STACKUP& initialStackup = testBoard->GetDesignSettings().GetStackupDescriptor();
280 const BOARD_STACKUP_ITEM* initialFirstDielectric = findFirstDielectric( initialStackup );
281 const int initialCopperLayerCount = testBoard->GetCopperLayerCount();
282 const LSET initialEnabledLayers = testBoard->GetEnabledLayers();
283 const wxString initialFinishType = initialStackup.m_FinishType;
284
285 BOOST_REQUIRE_EQUAL( initialCopperLayerCount, 4 );
286 BOOST_REQUIRE_EQUAL( countCopperLayers( initialStackup ), 4 );
287 BOOST_REQUIRE( initialFirstDielectric );
288 BOOST_REQUIRE_EQUAL( initialFinishType, wxS( "ENIG" ) );
289 const wxString initialFirstDielectricMaterial = initialFirstDielectric->GetMaterial();
290 const int initialFirstDielectricThickness = initialFirstDielectric->GetThickness();
291
292 kicadPlugin.LoadBoard( sourcePath, testBoard.get(), &props );
293
294 const int appendedCopperLayerCount = testBoard->GetCopperLayerCount();
295
296 if( appendedCopperLayerCount > initialCopperLayerCount )
297 testBoard->SetCopperLayerCount( appendedCopperLayerCount );
298
299 LSET enabledLayers = testBoard->GetEnabledLayers();
300 enabledLayers |= initialEnabledLayers;
301 testBoard->SetEnabledLayers( enabledLayers );
302 testBoard->GetDesignSettings().GetStackupDescriptor().SynchronizeWithBoard( &testBoard->GetDesignSettings() );
303
304 const BOARD_STACKUP& finalStackup = testBoard->GetDesignSettings().GetStackupDescriptor();
305 const BOARD_STACKUP_ITEM* finalFirstDielectric = findFirstDielectric( finalStackup );
306
307 BOOST_CHECK_EQUAL( testBoard->GetCopperLayerCount(), 6 );
308 BOOST_CHECK_EQUAL( countCopperLayers( finalStackup ), 6 );
309 BOOST_REQUIRE( finalFirstDielectric );
310 BOOST_CHECK_EQUAL( finalStackup.m_FinishType, initialFinishType );
311 BOOST_CHECK_EQUAL( finalFirstDielectric->GetMaterial(), initialFirstDielectricMaterial );
312 BOOST_CHECK_EQUAL( finalFirstDielectric->GetThickness(), initialFirstDielectricThickness );
313}
314
315
323BOOST_AUTO_TEST_CASE( Issue24642_AppendBoardPreservesDestinationLayerNames )
324{
325 std::string dataPath = KI_TEST::GetPcbnewTestDataDir();
326
327 // four layer board with custom copper layer names
328 std::string destinationPath = dataPath + "issue3812.kicad_pcb";
329 // six layer board using the standard copper layer names
330 std::string sourcePath = dataPath + "issue18142.kicad_pcb";
331
332 std::map<std::string, UTF8> props;
334
335 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
336
337 kicadPlugin.LoadBoard( destinationPath, testBoard.get() );
338
339 BOOST_REQUIRE_EQUAL( testBoard->GetLayerName( F_Cu ), wxS( "Top_layer" ) );
340 BOOST_REQUIRE_EQUAL( testBoard->GetLayerName( In1_Cu ), wxS( "GND_layer" ) );
341 BOOST_REQUIRE_EQUAL( testBoard->GetLayerName( In2_Cu ), wxS( "VDD_layer" ) );
342 BOOST_REQUIRE_EQUAL( testBoard->GetLayerName( B_Cu ), wxS( "Bottom_layer" ) );
343
344 kicadPlugin.LoadBoard( sourcePath, testBoard.get(), &props );
345
346 // The destination layer names must survive the append
347 BOOST_CHECK_EQUAL( testBoard->GetLayerName( F_Cu ), wxS( "Top_layer" ) );
348 BOOST_CHECK_EQUAL( testBoard->GetLayerName( In1_Cu ), wxS( "GND_layer" ) );
349 BOOST_CHECK_EQUAL( testBoard->GetLayerName( In2_Cu ), wxS( "VDD_layer" ) );
350 BOOST_CHECK_EQUAL( testBoard->GetLayerName( B_Cu ), wxS( "Bottom_layer" ) );
351
352 // The two extra copper layers from the source board are still added
353 BOOST_CHECK( testBoard->IsLayerEnabled( In3_Cu ) );
354 BOOST_CHECK( testBoard->IsLayerEnabled( In4_Cu ) );
355 BOOST_CHECK_EQUAL( testBoard->GetCopperLayerCount(), 6 );
356}
357
358
365BOOST_AUTO_TEST_CASE( Issue24642_AppendBoardRemapsLayersViaHandler )
366{
367 std::string dataPath = KI_TEST::GetPcbnewTestDataDir();
368 // four layer board with custom copper names and tracks on In2.Cu
369 std::string sourcePath = dataPath + "issue3812.kicad_pcb";
370
371 std::map<std::string, UTF8> props;
373
374 auto countTracksOn = []( BOARD* aBoard, PCB_LAYER_ID aLayer )
375 {
376 int n = 0;
377
378 for( PCB_TRACK* track : aBoard->Tracks() )
379 {
380 if( track->GetLayer() == aLayer )
381 n++;
382 }
383
384 return n;
385 };
386
387 // Source copper user names as the handler sees them
388 const wxString fName = wxS( "Top_layer" );
389 const wxString in1Name = wxS( "GND_layer" );
390 const wxString in2Name = wxS( "VDD_layer" );
391 const wxString bName = wxS( "Bottom_layer" );
392
393 // Identity mapping: appended layers stay where they are
394 std::vector<wxString> seenNames;
395
396 kicadPlugin.RegisterCallback(
397 [&]( const std::vector<INPUT_LAYER_DESC>& aDescs ) -> std::map<wxString, PCB_LAYER_ID>
398 {
399 for( const INPUT_LAYER_DESC& desc : aDescs )
400 seenNames.push_back( desc.Name );
401
402 return { { fName, F_Cu }, { in1Name, In1_Cu }, { in2Name, In2_Cu }, { bName, B_Cu } };
403 } );
404
405 std::unique_ptr<BOARD> identityBoard = std::make_unique<BOARD>();
406 kicadPlugin.LoadBoard( sourcePath, identityBoard.get(), &props );
407
408 // The dialog must see the source board's own layer names, not the canonical defaults
409 BOOST_CHECK( std::find( seenNames.begin(), seenNames.end(), in1Name ) != seenNames.end() );
410 BOOST_CHECK( std::find( seenNames.begin(), seenNames.end(), in2Name ) != seenNames.end() );
411
412 const int idIn1 = countTracksOn( identityBoard.get(), In1_Cu );
413 const int idIn2 = countTracksOn( identityBoard.get(), In2_Cu );
414
415 BOOST_REQUIRE_GT( idIn2, 0 ); // the source has tracks on In2.Cu
416
417 // Swap mapping: appended In1 <-> In2
418 kicadPlugin.RegisterCallback(
419 [&]( const std::vector<INPUT_LAYER_DESC>& ) -> std::map<wxString, PCB_LAYER_ID>
420 {
421 return { { fName, F_Cu }, { in1Name, In2_Cu }, { in2Name, In1_Cu }, { bName, B_Cu } };
422 } );
423
424 std::unique_ptr<BOARD> swapBoard = std::make_unique<BOARD>();
425 kicadPlugin.LoadBoard( sourcePath, swapBoard.get(), &props );
426
427 const int swIn1 = countTracksOn( swapBoard.get(), In1_Cu );
428 const int swIn2 = countTracksOn( swapBoard.get(), In2_Cu );
429
430 // The inner-copper track counts must have swapped
431 BOOST_CHECK_EQUAL( swIn1, idIn2 );
432 BOOST_CHECK_EQUAL( swIn2, idIn1 );
433}
434
435
441BOOST_AUTO_TEST_CASE( Issue24642_AppendBoardPromptsOnlyOnNameMismatch )
442{
443 std::string dataPath = KI_TEST::GetPcbnewTestDataDir();
444 std::string standardNames = dataPath + "issue18142.kicad_pcb"; // six layer, default names
445 std::string customNames = dataPath + "issue3812.kicad_pcb"; // four layer, custom copper names
446
447 std::map<std::string, UTF8> props;
449
450 bool handlerCalled = false;
451
452 kicadPlugin.RegisterCallback(
453 [&]( const std::vector<INPUT_LAYER_DESC>& ) -> std::map<wxString, PCB_LAYER_ID>
454 {
455 handlerCalled = true;
456 return {};
457 } );
458
459 // Appending onto a board with identical layer names must not prompt
460 std::unique_ptr<BOARD> matchBoard = std::make_unique<BOARD>();
461 kicadPlugin.LoadBoard( standardNames, matchBoard.get() );
462 handlerCalled = false;
463 kicadPlugin.LoadBoard( standardNames, matchBoard.get(), &props );
464 BOOST_CHECK( !handlerCalled );
465
466 // Appending a board with differently-named layers must prompt
467 std::unique_ptr<BOARD> mismatchBoard = std::make_unique<BOARD>();
468 kicadPlugin.LoadBoard( standardNames, mismatchBoard.get() );
469 handlerCalled = false;
470 kicadPlugin.LoadBoard( customNames, mismatchBoard.get(), &props );
471 BOOST_CHECK( handlerCalled );
472}
473
474
492BOOST_AUTO_TEST_CASE( FootprintSave_OmitsNetsOnAllBoardConnectedItems )
493{
494 auto tmpLib = std::filesystem::temp_directory_path() / "qa_fp_save_netinfo.pretty";
495 std::filesystem::remove_all( tmpLib );
496 std::filesystem::create_directories( tmpLib );
497
498 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
499
500 NETINFO_ITEM* net = new NETINFO_ITEM( board.get(), wxT( "TestNet" ), 1 );
501 board->Add( net );
502
503 FOOTPRINT* fp = new FOOTPRINT( board.get() );
504 board->Add( fp );
505 fp->SetFPID( LIB_ID( wxT( "scratch" ), wxT( "test_fp_save_netinfo" ) ) );
506
507 PAD* pad = new PAD( fp );
509 pad->SetSize( PADSTACK::ALL_LAYERS,
510 VECTOR2I( pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) ) );
511 pad->SetNet( net );
512 fp->Add( pad );
513
514 PCB_SHAPE* shape = new PCB_SHAPE( fp, SHAPE_T::SEGMENT );
515 shape->SetLayer( F_Cu );
516 shape->SetStart( VECTOR2I( 0, 0 ) );
517 shape->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 5 ), 0 ) );
518 shape->SetNet( net );
519 fp->Add( shape );
520
521 ZONE* zone = new ZONE( fp );
522 zone->SetLayer( F_Cu );
523 zone->SetNet( net );
524 fp->Add( zone );
525
526 BOOST_REQUIRE_EQUAL( pad->GetNet(), net );
527 BOOST_REQUIRE_EQUAL( shape->GetNet(), net );
528 BOOST_REQUIRE_EQUAL( zone->GetNet(), net );
529
530 // Save into the scratch library via the public library-save API. Must not throw.
531 BOOST_REQUIRE_NO_THROW( kicadPlugin.FootprintSave( tmpLib.string(), fp ) );
532
533 auto savedFile = tmpLib / "test_fp_save_netinfo.kicad_mod";
534 BOOST_REQUIRE( std::filesystem::exists( savedFile ) );
535
536 std::ifstream in( savedFile );
537 BOOST_REQUIRE_MESSAGE( in.is_open(),
538 "Failed to open serialized footprint: " << savedFile.string() );
539
540 std::stringstream ss;
541 ss << in.rdbuf();
542 BOOST_REQUIRE( !in.bad() );
543
544 const std::string contents = ss.str();
545 BOOST_REQUIRE( !contents.empty() );
546
547 BOOST_CHECK_MESSAGE( contents.find( "(net " ) == std::string::npos,
548 "Saved footprint library file must not contain (net ...) tokens:\n"
549 << contents );
550
551 // Verify the second defense directly on the save-path's transient state: clone the
552 // footprint, detach it from its parent board exactly as FootprintSave does, invoke
553 // ClearAllNets(), and assert every BOARD_CONNECTED_ITEM descendant is orphaned. This
554 // catches a regression where FootprintSave stops calling ClearAllNets() even if the
555 // serializer guards keep the file contents looking correct.
556 std::unique_ptr<FOOTPRINT> detached( static_cast<FOOTPRINT*>( fp->Clone() ) );
557 detached->SetParent( nullptr );
558 detached->SetParentGroup( nullptr );
559 detached->ClearAllNets();
560
561 BOARD_CONNECTED_ITEM* detachedPad = nullptr;
562 BOARD_CONNECTED_ITEM* detachedShape = nullptr;
563 BOARD_CONNECTED_ITEM* detachedZone = nullptr;
564
565 detached->RunOnChildren(
566 [&]( BOARD_ITEM* aItem )
567 {
568 switch( aItem->Type() )
569 {
570 case PCB_PAD_T: detachedPad = static_cast<BOARD_CONNECTED_ITEM*>( aItem ); break;
571 case PCB_SHAPE_T: detachedShape = static_cast<BOARD_CONNECTED_ITEM*>( aItem ); break;
572 case PCB_ZONE_T: detachedZone = static_cast<BOARD_CONNECTED_ITEM*>( aItem ); break;
573 default: break;
574 }
575 },
577
578 BOOST_REQUIRE( detachedPad );
579 BOOST_REQUIRE( detachedShape );
580 BOOST_REQUIRE( detachedZone );
584
585 std::filesystem::remove_all( tmpLib );
586}
587
588
597BOOST_AUTO_TEST_CASE( CopperThievingZone_RoundTrip )
598{
599 std::unique_ptr<BOARD> writeBoard = std::make_unique<BOARD>();
600
601 ZONE* zone = new ZONE( writeBoard.get() );
602 zone->SetLayer( F_Cu );
603 zone->AppendCorner( VECTOR2I( 0, 0 ), -1 );
604 zone->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 10 ), 0 ), -1 );
605 zone->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 10 ), pcbIUScale.mmToIU( 10 ) ), -1 );
606 zone->AppendCorner( VECTOR2I( 0, pcbIUScale.mmToIU( 10 ) ), -1 );
608
609 THIEVING_SETTINGS thieving;
611 thieving.element_size = pcbIUScale.mmToIU( 0.42 );
612 thieving.gap = pcbIUScale.mmToIU( 1.27 );
613 thieving.line_width = pcbIUScale.mmToIU( 0.35 );
614 thieving.stagger = true;
615 thieving.orientation = EDA_ANGLE( 30.0, DEGREES_T );
616 zone->SetThievingSettings( thieving );
617
618 writeBoard->Add( zone );
619
620 std::filesystem::path tmpPath = std::filesystem::temp_directory_path()
621 / "copper_thieving_roundtrip.kicad_pcb";
622
623 PCB_IO_KICAD_SEXPR writer;
624 writer.SaveBoard( tmpPath.string(), writeBoard.get() );
625
626 std::unique_ptr<BOARD> readBoard = std::make_unique<BOARD>();
627 PCB_IO_KICAD_SEXPR reader;
628 reader.LoadBoard( tmpPath.string(), readBoard.get() );
629
630 BOOST_REQUIRE_EQUAL( readBoard->Zones().size(), 1u );
631
632 ZONE* loaded = readBoard->Zones()[0];
633
634 BOOST_CHECK( loaded->GetFillMode() == ZONE_FILL_MODE::COPPER_THIEVING );
635 BOOST_CHECK( loaded->IsCopperThieving() );
636 BOOST_CHECK_EQUAL( loaded->GetNetCode(), 0 );
637
638 const THIEVING_SETTINGS& loadedSettings = loaded->GetThievingSettings();
639 BOOST_CHECK( loadedSettings.pattern == THIEVING_PATTERN::HATCH );
640 BOOST_CHECK_EQUAL( loadedSettings.element_size, thieving.element_size );
641 BOOST_CHECK_EQUAL( loadedSettings.gap, thieving.gap );
642 BOOST_CHECK_EQUAL( loadedSettings.line_width, thieving.line_width );
643 BOOST_CHECK_EQUAL( loadedSettings.stagger, true );
644 BOOST_CHECK( loadedSettings.orientation == EDA_ANGLE( 30.0, DEGREES_T ) );
645
646 std::filesystem::remove( tmpPath );
647}
648
649
654BOOST_AUTO_TEST_CASE( CopperThievingZone_AllPatternsRoundTrip )
655{
656 const std::array<THIEVING_PATTERN, 3> patterns = {
660 };
661
662 for( THIEVING_PATTERN pattern : patterns )
663 {
664 BOOST_TEST_CONTEXT( "pattern enum value " << static_cast<int>( pattern ) )
665 {
666 std::unique_ptr<BOARD> writeBoard = std::make_unique<BOARD>();
667
668 ZONE* zone = new ZONE( writeBoard.get() );
669 zone->SetLayer( F_Cu );
670 zone->AppendCorner( VECTOR2I( 0, 0 ), -1 );
671 zone->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 5 ), 0 ), -1 );
672 zone->AppendCorner( VECTOR2I( pcbIUScale.mmToIU( 5 ), pcbIUScale.mmToIU( 5 ) ), -1 );
673 zone->AppendCorner( VECTOR2I( 0, pcbIUScale.mmToIU( 5 ) ), -1 );
675
676 THIEVING_SETTINGS thieving = zone->GetThievingSettings();
677 thieving.pattern = pattern;
678 zone->SetThievingSettings( thieving );
679
680 writeBoard->Add( zone );
681
682 std::filesystem::path tmpPath = std::filesystem::temp_directory_path()
683 / "copper_thieving_pattern.kicad_pcb";
684 PCB_IO_KICAD_SEXPR writer;
685 writer.SaveBoard( tmpPath.string(), writeBoard.get() );
686
687 std::unique_ptr<BOARD> readBoard = std::make_unique<BOARD>();
688 PCB_IO_KICAD_SEXPR reader;
689 reader.LoadBoard( tmpPath.string(), readBoard.get() );
690
691 BOOST_REQUIRE_EQUAL( readBoard->Zones().size(), 1u );
692 BOOST_CHECK( readBoard->Zones()[0]->GetThievingSettings().pattern == pattern );
693
694 std::filesystem::remove( tmpPath );
695 }
696 }
697}
698
699
706BOOST_AUTO_TEST_CASE( CopperThievingZone_RejectedInOldFileVersion )
707{
708 std::filesystem::path tmpPath = std::filesystem::temp_directory_path()
709 / "copper_thieving_old_version.kicad_pcb";
710 std::ofstream out( tmpPath );
711 // 20260512 is the Net Chains release; thieving needs >= 20260513. Using the
712 // immediately-prior version exercises the boundary check.
713 out << "(kicad_pcb (version 20260512) (generator \"test\") (generator_version \"test\")"
714 << " (general (thickness 1.6)) (paper \"A4\")"
715 << " (layers (0 \"F.Cu\" signal) (31 \"B.Cu\" signal))"
716 << " (zone (net 0) (net_name \"\") (layer \"F.Cu\") (uuid \"00000000-0000-0000-0000-000000000001\")"
717 << " (hatch edge 0.5)"
718 << " (connect_pads (clearance 0))"
719 << " (min_thickness 0.25) (filled_areas_thickness no)"
720 << " (fill yes (mode thieving) (thermal_gap 0.5) (thermal_bridge_width 0.5)"
721 << " (island_removal_mode 0))"
722 << " (polygon (pts (xy 0 0) (xy 5 0) (xy 5 5) (xy 0 5))))"
723 << ")";
724 out.close();
725
726 std::unique_ptr<BOARD> readBoard = std::make_unique<BOARD>();
727 PCB_IO_KICAD_SEXPR reader;
728 BOOST_CHECK_THROW( reader.LoadBoard( tmpPath.string(), readBoard.get() ), IO_ERROR );
729
730 std::filesystem::remove( tmpPath );
731}
732
733
734BOOST_AUTO_TEST_CASE( MalformedDimensionTextThrowsCleanly )
735{
736 KI_TEST::TEMPORARY_DIRECTORY tempDir( "kicad_qa_malformed_dimension_text_", "" );
737 std::filesystem::path tmpPath = tempDir.GetPath() / "malformed_dimension_text.kicad_pcb";
738 std::ofstream out( tmpPath );
739 out << "(kicad_pcb (version 20240108) (generator \"test\")"
740 << " (general (thickness 1.6)) (paper \"A4\")"
741 << " (layers (0 \"F.Cu\" signal) (31 \"B.Cu\" signal) (36 \"B.SilkS\" user \"b.silkscreen\"))"
742 << " (dimension (type aligned) (layer \"B.SilkS\")"
743 << " (gr_text \"1 mm\" (at broken))))";
744 out.close();
745
746 std::unique_ptr<BOARD> readBoard = std::make_unique<BOARD>();
747 PCB_IO_KICAD_SEXPR reader;
748 BOOST_CHECK_THROW( reader.LoadBoard( tmpPath.string(), readBoard.get() ), IO_ERROR );
749}
750
751
758BOOST_AUTO_TEST_CASE( CopperThievingZone_RejectsMalformedGeometry )
759{
760 std::filesystem::path tmpPath = std::filesystem::temp_directory_path()
761 / "copper_thieving_malformed.kicad_pcb";
762 std::ofstream out( tmpPath );
763 out << "(kicad_pcb (version 20260513) (generator \"test\") (generator_version \"test\")"
764 << " (general (thickness 1.6)) (paper \"A4\")"
765 << " (layers (0 \"F.Cu\" signal) (31 \"B.Cu\" signal))"
766 << " (zone (net 0) (net_name \"\") (layer \"F.Cu\") (uuid \"00000000-0000-0000-0000-000000000002\")"
767 << " (hatch edge 0.5)"
768 << " (connect_pads (clearance 0))"
769 << " (min_thickness 0.25) (filled_areas_thickness no)"
770 << " (fill yes (mode thieving)"
771 << " (thermal_gap 0.5) (thermal_bridge_width 0.5)"
772 << " (island_removal_mode 0)"
773 << " (thieving (type dots) (size -1) (gap 0)"
774 << " (width -5) (stagger no) (orientation 0)))"
775 << " (polygon (pts (xy 0 0) (xy 5 0) (xy 5 5) (xy 0 5))))"
776 << ")";
777 out.close();
778
779 std::unique_ptr<BOARD> readBoard = std::make_unique<BOARD>();
780 PCB_IO_KICAD_SEXPR reader;
781 reader.LoadBoard( tmpPath.string(), readBoard.get() );
782
783 BOOST_REQUIRE_EQUAL( readBoard->Zones().size(), 1u );
784
785 const THIEVING_SETTINGS& loaded = readBoard->Zones()[0]->GetThievingSettings();
786 BOOST_CHECK_GT( loaded.element_size, 0 );
787 BOOST_CHECK_GT( loaded.gap, 0 );
788 BOOST_CHECK_GT( loaded.line_width, 0 );
789
790 std::filesystem::remove( tmpPath );
791}
792
793
801BOOST_AUTO_TEST_CASE( Issue24955_AppendDoesNotInheritSessionZoneDefaults )
802{
803 KI_TEST::TEMPORARY_DIRECTORY tempDir( "kicad_qa_zone_defaults_append_", "" );
804 std::filesystem::path tmpPath = tempDir.GetPath() / "thermal_zone_block.kicad_pcb";
805 std::ofstream out( tmpPath );
806
807 // Two zones, first with every omitted-when-default token left out (thermal, polygon
808 // fill, no smoothing, unlocked), second with the explicit tokens
809 out << "(kicad_pcb (version 20260513) (generator \"test\") (generator_version \"test\")"
810 << " (general (thickness 1.6)) (paper \"A4\")"
811 << " (layers (0 \"F.Cu\" signal) (31 \"B.Cu\" signal))"
812 << " (zone (net 0) (net_name \"\") (layer \"F.Cu\") (uuid \"00000000-0000-0000-0000-000000000001\")"
813 << " (hatch edge 0.5)"
814 << " (connect_pads (clearance 0.5))"
815 << " (min_thickness 0.25) (filled_areas_thickness no)"
816 << " (fill yes (thermal_gap 0.5) (thermal_bridge_width 0.5) (island_removal_mode 0))"
817 << " (polygon (pts (xy 0 0) (xy 5 0) (xy 5 5) (xy 0 5))))"
818 << " (zone (net 0) (net_name \"\") (layer \"F.Cu\") (uuid \"00000000-0000-0000-0000-000000000002\")"
819 << " (locked yes)"
820 << " (hatch edge 0.5)"
821 << " (connect_pads yes (clearance 0.5))"
822 << " (min_thickness 0.25) (filled_areas_thickness no)"
823 << " (fill yes (thermal_gap 0.5) (thermal_bridge_width 0.5) (island_removal_mode 0)"
824 << " (smoothing fillet) (radius 1))"
825 << " (polygon (pts (xy 10 0) (xy 15 0) (xy 15 5) (xy 10 5))))"
826 << ")";
827 out.close();
828
829 // A destination board whose session zone defaults differ from the format defaults
830 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
831
832 ZONE_SETTINGS settings = board->GetDesignSettings().GetDefaultZoneSettings();
836 settings.SetCornerRadius( pcbIUScale.mmToIU( 1 ) );
837 settings.m_HatchSmoothingLevel = 2;
838 settings.m_Locked = true;
839 board->GetDesignSettings().SetDefaultZoneSettings( settings );
840
841 kicadPlugin.LoadBoard( tmpPath.string(), board.get() );
842
843 BOOST_REQUIRE_EQUAL( board->Zones().size(), 2u );
844
845 // The all-defaults zone must not pick up the board's session defaults
846 ZONE* omitted = board->Zones()[0];
847 BOOST_CHECK( omitted->GetPadConnection() == ZONE_CONNECTION::THERMAL );
848 BOOST_CHECK( omitted->GetFillMode() == ZONE_FILL_MODE::POLYGONS );
850 BOOST_CHECK_EQUAL( omitted->GetCornerRadius(), 0 );
852 BOOST_CHECK( !omitted->IsLocked() );
853
854 // Explicit tokens still win
855 ZONE* explicitZone = board->Zones()[1];
856 BOOST_CHECK( explicitZone->GetPadConnection() == ZONE_CONNECTION::FULL );
858 BOOST_CHECK_EQUAL( explicitZone->GetCornerRadius(), pcbIUScale.mmToIU( 1 ) );
859 BOOST_CHECK( explicitZone->IsLocked() );
860}
861
862
867BOOST_AUTO_TEST_CASE( Issue24911_CancelledAppendLeavesBoardUntouched )
868{
869 // Requests cancellation once the parse reaches the given line, then stalls long
870 // enough to open the parser 250ms cancel checkpoint window
871 class CANCEL_AT_LINE_READER : public STRING_LINE_READER
872 {
873 public:
874 CANCEL_AT_LINE_READER( const std::string& aText, unsigned aCancelLine, bool* aCancelFlag ) :
875 STRING_LINE_READER( aText, wxT( "cancel-test" ) ),
876 m_cancelLine( aCancelLine ),
877 m_cancelFlag( aCancelFlag )
878 {
879 }
880
881 char* ReadLine() override
882 {
883 if( !*m_cancelFlag && LineNumber() >= m_cancelLine )
884 {
885 *m_cancelFlag = true;
886 std::this_thread::sleep_for( std::chrono::milliseconds( 300 ) );
887 }
888
890 }
891
892 private:
893 unsigned m_cancelLine;
894 bool* m_cancelFlag;
895 };
896
897 class CANCELLING_REPORTER : public PROGRESS_REPORTER
898 {
899 public:
900 CANCELLING_REPORTER( bool* aCancelFlag ) :
901 m_cancelFlag( aCancelFlag )
902 {
903 }
904
905 void SetNumPhases( int ) override {}
906 void AddPhases( int ) override {}
907 void BeginPhase( int ) override {}
908 void AdvancePhase() override {}
909 void AdvancePhase( const wxString& ) override {}
910 void Report( const wxString& ) override {}
911 void SetCurrentProgress( double ) override {}
912 void SetMaxProgress( int ) override {}
913 void AdvanceProgress() override {}
914 void SetTitle( const wxString& ) override {}
915 bool IsCancelled() const override { return *m_cancelFlag; }
916 bool KeepRefreshing( bool ) override { return !*m_cancelFlag; }
917
918 private:
919 bool* m_cancelFlag;
920 };
921
922 // Source board with one net and enough tracks that the cancel lands mid-parse
923 std::unique_ptr<BOARD> source = std::make_unique<BOARD>();
924
925 NETINFO_ITEM* net = new NETINFO_ITEM( source.get(), wxT( "GHOST_NET" ), 1 );
926 source->Add( net );
927
928 for( int i = 0; i < 400; i++ )
929 {
930 PCB_TRACK* track = new PCB_TRACK( source.get() );
931 track->SetStart( VECTOR2I( i * 10000, 0 ) );
932 track->SetEnd( VECTOR2I( i * 10000, 10000 ) );
933 track->SetWidth( 200000 );
934 track->SetLayer( F_Cu );
935 track->SetNet( net );
936 source->Add( track );
937 }
938
940 kicadPlugin.FormatBoardToFormatter( &fmt, source.get() );
941
942 // The formatter emits a single line, the parser can only be interrupted between lines
943 std::string text = fmt.GetString();
945
946 unsigned lineCount = std::count( text.begin(), text.end(), '\n' );
947
948 // Destination board with one pre-existing track
949 std::unique_ptr<BOARD> dest = std::make_unique<BOARD>();
950
951 PCB_TRACK* existing = new PCB_TRACK( dest.get() );
952 existing->SetStart( VECTOR2I( 0, 0 ) );
953 existing->SetEnd( VECTOR2I( 5000, 0 ) );
954 existing->SetWidth( 200000 );
955 existing->SetLayer( F_Cu );
956 dest->Add( existing );
957
958 size_t tracksBefore = dest->Tracks().size();
959 unsigned netsBefore = dest->GetNetInfo().GetNetCount();
960
961 bool cancelRequested = false;
962
963 CANCEL_AT_LINE_READER reader( text, lineCount / 2, &cancelRequested );
964 CANCELLING_REPORTER reporter( &cancelRequested );
965
966 BOOST_CHECK_THROW( kicadPlugin.DoLoad( reader, dest.get(), nullptr, &reporter, lineCount ), IO_ERROR );
967 BOOST_CHECK( cancelRequested );
968
969 BOOST_CHECK_EQUAL( dest->Tracks().size(), tracksBefore );
970 BOOST_CHECK_EQUAL( dest->GetNetInfo().GetNetCount(), netsBefore );
971}
972
973
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.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
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
bool IsLocked() const override
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:373
const TRACKS & Tracks() const
Definition board.h:419
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 temporary directory that will be deleted when it goes out of scope.
const std::filesystem::path & GetPath() const
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
virtual unsigned LineNumber() const
Return the line number of the last line read from this LINE_READER.
Definition richio.h:116
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
void SetEnd(const VECTOR2I &aEnd)
Definition pcb_track.h:89
void SetStart(const VECTOR2I &aStart)
Definition pcb_track.h:92
virtual void SetWidth(int aWidth)
Definition pcb_track.h:86
A progress reporter interface for use in multi-threaded environments.
virtual bool IsCancelled() const =0
virtual void AddPhases(int aNumPhases)=0
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void SetNumPhases(int aNumPhases)=0
Set the number of phases.
virtual void Report(const wxString &aMessage)=0
Display aMessage in the progress bar dialog.
virtual void BeginPhase(int aPhase)=0
Initialize the aPhase virtual zone of the dialog progress bar.
virtual void AdvancePhase()=0
Use the next available virtual zone of the dialog progress bar.
virtual void SetTitle(const wxString &aTitle)=0
Change the title displayed on the window caption.
virtual void AdvanceProgress()=0
Increment the progress bar length (inside the current virtual zone).
virtual void SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
virtual void SetMaxProgress(int aMaxProgress)=0
Fix the value that gives the 100 percent progress bar length (inside the current virtual zone).
int TotalVertices() const
Return total number of vertices stored in the set.
Implement an OUTPUTFORMATTER to a memory buffer.
Definition richio.h:418
const std::string & GetString()
Definition richio.h:441
Is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition richio.h:222
STRING_LINE_READER(const std::string &aString, const wxString &aSource)
Construct a string line reader.
Definition richio.cpp:242
char * ReadLine() override
Read a line of text into the buffer and increments the line number counter.
Definition richio.cpp:265
ZONE_SETTINGS handles zones parameters.
void SetPadConnection(ZONE_CONNECTION aPadConnection)
void SetCornerRadius(int aRadius)
ZONE_FILL_MODE m_FillMode
void SetCornerSmoothingType(int aType)
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:619
bool IsCopperThieving() const
Definition zone.h:349
void SetFillMode(ZONE_FILL_MODE aFillMode)
Definition zone.cpp:625
ZONE_CONNECTION GetPadConnection() const
Definition zone.h:312
void SetThievingSettings(const THIEVING_SETTINGS &aSettings)
Definition zone.h:352
VECTOR2I GetPosition() const override
Definition zone.cpp:543
void SetNet(NETINFO_ITEM *aNetInfo) override
Override that drops aNetInfo when this zone is in copper-thieving fill mode.
Definition zone.cpp:610
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:1410
int GetHatchSmoothingLevel() const
Definition zone.h:334
unsigned int GetCornerRadius() const
Definition zone.h:756
int GetCornerSmoothingType() const
Definition zone.h:752
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
void Prettify(std::string &aSource, FORMAT_MODE aMode)
Pretty-prints s-expression text according to KiCad format rules.
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)
IbisParser parser & reporter
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.
@ THERMAL
Use thermal relief for pads.
Definition zones.h:46
@ FULL
pads are covered by copper
Definition zones.h:47