KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_eagle_board_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 <pcb_shape.h>
34#include <netinfo.h>
35#include <pcb_text.h>
36#include <pcb_track.h>
37
38#include <map>
39#include <vector>
40#include <wx/filename.h>
41
42
47
48
49BOOST_FIXTURE_TEST_SUITE( EagleBoardImport, EAGLE_BOARD_IMPORT_FIXTURE )
50
51
52
62BOOST_AUTO_TEST_CASE( ViaNetAssignment )
63{
64 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
65 + "plugins/eagle/Adafruit-AHT20-PCB/"
66 "Adafruit AHT20 Temperature & Humidity.brd";
67
68 BOOST_REQUIRE_MESSAGE( wxFileName::FileExists( dataPath ),
69 "Test board file not found: " + dataPath );
70
71 PCB_IO_EAGLE eaglePlugin;
72 BOARD* rawBoard = nullptr;
73
74 try
75 {
76 rawBoard = eaglePlugin.LoadBoard( dataPath, nullptr, nullptr );
77 }
78 catch( const IO_ERROR& e )
79 {
80 BOOST_FAIL( "IO_ERROR loading Eagle board: " + e.What().ToStdString() );
81 }
82 catch( const std::exception& e )
83 {
84 BOOST_FAIL( std::string( "Exception loading Eagle board: " ) + e.what() );
85 }
86
87 std::unique_ptr<BOARD> board( rawBoard );
88
89 BOOST_REQUIRE( board );
90
91 // Collect vias grouped by net name
92 std::map<wxString, int> viasPerNet;
93
94 for( PCB_TRACK* track : board->Tracks() )
95 {
96 if( track->Type() == PCB_VIA_T )
97 {
98 NETINFO_ITEM* net = track->GetNet();
99 BOOST_REQUIRE( net );
100
101 wxString netName = net->GetNetname();
102 viasPerNet[netName]++;
103 }
104 }
105
106 // The Eagle board has vias on these nets (verified from the XML source)
107 BOOST_CHECK_GT( viasPerNet[wxT( "SDA" )], 0 );
108 BOOST_CHECK_GT( viasPerNet[wxT( "SCL" )], 0 );
109 BOOST_CHECK_GT( viasPerNet[wxT( "GND" )], 0 );
110 BOOST_CHECK_GT( viasPerNet[wxT( "VCC" )], 0 );
111 BOOST_CHECK_GT( viasPerNet[wxT( "VDD" )], 0 );
112
113 // Verify exact counts from the Eagle XML
114 BOOST_CHECK_EQUAL( viasPerNet[wxT( "SDA" )], 2 );
115 BOOST_CHECK_EQUAL( viasPerNet[wxT( "SCL" )], 2 );
116 BOOST_CHECK_EQUAL( viasPerNet[wxT( "GND" )], 3 );
117 BOOST_CHECK_EQUAL( viasPerNet[wxT( "VCC" )], 2 );
118 BOOST_CHECK_EQUAL( viasPerNet[wxT( "VDD" )], 2 );
119
120 // No via should be unassigned (net code 0)
121 BOOST_CHECK_EQUAL( viasPerNet[wxT( "" )], 0 );
122}
123
124
138BOOST_AUTO_TEST_CASE( TextJustification )
139{
140 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
141 + "plugins/eagle/test_eagle_23016/test_eagle.brd";
142
143 BOOST_REQUIRE_MESSAGE( wxFileName::FileExists( dataPath ),
144 "Test board file not found: " + dataPath );
145
146 PCB_IO_EAGLE eaglePlugin;
147 BOARD* rawBoard = nullptr;
148
149 try
150 {
151 rawBoard = eaglePlugin.LoadBoard( dataPath, nullptr, nullptr );
152 }
153 catch( const IO_ERROR& e )
154 {
155 BOOST_FAIL( "IO_ERROR loading Eagle board: " + e.What().ToStdString() );
156 }
157 catch( const std::exception& e )
158 {
159 BOOST_FAIL( std::string( "Exception loading Eagle board: " ) + e.what() );
160 }
161
162 std::unique_ptr<BOARD> board( rawBoard );
163
164 BOOST_REQUIRE( board );
165
166 // Board should have 13 elements from the Eagle file
167 BOOST_CHECK_EQUAL( board->Footprints().size(), 13 );
168
169 // Find the I2C connector footprints and verify reference text justification.
170 // I2C0 and I2C1 are smashed but have no NAME attribute, so their Reference text
171 // goes through the non-smashed orientFPText() code path. With the footprint at R90
172 // and the package text at 0 degrees, the combined angle is 180, triggering the
173 // justification flip to RIGHT/TOP. Before the fix, the dead code branch
174 // (abs(degrees) <= -180) would never fire and justification stayed at LEFT/BOTTOM.
175 for( FOOTPRINT* fp : board->Footprints() )
176 {
177 wxString ref = fp->GetReference();
178
179 if( ref == wxT( "I2C0" ) || ref == wxT( "I2C1" ) )
180 {
181 PCB_TEXT& refText = fp->Reference();
182
185 ref + " reference horizontal justification should be RIGHT, got "
186 + std::to_string( refText.GetHorizJustify() ) );
187
190 ref + " reference vertical justification should be TOP, got "
191 + std::to_string( refText.GetVertJustify() ) );
192 }
193 }
194
195 // Verify the plain text pin labels have correct justification.
196 // "G SDA SCL V+" on F.SilkS (Eagle layer 21) should have LEFT/BOTTOM justification
197 // with 90-degree rotation so the text reads bottom-to-top matching the pin order.
198 int topPinLabelCount = 0;
199
200 for( BOARD_ITEM* item : board->Drawings() )
201 {
202 if( item->Type() != PCB_TEXT_T )
203 continue;
204
205 PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
206
207 if( text->GetText() != wxT( "G SDA SCL V+" ) )
208 continue;
209
210 if( text->GetLayer() != F_SilkS )
211 continue;
212
213 topPinLabelCount++;
214
216 text->GetHorizJustify() == GR_TEXT_H_ALIGN_LEFT,
217 "Pin label text horizontal justification should be LEFT" );
218
220 text->GetVertJustify() == GR_TEXT_V_ALIGN_BOTTOM,
221 "Pin label text vertical justification should be BOTTOM" );
222
224 text->GetTextAngle() == EDA_ANGLE( 90, DEGREES_T ),
225 "Pin label text angle should be 90 degrees" );
226 }
227
228 // Two instances of "G SDA SCL V+" on F.SilkS (one per connector)
229 BOOST_CHECK_EQUAL( topPinLabelCount, 2 );
230}
231
232
244BOOST_AUTO_TEST_CASE( UserLayerMapping )
245{
246 std::string dataPath = KI_TEST::GetPcbnewTestDataDir()
247 + "plugins/eagle/lbr/user-layers-test.lbr";
248
249 BOOST_REQUIRE_MESSAGE( wxFileName::FileExists( dataPath ),
250 "Test library file not found: " + dataPath );
251
252 PCB_IO_EAGLE eaglePlugin;
253
254 FOOTPRINT* fp = nullptr;
255
256 try
257 {
258 fp = eaglePlugin.FootprintLoad( dataPath, wxT( "USER_LAYERS_TEST" ), false, nullptr );
259 }
260 catch( const IO_ERROR& e )
261 {
262 BOOST_FAIL( "IO_ERROR loading Eagle footprint: " + e.What().ToStdString() );
263 }
264 catch( const std::exception& e )
265 {
266 BOOST_FAIL( std::string( "Exception loading Eagle footprint: " ) + e.what() );
267 }
268
269 std::unique_ptr<FOOTPRINT> guard( fp );
270
271 BOOST_REQUIRE( fp );
272
273 // Each wire in the test library is on a unique Eagle layer at a unique Y-offset (mm).
274 // Wires are 10mm long (x=-5 to x=5) and spaced 2mm apart starting at y=5mm.
275 struct LAYER_CHECK
276 {
277 int yOffsetNm; // Y-offset in nanometers
278 PCB_LAYER_ID expectedLayer;
279 const char* description;
280 };
281
282 // Y is negated because KiCad's Y axis is inverted relative to Eagle's
283 // clang-format off
284 std::vector<LAYER_CHECK> checks = {
285 { -5000000, Eco1_User, "Eagle 160 -> Eco1.User" },
286 { -7000000, Eco2_User, "Eagle 161 -> Eco2.User" },
287 { -9000000, Dwgs_User, "Eagle 162 -> Dwgs.User" },
288 { -11000000, Margin, "Eagle 163 -> Margin" },
289 { -13000000, User_1, "Eagle 170 -> User.1" },
290 { -15000000, User_2, "Eagle 171 -> User.2" },
291 { -17000000, User_3, "Eagle 172 -> User.3" },
292 { -19000000, User_4, "Eagle 173 -> User.4" },
293 { -21000000, User_5, "Eagle 174 -> User.5" },
294 { -23000000, User_6, "Eagle 175 -> User.6" },
295 { -25000000, User_7, "Eagle 176 -> User.7" },
296 { -27000000, User_8, "Eagle 177 -> User.8" },
297 { -29000000, User_9, "Eagle 178 -> User.9" },
298 };
299 // clang-format on
300
301 // Collect all segment shapes keyed by Y midpoint (exact nm).
302 // Test wires are horizontal and at integer mm offsets, so midpoints are exact.
303 std::map<int, PCB_LAYER_ID> linesByY;
304
305 for( BOARD_ITEM* item : fp->GraphicalItems() )
306 {
307 if( item->Type() != PCB_SHAPE_T )
308 continue;
309
310 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
311
312 if( shape->GetShape() != SHAPE_T::SEGMENT )
313 continue;
314
315 int yMid = ( shape->GetStart().y + shape->GetEnd().y ) / 2;
316
317 // A duplicate Y would let a stray segment mask a mis-imported wire.
318 BOOST_CHECK_MESSAGE( linesByY.emplace( yMid, shape->GetLayer() ).second,
319 "Duplicate segment at y=" + std::to_string( yMid ) + "nm" );
320 }
321
322 BOOST_CHECK_EQUAL( linesByY.size(), checks.size() );
323
324 for( const LAYER_CHECK& check : checks )
325 {
326 auto it = linesByY.find( check.yOffsetNm );
327
328 BOOST_CHECK_MESSAGE( it != linesByY.end(),
329 std::string( check.description )
330 + " - no line found at y=" + std::to_string( check.yOffsetNm )
331 + "nm" );
332
333 if( it != linesByY.end() )
334 {
335 BOOST_CHECK_MESSAGE( it->second == check.expectedLayer,
336 std::string( check.description )
337 + " - expected layer " + std::to_string( check.expectedLayer )
338 + " but got " + std::to_string( it->second ) );
339 }
340 }
341}
342
343
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
SHAPE_T GetShape() const
Definition eda_shape.h:185
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:240
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:190
GR_TEXT_H_ALIGN_T GetHorizJustify() const
Definition eda_text.h:221
GR_TEXT_V_ALIGN_T GetVertJustify() const
Definition eda_text.h:224
DRAWINGS & GraphicalItems()
Definition footprint.h:378
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
Handle the data for a net.
Definition netinfo.h:46
const wxString & GetNetname() const
Definition netinfo.h:100
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 ...
FOOTPRINT * FootprintLoad(const wxString &aLibraryPath, const wxString &aFootprintName, bool aKeepUUID=false, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Load a footprint having aFootprintName from the aLibraryPath containing a library format that this PC...
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition pcb_shape.h:68
@ DEGREES_T
Definition eda_angle.h:31
@ SEGMENT
Definition eda_shape.h:46
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ User_8
Definition layer_ids.h:127
@ Dwgs_User
Definition layer_ids.h:103
@ User_6
Definition layer_ids.h:125
@ User_7
Definition layer_ids.h:126
@ User_5
Definition layer_ids.h:124
@ Eco1_User
Definition layer_ids.h:105
@ User_9
Definition layer_ids.h:128
@ Margin
Definition layer_ids.h:109
@ F_SilkS
Definition layer_ids.h:96
@ Eco2_User
Definition layer_ids.h:106
@ User_3
Definition layer_ids.h:122
@ User_1
Definition layer_ids.h:120
@ User_4
Definition layer_ids.h:123
@ User_2
Definition layer_ids.h:121
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_CASE(ViaNetAssignment)
Verify that vias imported from an Eagle board are assigned to the correct nets.
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_CHECK_EQUAL(result, "25.4")
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_TOP
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:81
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:90
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:85