KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_easyeda_plugin.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) 2023 Alex Shvartzkop <[email protected]>
5 * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include "pcb_easyeda_plugin.h"
26#include "pcb_easyeda_parser.h"
28
29#include <progress_reporter.h>
30#include <common.h>
31#include <macros.h>
32#include <board.h>
33#include <footprint.h>
35
36#include <wx/log.h>
37#include <wx/wfstream.h>
38#include <wx/zipstrm.h>
39#include <wx/stdstream.h>
40
41#include <nlohmann/json.hpp>
42#include <core/map_helpers.h>
43
44
46{
47 m_board = nullptr;
48 m_props = nullptr;
49}
50
51
53{
54}
55
56
57static bool FindBoardInStream( const wxString& aName, wxInputStream& aStream, nlohmann::json& aOut,
58 EASYEDA::DOCUMENT& aDoc )
59{
60 if( aName.Lower().EndsWith( wxS( ".json" ) ) )
61 {
62 wxStdInputStream sin( aStream );
63 nlohmann::json js = nlohmann::json::parse( sin, nullptr, false );
64
65 if( js.is_discarded() )
66 return false;
67
69
73 {
74 aOut = js;
75 aDoc = doc;
76 return true;
77 }
78 }
79 else if( aName.Lower().EndsWith( wxS( ".zip" ) ) )
80 {
81 std::shared_ptr<wxZipEntry> entry;
82 wxZipInputStream zip( aStream );
83
84 if( !zip.IsOk() )
85 return false;
86
87 while( entry.reset( zip.GetNextEntry() ), entry.get() != NULL )
88 {
89 wxString name = entry->GetName();
90
91 if( FindBoardInStream( name, zip, aOut, aDoc ) )
92 return true;
93 }
94 }
95
96 return false;
97}
98
99
100bool EASYEDA_PLUGIN::CanReadBoard( const wxString& aFileName ) const
101{
102 if( !PLUGIN::CanReadBoard( aFileName ) )
103 return false;
104
105 try
106 {
107 wxFFileInputStream in( aFileName );
108 nlohmann::json js;
110
111 return FindBoardInStream( aFileName, in, js, doc );
112 }
113 catch( nlohmann::json::exception& )
114 {
115 }
116 catch( std::exception& )
117 {
118 }
119
120 return false;
121}
122
123
124bool EASYEDA_PLUGIN::CanReadFootprint( const wxString& aFileName ) const
125{
126 return CanReadBoard( aFileName );
127}
128
129
130bool EASYEDA_PLUGIN::CanReadFootprintLib( const wxString& aFileName ) const
131{
132 return CanReadBoard( aFileName );
133}
134
135
136BOARD* EASYEDA_PLUGIN::LoadBoard( const wxString& aFileName, BOARD* aAppendToMe,
137 const STRING_UTF8_MAP* aProperties, PROJECT* aProject,
138 PROGRESS_REPORTER* aProgressReporter )
139{
140 m_loadedFootprints.clear();
141
142 m_props = aProperties;
143 m_board = aAppendToMe ? aAppendToMe : new BOARD();
144
145 // Give the filename to the board if it's new
146 if( !aAppendToMe )
147 m_board->SetFileName( aFileName );
148
149 if( aProgressReporter )
150 {
151 aProgressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) );
152
153 if( !aProgressReporter->KeepRefreshing() )
154 THROW_IO_ERROR( _( "Open cancelled by user." ) );
155 }
156
157 PCB_EASYEDA_PARSER parser( nullptr );
158
159 try
160 {
161 wxFFileInputStream in( aFileName );
162 nlohmann::json js;
164
165 if( !FindBoardInStream( aFileName, in, js, doc ) )
166 {
168 wxString::Format( _( "Unable to find a valid board in '%s'" ), aFileName ) );
169 }
170
172
173 const int innerStart = 21;
174 const int innerEnd = 52;
175
176 int maxLayer = innerStart;
177 std::map<PCB_LAYER_ID, wxString> layerNames;
178
179 for( const wxString& layerLine : pcbDoc.layers )
180 {
181 wxArrayString parts = wxSplit( layerLine, '~', '\0' );
182 int layerId = wxAtoi( parts[0] );
183 wxString layerName = parts[1];
184 wxString layerColor = parts[2];
185 wxString visible = parts[3];
186 wxString active = parts[4];
187 bool enabled = parts[5] != wxS( "false" );
188
189 if( layerId >= innerStart && layerId <= innerEnd && enabled )
190 maxLayer = layerId + 1;
191
192 layerNames[parser.LayerToKi( parts[0] )] = layerName;
193 }
194
195 m_board->SetCopperLayerCount( 2 + maxLayer - innerStart );
196
197 for( auto& [klayer, name] : layerNames )
198 m_board->SetLayerName( klayer, name );
199
201 std::shared_ptr<NETCLASS> defNetclass = bds.m_NetSettings->m_DefaultNetClass;
202
203 if( pcbDoc.DRCRULE )
204 {
205 std::map<wxString, nlohmann::json>& rules = *pcbDoc.DRCRULE;
206
207 if( auto defRules = get_opt( rules, "Default" ) )
208 {
209 wxString key;
210
211 key = wxS( "trackWidth" );
212 if( defRules->find( key ) != defRules->end() && defRules->at( key ).is_number() )
213 {
214 double val = parser.ScaleSize( defRules->at( key ) );
215 defNetclass->SetTrackWidth( val );
216 }
217
218 key = wxS( "clearance" );
219 if( defRules->find( key ) != defRules->end() && defRules->at( key ).is_number() )
220 {
221 double val = parser.ScaleSize( defRules->at( key ) );
222 defNetclass->SetClearance( val );
223 }
224
225 key = wxS( "viaHoleD" );
226 if( defRules->find( key ) != defRules->end() && defRules->at( key ).is_number() )
227 {
228 double val = parser.ScaleSize( defRules->at( key ) );
229
230 defNetclass->SetViaDrill( val );
231 }
232
233 key = wxS( "viaHoleDiameter" ); // Yes, this is via diameter, not drill diameter
234 if( defRules->find( key ) != defRules->end() && defRules->at( key ).is_number() )
235 {
236 double val = parser.ScaleSize( defRules->at( key ) );
237 defNetclass->SetViaDiameter( val );
238 }
239 }
240 }
241
242 VECTOR2D origin( doc.head.x, doc.head.y );
243 parser.ParseBoard( m_board, origin, m_loadedFootprints, doc.shape );
244
245 // Center the board
246 BOX2I outlineBbox = m_board->ComputeBoundingBox( true );
247 PAGE_INFO pageInfo = m_board->GetPageSettings();
248
249 VECTOR2D pageCenter( pcbIUScale.MilsToIU( pageInfo.GetWidthMils() / 2 ),
250 pcbIUScale.MilsToIU( pageInfo.GetHeightMils() / 2 ) );
251
252 VECTOR2D offset = pageCenter - outlineBbox.GetCenter();
253
254 int alignGrid = pcbIUScale.mmToIU( 10 );
255 offset.x = KiROUND( offset.x / alignGrid ) * alignGrid;
256 offset.y = KiROUND( offset.y / alignGrid ) * alignGrid;
257
258 m_board->Move( offset );
259 bds.SetAuxOrigin( offset );
260
261 return m_board;
262 }
263 catch( nlohmann::json::exception& e )
264 {
266 wxString::Format( _( "Error loading board '%s': %s" ), aFileName, e.what() ) );
267 }
268 catch( std::exception& e )
269 {
271 wxString::Format( _( "Error loading board '%s': %s" ), aFileName, e.what() ) );
272 }
273
274 return m_board;
275}
276
277
278long long EASYEDA_PLUGIN::GetLibraryTimestamp( const wxString& aLibraryPath ) const
279{
280 return 0;
281}
282
283
284void EASYEDA_PLUGIN::FootprintEnumerate( wxArrayString& aFootprintNames,
285 const wxString& aLibraryPath, bool aBestEfforts,
286 const STRING_UTF8_MAP* aProperties )
287{
288 try
289 {
290 wxFFileInputStream in( aLibraryPath );
291 nlohmann::json js;
293
294 if( !FindBoardInStream( aLibraryPath, in, js, doc ) )
295 {
296 THROW_IO_ERROR( wxString::Format( _( "Unable to find valid footprints in '%s'" ),
297 aLibraryPath ) );
298 }
299
302 {
303 for( wxString shap : doc.shape )
304 {
305 shap.Replace( wxS( "#@$" ), "\n" );
306 wxArrayString parts = wxSplit( shap, '\n', '\0' );
307
308 if( parts.size() < 1 )
309 continue;
310
311 wxArrayString paramsRoot = wxSplit( parts[0], '~', '\0' );
312
313 if( paramsRoot.size() < 1 )
314 continue;
315
316 wxString rootType = paramsRoot[0];
317
318 if( rootType == wxS( "LIB" ) )
319 {
320 if( paramsRoot.size() < 4 )
321 continue;
322
323 wxString packageName = wxString::Format( wxS( "Unknown_%s_%s" ), paramsRoot[1],
324 paramsRoot[2] );
325
326 wxArrayString paramParts = wxSplit( paramsRoot[3], '`', '\0' );
327
328 std::map<wxString, wxString> paramMap;
329
330 for( int i = 1; i < paramParts.size(); i += 2 )
331 {
332 wxString key = paramParts[i - 1];
333 wxString value = paramParts[i];
334
335 if( key == wxS( "package" ) )
336 packageName = value;
337
338 paramMap[key] = value;
339 }
340
341 aFootprintNames.Add( packageName );
342 }
343 }
344 }
346 {
348
349 wxString packageName = wxString::Format( wxS( "Unknown_%s" ),
350 pcbDoc.uuid.value_or( wxS( "Unknown" ) ) );
351
352 if( pcbDoc.c_para )
353 {
354 packageName = get_def( *pcbDoc.c_para, wxS( "package" ), packageName );
355 }
356
357 aFootprintNames.Add( packageName );
358 }
359 }
360 catch( nlohmann::json::exception& e )
361 {
362 THROW_IO_ERROR( wxString::Format( _( "Error enumerating footprints in library '%s': %s" ),
363 aLibraryPath, e.what() ) );
364 }
365 catch( std::exception& e )
366 {
367 THROW_IO_ERROR( wxString::Format( _( "Error enumerating footprints in library '%s': %s" ),
368 aLibraryPath, e.what() ) );
369 }
370}
371
372
373FOOTPRINT* EASYEDA_PLUGIN::FootprintLoad( const wxString& aLibraryPath,
374 const wxString& aFootprintName, bool aKeepUUID,
375 const STRING_UTF8_MAP* aProperties )
376{
377 PCB_EASYEDA_PARSER parser( nullptr );
378
379 m_loadedFootprints.clear();
380
381 try
382 {
383 wxFFileInputStream in( aLibraryPath );
384 nlohmann::json js;
386
387 if( !FindBoardInStream( aLibraryPath, in, js, doc ) )
388 {
389 THROW_IO_ERROR( wxString::Format( _( "Unable to find valid footprints in '%s'" ),
390 aLibraryPath ) );
391 }
392
395 {
396 for( wxString shap : doc.shape )
397 {
398 if( !shap.Contains( wxS( "LIB" ) ) )
399 continue;
400
401 shap.Replace( wxS( "#@$" ), "\n" );
402 wxArrayString parts = wxSplit( shap, '\n', '\0' );
403
404 if( parts.size() < 1 )
405 continue;
406
407 wxArrayString paramsRoot = wxSplit( parts[0], '~', '\0' );
408
409 if( paramsRoot.size() < 1 )
410 continue;
411
412 wxString rootType = paramsRoot[0];
413
414 if( rootType == wxS( "LIB" ) )
415 {
416 if( paramsRoot.size() < 4 )
417 continue;
418
419 VECTOR2D origin( parser.Convert( paramsRoot[1] ),
420 parser.Convert( paramsRoot[2] ) );
421
422 wxString packageName = wxString::Format( wxS( "Unknown_%s_%s" ), paramsRoot[1],
423 paramsRoot[2] );
424
425 wxArrayString paramParts = wxSplit( paramsRoot[3], '`', '\0' );
426
427 std::map<wxString, wxString> paramMap;
428
429 for( int i = 1; i < paramParts.size(); i += 2 )
430 {
431 wxString key = paramParts[i - 1];
432 wxString value = paramParts[i];
433
434 if( key == wxS( "package" ) )
435 packageName = value;
436
437 paramMap[key] = value;
438 }
439
440 EDA_ANGLE orientation;
441 if( !paramsRoot[4].IsEmpty() )
442 orientation = EDA_ANGLE( parser.Convert( paramsRoot[4] ), DEGREES_T );
443
444 int layer = 1;
445
446 if( !paramsRoot[7].IsEmpty() )
447 layer = parser.Convert( paramsRoot[7] );
448
449 if( packageName == aFootprintName )
450 {
451 parts.RemoveAt( 0 );
452
453 FOOTPRINT* footprint =
454 parser.ParseFootprint( origin, orientation, layer, nullptr,
455 paramMap, m_loadedFootprints, parts );
456
457 if( !footprint )
458 return nullptr;
459
460 footprint->Reference().SetPosition( VECTOR2I() );
461 footprint->Reference().SetTextAngle( ANGLE_0 );
462 footprint->Reference().SetVisible( true );
463
464 footprint->Value().SetPosition( VECTOR2I() );
465 footprint->Value().SetTextAngle( ANGLE_0 );
466 footprint->Value().SetVisible( true );
467
468 footprint->AutoPositionFields();
469
470 return footprint;
471 }
472 }
473 }
474 }
476 {
478
479 wxString packageName = wxString::Format( wxS( "Unknown_%s" ),
480 pcbDoc.uuid.value_or( wxS( "Unknown" ) ) );
481
482 if( pcbDoc.c_para )
483 {
484 packageName = get_def( *pcbDoc.c_para, wxS( "package" ), packageName );
485
486 if( packageName != aFootprintName )
487 return nullptr;
488
489 VECTOR2D origin( doc.head.x, doc.head.y );
490
491 FOOTPRINT* footprint =
492 parser.ParseFootprint( origin, ANGLE_0, F_Cu, nullptr, *pcbDoc.c_para,
494
495 if( !footprint )
496 return nullptr;
497
498 footprint->Reference().SetPosition( VECTOR2I() );
499 footprint->Reference().SetTextAngle( ANGLE_0 );
500 footprint->Reference().SetVisible( true );
501
502 footprint->Value().SetPosition( VECTOR2I() );
503 footprint->Value().SetTextAngle( ANGLE_0 );
504 footprint->Value().SetVisible( true );
505
506 footprint->AutoPositionFields();
507
508 return footprint;
509 }
510 }
511 }
512 catch( nlohmann::json::exception& e )
513 {
514 THROW_IO_ERROR( wxString::Format( _( "Error reading footprint '%s' from library '%s': %s" ),
515 aFootprintName, aLibraryPath, e.what() ) );
516 }
517 catch( std::exception& e )
518 {
519 THROW_IO_ERROR( wxString::Format( _( "Error reading footprint '%s' from library '%s': %s" ),
520 aFootprintName, aLibraryPath, e.what() ) );
521 }
522
523 return nullptr;
524}
525
526
528{
529 std::vector<FOOTPRINT*> result;
530
531 for( auto& [fpUuid, footprint] : m_loadedFootprints )
532 {
533 result.push_back( static_cast<FOOTPRINT*>( footprint->Clone() ) );
534 }
535
536 return result;
537}
const char * name
Definition: DXF_plotter.cpp:57
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:109
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
void SetAuxOrigin(const VECTOR2I &aOrigin)
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:276
void SetFileName(const wxString &aFileName)
Definition: board.h:311
const PAGE_INFO & GetPageSettings() const
Definition: board.h:649
bool SetLayerName(PCB_LAYER_ID aLayer, const wxString &aLayerName)
Changes the name of the layer given by aLayer.
Definition: board.cpp:552
void Move(const VECTOR2I &aMoveVector) override
Move this object.
Definition: board.cpp:462
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition: board.cpp:1389
void SetCopperLayerCount(int aCount)
Definition: board.cpp:631
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:766
const Vec GetCenter() const
Definition: box2.h:196
static double Convert(const wxString &aValue)
bool CanReadFootprintLib(const wxString &aFileName) const override
Checks if this PLUGIN can read footprint library from specified file or directory.
FOOTPRINT * FootprintLoad(const wxString &aLibraryPath, const wxString &aFootprintName, bool aKeepUUID=false, const STRING_UTF8_MAP *aProperties=nullptr) override
Load a footprint having aFootprintName from the aLibraryPath containing a library format that this PL...
const STRING_UTF8_MAP * m_props
BOARD * LoadBoard(const wxString &aFileName, BOARD *aAppendToMe, const STRING_UTF8_MAP *aProperties=nullptr, PROJECT *aProject=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr) override
Load information from some input file format that this PLUGIN implementation knows about into either ...
long long GetLibraryTimestamp(const wxString &aLibraryPath) const override
Generate a timestamp representing all the files in the library (including the library directory).
std::map< wxString, std::unique_ptr< FOOTPRINT > > m_loadedFootprints
bool CanReadBoard(const wxString &aFileName) const override
Checks if this PLUGIN can read the specified board file.
void FootprintEnumerate(wxArrayString &aFootprintNames, const wxString &aLibraryPath, bool aBestEfforts, const STRING_UTF8_MAP *aProperties=nullptr) override
Return a list of footprint names contained within the library at aLibraryPath.
std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints() override
Return a container with the cached library footprints generated in the last call to Load.
bool CanReadFootprint(const wxString &aFileName) const override
Checks if this PLUGIN can read a footprint from specified file or directory.
virtual void SetVisible(bool aVisible)
Definition: eda_text.cpp:229
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition: eda_text.cpp:205
PCB_FIELD & Value()
read/write accessors:
Definition: footprint.h:592
PCB_FIELD & Reference()
Definition: footprint.h:593
void AutoPositionFields()
Position Reference and Value fields at the top and bottom of footprint's bounding box.
Definition: footprint.cpp:2244
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition: page_info.h:58
double GetHeightMils() const
Definition: page_info.h:140
double GetWidthMils() const
Definition: page_info.h:135
void ParseBoard(BOARD *aBoard, const VECTOR2D &aOrigin, std::map< wxString, std::unique_ptr< FOOTPRINT > > &aFootprintMap, wxArrayString aShapes)
PCB_LAYER_ID LayerToKi(const wxString &aLayer)
double ScaleSize(double aValue) override
FOOTPRINT * ParseFootprint(const VECTOR2D &aOrigin, const EDA_ANGLE &aOrientation, int aLayer, BOARD *aParent, std::map< wxString, wxString > aParams, std::map< wxString, std::unique_ptr< FOOTPRINT > > &aFootprintMap, wxArrayString aShapes)
virtual void SetPosition(const VECTOR2I &aPos) override
Definition: pcb_text.h:84
virtual bool CanReadBoard(const wxString &aFileName) const
Checks if this PLUGIN can read the specified board file.
Definition: plugin.cpp:61
A progress reporter interface for use in multi-threaded environments.
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void Report(const wxString &aMessage)=0
Display aMessage in the progress bar dialog.
Container for project specific data.
Definition: project.h:62
A name/value tuple with unique names and optional values.
The common library.
#define _(s)
@ DEGREES_T
Definition: eda_angle.h:31
static constexpr EDA_ANGLE & ANGLE_0
Definition: eda_angle.h:437
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:39
@ F_Cu
Definition: layer_ids.h:65
This file contains miscellaneous commonly used macros and functions.
wxString get_def(const std::map< wxString, wxString > &aMap, const char *aKey, const char *aDefval="")
Definition: map_helpers.h:64
std::optional< V > get_opt(const std::map< wxString, V > &aMap, const wxString &aKey)
Definition: map_helpers.h:34
static bool FindBoardInStream(const wxString &aName, wxInputStream &aStream, nlohmann::json &aOut, EASYEDA::DOCUMENT &aDoc)
std::optional< std::map< wxString, nlohmann::json > > DRCRULE
std::optional< std::map< wxString, wxString > > c_para
std::vector< wxString > layers
std::optional< wxString > uuid
constexpr int MilsToIU(int mils) const
Definition: base_units.h:94
constexpr int mmToIU(double mm) const
Definition: base_units.h:89
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:85
VECTOR2< int > VECTOR2I
Definition: vector2d.h:588