KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_pads_binary_import.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 (C) 2026 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
27
30#include <layer_ids.h>
31#include <padstack.h>
32#include <board.h>
33#include <pcb_text.h>
34#include <pcb_shape.h>
35#include <pcb_field.h>
36#include <pad.h>
37#include <pcb_track.h>
38#include <footprint.h>
39#include <zone.h>
41#include <set>
42
43
45{
46 std::string dir;
47 std::string binaryFile;
48 std::string ascFile;
50};
51
52
54 { "TMS1mmX19", "TMS1mmX19.pcb", "TMS1mmX19.asc", false },
55 { "MC4_PLUS_CSHAPE", "MC4_PLUS_CSHAPE.pcb", "MC4_PLUS_CSHAPE.asc", false },
56 { "MC2_PLUS_REV1", "MC2_PLUS_REV1.pcb", "MC2_PLUS_REV1.asc", true },
57 { "Ems4_Rev2", "Ems4_Rev2.pcb", "Ems4_Rev2.asc", false },
58 { "LCORE_4", "LCORE_4.pcb", "LCORE_4.asc", false },
59 { "LCORE_2", "LCORE_2.pcb", "LCORE_2.asc", false },
60 { "Dexter_MotorCtrl", "Dexter_MotorCtrl.pcb", "Dexter_MotorCtrl.asc", true },
61 { "MAIS_FC", "MAIS_FC.pcb", "MAIS_FC.asc", true },
62};
63
64
65static wxString GetBinaryPath( const PADS_BINARY_BOARD_INFO& aBoard )
66{
67 return KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/" + aBoard.dir + "/"
68 + aBoard.binaryFile;
69}
70
71
72static wxString GetAscPath( const PADS_BINARY_BOARD_INFO& aBoard )
73{
74 return KI_TEST::GetPcbnewTestDataDir() + "plugins/pads/" + aBoard.dir + "/" + aBoard.ascFile;
75}
76
77
82static std::unique_ptr<BOARD> LoadBinary( const PADS_BINARY_BOARD_INFO& aBoard )
83{
84 PCB_IO_PADS_BINARY plugin;
85 wxString filename = GetBinaryPath( aBoard );
86
87 BOOST_CHECK_MESSAGE( plugin.CanReadBoard( filename ),
88 aBoard.dir << " binary should be readable by PCB_IO_PADS_BINARY" );
89
90 std::unique_ptr<BOARD> board;
91
92 try
93 {
94 board.reset( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
95 }
96 catch( const std::exception& e )
97 {
98 BOOST_WARN_MESSAGE( false,
99 aBoard.dir << " binary threw exception during load: " << e.what() );
100 return nullptr;
101 }
102
103 BOOST_CHECK_MESSAGE( board != nullptr, aBoard.dir << " binary failed to load" );
104 return board;
105}
106
107
108static std::unique_ptr<BOARD> LoadAsc( const PADS_BINARY_BOARD_INFO& aBoard )
109{
110 PCB_IO_PADS plugin;
111 wxString filename = GetAscPath( aBoard );
112
113 std::unique_ptr<BOARD> board;
114
115 try
116 {
117 board.reset( plugin.LoadBoard( filename, nullptr, nullptr, nullptr ) );
118 }
119 catch( const std::exception& e )
120 {
121 BOOST_FAIL( aBoard.dir << " ASC threw exception during load: " << e.what() );
122 return nullptr;
123 }
124
125 BOOST_REQUIRE_MESSAGE( board != nullptr, aBoard.dir << " ASC failed to load" );
126 return board;
127}
128
129
130static int CountEdgeCutsShapes( const BOARD* aBoard )
131{
132 int count = 0;
133
134 for( BOARD_ITEM* item : aBoard->Drawings() )
135 {
136 if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item ) )
137 {
138 if( shape->GetLayer() == Edge_Cuts )
139 count++;
140 }
141 }
142
143 return count;
144}
145
146
152static void CheckCountWithTolerance( const std::string& aLabel, size_t aBinaryCount,
153 size_t aAscCount, bool aDifferentRevision )
154{
155 if( aDifferentRevision )
156 {
157 BOOST_WARN_MESSAGE( aBinaryCount == aAscCount,
158 aLabel << " binary=" << aBinaryCount
159 << " asc=" << aAscCount << " (different revision)" );
160 return;
161 }
162
163 if( aBinaryCount == aAscCount )
164 {
165 BOOST_CHECK_MESSAGE( true, aLabel << " counts match: " << aBinaryCount );
166 return;
167 }
168
169 size_t maxCount = std::max( aBinaryCount, aAscCount );
170 size_t diff = ( aBinaryCount > aAscCount ) ? aBinaryCount - aAscCount
171 : aAscCount - aBinaryCount;
172
173 bool withinTolerance = ( diff <= 2 ) || ( diff * 100 / maxCount <= 5 );
174
175 BOOST_CHECK_MESSAGE( withinTolerance,
176 aLabel << " counts differ beyond tolerance: binary=" << aBinaryCount
177 << " asc=" << aAscCount );
178
179 BOOST_WARN_MESSAGE( aBinaryCount == aAscCount,
180 aLabel << " exact count mismatch: binary=" << aBinaryCount
181 << " asc=" << aAscCount );
182}
183
184
190 const BOARD* aBinaryBoard )
191{
192 BOOST_WARN_MESSAGE( aBinaryBoard->Tracks().size() > 0,
193 aBoard.dir << " binary has no tracks (v0x2021 not yet supported)" );
194
195 std::set<std::pair<int, int>> viaPositions;
196 bool hasDuplicate = false;
197
198 for( PCB_TRACK* trk : aBinaryBoard->Tracks() )
199 {
200 PCB_VIA* via = dynamic_cast<PCB_VIA*>( trk );
201
202 if( !via || via->GetViaType() != VIATYPE::THROUGH )
203 continue;
204
205 auto key = std::make_pair( via->GetPosition().x, via->GetPosition().y );
206
207 if( viaPositions.count( key ) )
208 {
209 hasDuplicate = true;
210 break;
211 }
212
213 viaPositions.insert( key );
214 }
215
216 BOOST_CHECK_MESSAGE( !hasDuplicate,
217 aBoard.dir << " binary should have no duplicate through-hole vias" );
218
219 for( PCB_TRACK* trk : aBinaryBoard->Tracks() )
220 {
221 if( trk->Type() == PCB_TRACE_T || trk->Type() == PCB_ARC_T )
222 {
223 BOOST_CHECK_MESSAGE( IsCopperLayer( trk->GetLayer() ),
224 aBoard.dir << " binary track on non-copper layer "
225 << trk->GetLayer() );
226 }
227 }
228
229 for( FOOTPRINT* fp : aBinaryBoard->Footprints() )
230 {
231 for( PAD* pad : fp->Pads() )
232 {
233 BOOST_WARN_MESSAGE( pad->GetSize( PADSTACK::ALL_LAYERS ).x > 0
234 && pad->GetSize( PADSTACK::ALL_LAYERS ).y > 0,
235 aBoard.dir << " " << fp->GetReference() << " pad has zero size" );
236 }
237 }
238}
239
240
241BOOST_AUTO_TEST_SUITE( PadsBinaryImport )
242
243
244BOOST_AUTO_TEST_CASE( BinaryFileDetection )
245{
246 PCB_IO_PADS_BINARY binaryPlugin;
247 PCB_IO_PADS ascPlugin;
248
249 for( const auto& board : PADS_BINARY_BOARDS )
250 {
251 wxString binaryPath = GetBinaryPath( board );
252
253 BOOST_CHECK_MESSAGE( binaryPlugin.CanReadBoard( binaryPath ),
254 board.dir << " binary should be recognized by PCB_IO_PADS_BINARY" );
255
256 BOOST_CHECK_MESSAGE( !ascPlugin.CanReadBoard( binaryPath ),
257 board.dir << " binary should NOT be recognized by PCB_IO_PADS" );
258 }
259}
260
261
262BOOST_AUTO_TEST_CASE( AsciiFileRejection )
263{
264 PCB_IO_PADS_BINARY binaryPlugin;
265
266 for( const auto& board : PADS_BINARY_BOARDS )
267 {
268 wxString ascPath = GetAscPath( board );
269
270 BOOST_CHECK_MESSAGE( !binaryPlugin.CanReadBoard( ascPath ),
271 board.dir << " ASCII should NOT be recognized by PCB_IO_PADS_BINARY" );
272 }
273}
274
275
276#define BINARY_LOAD_TEST( name, idx ) \
277 BOOST_AUTO_TEST_CASE( BasicLoad_##name ) \
278 { \
279 auto board = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
280 \
281 if( board ) \
282 BOOST_CHECK( board->Footprints().size() > 0 ); \
283 }
284
285BINARY_LOAD_TEST( TMS1mmX19, 0 )
286BINARY_LOAD_TEST( MC4_PLUS_CSHAPE, 1 )
287BINARY_LOAD_TEST( MC2_PLUS_REV1, 2 )
288BINARY_LOAD_TEST( Ems4_Rev2, 3 )
289BINARY_LOAD_TEST( LCORE_4, 4 )
290BINARY_LOAD_TEST( LCORE_2, 5 )
291BINARY_LOAD_TEST( Dexter_MotorCtrl, 6 )
292BINARY_LOAD_TEST( MAIS_FC, 7 )
293
294
295#define FOOTPRINT_COUNT_TEST( name, idx ) \
296 BOOST_AUTO_TEST_CASE( FootprintCount_##name ) \
297 { \
298 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
299 \
300 if( !bin ) \
301 return; \
302 \
303 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
304 \
305 CheckCountWithTolerance( #name " footprints", bin->Footprints().size(), \
306 asc->Footprints().size(), \
307 PADS_BINARY_BOARDS[idx].differentRevision ); \
308 }
309
310FOOTPRINT_COUNT_TEST( TMS1mmX19, 0 )
311FOOTPRINT_COUNT_TEST( MC4_PLUS_CSHAPE, 1 )
312FOOTPRINT_COUNT_TEST( MC2_PLUS_REV1, 2 )
313FOOTPRINT_COUNT_TEST( Ems4_Rev2, 3 )
314FOOTPRINT_COUNT_TEST( LCORE_4, 4 )
315FOOTPRINT_COUNT_TEST( LCORE_2, 5 )
316FOOTPRINT_COUNT_TEST( Dexter_MotorCtrl, 6 )
317FOOTPRINT_COUNT_TEST( MAIS_FC, 7 )
318
319
320#define NET_COUNT_TEST( name, idx ) \
321 BOOST_AUTO_TEST_CASE( NetCount_##name ) \
322 { \
323 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
324 \
325 if( !bin ) \
326 return; \
327 \
328 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
329 \
330 CheckCountWithTolerance( #name " nets", bin->GetNetCount(), asc->GetNetCount(), \
331 PADS_BINARY_BOARDS[idx].differentRevision ); \
332 }
333
334NET_COUNT_TEST( TMS1mmX19, 0 )
335NET_COUNT_TEST( MC4_PLUS_CSHAPE, 1 )
336NET_COUNT_TEST( MC2_PLUS_REV1, 2 )
337NET_COUNT_TEST( Ems4_Rev2, 3 )
338NET_COUNT_TEST( LCORE_4, 4 )
339NET_COUNT_TEST( LCORE_2, 5 )
340NET_COUNT_TEST( Dexter_MotorCtrl, 6 )
341NET_COUNT_TEST( MAIS_FC, 7 )
342
343
344#define TRACK_COUNT_TEST( name, idx ) \
345 BOOST_AUTO_TEST_CASE( TrackCount_##name ) \
346 { \
347 auto bin = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
348 \
349 if( !bin ) \
350 return; \
351 \
352 auto asc = LoadAsc( PADS_BINARY_BOARDS[idx] ); \
353 \
354 BOOST_WARN_MESSAGE( bin->Tracks().size() > 0, \
355 #name " binary track parsing not supported for v0x2021" ); \
356 \
357 BOOST_WARN_EQUAL( bin->Tracks().size(), asc->Tracks().size() ); \
358 }
359
360TRACK_COUNT_TEST( TMS1mmX19, 0 )
361TRACK_COUNT_TEST( MC4_PLUS_CSHAPE, 1 )
362TRACK_COUNT_TEST( Ems4_Rev2, 3 )
363TRACK_COUNT_TEST( LCORE_4, 4 )
364TRACK_COUNT_TEST( LCORE_2, 5 )
365TRACK_COUNT_TEST( MAIS_FC, 7 )
366
367
368BOOST_AUTO_TEST_CASE( BoardOutline_LCORE_4 )
369{
370 auto board = LoadBinary( PADS_BINARY_BOARDS[4] );
371
372 if( !board )
373 return;
374
375 BOOST_CHECK_MESSAGE( CountEdgeCutsShapes( board.get() ) > 0,
376 "LCORE_4 binary should have board outline shapes" );
377}
378
379
380BOOST_AUTO_TEST_CASE( BoardOutline_LCORE_2 )
381{
382 auto board = LoadBinary( PADS_BINARY_BOARDS[5] );
383
384 if( !board )
385 return;
386
387 BOOST_CHECK_MESSAGE( CountEdgeCutsShapes( board.get() ) > 0,
388 "LCORE_2 binary should have board outline shapes" );
389}
390
391
392BOOST_AUTO_TEST_CASE( BoardOutline_OtherVersions )
393{
394 int indices[] = { 0, 1, 2, 3, 6, 7 };
395
396 for( int i : indices )
397 {
398 auto board = LoadBinary( PADS_BINARY_BOARDS[i] );
399
400 if( !board )
401 continue;
402
403 BOOST_WARN_MESSAGE( CountEdgeCutsShapes( board.get() ) > 0,
404 PADS_BINARY_BOARDS[i].dir
405 << " binary outline parsing not yet complete" );
406 }
407}
408
409
410#define STRUCTURAL_INTEGRITY_TEST( name, idx ) \
411 BOOST_AUTO_TEST_CASE( StructuralIntegrity_##name ) \
412 { \
413 auto board = LoadBinary( PADS_BINARY_BOARDS[idx] ); \
414 \
415 if( !board ) \
416 return; \
417 \
418 RunStructuralChecks( PADS_BINARY_BOARDS[idx], board.get() ); \
419 }
420
421STRUCTURAL_INTEGRITY_TEST( TMS1mmX19, 0 )
422STRUCTURAL_INTEGRITY_TEST( MC4_PLUS_CSHAPE, 1 )
423STRUCTURAL_INTEGRITY_TEST( MC2_PLUS_REV1, 2 )
424STRUCTURAL_INTEGRITY_TEST( Ems4_Rev2, 3 )
425STRUCTURAL_INTEGRITY_TEST( LCORE_4, 4 )
426STRUCTURAL_INTEGRITY_TEST( LCORE_2, 5 )
427STRUCTURAL_INTEGRITY_TEST( Dexter_MotorCtrl, 6 )
428STRUCTURAL_INTEGRITY_TEST( MAIS_FC, 7 )
429
430
General utilities for PCB file IO for QA programs.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
const FOOTPRINTS & Footprints() const
Definition board.h:363
const TRACKS & Tracks() const
Definition board.h:361
const DRAWINGS & Drawings() const
Definition board.h:365
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
PCB I/O plugin for importing PADS binary .pcb files.
BOARD * LoadBoard(const wxString &aFileName, BOARD *aAppendToMe, const std::map< std::string, UTF8 > *aProperties, PROJECT *aProject) override
Load information from some input file format that this PCB_IO implementation knows about into either ...
bool CanReadBoard(const wxString &aFileName) const override
Checks if this PCB_IO can read the specified board file.
BOARD * LoadBoard(const wxString &aFileName, BOARD *aAppendToMe, const std::map< std::string, UTF8 > *aProperties, PROJECT *aProject) override
Load information from some input file format that this PCB_IO implementation knows about into either ...
bool CanReadBoard(const wxString &aFileName) const override
Checks if this PCB_IO can read the specified board file.
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:677
@ Edge_Cuts
Definition layer_ids.h:112
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
@ THROUGH
Definition pcb_track.h:68
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
static int CountEdgeCutsShapes(const BOARD *aBoard)
static std::unique_ptr< BOARD > LoadAsc(const PADS_BINARY_BOARD_INFO &aBoard)
#define TRACK_COUNT_TEST(name, idx)
static std::unique_ptr< BOARD > LoadBinary(const PADS_BINARY_BOARD_INFO &aBoard)
Load a binary .pcb file.
BOOST_AUTO_TEST_CASE(BinaryFileDetection)
#define BINARY_LOAD_TEST(name, idx)
#define FOOTPRINT_COUNT_TEST(name, idx)
#define STRUCTURAL_INTEGRITY_TEST(name, idx)
static wxString GetAscPath(const PADS_BINARY_BOARD_INFO &aBoard)
#define NET_COUNT_TEST(name, idx)
static void CheckCountWithTolerance(const std::string &aLabel, size_t aBinaryCount, size_t aAscCount, bool aDifferentRevision)
Compare counts with tolerance for binary/ASC differences.
static const PADS_BINARY_BOARD_INFO PADS_BINARY_BOARDS[]
static wxString GetBinaryPath(const PADS_BINARY_BOARD_INFO &aBoard)
static void RunStructuralChecks(const PADS_BINARY_BOARD_INFO &aBoard, const BOARD *aBinaryBoard)
Structural integrity checks.
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:98
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:96