KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_eagle_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 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
24
28
30
31#include <board.h>
32#include <footprint.h>
33#include <netinfo.h>
34#include <pad.h>
35#include <pcb_shape.h>
36#include <pcb_text.h>
37#include <pcb_track.h>
38#include <zone.h>
39
40#include <algorithm>
41#include <map>
42#include <set>
43#include <vector>
44
45#include <wx/filename.h>
46
47
49{
51
52 BOARD* loadBoard( const std::string& aRelPath )
53 {
54 std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + aRelPath;
55
56 // Every sample these tests name is committed, so a missing one is a broken fixture
57 // rather than an optional extra; skipping it silently would assert nothing at all
58 BOOST_REQUIRE_MESSAGE( wxFileName::FileExists( dataPath ),
59 "missing binary Eagle sample " + dataPath );
60
61 PCB_IO_EAGLE eaglePlugin;
62
63 // The binary format is identified by content, never by extension.
64 BOOST_CHECK( eaglePlugin.CanReadBoard( dataPath ) );
65
66 BOARD* board = nullptr;
67
68 try
69 {
70 board = eaglePlugin.LoadBoard( dataPath, nullptr, nullptr );
71 }
72 catch( const IO_ERROR& e )
73 {
74 BOOST_FAIL( "IO_ERROR loading binary Eagle board: " + e.What().ToStdString() );
75 }
76 catch( const std::exception& e )
77 {
78 BOOST_FAIL( std::string( "Exception loading binary Eagle board: " ) + e.what() );
79 }
80
81 return board;
82 }
83
84 static std::vector<ZONE*> copperPours( BOARD* aBoard )
85 {
86 std::vector<ZONE*> pours;
87
88 for( ZONE* zone : aBoard->Zones() )
89 {
90 if( !zone->GetIsRuleArea() && IsCopperLayer( zone->GetFirstLayer() ) )
91 pours.push_back( zone );
92 }
93
94 return pours;
95 }
96};
97
98
99BOOST_FIXTURE_TEST_SUITE( EagleBinaryImport, EAGLE_BINARY_IMPORT_FIXTURE )
100
101
102
107BOOST_AUTO_TEST_CASE( LoadBinaryV4V5 )
108{
109 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/blink1_b1a.brd" ) );
110 BOOST_REQUIRE( board );
111
112 BOOST_CHECK_GT( board->Footprints().size(), 0u );
113 BOOST_CHECK_GT( board->Tracks().size(), 0u );
114 BOOST_CHECK_GT( board->GetNetInfo().GetNetCount(), 1u );
115}
116
117
123BOOST_AUTO_TEST_CASE( LoadBinaryV3 )
124{
125 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/blink1_v1a.brd" ) );
126 BOOST_REQUIRE( board );
127
128 BOOST_CHECK_GT( board->Footprints().size(), 0u );
129 BOOST_CHECK_GT( board->Tracks().size(), 0u );
130}
131
132
139BOOST_AUTO_TEST_CASE( LoadV3CustomAttributes )
140{
141 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/rocketgps.brd" ) );
142 BOOST_REQUIRE( board );
143
144 BOOST_CHECK_GT( board->Footprints().size(), 0u );
145 BOOST_CHECK_GT( board->Tracks().size(), 0u );
146 BOOST_CHECK_GT( board->GetNetInfo().GetNetCount(), 1u );
147}
148
149
157BOOST_AUTO_TEST_CASE( LoadV3UnnamedSignals )
158{
159 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/boomchak.brd" ) );
160 BOOST_REQUIRE( board );
161
162 BOOST_CHECK_GT( board->Footprints().size(), 0u );
163 BOOST_CHECK_GT( board->Tracks().size(), 0u );
164 BOOST_CHECK_GT( board->GetNetInfo().GetNetCount(), 1u );
165
166 // Every routed item must resolve to a real net; an orphaned net code would
167 // have aborted the load before this point.
168 for( PCB_TRACK* track : board->Tracks() )
169 BOOST_CHECK( track->GetNet() != nullptr );
170}
171
172
178BOOST_AUTO_TEST_CASE( LoadV4V5DegeneratePolygons )
179{
180 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/turnemoff.brd" ) );
181 BOOST_REQUIRE( board );
182
183 BOOST_CHECK_GT( board->Footprints().size(), 0u );
184 BOOST_CHECK_GT( board->Tracks().size(), 0u );
185}
186
187
194BOOST_AUTO_TEST_CASE( LoadBinaryLongText )
195{
196 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/blink1_b1a.brd" ) );
197 BOOST_REQUIRE( board );
198
199 std::set<wxString> texts;
200
201 for( BOARD_ITEM* item : board->Drawings() )
202 {
203 if( PCB_TEXT* text = dynamic_cast<PCB_TEXT*>( item ) )
204 texts.insert( text->GetText() );
205 }
206
207 BOOST_CHECK( texts.count( wxS( "blinkm.thingm.com" ) ) );
208 BOOST_CHECK( texts.count( wxS( "BlinkM USB" ) ) );
209}
210
211
219BOOST_AUTO_TEST_CASE( LoadV3FootprintRotationRing )
220{
221 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/boomchak.brd" ) );
222 BOOST_REQUIRE( board );
223
224 std::map<int, double> ledRot;
225
226 for( FOOTPRINT* fp : board->Footprints() )
227 {
228 long n = 0;
229
230 if( fp->GetReference().StartsWith( wxS( "LED" ) ) && fp->GetReference().Mid( 3 ).ToLong( &n ) )
231 ledRot[(int) n] = fp->GetOrientation().AsDegrees();
232 }
233
234 for( int n = 1; n < 16; n++ )
235 {
236 BOOST_REQUIRE_MESSAGE( ledRot.count( n ) && ledRot.count( n + 1 ),
237 "expected ring footprints LED" << n << " and LED" << ( n + 1 ) );
238
239 double step = ledRot[n + 1] - ledRot[n];
240
241 while( step > 180.0 )
242 step -= 360.0;
243
244 while( step <= -180.0 )
245 step += 360.0;
246
247 BOOST_CHECK_CLOSE( step, 22.5, 0.5 );
248 }
249}
250
251
261BOOST_AUTO_TEST_CASE( LoadDropsUnmappedLayers )
262{
263 auto invalidLayerItems = []( BOARD* board )
264 {
265 auto bad = []( PCB_LAYER_ID l ) { return (int) l < 0 || (int) l >= PCB_LAYER_ID_COUNT; };
266
267 int count = 0;
268
269 for( BOARD_ITEM* item : board->Drawings() )
270 count += bad( item->GetLayer() );
271
272 for( PCB_TRACK* track : board->Tracks() )
273 count += bad( track->GetLayer() );
274
275 for( FOOTPRINT* fp : board->Footprints() )
276 {
277 for( BOARD_ITEM* item : fp->GraphicalItems() )
278 count += bad( item->GetLayer() );
279 }
280
281 return count;
282 };
283
284 auto ruleAreas = []( BOARD* board )
285 {
286 int count = 0;
287
288 for( ZONE* zone : board->Zones() )
289 count += zone->GetIsRuleArea();
290
291 return count;
292 };
293
294 for( const char* relPath : { "plugins/eagle_binary/boomchak.brd",
295 "plugins/eagle_binary/turnemoff.brd" } )
296 {
297 std::unique_ptr<BOARD> board( loadBoard( relPath ) );
298 BOOST_REQUIRE( board );
299
300 BOOST_CHECK_EQUAL( invalidLayerItems( board.get() ), 0 );
301 }
302
303 // turnemoff carries hundreds of via-keepout rectangles on the vRestrict layer; the
304 // unmapped-layer handling must keep them as rule areas, not drop them along with the
305 // unmappable graphics.
306 std::unique_ptr<BOARD> turnemoff( loadBoard( "plugins/eagle_binary/turnemoff.brd" ) );
307
308 if( turnemoff )
309 BOOST_CHECK_GT( ruleAreas( turnemoff.get() ), 500 );
310}
311
312
322BOOST_AUTO_TEST_CASE( LoadRoutesSmashedValueText )
323{
324 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/blink1_b1a.brd" ) );
325 BOOST_REQUIRE( board );
326
327 auto footprintTexts = [&]()
328 {
329 std::set<wxString> texts;
330
331 for( FOOTPRINT* fp : board->Footprints() )
332 {
333 texts.insert( fp->Value().GetText() );
334
335 for( BOARD_ITEM* item : fp->GraphicalItems() )
336 {
337 if( PCB_TEXT* text = dynamic_cast<PCB_TEXT*>( item ) )
338 texts.insert( text->GetText() );
339 }
340 }
341
342 return texts;
343 }();
344
345 BOOST_CHECK( !footprintTexts.count( wxS( "${VALU}" ) ) );
346 BOOST_CHECK( !footprintTexts.count( wxS( ">VALU" ) ) );
347}
348
349
357BOOST_AUTO_TEST_CASE( LoadV3CopperPourPolygons )
358{
359 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/boomchak.brd" ) );
360 BOOST_REQUIRE( board );
361
362 std::vector<ZONE*> pours = copperPours( board.get() );
363
364 BOOST_REQUIRE_GE( pours.size(), 2u );
365
366 // A pour rebuilt from segment start points must yield a single closed outline with at
367 // least three corners; a botched conversion would collapse it or leave it empty.
368 for( ZONE* zone : pours )
369 {
370 BOOST_CHECK_EQUAL( zone->Outline()->OutlineCount(), 1 );
371 BOOST_CHECK_GE( zone->GetNumCorners(), 3 );
372 }
373
374 // A pour also keeps the signal it was poured on, so it can still be filled after import
375 BOOST_CHECK( std::any_of( pours.begin(), pours.end(),
376 []( ZONE* aZone )
377 {
378 return aZone->GetNetname() == wxS( "GND" );
379 } ) );
380}
381
382
392BOOST_AUTO_TEST_CASE( LoadIssue24827PadNamesAndSignals )
393{
394 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/issue24827_brenner57e.brd" ) );
395 BOOST_REQUIRE( board );
396
397 BOOST_REQUIRE_GT( board->Footprints().size(), 0u );
398
399 int padsWithNumber = 0;
400 int padsWithoutNumber = 0;
401 int multiPinPartsWithDistinctNets = 0;
402
403 for( FOOTPRINT* fp : board->Footprints() )
404 {
405 std::set<int> nets;
406
407 for( PAD* pad : fp->Pads() )
408 {
409 if( pad->GetNumber().IsEmpty() )
410 padsWithoutNumber++;
411 else
412 padsWithNumber++;
413
414 nets.insert( pad->GetNetCode() );
415 }
416
417 if( fp->Pads().size() > 1 && nets.size() > 1 )
418 multiPinPartsWithDistinctNets++;
419 }
420
421 // The pad-name decode is the whole fix: every through-hole pad in this board
422 // carries a numeric name, so an empty-name pad means the short row still won.
423 BOOST_CHECK_GT( padsWithNumber, 0 );
424 BOOST_CHECK_EQUAL( padsWithoutNumber, 0 );
425
426 // With names restored the contactref keys no longer collide, so multi-pin parts
427 // spread across several signals instead of collapsing onto one.
428 BOOST_CHECK_GT( multiPinPartsWithDistinctNets, 0 );
429}
430
431
440BOOST_AUTO_TEST_CASE( LoadIssue24827PadShapes )
441{
442 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/issue24827_brenner57e.brd" ) );
443 BOOST_REQUIRE( board );
444
445 auto shapeOf = [&]( const wxString& aRef ) -> PAD_SHAPE
446 {
447 for( FOOTPRINT* fp : board->Footprints() )
448 {
449 if( fp->GetReference() == aRef && !fp->Pads().empty() )
450 return fp->Pads().front()->GetShape( PADSTACK::ALL_LAYERS );
451 }
452
453 return PAD_SHAPE::CIRCLE;
454 };
455
456 // R10 is an 0207 resistor (Eagle octagon pads); Q2 is a TO-92 transistor (oblong).
457 // Both decoded to circles while the shape ordinal was ignored, and swapping the
458 // mapping would trade their shapes.
459 BOOST_CHECK_EQUAL( static_cast<int>( shapeOf( wxS( "R10" ) ) ),
460 static_cast<int>( PAD_SHAPE::CHAMFERED_RECT ) );
461 BOOST_CHECK_EQUAL( static_cast<int>( shapeOf( wxS( "Q2" ) ) ),
462 static_cast<int>( PAD_SHAPE::OVAL ) );
463}
464
474BOOST_AUTO_TEST_CASE( LoadIssue24827CurvedWireArcs )
475{
476 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/issue24827_brenner57e.brd" ) );
477 BOOST_REQUIRE( board );
478
479 auto bodyArcs = [&]( const wxString& aRef )
480 {
481 std::vector<PCB_SHAPE*> arcs;
482
483 for( FOOTPRINT* fp : board->Footprints() )
484 {
485 if( fp->GetReference() != aRef )
486 continue;
487
488 for( BOARD_ITEM* item : fp->GraphicalItems() )
489 {
490 PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
491
492 if( shape && shape->GetShape() == SHAPE_T::ARC && shape->GetLayer() == F_SilkS )
493 arcs.push_back( shape );
494 }
495 }
496
497 return arcs;
498 };
499
500 // A scattered center is exactly the flattened/indented arc symptom, so require the
501 // body arcs of each transistor to stay concentric to a fraction of the body radius.
502 for( const wxString& ref : { wxS( "Q2" ), wxS( "IC3" ) } )
503 {
504 std::vector<PCB_SHAPE*> arcs = bodyArcs( ref );
505 BOOST_REQUIRE_GT( arcs.size(), 1u );
506
507 VECTOR2I center = arcs.front()->GetCenter();
508
509 for( PCB_SHAPE* arc : arcs )
510 {
511 int dist = ( arc->GetCenter() - center ).EuclideanNorm();
512 BOOST_CHECK_MESSAGE( dist < pcbIUScale.mmToIU( 0.5 ),
513 ref << " silk arc center is " << dist / 1e6 << " mm off the body center" );
514 }
515 }
516}
517
518
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
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:83
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
const ZONES & Zones() const
Definition board.h:425
SHAPE_T GetShape() const
Definition eda_shape.h:185
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
virtual const char * what() const override
std::exception interface, returned as UTF-8
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
Works with Eagle 6.x XML board files and footprints to implement the Pcbnew #PLUGIN API or a portion ...
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 ...
bool CanReadBoard(const wxString &aFileName) const override
Checks if this PCB_IO can read the specified board file.
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition pcb_shape.h:68
Handle a list of polygons defining a copper zone.
Definition zone.h:70
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:683
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_SilkS
Definition layer_ids.h:96
@ PCB_LAYER_ID_COUNT
Definition layer_ids.h:167
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
PAD_SHAPE
The set of pad shapes, used with PAD::{Set,Get}Shape()
Definition padstack.h:52
@ CHAMFERED_RECT
Definition padstack.h:60
BOARD * loadBoard(const std::string &aRelPath)
static std::vector< ZONE * > copperPours(BOARD *aBoard)
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_CASE(LoadBinaryV4V5)
Load a v4/v5 binary board (magic 0x10 0x00) which also carries the trailing free-text and DRC section...
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR2I center
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683