KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_io_easyedapro_v3.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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
28
31
32#include <board.h>
33#include <font/fontconfig.h>
34#include <footprint.h>
35#include <ki_exception.h>
36#include <progress_reporter.h>
37#include <reporter.h>
38
39#include <set>
40
41#include <wx/filename.h>
42#include <wx/log.h>
43
44
46 PCB_IO( wxS( "EasyEDA (JLCEDA) Pro v3" ) )
47{
48}
49
50
52
53
55{
56 long long timestamp = GetLibraryTimestamp( aLibraryPath );
57
58 if( !m_cachedLibraryParser || m_cachedLibraryPath != aLibraryPath || m_cachedLibraryTimestamp != timestamp )
59 {
60 m_cachedLibraryParser = std::make_unique<EASYEDAPRO::V3_DOC_PARSER>( aLibraryPath );
61 m_cachedLibraryParser->LoadLibrary();
62 m_cachedLibraryPath = aLibraryPath;
63 m_cachedLibraryTimestamp = timestamp;
64 }
65
67}
68
69
70bool PCB_IO_EASYEDAPRO_V3::CanReadBoard( const wxString& aFileName ) const
71{
73}
74
75
76bool PCB_IO_EASYEDAPRO_V3::CanReadLibrary( const wxString& aFileName ) const
77{
78 if( !PCB_IO::CanReadLibrary( aFileName ) )
79 return false;
80
81 return EASYEDAPRO::V3_DOC_PARSER::IsV3Library( aFileName, wxS( "FOOTPRINT" ) );
82}
83
84
85long long PCB_IO_EASYEDAPRO_V3::GetLibraryTimestamp( const wxString& aLibraryPath ) const
86{
87 wxFileName fileName( aLibraryPath );
88
89 if( !fileName.FileExists() )
90 return 0;
91
92 wxDateTime modified = fileName.GetModificationTime();
93
94 if( !modified.IsValid() )
95 return 0;
96
97 return modified.GetTicks();
98}
99
100
101void PCB_IO_EASYEDAPRO_V3::FootprintEnumerate( wxArrayString& aFootprintNames, const wxString& aLibraryPath,
102 bool aBestEfforts, const std::map<std::string, UTF8>* aProperties )
103{
104 const EASYEDAPRO::V3_DOC_PARSER& v3 = getCachedLibraryParser( aLibraryPath );
105
106 std::map<wxString, wxString> footprintMap =
107 EASYEDAPRO::BuildV3LibraryItemMap( v3, "footprints", wxS( "FOOTPRINT" ) );
108
109 for( const auto& [name, uuid] : footprintMap )
110 aFootprintNames.Add( name );
111}
112
113
115{
116 std::vector<FOOTPRINT*> result;
117
118 result.reserve( m_importedLibFootprints.size() );
119
120 for( const std::unique_ptr<FOOTPRINT>& footprint : m_importedLibFootprints )
121 result.push_back( static_cast<FOOTPRINT*>( footprint->Clone() ) );
122
123 return result;
124}
125
126
127FOOTPRINT* PCB_IO_EASYEDAPRO_V3::FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName,
128 bool aKeepUUID, const std::map<std::string, UTF8>* aProperties )
129{
130 FONTCONFIG_REPORTER_SCOPE fontconfigScope( nullptr );
131
132 const EASYEDAPRO::V3_DOC_PARSER& v3 = getCachedLibraryParser( aLibraryPath );
133
134 std::map<wxString, wxString> footprintMap =
135 EASYEDAPRO::BuildV3LibraryItemMap( v3, "footprints", wxS( "FOOTPRINT" ) );
136
137 auto fpIt = footprintMap.find( aFootprintName );
138
139 if( fpIt == footprintMap.end() )
140 return nullptr;
141
142 const EASYEDAPRO::V3_DOC_RAW* rawDoc = v3.FindRawDoc( wxS( "FOOTPRINT" ), fpIt->second );
143
144 if( !rawDoc )
145 return nullptr;
146
147 const nlohmann::json& index = v3.GetLibraryIndex();
148
149 PCB_IO_EASYEDAPRO_V3_PARSER parser( nullptr, nullptr );
150 FOOTPRINT* footprint = nullptr;
151
152 try
153 {
154 footprint = parser.ParseFootprint( EASYEDAPRO::BuildV3BlobMap( v3 ), *rawDoc );
155 }
156 catch( nlohmann::json::exception& e )
157 {
158 THROW_IO_ERRORF( _( "Cannot load footprint '%s' from '%s': %s" ), aFootprintName, aLibraryPath, e.what() );
159 }
160
161 if( !footprint )
162 THROW_IO_ERRORF( _( "Cannot load footprint '%s' from '%s'" ), aFootprintName, aLibraryPath );
163
164 // Library footprints are keyed by footprint geometry; pick any matching device's 3D model.
165 if( index.contains( "devices" ) && index.at( "devices" ).is_object() )
166 {
167 for( const auto& [devUuid, deviceJson] : index.at( "devices" ).items() )
168 {
169 if( !deviceJson.is_object() || !deviceJson.contains( "attributes" )
170 || !deviceJson.at( "attributes" ).is_object() )
171 {
172 continue;
173 }
174
175 const nlohmann::json& attrs = deviceJson.at( "attributes" );
176
177 if( !attrs.contains( "Footprint" )
178 || EASYEDAPRO::V3JsonToString( attrs.at( "Footprint" ) ) != fpIt->second )
179 {
180 continue;
181 }
182
183 wxString modelUuid;
184 wxString modelTitle;
185 wxString modelTransform;
186
187 if( attrs.contains( "3D Model" ) )
188 modelUuid = EASYEDAPRO::V3JsonToString( attrs.at( "3D Model" ) );
189
190 if( attrs.contains( "3D Model Title" ) )
191 modelTitle = EASYEDAPRO::V3JsonToString( attrs.at( "3D Model Title" ) ).Trim();
192 else
193 modelTitle = modelUuid;
194
195 if( attrs.contains( "3D Model Transform" ) )
196 modelTransform = EASYEDAPRO::V3JsonToString( attrs.at( "3D Model Transform" ) );
197
198 PCB_IO_EASYEDAPRO_PARSER modelParser( nullptr, nullptr );
199 modelParser.FillFootprintModelInfo( footprint, modelUuid, modelTitle, modelTransform );
200
201 break;
202 }
203 }
204
205 footprint->SetFPID( EASYEDAPRO::ToKiCadLibID( wxEmptyString, aFootprintName ) );
206 footprint->Reference().SetVisible( true );
207 footprint->Value().SetText( aFootprintName );
208 footprint->Value().SetVisible( true );
209 footprint->AutoPositionFields();
210
211 return footprint;
212}
213
214
215BOARD* PCB_IO_EASYEDAPRO_V3::LoadBoard( const wxString& aFileName, BOARD* aAppendToMe,
216 const std::map<std::string, UTF8>* aProperties, PROJECT* aProject )
217{
218 m_props = aProperties;
219
220 m_board = aAppendToMe ? aAppendToMe : new BOARD();
221
222 if( !aAppendToMe )
223 m_board->SetFileName( aFileName );
224
226
228 {
229 m_progressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) );
230
231 if( !m_progressReporter->KeepRefreshing() )
232 THROW_IO_ERROR( _( "File import canceled by user." ) );
233 }
234
235 EASYEDAPRO::V3_DOC_PARSER adapter( aFileName );
236 adapter.Load();
237
238 nlohmann::json project = EASYEDAPRO::BuildV3ProjectIndexFromRawDocs( adapter );
239
240 wxString pcbToLoad;
241
242 if( m_props && m_props->contains( "pcb_id" ) )
243 {
244 pcbToLoad = wxString::FromUTF8( m_props->at( "pcb_id" ) );
245 }
246 else
247 {
248 std::map<wxString, nlohmann::json> prjPcbs = project.at( "pcbs" );
249
250 if( prjPcbs.size() == 1 )
251 {
252 pcbToLoad = prjPcbs.begin()->first;
253 }
254 else if( m_choose_project_handler )
255 {
256 std::vector<IMPORT_PROJECT_DESC> chosen =
258
259 if( !chosen.empty() )
260 pcbToLoad = chosen[0].PCBId;
261 }
262 else if( !prjPcbs.empty() )
263 {
264 pcbToLoad = prjPcbs.begin()->first;
265 }
266 }
267
268 if( pcbToLoad.empty() )
269 return nullptr;
270
271 PCB_IO_EASYEDAPRO_V3_PARSER parser( nullptr, nullptr );
272
273 std::map<wxString, EASYEDAPRO::BLOB> blobs = EASYEDAPRO::BuildV3BlobMap( adapter );
274
275 wxFileName sourceName( aFileName );
276 wxString fpLibName = EASYEDAPRO::ShortenLibName( sourceName.GetName() );
277
278 // PCB components reference Footprint (geometry) and Device (BOM / 3D attrs) separately, so
279 // definitions are keyed by footprint package title (e.g. C0402)
280 std::map<wxString, std::unique_ptr<FOOTPRINT>> footprints;
281 std::set<wxString> usedLibNames;
282
283 for( const auto& [uuid, rawDoc] : adapter.GetRawDocs( wxS( "FOOTPRINT" ) ) )
284 {
285 std::unique_ptr<FOOTPRINT> footprint( parser.ParseFootprint( blobs, rawDoc ) );
286
287 if( !footprint )
288 continue;
289
290 std::string uuidKey = std::string( uuid.ToUTF8() );
291 const nlohmann::json& fpMetas = project.at( "footprints" );
292 wxString fpTitle = uuid;
293
294 if( fpMetas.contains( uuidKey ) )
295 fpTitle = EASYEDAPRO::GetV3LibraryItemTitle( fpMetas.at( uuidKey ), uuid );
296
297 wxString itemName = EASYEDAPRO::MakeUniqueLibName( usedLibNames, fpTitle, uuid );
298 footprint->SetFPID( EASYEDAPRO::ToKiCadLibID( fpLibName, itemName ) );
299 footprints.emplace( uuid, std::move( footprint ) );
300 }
301
302 const EASYEDAPRO::V3_DOC_RAW* pcbRawDoc = adapter.FindRawDoc( wxS( "PCB" ), pcbToLoad );
303
304 if( !pcbRawDoc )
305 THROW_IO_ERRORF( _( "PCB document '%s' not found in '%s'" ), pcbToLoad, aFileName );
306
307 std::multimap<wxString, EASYEDAPRO::POURED> poured; // Empty - v3 parser extracts from raw doc
308
309 parser.ParseBoard( m_board, project, footprints, blobs, poured, *pcbRawDoc, fpLibName );
310
311 // Loading a board must not write to disk, so the project library is left to the reconciler
313
314 for( auto& [uuid, footprint] : footprints )
315 {
316 if( footprint )
317 m_importedLibFootprints.emplace_back( std::move( footprint ) );
318 }
319
320 if( adapter.GetSkippedCount() > 0 )
321 {
322 Report( wxString::Format( _( "EasyEDA (JLCEDA) Pro v3 import skipped %d unsupported object(s)." ),
324 }
325
326 return m_board;
327}
int index
const char * name
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
Parses EasyEDA Pro v3 .epro2 archives.
void Load()
Load all v3 docs from the archive.
const V3_DOC_RAW * FindRawDoc(const wxString &aDocType, const wxString &aUuid) const
const std::map< wxString, V3_DOC_RAW > & GetRawDocs(const wxString &aDocType) const
static bool IsV3Library(const wxString &aFileName, const wxString &aRequiredDocType=wxEmptyString)
Return true if aFileName looks like an EasyEDA Pro v3 library archive.
static bool IsV3Archive(const wxString &aFileName)
Return true if aFileName looks like an EasyEDA Pro v3 archive.
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:381
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:265
RAII class to set and restore the fontconfig reporter.
Definition reporter.h:368
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:445
PCB_FIELD & Value()
read/write accessors:
Definition footprint.h:893
PCB_FIELD & Reference()
Definition footprint.h:894
void AutoPositionFields()
Position Reference and Value fields at the top and bottom of footprint's bounding box.
virtual void Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) const
Definition io_base.cpp:124
PROGRESS_REPORTER * m_progressReporter
Progress reporter to track the progress of the operation, may be nullptr.
Definition io_base.h:241
virtual bool CanReadLibrary(const wxString &aFileName) const
Checks if this IO object can read the specified library file/directory.
Definition io_base.cpp:71
static LOAD_INFO_REPORTER & GetInstance()
Definition reporter.cpp:306
void FillFootprintModelInfo(FOOTPRINT *footprint, const wxString &modelUuid, const wxString &modelTitle, const wxString &modelTransform) const
void ParseBoard(BOARD *aBoard, const nlohmann::json &aProject, std::map< wxString, std::unique_ptr< FOOTPRINT > > &aFootprintMap, const std::map< wxString, EASYEDAPRO::BLOB > &aBlobMap, const std::multimap< wxString, EASYEDAPRO::POURED > &aPouredMap, const EASYEDAPRO::V3_DOC_RAW &aDoc, const wxString &aFpLibName)
FOOTPRINT * ParseFootprint(const std::map< wxString, EASYEDAPRO::BLOB > &aBlobMap, const EASYEDAPRO::V3_DOC_RAW &aDoc)
long long GetLibraryTimestamp(const wxString &aLibraryPath) const override
Generate a timestamp representing all the files in the library (including the library directory).
std::unique_ptr< EASYEDAPRO::V3_DOC_PARSER > m_cachedLibraryParser
bool CanReadBoard(const wxString &aFileName) const override
Checks if this PCB_IO can read the specified board file.
~PCB_IO_EASYEDAPRO_V3() override
bool CanReadLibrary(const wxString &aFileName) const override
Checks if this IO object can read the specified library file/directory.
std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints() override
Return a container with the cached library footprints generated in the last call to Load.
std::vector< std::unique_ptr< FOOTPRINT > > m_importedLibFootprints
Definitions from the last LoadBoard, cloned out to the import reconciler on request.
void FootprintEnumerate(wxArrayString &aFootprintNames, const wxString &aLibraryPath, bool aBestEfforts, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Return a list of footprint names contained within the library at aLibraryPath.
const EASYEDAPRO::V3_DOC_PARSER & getCachedLibraryParser(const wxString &aLibraryPath) const
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...
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 ...
BOARD * m_board
The board BOARD being worked on, no ownership here.
Definition pcb_io.h:349
PCB_IO(const wxString &aName)
Definition pcb_io.h:342
const std::map< std::string, UTF8 > * m_props
Properties passed via Save() or Load(), no ownership, may be NULL.
Definition pcb_io.h:352
CHOOSE_PROJECT_HANDLER m_choose_project_handler
Callback to choose projects to import.
Container for project specific data.
Definition project.h:63
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define THROW_IO_ERRORF(msg,...)
nlohmann::json BuildV3ProjectIndexFromRawDocs(const V3_DOC_PARSER &aParser, bool aIncludeLibraryMetadata=true)
Build a minimal legacy-style project index from parsed v3 raw documents.
LIB_ID ToKiCadLibID(const wxString &aLibName, const wxString &aLibReference)
wxString MakeUniqueLibName(std::set< wxString > &aUsedNames, const wxString &aName, const wxString &aFallback)
Allocate a unique library item name, recording it in aUsedNames.
wxString GetV3LibraryItemTitle(const nlohmann::json &aMetadata, const wxString &aUuid)
std::vector< IMPORT_PROJECT_DESC > ProjectToSelectorDialog(const nlohmann::json &aProject, bool aPcbOnly=false, bool aSchOnly=false)
wxString ShortenLibName(wxString aProjectName)
wxString V3JsonToString(const nlohmann::json &aValue, const wxString &aDefault)
std::map< wxString, wxString > BuildV3LibraryItemMap(const V3_DOC_PARSER &aParser, const char *aIndexKey, const wxString &aDocType)
std::map< wxString, BLOB > BuildV3BlobMap(const V3_DOC_PARSER &aParser)
@ RPT_SEVERITY_WARNING
Raw parsed document from an EasyEDA Pro v3 .epru stream.
wxString result
Test unit parsing edge cases and error handling.
VECTOR2I v3(-2, 1)