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 std::unique_ptr<BOARD> board;
67
68 try
69 {
70 board = eaglePlugin.LoadBoard( dataPath );
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 return board.release();
81 }
82
83 static std::vector<ZONE*> copperPours( BOARD* aBoard )
84 {
85 std::vector<ZONE*> pours;
86
87 for( ZONE* zone : aBoard->Zones() )
88 {
89 if( !zone->GetIsRuleArea() && IsCopperLayer( zone->GetFirstLayer() ) )
90 pours.push_back( zone );
91 }
92
93 return pours;
94 }
95};
96
97
98BOOST_FIXTURE_TEST_SUITE( EagleBinaryImport, EAGLE_BINARY_IMPORT_FIXTURE )
99
100
101
106BOOST_AUTO_TEST_CASE( LoadBinaryV4V5 )
107{
108 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/blink1_b1a.brd" ) );
109 BOOST_REQUIRE( board );
110
111 BOOST_CHECK_GT( board->Footprints().size(), 0u );
112 BOOST_CHECK_GT( board->Tracks().size(), 0u );
113 BOOST_CHECK_GT( board->GetNetInfo().GetNetCount(), 1u );
114}
115
116
122BOOST_AUTO_TEST_CASE( LoadBinaryV3 )
123{
124 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/blink1_v1a.brd" ) );
125 BOOST_REQUIRE( board );
126
127 BOOST_CHECK_GT( board->Footprints().size(), 0u );
128 BOOST_CHECK_GT( board->Tracks().size(), 0u );
129}
130
131
138BOOST_AUTO_TEST_CASE( LoadV3CustomAttributes )
139{
140 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/rocketgps.brd" ) );
141 BOOST_REQUIRE( board );
142
143 BOOST_CHECK_GT( board->Footprints().size(), 0u );
144 BOOST_CHECK_GT( board->Tracks().size(), 0u );
145 BOOST_CHECK_GT( board->GetNetInfo().GetNetCount(), 1u );
146}
147
148
156BOOST_AUTO_TEST_CASE( LoadV3UnnamedSignals )
157{
158 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/boomchak.brd" ) );
159 BOOST_REQUIRE( board );
160
161 BOOST_CHECK_GT( board->Footprints().size(), 0u );
162 BOOST_CHECK_GT( board->Tracks().size(), 0u );
163 BOOST_CHECK_GT( board->GetNetInfo().GetNetCount(), 1u );
164
165 // Every routed item must resolve to a real net; an orphaned net code would
166 // have aborted the load before this point.
167 for( PCB_TRACK* track : board->Tracks() )
168 BOOST_CHECK( track->GetNet() != nullptr );
169}
170
171
177BOOST_AUTO_TEST_CASE( LoadV4V5DegeneratePolygons )
178{
179 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/turnemoff.brd" ) );
180 BOOST_REQUIRE( board );
181
182 BOOST_CHECK_GT( board->Footprints().size(), 0u );
183 BOOST_CHECK_GT( board->Tracks().size(), 0u );
184}
185
186
193BOOST_AUTO_TEST_CASE( LoadBinaryLongText )
194{
195 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/blink1_b1a.brd" ) );
196 BOOST_REQUIRE( board );
197
198 std::set<wxString> texts;
199
200 for( BOARD_ITEM* item : board->Drawings() )
201 {
202 if( PCB_TEXT* text = dynamic_cast<PCB_TEXT*>( item ) )
203 texts.insert( text->GetText() );
204 }
205
206 BOOST_CHECK( texts.count( wxS( "blinkm.thingm.com" ) ) );
207 BOOST_CHECK( texts.count( wxS( "BlinkM USB" ) ) );
208}
209
210
218BOOST_AUTO_TEST_CASE( LoadV3FootprintRotationRing )
219{
220 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/boomchak.brd" ) );
221 BOOST_REQUIRE( board );
222
223 std::map<int, double> ledRot;
224
225 for( FOOTPRINT* fp : board->Footprints() )
226 {
227 long n = 0;
228
229 if( fp->GetReference().StartsWith( wxS( "LED" ) ) && fp->GetReference().Mid( 3 ).ToLong( &n ) )
230 ledRot[(int) n] = fp->GetOrientation().AsDegrees();
231 }
232
233 for( int n = 1; n < 16; n++ )
234 {
235 BOOST_REQUIRE_MESSAGE( ledRot.count( n ) && ledRot.count( n + 1 ),
236 "expected ring footprints LED" << n << " and LED" << ( n + 1 ) );
237
238 double step = ledRot[n + 1] - ledRot[n];
239
240 while( step > 180.0 )
241 step -= 360.0;
242
243 while( step <= -180.0 )
244 step += 360.0;
245
246 BOOST_CHECK_CLOSE( step, 22.5, 0.5 );
247 }
248}
249
250
260BOOST_AUTO_TEST_CASE( LoadDropsUnmappedLayers )
261{
262 auto invalidLayerItems =
263 []( 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 =
285 []( BOARD* board )
286 {
287 int count = 0;
288
289 for( ZONE* zone : board->Zones() )
290 count += zone->GetIsRuleArea();
291
292 return count;
293 };
294
295 for( const char* relPath : { "plugins/eagle_binary/boomchak.brd",
296 "plugins/eagle_binary/turnemoff.brd" } )
297 {
298 std::unique_ptr<BOARD> board( loadBoard( relPath ) );
299 BOOST_REQUIRE( board );
300
301 BOOST_CHECK_EQUAL( invalidLayerItems( board.get() ), 0 );
302 }
303
304 // turnemoff carries hundreds of via-keepout rectangles on the vRestrict layer; the
305 // unmapped-layer handling must keep them as rule areas, not drop them along with the
306 // unmappable graphics.
307 std::unique_ptr<BOARD> turnemoff( loadBoard( "plugins/eagle_binary/turnemoff.brd" ) );
308
309 if( turnemoff )
310 BOOST_CHECK_GT( ruleAreas( turnemoff.get() ), 500 );
311}
312
313
323BOOST_AUTO_TEST_CASE( LoadRoutesSmashedValueText )
324{
325 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/blink1_b1a.brd" ) );
326 BOOST_REQUIRE( board );
327
328 auto footprintTexts =
329 [&]()
330 {
331 std::set<wxString> texts;
332
333 for( FOOTPRINT* fp : board->Footprints() )
334 {
335 texts.insert( fp->Value().GetText() );
336
337 for( BOARD_ITEM* item : fp->GraphicalItems() )
338 {
339 if( PCB_TEXT* text = dynamic_cast<PCB_TEXT*>( item ) )
340 texts.insert( text->GetText() );
341 }
342 }
343
344 return texts;
345 };
346
347 std::set<wxString> fpTexts = footprintTexts();
348
349 BOOST_CHECK( !fpTexts.contains( wxS( "${VALU}" ) ) );
350 BOOST_CHECK( !fpTexts.contains( wxS( ">VALU" ) ) );
351}
352
353
361BOOST_AUTO_TEST_CASE( LoadV3CopperPourPolygons )
362{
363 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/boomchak.brd" ) );
364 BOOST_REQUIRE( board );
365
366 std::vector<ZONE*> pours = copperPours( board.get() );
367
368 BOOST_REQUIRE_GE( pours.size(), 2u );
369
370 // A pour rebuilt from segment start points must yield a single closed outline with at
371 // least three corners; a botched conversion would collapse it or leave it empty.
372 for( ZONE* zone : pours )
373 {
374 BOOST_CHECK_EQUAL( zone->Outline()->OutlineCount(), 1 );
375 BOOST_CHECK_GE( zone->GetNumCorners(), 3 );
376 }
377
378 // A pour also keeps the signal it was poured on, so it can still be filled after import
379 BOOST_CHECK( std::any_of( pours.begin(), pours.end(),
380 []( ZONE* aZone )
381 {
382 return aZone->GetNetname() == wxS( "GND" );
383 } ) );
384}
385
386
396BOOST_AUTO_TEST_CASE( LoadIssue24827PadNamesAndSignals )
397{
398 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/issue24827_brenner57e.brd" ) );
399 BOOST_REQUIRE( board );
400
401 BOOST_REQUIRE_GT( board->Footprints().size(), 0u );
402
403 int padsWithNumber = 0;
404 int padsWithoutNumber = 0;
405 int multiPinPartsWithDistinctNets = 0;
406
407 for( FOOTPRINT* fp : board->Footprints() )
408 {
409 std::set<int> nets;
410
411 for( PAD* pad : fp->Pads() )
412 {
413 if( pad->GetNumber().IsEmpty() )
414 padsWithoutNumber++;
415 else
416 padsWithNumber++;
417
418 nets.insert( pad->GetNetCode() );
419 }
420
421 if( fp->Pads().size() > 1 && nets.size() > 1 )
422 multiPinPartsWithDistinctNets++;
423 }
424
425 // The pad-name decode is the whole fix: every through-hole pad in this board
426 // carries a numeric name, so an empty-name pad means the short row still won.
427 BOOST_CHECK_GT( padsWithNumber, 0 );
428 BOOST_CHECK_EQUAL( padsWithoutNumber, 0 );
429
430 // With names restored the contactref keys no longer collide, so multi-pin parts
431 // spread across several signals instead of collapsing onto one.
432 BOOST_CHECK_GT( multiPinPartsWithDistinctNets, 0 );
433}
434
435
444BOOST_AUTO_TEST_CASE( LoadIssue24827PadShapes )
445{
446 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/issue24827_brenner57e.brd" ) );
447 BOOST_REQUIRE( board );
448
449 auto shapeOf =
450 [&]( const wxString& aRef ) -> PAD_SHAPE
451 {
452 for( FOOTPRINT* fp : board->Footprints() )
453 {
454 if( fp->GetReference() == aRef && !fp->Pads().empty() )
455 return fp->Pads().front()->GetShape( PADSTACK::ALL_LAYERS );
456 }
457
458 return PAD_SHAPE::CIRCLE;
459 };
460
461 // R10 is an 0207 resistor (Eagle octagon pads); Q2 is a TO-92 transistor (oblong).
462 // Both decoded to circles while the shape ordinal was ignored, and swapping the
463 // mapping would trade their shapes.
464 BOOST_CHECK_EQUAL( static_cast<int>( shapeOf( wxS( "R10" ) ) ),
465 static_cast<int>( PAD_SHAPE::CHAMFERED_RECT ) );
466 BOOST_CHECK_EQUAL( static_cast<int>( shapeOf( wxS( "Q2" ) ) ),
467 static_cast<int>( PAD_SHAPE::OVAL ) );
468}
469
479BOOST_AUTO_TEST_CASE( LoadIssue24827CurvedWireArcs )
480{
481 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/issue24827_brenner57e.brd" ) );
482 BOOST_REQUIRE( board );
483
484 auto bodyArcs =
485 [&]( const wxString& aRef )
486 {
487 std::vector<PCB_SHAPE*> arcs;
488
489 for( FOOTPRINT* fp : board->Footprints() )
490 {
491 if( fp->GetReference() != aRef )
492 continue;
493
494 for( BOARD_ITEM* item : fp->GraphicalItems() )
495 {
496 PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
497
498 if( shape && shape->GetShape() == SHAPE_T::ARC && shape->GetLayer() == F_SilkS )
499 arcs.push_back( shape );
500 }
501 }
502
503 return arcs;
504 };
505
506 // A scattered center is exactly the flattened/indented arc symptom, so require the
507 // body arcs of each transistor to stay concentric to a fraction of the body radius.
508 for( const wxString& ref : { wxString( "Q2" ), wxString( "IC3" ) } )
509 {
510 std::vector<PCB_SHAPE*> arcs = bodyArcs( ref );
511 BOOST_REQUIRE_GT( arcs.size(), 1u );
512
513 VECTOR2I center = arcs.front()->GetCenter();
514
515 for( PCB_SHAPE* arc : arcs )
516 {
517 int dist = ( arc->GetCenter() - center ).EuclideanNorm();
518 BOOST_CHECK_MESSAGE( dist < pcbIUScale.mmToIU( 0.5 ),
519 ref << " silk arc center is " << dist / 1e6 << " mm off the body center" );
520 }
521 }
522}
523
524
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:84
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
const ZONES & Zones() const
Definition board.h:467
SHAPE_T GetShape() const
Definition eda_shape.h:175
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
! The layer identifier to use for the single defintion on normal padstacks
Definition padstack.h:179
Definition pad.h:61
Works with Eagle 6.x XML board files and footprints to implement the Pcbnew #PLUGIN API or a portion ...
bool CanReadBoard(const wxString &aFileName) const override
Checks if this PCB_IO can read the specified board file.
std::unique_ptr< BOARD > LoadBoard(const wxString &aFileName, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr)
Load information from some input file format that this PCB_IO implementation knows about into new BOA...
Definition pcb_io.cpp:72
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:703
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:51
@ CHAMFERED_RECT
Definition padstack.h:59
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