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 <map>
41#include <set>
42#include <vector>
43
44#include <wx/filename.h>
45
46
48{
50
51 BOARD* loadBoard( const std::string& aRelPath )
52 {
53 std::string dataPath = KI_TEST::GetPcbnewTestDataDir() + aRelPath;
54
55 if( !wxFileName::FileExists( dataPath ) )
56 {
57 BOOST_TEST_MESSAGE( "no real binary Eagle sample available at " + dataPath + "; load test skipped" );
58 return nullptr;
59 }
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
111 if( !board )
112 return;
113
114 BOOST_CHECK_GT( board->Footprints().size(), 0u );
115 BOOST_CHECK_GT( board->Tracks().size(), 0u );
116 BOOST_CHECK_GT( board->GetNetInfo().GetNetCount(), 1u );
117}
118
119
125BOOST_AUTO_TEST_CASE( LoadBinaryV3 )
126{
127 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/blink1_v1a.brd" ) );
128
129 if( !board )
130 return;
131
132 BOOST_CHECK_GT( board->Footprints().size(), 0u );
133 BOOST_CHECK_GT( board->Tracks().size(), 0u );
134}
135
136
143BOOST_AUTO_TEST_CASE( LoadV3CustomAttributes )
144{
145 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/rocketgps.brd" ) );
146
147 if( !board )
148 return;
149
150 BOOST_CHECK_GT( board->Footprints().size(), 0u );
151 BOOST_CHECK_GT( board->Tracks().size(), 0u );
152 BOOST_CHECK_GT( board->GetNetInfo().GetNetCount(), 1u );
153}
154
155
163BOOST_AUTO_TEST_CASE( LoadV3UnnamedSignals )
164{
165 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/boomchak.brd" ) );
166
167 if( !board )
168 return;
169
170 BOOST_CHECK_GT( board->Footprints().size(), 0u );
171 BOOST_CHECK_GT( board->Tracks().size(), 0u );
172 BOOST_CHECK_GT( board->GetNetInfo().GetNetCount(), 1u );
173
174 // Every routed item must resolve to a real net; an orphaned net code would
175 // have aborted the load before this point.
176 for( PCB_TRACK* track : board->Tracks() )
177 BOOST_CHECK( track->GetNet() != nullptr );
178}
179
180
186BOOST_AUTO_TEST_CASE( LoadV4V5DegeneratePolygons )
187{
188 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/turnemoff.brd" ) );
189
190 if( !board )
191 return;
192
193 BOOST_CHECK_GT( board->Footprints().size(), 0u );
194 BOOST_CHECK_GT( board->Tracks().size(), 0u );
195}
196
197
206BOOST_AUTO_TEST_CASE( LoadBinaryLongText )
207{
208 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/issue24612_nova_usbbox.brd" ) );
209
210 if( !board )
211 return;
212
213 BOOST_CHECK_GT( board->Footprints().size(), 0u );
214 BOOST_CHECK_GT( board->Tracks().size(), 0u );
215
216 std::set<wxString> texts;
217
218 for( BOARD_ITEM* item : board->Drawings() )
219 {
220 if( PCB_TEXT* text = dynamic_cast<PCB_TEXT*>( item ) )
221 texts.insert( text->GetText() );
222 }
223
224 BOOST_CHECK( texts.count( wxS( "ASTROELEKTRONIK" ) ) );
225 BOOST_CHECK( texts.count( wxS( "Nova+ USB-Box" ) ) );
226}
227
228
237BOOST_AUTO_TEST_CASE( LoadV3RecursiveCountOverrun )
238{
239 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/Sigma2_e.brd" ) );
240
241 if( !board )
242 return;
243
244 BOOST_CHECK_GT( board->Footprints().size(), 0u );
245 BOOST_CHECK_GT( board->Tracks().size(), 0u );
246 BOOST_CHECK_GT( board->GetNetInfo().GetNetCount(), 1u );
247}
248
249
257BOOST_AUTO_TEST_CASE( LoadV3FootprintRotationRing )
258{
259 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/boomchak.brd" ) );
260
261 if( !board )
262 return;
263
264 std::map<int, double> ledRot;
265
266 for( FOOTPRINT* fp : board->Footprints() )
267 {
268 long n = 0;
269
270 if( fp->GetReference().StartsWith( wxS( "LED" ) ) && fp->GetReference().Mid( 3 ).ToLong( &n ) )
271 ledRot[(int) n] = fp->GetOrientation().AsDegrees();
272 }
273
274 for( int n = 1; n < 16; n++ )
275 {
276 BOOST_REQUIRE_MESSAGE( ledRot.count( n ) && ledRot.count( n + 1 ),
277 "expected ring footprints LED" << n << " and LED" << ( n + 1 ) );
278
279 double step = ledRot[n + 1] - ledRot[n];
280
281 while( step > 180.0 )
282 step -= 360.0;
283
284 while( step <= -180.0 )
285 step += 360.0;
286
287 BOOST_CHECK_CLOSE( step, 22.5, 0.5 );
288 }
289}
290
291
301BOOST_AUTO_TEST_CASE( LoadDropsUnmappedLayers )
302{
303 auto invalidLayerItems = []( BOARD* board )
304 {
305 auto bad = []( PCB_LAYER_ID l ) { return (int) l < 0 || (int) l >= PCB_LAYER_ID_COUNT; };
306
307 int count = 0;
308
309 for( BOARD_ITEM* item : board->Drawings() )
310 count += bad( item->GetLayer() );
311
312 for( PCB_TRACK* track : board->Tracks() )
313 count += bad( track->GetLayer() );
314
315 for( FOOTPRINT* fp : board->Footprints() )
316 {
317 for( BOARD_ITEM* item : fp->GraphicalItems() )
318 count += bad( item->GetLayer() );
319 }
320
321 return count;
322 };
323
324 auto ruleAreas = []( BOARD* board )
325 {
326 int count = 0;
327
328 for( ZONE* zone : board->Zones() )
329 count += zone->GetIsRuleArea();
330
331 return count;
332 };
333
334 for( const char* relPath : { "plugins/eagle_binary/boomchak.brd",
335 "plugins/eagle_binary/turnemoff.brd" } )
336 {
337 std::unique_ptr<BOARD> board( loadBoard( relPath ) );
338
339 if( !board )
340 continue;
341
342 BOOST_CHECK_EQUAL( invalidLayerItems( board.get() ), 0 );
343 }
344
345 // turnemoff carries hundreds of via-keepout rectangles on the vRestrict layer; the
346 // unmapped-layer handling must keep them as rule areas, not drop them along with the
347 // unmappable graphics.
348 std::unique_ptr<BOARD> turnemoff( loadBoard( "plugins/eagle_binary/turnemoff.brd" ) );
349
350 if( turnemoff )
351 BOOST_CHECK_GT( ruleAreas( turnemoff.get() ), 500 );
352}
353
354
364BOOST_AUTO_TEST_CASE( LoadRoutesSmashedValueText )
365{
366 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/blink1_b1a.brd" ) );
367
368 if( !board )
369 return;
370
371 auto footprintTexts = [&]()
372 {
373 std::set<wxString> texts;
374
375 for( FOOTPRINT* fp : board->Footprints() )
376 {
377 texts.insert( fp->Value().GetText() );
378
379 for( BOARD_ITEM* item : fp->GraphicalItems() )
380 {
381 if( PCB_TEXT* text = dynamic_cast<PCB_TEXT*>( item ) )
382 texts.insert( text->GetText() );
383 }
384 }
385
386 return texts;
387 }();
388
389 BOOST_CHECK( !footprintTexts.count( wxS( "${VALU}" ) ) );
390 BOOST_CHECK( !footprintTexts.count( wxS( ">VALU" ) ) );
391}
392
393
401BOOST_AUTO_TEST_CASE( LoadV3CopperPourPolygons )
402{
403 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/boomchak.brd" ) );
404
405 if( !board )
406 return;
407
408 std::vector<ZONE*> pours = copperPours( board.get() );
409
410 BOOST_REQUIRE_GE( pours.size(), 2u );
411
412 // A pour rebuilt from segment start points must yield a single closed outline with at
413 // least three corners; a botched conversion would collapse it or leave it empty.
414 for( ZONE* zone : pours )
415 {
416 BOOST_CHECK_EQUAL( zone->Outline()->OutlineCount(), 1 );
417 BOOST_CHECK_GE( zone->GetNumCorners(), 3 );
418 }
419}
420
421
428BOOST_AUTO_TEST_CASE( LoadIssue24812CopperPours )
429{
430 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/issue24812_vertice_error.brd" ) );
431
432 if( !board )
433 return;
434
435 BOOST_CHECK_GE( copperPours( board.get() ).size(), 2u );
436}
437
438
449BOOST_AUTO_TEST_CASE( LoadIssue24827PadNamesAndSignals )
450{
451 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/issue24827_brenner57e.brd" ) );
452
453 if( !board )
454 return;
455
456 BOOST_REQUIRE_GT( board->Footprints().size(), 0u );
457
458 int padsWithNumber = 0;
459 int padsWithoutNumber = 0;
460 int multiPinPartsWithDistinctNets = 0;
461
462 for( FOOTPRINT* fp : board->Footprints() )
463 {
464 std::set<int> nets;
465
466 for( PAD* pad : fp->Pads() )
467 {
468 if( pad->GetNumber().IsEmpty() )
469 padsWithoutNumber++;
470 else
471 padsWithNumber++;
472
473 nets.insert( pad->GetNetCode() );
474 }
475
476 if( fp->Pads().size() > 1 && nets.size() > 1 )
477 multiPinPartsWithDistinctNets++;
478 }
479
480 // The pad-name decode is the whole fix: every through-hole pad in this board
481 // carries a numeric name, so an empty-name pad means the short row still won.
482 BOOST_CHECK_GT( padsWithNumber, 0 );
483 BOOST_CHECK_EQUAL( padsWithoutNumber, 0 );
484
485 // With names restored the contactref keys no longer collide, so multi-pin parts
486 // spread across several signals instead of collapsing onto one.
487 BOOST_CHECK_GT( multiPinPartsWithDistinctNets, 0 );
488}
489
490
500BOOST_AUTO_TEST_CASE( LoadIssue24827PadShapes )
501{
502 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/issue24827_brenner57e.brd" ) );
503
504 if( !board )
505 return;
506
507 auto shapeOf = [&]( const wxString& aRef ) -> PAD_SHAPE
508 {
509 for( FOOTPRINT* fp : board->Footprints() )
510 {
511 if( fp->GetReference() == aRef && !fp->Pads().empty() )
512 return fp->Pads().front()->GetShape( PADSTACK::ALL_LAYERS );
513 }
514
515 return PAD_SHAPE::CIRCLE;
516 };
517
518 // R10 is an 0207 resistor (Eagle octagon pads); Q2 is a TO-92 transistor (oblong).
519 // Both decoded to circles while the shape ordinal was ignored, and swapping the
520 // mapping would trade their shapes.
521 BOOST_CHECK_EQUAL( static_cast<int>( shapeOf( wxS( "R10" ) ) ),
522 static_cast<int>( PAD_SHAPE::CHAMFERED_RECT ) );
523 BOOST_CHECK_EQUAL( static_cast<int>( shapeOf( wxS( "Q2" ) ) ),
524 static_cast<int>( PAD_SHAPE::OVAL ) );
525}
526
536BOOST_AUTO_TEST_CASE( LoadIssue24827CurvedWireArcs )
537{
538 std::unique_ptr<BOARD> board( loadBoard( "plugins/eagle_binary/issue24827_brenner57e.brd" ) );
539
540 if( !board )
541 return;
542
543 auto bodyArcs = [&]( const wxString& aRef )
544 {
545 std::vector<PCB_SHAPE*> arcs;
546
547 for( FOOTPRINT* fp : board->Footprints() )
548 {
549 if( fp->GetReference() != aRef )
550 continue;
551
552 for( BOARD_ITEM* item : fp->GraphicalItems() )
553 {
554 PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
555
556 if( shape && shape->GetShape() == SHAPE_T::ARC && shape->GetLayer() == F_SilkS )
557 arcs.push_back( shape );
558 }
559 }
560
561 return arcs;
562 };
563
564 // A scattered center is exactly the flattened/indented arc symptom, so require the
565 // body arcs of each transistor to stay concentric to a fraction of the body radius.
566 for( const wxString& ref : { wxS( "Q2" ), wxS( "IC3" ) } )
567 {
568 std::vector<PCB_SHAPE*> arcs = bodyArcs( ref );
569 BOOST_REQUIRE_GT( arcs.size(), 1u );
570
571 VECTOR2I center = arcs.front()->GetCenter();
572
573 for( PCB_SHAPE* arc : arcs )
574 {
575 int dist = ( arc->GetCenter() - center ).EuclideanNorm();
576 BOOST_CHECK_MESSAGE( dist < pcbIUScale.mmToIU( 0.5 ),
577 ref << " silk arc center is " << dist / 1e6 << " mm off the body center" );
578 }
579 }
580}
581
582
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:81
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:675
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_AUTO_TEST_SUITE_END()
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
VECTOR2I center
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683