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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include <filesystem>
25#include <fstream>
26#include <memory>
27#include <sstream>
28#include <string>
29
33
35
36#include <board.h>
40#include <footprint.h>
41#include <netinfo.h>
42#include <pad.h>
43#include <pcb_shape.h>
44#include <zone.h>
45
46
53
54
58BOOST_FIXTURE_TEST_SUITE( KiCadSexprIO, KICAD_SEXPR_FIXTURE )
59
60
61
64BOOST_AUTO_TEST_CASE( Issue19775_ZoneLayerWildcards )
65{
66 std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + "plugins/kicad_sexpr/Issue19775_ZoneLayers/";
67
68 BOOST_TEST_CONTEXT( "Zone layers with wildcards" )
69 {
70 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
71
72 kicadPlugin.LoadBoard( dataPath + "LayerWildcard.kicad_pcb", testBoard.get() );
73
74 // One zone in the file
75 BOOST_CHECK( testBoard->Zones().size() == 1 );
76
77 ZONE* z = testBoard->Zones()[0];
78
79 // On both front and back layers, with zone fill on both
80 BOOST_CHECK( z->GetLayerSet().Contains( F_Cu ) && z->GetLayerSet().Contains( B_Cu ) );
81 BOOST_CHECK( z->GetFilledPolysList( F_Cu )->TotalVertices() > 0 );
82 BOOST_CHECK( z->GetFilledPolysList( B_Cu )->TotalVertices() > 0 );
83 }
84
85 BOOST_TEST_CONTEXT( "Round trip layers" )
86 {
87 auto tmpBoard = std::filesystem::temp_directory_path() / "Issue19775_RoundTrip.kicad_pcb";
88
89 // Load and save the board from above to test how we write the zones into it
90 {
91 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
92 kicadPlugin.LoadBoard( dataPath + "LayerEnumerate.kicad_pcb", testBoard.get() );
93 kicadPlugin.SaveBoard( tmpBoard.string(), testBoard.get() );
94 }
95
96 // Read the new board
97 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
98 kicadPlugin.LoadBoard( tmpBoard.string(), testBoard.get() );
99
100 // One zone in the file
101 BOOST_CHECK( testBoard->Zones().size() == 1 );
102
103 ZONE* z = testBoard->Zones()[0];
104
105 // On both front and back layers, with zone fill on both
106 BOOST_CHECK( z->GetLayerSet().Contains( F_Cu ) && z->GetLayerSet().Contains( B_Cu ) );
107 BOOST_CHECK( z->GetFilledPolysList( F_Cu )->TotalVertices() > 0 );
108 BOOST_CHECK( z->GetFilledPolysList( B_Cu )->TotalVertices() > 0 );
109 BOOST_CHECK( z->LayerProperties().contains( F_Cu ) );
110 }
111}
112
113
121BOOST_AUTO_TEST_CASE( Issue23125_EmptyZoneDiscarded )
122{
123 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
124 + "plugins/kicad_sexpr/Issue23125_EmptyZone/";
125
126 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
127
128 kicadPlugin.LoadBoard( dataPath + "EmptyZone.kicad_pcb", testBoard.get() );
129
130 // The file contains 3 zones: 1 valid (with polygon) and 2 empty (no polygon).
131 // The 2 empty zones should have been discarded during loading.
132 BOOST_CHECK_EQUAL( testBoard->Zones().size(), 1 );
133
134 // The surviving zone should have a valid position
135 ZONE* z = testBoard->Zones()[0];
136 BOOST_CHECK_NO_THROW( z->GetPosition() );
137 BOOST_CHECK( z->GetNumCorners() > 0 );
138}
139
140
146BOOST_AUTO_TEST_CASE( ScientificNotationLoading )
147{
148 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
149 + "plugins/kicad_sexpr/";
150
151 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
152
153 kicadPlugin.LoadBoard( dataPath + "ScientificNotation.kicad_pcb", testBoard.get() );
154
155 // The file contains 1 arc with scientific notation in its coordinates
156 BOOST_CHECK_EQUAL( testBoard->Drawings().size(), 1 );
157
158 PCB_SHAPE* arc = dynamic_cast<PCB_SHAPE*>( testBoard->Drawings().front() );
159
160 // The arc's midpoint should be located at (4.17, -4.5e-05) in file units
161 BOOST_REQUIRE( arc );
162 BOOST_TEST( arc->GetArcMid().x == 4170000 );
163 BOOST_TEST( arc->GetArcMid().y == -45 );
164}
165
166
173BOOST_AUTO_TEST_CASE( Issue23625_CorruptedStackupCapped )
174{
175 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
176 + "plugins/kicad_sexpr/Issue23625_CorruptedStackup/";
177
178 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
179
180 BOOST_CHECK_NO_THROW( kicadPlugin.LoadBoard( dataPath + "corrupted_stackup.kicad_pcb",
181 testBoard.get() ) );
182
183 const BOARD_STACKUP& stackup =
184 testBoard->GetDesignSettings().GetStackupDescriptor();
185
186 // The test file has 200 dielectric layers (plus copper, silk, mask), well
187 // beyond the parser's 128-item cap. After loading, the count must be
188 // clamped and the board must still be usable.
189 BOOST_CHECK_LE( stackup.GetCount(), 128 );
190 BOOST_CHECK_GT( stackup.GetCount(), 0 );
191}
192
193
200BOOST_AUTO_TEST_CASE( Issue23752_AppendBoardPreservesStackupAndGrowsToSixCopperLayers )
201{
202 std::string dataPath = KI_TEST::GetPcbnewTestDataDir();
203
204 // four layer board
205 std::string destinationPath = dataPath + "issue3812.kicad_pcb";
206 // six layer board
207 std::string sourcePath = dataPath + "issue18142.kicad_pcb";
208 std::map<std::string, UTF8> props;
209
210 // basically, make sure that we start with a four layer board, append a six layer
211 // board like this is a design block apply-layout with six layers, and make
212 // sure we end up with a six layer board and a six layer stackup and not a
213 // ten layer board
214 //
215 // we also need to test the stackup properties are preserved, because the increase
216 // in copper layers should cause a stackup growth from from four to six layers,
217 // but not replace the stackup with the source board's stackup, which has different properties (e.g. finish type)
219
220 auto countCopperLayers = []( const BOARD_STACKUP& aStackup )
221 {
222 int copperLayerCount = 0;
223
224 for( BOARD_STACKUP_ITEM* item : aStackup.GetList() )
225 {
226 if( item->GetType() == BS_ITEM_TYPE_COPPER )
227 ++copperLayerCount;
228 }
229
230 return copperLayerCount;
231 };
232
233 auto findFirstDielectric = []( const BOARD_STACKUP& aStackup ) -> const BOARD_STACKUP_ITEM*
234 {
235 for( BOARD_STACKUP_ITEM* item : aStackup.GetList() )
236 {
237 if( item->GetType() == BS_ITEM_TYPE_DIELECTRIC )
238 return item;
239 }
240
241 return nullptr;
242 };
243
244 std::unique_ptr<BOARD> testBoard = std::make_unique<BOARD>();
245
246 kicadPlugin.LoadBoard( destinationPath, testBoard.get() );
247
248 const BOARD_STACKUP& initialStackup = testBoard->GetDesignSettings().GetStackupDescriptor();
249 const BOARD_STACKUP_ITEM* initialFirstDielectric = findFirstDielectric( initialStackup );
250 const int initialCopperLayerCount = testBoard->GetCopperLayerCount();
251 const LSET initialEnabledLayers = testBoard->GetEnabledLayers();
252 const wxString initialFinishType = initialStackup.m_FinishType;
253
254 BOOST_REQUIRE_EQUAL( initialCopperLayerCount, 4 );
255 BOOST_REQUIRE_EQUAL( countCopperLayers( initialStackup ), 4 );
256 BOOST_REQUIRE( initialFirstDielectric );
257 BOOST_REQUIRE_EQUAL( initialFinishType, wxS( "ENIG" ) );
258 const wxString initialFirstDielectricMaterial = initialFirstDielectric->GetMaterial();
259 const int initialFirstDielectricThickness = initialFirstDielectric->GetThickness();
260
261 kicadPlugin.LoadBoard( sourcePath, testBoard.get(), &props );
262
263 const int appendedCopperLayerCount = testBoard->GetCopperLayerCount();
264
265 if( appendedCopperLayerCount > initialCopperLayerCount )
266 testBoard->SetCopperLayerCount( appendedCopperLayerCount );
267
268 LSET enabledLayers = testBoard->GetEnabledLayers();
269 enabledLayers |= initialEnabledLayers;
270 testBoard->SetEnabledLayers( enabledLayers );
271 testBoard->GetDesignSettings().GetStackupDescriptor().SynchronizeWithBoard( &testBoard->GetDesignSettings() );
272
273 const BOARD_STACKUP& finalStackup = testBoard->GetDesignSettings().GetStackupDescriptor();
274 const BOARD_STACKUP_ITEM* finalFirstDielectric = findFirstDielectric( finalStackup );
275
276 BOOST_CHECK_EQUAL( testBoard->GetCopperLayerCount(), 6 );
277 BOOST_CHECK_EQUAL( countCopperLayers( finalStackup ), 6 );
278 BOOST_REQUIRE( finalFirstDielectric );
279 BOOST_CHECK_EQUAL( finalStackup.m_FinishType, initialFinishType );
280 BOOST_CHECK_EQUAL( finalFirstDielectric->GetMaterial(), initialFirstDielectricMaterial );
281 BOOST_CHECK_EQUAL( finalFirstDielectric->GetThickness(), initialFirstDielectricThickness );
282}
283
284
302BOOST_AUTO_TEST_CASE( FootprintSave_OmitsNetsOnAllBoardConnectedItems )
303{
304 auto tmpLib = std::filesystem::temp_directory_path() / "qa_fp_save_netinfo.pretty";
305 std::filesystem::remove_all( tmpLib );
306 std::filesystem::create_directories( tmpLib );
307
308 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
309
310 NETINFO_ITEM* net = new NETINFO_ITEM( board.get(), wxT( "TestNet" ), 1 );
311 board->Add( net );
312
313 FOOTPRINT* fp = new FOOTPRINT( board.get() );
314 board->Add( fp );
315 fp->SetFPID( LIB_ID( wxT( "scratch" ), wxT( "test_fp_save_netinfo" ) ) );
316
317 PAD* pad = new PAD( fp );
319 pad->SetSize( PADSTACK::ALL_LAYERS,
320 VECTOR2I( pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) ) );
321 pad->SetNet( net );
322 fp->Add( pad );
323
324 PCB_SHAPE* shape = new PCB_SHAPE( fp, SHAPE_T::SEGMENT );
325 shape->SetLayer( F_Cu );
326 shape->SetStart( VECTOR2I( 0, 0 ) );
327 shape->SetEnd( VECTOR2I( pcbIUScale.mmToIU( 5 ), 0 ) );
328 shape->SetNet( net );
329 fp->Add( shape );
330
331 ZONE* zone = new ZONE( fp );
332 zone->SetLayer( F_Cu );
333 zone->SetNet( net );
334 fp->Add( zone );
335
336 BOOST_REQUIRE_EQUAL( pad->GetNet(), net );
337 BOOST_REQUIRE_EQUAL( shape->GetNet(), net );
338 BOOST_REQUIRE_EQUAL( zone->GetNet(), net );
339
340 // Save into the scratch library via the public library-save API. Must not throw.
341 BOOST_REQUIRE_NO_THROW( kicadPlugin.FootprintSave( tmpLib.string(), fp ) );
342
343 auto savedFile = tmpLib / "test_fp_save_netinfo.kicad_mod";
344 BOOST_REQUIRE( std::filesystem::exists( savedFile ) );
345
346 std::ifstream in( savedFile );
347 BOOST_REQUIRE_MESSAGE( in.is_open(),
348 "Failed to open serialized footprint: " << savedFile.string() );
349
350 std::stringstream ss;
351 ss << in.rdbuf();
352 BOOST_REQUIRE( !in.bad() );
353
354 const std::string contents = ss.str();
355 BOOST_REQUIRE( !contents.empty() );
356
357 BOOST_CHECK_MESSAGE( contents.find( "(net " ) == std::string::npos,
358 "Saved footprint library file must not contain (net ...) tokens:\n"
359 << contents );
360
361 // Verify the second defense directly on the save-path's transient state: clone the
362 // footprint, detach it from its parent board exactly as FootprintSave does, invoke
363 // ClearAllNets(), and assert every BOARD_CONNECTED_ITEM descendant is orphaned. This
364 // catches a regression where FootprintSave stops calling ClearAllNets() even if the
365 // serializer guards keep the file contents looking correct.
366 std::unique_ptr<FOOTPRINT> detached( static_cast<FOOTPRINT*>( fp->Clone() ) );
367 detached->SetParent( nullptr );
368 detached->SetParentGroup( nullptr );
369 detached->ClearAllNets();
370
371 BOARD_CONNECTED_ITEM* detachedPad = nullptr;
372 BOARD_CONNECTED_ITEM* detachedShape = nullptr;
373 BOARD_CONNECTED_ITEM* detachedZone = nullptr;
374
375 detached->RunOnChildren(
376 [&]( BOARD_ITEM* aItem )
377 {
378 switch( aItem->Type() )
379 {
380 case PCB_PAD_T: detachedPad = static_cast<BOARD_CONNECTED_ITEM*>( aItem ); break;
381 case PCB_SHAPE_T: detachedShape = static_cast<BOARD_CONNECTED_ITEM*>( aItem ); break;
382 case PCB_ZONE_T: detachedZone = static_cast<BOARD_CONNECTED_ITEM*>( aItem ); break;
383 default: break;
384 }
385 },
387
388 BOOST_REQUIRE( detachedPad );
389 BOOST_REQUIRE( detachedShape );
390 BOOST_REQUIRE( detachedZone );
394
395 std::filesystem::remove_all( tmpLib );
396}
397
398
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:125
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,...
NETINFO_ITEM * GetNet() const
Return #NET_INFO object for a given item.
void SetNet(NETINFO_ITEM *aNetInfo)
Set a NET_INFO object for the item.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
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
wxString m_FinishType
The name of external copper finish.
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:112
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:194
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:236
VECTOR2I GetArcMid() const
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:430
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.
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:49
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:50
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:236
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:55
A #PLUGIN derivation for saving and loading Pcbnew s-expression formatted files.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
int TotalVertices() const
Return total number of vertices stored in the set.
Handle a list of polygons defining a copper zone.
Definition zone.h:74
ZONE_LAYER_PROPERTIES & LayerProperties(PCB_LAYER_ID aLayer)
Definition zone.h:150
std::shared_ptr< SHAPE_POLY_SET > GetFilledPolysList(PCB_LAYER_ID aLayer) const
Definition zone.h:602
virtual void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition zone.cpp:550
VECTOR2I GetPosition() const override
Definition zone.cpp:492
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:137
int GetNumCorners(void) const
Access to m_Poly parameters.
Definition zone.h:520
@ RECURSE
Definition eda_item.h:53
@ SEGMENT
Definition eda_shape.h:48
@ B_Cu
Definition layer_ids.h:65
@ F_Cu
Definition layer_ids.h:64
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:47
PCB_IO_KICAD_SEXPR kicadPlugin
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_TEST(contains==c.ExpectedContains)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
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:85
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:105
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:84
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687