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
70std::vector<std::pair<wxString, wxString>>
71PCB_IO_EASYEDAPRO_V3::EnumerateProjectBoards( const wxString& aFileName ) const
72{
73 EASYEDAPRO::V3_DOC_PARSER parser( aFileName );
74 parser.Load();
75
76 const nlohmann::json sourceProject = EASYEDAPRO::BuildV3ProjectIndexFromRawDocs( parser, false );
77 const nlohmann::json& pcbsIndex = sourceProject.at( "pcbs" );
78
79 std::vector<std::pair<wxString, wxString>> result;
80 result.reserve( parser.GetRawDocs( wxS( "PCB" ) ).size() );
81
82 for( const auto& [uuid, doc] : parser.GetRawDocs( wxS( "PCB" ) ) )
83 {
84 wxString title = uuid;
85 auto pcbIt = pcbsIndex.find( std::string( uuid.ToUTF8() ) );
86
87 if( pcbIt != pcbsIndex.end() )
88 title = EASYEDAPRO::V3GetString( pcbIt.value(), "title", title );
89
90 result.emplace_back( uuid, title );
91 }
92
93 return result;
94}
95
96
97bool PCB_IO_EASYEDAPRO_V3::CanReadBoard( const wxString& aFileName ) const
98{
100}
101
102
103bool PCB_IO_EASYEDAPRO_V3::CanReadLibrary( const wxString& aFileName ) const
104{
105 if( !PCB_IO::CanReadLibrary( aFileName ) )
106 return false;
107
108 return EASYEDAPRO::V3_DOC_PARSER::IsV3Library( aFileName, wxS( "FOOTPRINT" ) );
109}
110
111
112long long PCB_IO_EASYEDAPRO_V3::GetLibraryTimestamp( const wxString& aLibraryPath ) const
113{
114 wxFileName fileName( aLibraryPath );
115
116 if( !fileName.FileExists() )
117 return 0;
118
119 wxDateTime modified = fileName.GetModificationTime();
120
121 if( !modified.IsValid() )
122 return 0;
123
124 return modified.GetTicks();
125}
126
127
128void PCB_IO_EASYEDAPRO_V3::FootprintEnumerate( wxArrayString& aFootprintNames, const wxString& aLibraryPath,
129 bool aBestEfforts, const std::map<std::string, UTF8>* aProperties )
130{
131 const EASYEDAPRO::V3_DOC_PARSER& v3 = getCachedLibraryParser( aLibraryPath );
132
133 std::map<wxString, wxString> footprintMap =
134 EASYEDAPRO::BuildV3LibraryItemMap( v3, "footprints", wxS( "FOOTPRINT" ) );
135
136 for( const auto& [name, uuid] : footprintMap )
137 aFootprintNames.Add( name );
138}
139
140
142{
143 std::vector<FOOTPRINT*> result;
144
145 result.reserve( m_importedLibFootprints.size() );
146
147 for( const std::unique_ptr<FOOTPRINT>& footprint : m_importedLibFootprints )
148 result.push_back( static_cast<FOOTPRINT*>( footprint->Clone() ) );
149
150 return result;
151}
152
153
154std::unique_ptr<FOOTPRINT> PCB_IO_EASYEDAPRO_V3::FootprintLoad( const wxString& aLibraryPath,
155 const wxString& aFootprintName, bool aKeepUUID,
156 const std::map<std::string, UTF8>* aProperties )
157{
158 FONTCONFIG_REPORTER_SCOPE fontconfigScope( nullptr );
159
160 const EASYEDAPRO::V3_DOC_PARSER& v3 = getCachedLibraryParser( aLibraryPath );
161
162 std::map<wxString, wxString> footprintMap =
163 EASYEDAPRO::BuildV3LibraryItemMap( v3, "footprints", wxS( "FOOTPRINT" ) );
164
165 auto fpIt = footprintMap.find( aFootprintName );
166
167 if( fpIt == footprintMap.end() )
168 return nullptr;
169
170 const EASYEDAPRO::V3_DOC_RAW* rawDoc = v3.FindRawDoc( wxS( "FOOTPRINT" ), fpIt->second );
171
172 if( !rawDoc )
173 return nullptr;
174
175 const nlohmann::json& index = v3.GetLibraryIndex();
176
177 PCB_IO_EASYEDAPRO_V3_PARSER parser( nullptr, nullptr );
178 std::unique_ptr<FOOTPRINT> footprint;
179
180 try
181 {
182 footprint = parser.ParseFootprint( EASYEDAPRO::BuildV3BlobMap( v3 ), *rawDoc );
183 }
184 catch( nlohmann::json::exception& e )
185 {
186 THROW_IO_ERRORF( _( "Cannot load footprint '%s' from '%s': %s" ), aFootprintName, aLibraryPath, e.what() );
187 }
188
189 if( !footprint )
190 THROW_IO_ERRORF( _( "Cannot load footprint '%s' from '%s'" ), aFootprintName, aLibraryPath );
191
192 // Library footprints are keyed by footprint geometry; pick any matching device's 3D model.
193 if( index.contains( "devices" ) && index.at( "devices" ).is_object() )
194 {
195 for( const auto& [devUuid, deviceJson] : index.at( "devices" ).items() )
196 {
197 if( !deviceJson.is_object() || !deviceJson.contains( "attributes" )
198 || !deviceJson.at( "attributes" ).is_object() )
199 {
200 continue;
201 }
202
203 const nlohmann::json& attrs = deviceJson.at( "attributes" );
204
205 if( !attrs.contains( "Footprint" )
206 || EASYEDAPRO::V3JsonToString( attrs.at( "Footprint" ) ) != fpIt->second )
207 {
208 continue;
209 }
210
211 wxString modelUuid;
212 wxString modelTitle;
213 wxString modelTransform;
214
215 if( attrs.contains( "3D Model" ) )
216 modelUuid = EASYEDAPRO::V3JsonToString( attrs.at( "3D Model" ) );
217
218 if( attrs.contains( "3D Model Title" ) )
219 modelTitle = EASYEDAPRO::V3JsonToString( attrs.at( "3D Model Title" ) ).Trim();
220 else
221 modelTitle = modelUuid;
222
223 if( attrs.contains( "3D Model Transform" ) )
224 modelTransform = EASYEDAPRO::V3JsonToString( attrs.at( "3D Model Transform" ) );
225
226 PCB_IO_EASYEDAPRO_PARSER modelParser( nullptr, nullptr );
227 modelParser.FillFootprintModelInfo( footprint.get(), modelUuid, modelTitle, modelTransform );
228
229 break;
230 }
231 }
232
233 footprint->SetFPID( EASYEDAPRO::ToKiCadLibID( wxEmptyString, aFootprintName ) );
234 footprint->Reference().SetVisible( true );
235 footprint->Value().SetText( aFootprintName );
236 footprint->Value().SetVisible( true );
237 footprint->AutoPositionFields();
238
239 return footprint;
240}
241
242
243void PCB_IO_EASYEDAPRO_V3::loadBoard( const wxString& aFileName, BOARD& aBoard, bool aIsNewLoad,
244 const std::map<std::string, UTF8>* aProperties, PROJECT* aProject )
245{
246 m_props = aProperties;
247 m_importedDesignRules.clear();
248
249 m_board = &aBoard;
250
252
254 {
255 m_progressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) );
256
257 if( !m_progressReporter->KeepRefreshing() )
259 }
260
261 EASYEDAPRO::V3_DOC_PARSER adapter( aFileName );
262 adapter.Load();
263
264 nlohmann::json project = EASYEDAPRO::BuildV3ProjectIndexFromRawDocs( adapter );
265
266 wxString pcbToLoad;
267
268 if( m_props && m_props->contains( "pcb_id" ) )
269 {
270 pcbToLoad = wxString::FromUTF8( m_props->at( "pcb_id" ) );
271 }
272 else
273 {
274 std::map<wxString, nlohmann::json> prjPcbs = project.at( "pcbs" );
275
276 if( prjPcbs.size() == 1 )
277 {
278 pcbToLoad = prjPcbs.begin()->first;
279 }
280 else if( m_choose_project_handler )
281 {
282 std::vector<IMPORT_PROJECT_DESC> chosen =
284
285 if( !chosen.empty() )
286 pcbToLoad = chosen[0].PCBId;
287 }
288 else if( !prjPcbs.empty() )
289 {
290 pcbToLoad = prjPcbs.begin()->first;
291 }
292 }
293
294 if( pcbToLoad.empty() )
295 THROW_IO_ERROR( _( "No PCB was found in the project to import." ) );
296
297 PCB_IO_EASYEDAPRO_V3_PARSER parser( nullptr, nullptr );
298
299 std::map<wxString, EASYEDAPRO::BLOB> blobs = EASYEDAPRO::BuildV3BlobMap( adapter );
300
301 wxFileName sourceName( aFileName );
302 wxString fpLibName = EASYEDAPRO::ShortenLibName( sourceName.GetName() );
303
304 // PCB components reference Footprint (geometry) and Device (BOM / 3D attrs) separately, so
305 // definitions are keyed by footprint package title (e.g. C0402)
306 std::map<wxString, std::unique_ptr<FOOTPRINT>> footprints;
307 std::set<wxString> usedLibNames;
308
309 for( const auto& [uuid, rawDoc] : adapter.GetRawDocs( wxS( "FOOTPRINT" ) ) )
310 {
311 std::unique_ptr<FOOTPRINT> footprint( parser.ParseFootprint( blobs, rawDoc ) );
312
313 if( !footprint )
314 continue;
315
316 std::string uuidKey = std::string( uuid.ToUTF8() );
317 const nlohmann::json& fpMetas = project.at( "footprints" );
318 wxString fpTitle = uuid;
319
320 if( fpMetas.contains( uuidKey ) )
321 fpTitle = EASYEDAPRO::GetV3LibraryItemTitle( fpMetas.at( uuidKey ), uuid );
322
323 wxString itemName = EASYEDAPRO::MakeUniqueLibName( usedLibNames, fpTitle, uuid );
324 footprint->SetFPID( EASYEDAPRO::ToKiCadLibID( fpLibName, itemName ) );
325 footprints.emplace( uuid, std::move( footprint ) );
326 }
327
328 const EASYEDAPRO::V3_DOC_RAW* pcbRawDoc = adapter.FindRawDoc( wxS( "PCB" ), pcbToLoad );
329
330 if( !pcbRawDoc )
331 THROW_IO_ERRORF( _( "PCB document '%s' not found in '%s'" ), pcbToLoad, aFileName );
332
333 std::multimap<wxString, EASYEDAPRO::POURED> poured; // Empty - v3 parser extracts from raw doc
334
335 parser.ParseBoard( m_board, project, footprints, blobs, poured, *pcbRawDoc, fpLibName );
337
338
339 // Loading a board must not write to disk, so the project library is left to the reconciler
341
342 for( auto& [uuid, footprint] : footprints )
343 {
344 if( footprint )
345 m_importedLibFootprints.emplace_back( std::move( footprint ) );
346 }
347
348 if( adapter.GetSkippedCount() > 0 )
349 {
350 Report( wxString::Format( _( "EasyEDA (JLCEDA) Pro v3 import skipped %d unsupported object(s)." ),
352 }
353}
int index
const char * name
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
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.
RAII class to set and restore the fontconfig reporter.
Definition reporter.h:385
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:351
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)
std::unique_ptr< 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
~PCB_IO_EASYEDAPRO_V3() override
bool CanReadLibrary(const wxString &aFileName) const override
Checks if this IO object can read the specified library file/directory.
void loadBoard(const wxString &aFileName, BOARD &aBoard, bool aIsNewLoad, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr) override
Parse aFileName into aBoard.
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.
std::vector< std::pair< wxString, wxString > > EnumerateProjectBoards(const wxString &aFileName) const override
Check if this PCB_IO can read the specified board file.
std::unique_ptr< 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...
const EASYEDAPRO::V3_DOC_PARSER & getCachedLibraryParser(const wxString &aLibraryPath) const
BOARD * m_board
The board BOARD being worked on, no ownership here.
Definition pcb_io.h:393
PCB_IO(const wxString &aName)
Definition pcb_io.h:372
const std::map< std::string, UTF8 > * m_props
Properties passed via Save() or Load(), no ownership, may be NULL.
Definition pcb_io.h:396
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,...)
#define THROW_IO_CANCELLED()
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)
wxString V3GetString(const nlohmann::json &aObj, const char *aKey, const wxString &aDefault)
@ 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)