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
127std::unique_ptr<FOOTPRINT> PCB_IO_EASYEDAPRO_V3::FootprintLoad( const wxString& aLibraryPath,
128 const wxString& aFootprintName, bool aKeepUUID,
129 const std::map<std::string, UTF8>* aProperties )
130{
131 FONTCONFIG_REPORTER_SCOPE fontconfigScope( nullptr );
132
133 const EASYEDAPRO::V3_DOC_PARSER& v3 = getCachedLibraryParser( aLibraryPath );
134
135 std::map<wxString, wxString> footprintMap =
136 EASYEDAPRO::BuildV3LibraryItemMap( v3, "footprints", wxS( "FOOTPRINT" ) );
137
138 auto fpIt = footprintMap.find( aFootprintName );
139
140 if( fpIt == footprintMap.end() )
141 return nullptr;
142
143 const EASYEDAPRO::V3_DOC_RAW* rawDoc = v3.FindRawDoc( wxS( "FOOTPRINT" ), fpIt->second );
144
145 if( !rawDoc )
146 return nullptr;
147
148 const nlohmann::json& index = v3.GetLibraryIndex();
149
150 PCB_IO_EASYEDAPRO_V3_PARSER parser( nullptr, nullptr );
151 std::unique_ptr<FOOTPRINT> footprint;
152
153 try
154 {
155 footprint = parser.ParseFootprint( EASYEDAPRO::BuildV3BlobMap( v3 ), *rawDoc );
156 }
157 catch( nlohmann::json::exception& e )
158 {
159 THROW_IO_ERRORF( _( "Cannot load footprint '%s' from '%s': %s" ), aFootprintName, aLibraryPath, e.what() );
160 }
161
162 if( !footprint )
163 THROW_IO_ERRORF( _( "Cannot load footprint '%s' from '%s'" ), aFootprintName, aLibraryPath );
164
165 // Library footprints are keyed by footprint geometry; pick any matching device's 3D model.
166 if( index.contains( "devices" ) && index.at( "devices" ).is_object() )
167 {
168 for( const auto& [devUuid, deviceJson] : index.at( "devices" ).items() )
169 {
170 if( !deviceJson.is_object() || !deviceJson.contains( "attributes" )
171 || !deviceJson.at( "attributes" ).is_object() )
172 {
173 continue;
174 }
175
176 const nlohmann::json& attrs = deviceJson.at( "attributes" );
177
178 if( !attrs.contains( "Footprint" )
179 || EASYEDAPRO::V3JsonToString( attrs.at( "Footprint" ) ) != fpIt->second )
180 {
181 continue;
182 }
183
184 wxString modelUuid;
185 wxString modelTitle;
186 wxString modelTransform;
187
188 if( attrs.contains( "3D Model" ) )
189 modelUuid = EASYEDAPRO::V3JsonToString( attrs.at( "3D Model" ) );
190
191 if( attrs.contains( "3D Model Title" ) )
192 modelTitle = EASYEDAPRO::V3JsonToString( attrs.at( "3D Model Title" ) ).Trim();
193 else
194 modelTitle = modelUuid;
195
196 if( attrs.contains( "3D Model Transform" ) )
197 modelTransform = EASYEDAPRO::V3JsonToString( attrs.at( "3D Model Transform" ) );
198
199 PCB_IO_EASYEDAPRO_PARSER modelParser( nullptr, nullptr );
200 modelParser.FillFootprintModelInfo( footprint.get(), modelUuid, modelTitle, modelTransform );
201
202 break;
203 }
204 }
205
206 footprint->SetFPID( EASYEDAPRO::ToKiCadLibID( wxEmptyString, aFootprintName ) );
207 footprint->Reference().SetVisible( true );
208 footprint->Value().SetText( aFootprintName );
209 footprint->Value().SetVisible( true );
210 footprint->AutoPositionFields();
211
212 return footprint;
213}
214
215
216void PCB_IO_EASYEDAPRO_V3::loadBoard( const wxString& aFileName, BOARD& aBoard, bool aIsNewLoad,
217 const std::map<std::string, UTF8>* aProperties, PROJECT* aProject )
218{
219 m_props = aProperties;
220 m_board = &aBoard;
221
223
225 {
226 m_progressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) );
227
228 if( !m_progressReporter->KeepRefreshing() )
230 }
231
232 EASYEDAPRO::V3_DOC_PARSER adapter( aFileName );
233 adapter.Load();
234
235 nlohmann::json project = EASYEDAPRO::BuildV3ProjectIndexFromRawDocs( adapter );
236
237 wxString pcbToLoad;
238
239 if( m_props && m_props->contains( "pcb_id" ) )
240 {
241 pcbToLoad = wxString::FromUTF8( m_props->at( "pcb_id" ) );
242 }
243 else
244 {
245 std::map<wxString, nlohmann::json> prjPcbs = project.at( "pcbs" );
246
247 if( prjPcbs.size() == 1 )
248 {
249 pcbToLoad = prjPcbs.begin()->first;
250 }
251 else if( m_choose_project_handler )
252 {
253 std::vector<IMPORT_PROJECT_DESC> chosen =
255
256 if( !chosen.empty() )
257 pcbToLoad = chosen[0].PCBId;
258 }
259 else if( !prjPcbs.empty() )
260 {
261 pcbToLoad = prjPcbs.begin()->first;
262 }
263 }
264
265 if( pcbToLoad.empty() )
266 THROW_IO_ERROR( _( "No PCB was found in the project to import." ) );
267
268 PCB_IO_EASYEDAPRO_V3_PARSER parser( nullptr, nullptr );
269
270 std::map<wxString, EASYEDAPRO::BLOB> blobs = EASYEDAPRO::BuildV3BlobMap( adapter );
271
272 wxFileName sourceName( aFileName );
273 wxString fpLibName = EASYEDAPRO::ShortenLibName( sourceName.GetName() );
274
275 // PCB components reference Footprint (geometry) and Device (BOM / 3D attrs) separately, so
276 // definitions are keyed by footprint package title (e.g. C0402)
277 std::map<wxString, std::unique_ptr<FOOTPRINT>> footprints;
278 std::set<wxString> usedLibNames;
279
280 for( const auto& [uuid, rawDoc] : adapter.GetRawDocs( wxS( "FOOTPRINT" ) ) )
281 {
282 std::unique_ptr<FOOTPRINT> footprint( parser.ParseFootprint( blobs, rawDoc ) );
283
284 if( !footprint )
285 continue;
286
287 std::string uuidKey = std::string( uuid.ToUTF8() );
288 const nlohmann::json& fpMetas = project.at( "footprints" );
289 wxString fpTitle = uuid;
290
291 if( fpMetas.contains( uuidKey ) )
292 fpTitle = EASYEDAPRO::GetV3LibraryItemTitle( fpMetas.at( uuidKey ), uuid );
293
294 wxString itemName = EASYEDAPRO::MakeUniqueLibName( usedLibNames, fpTitle, uuid );
295 footprint->SetFPID( EASYEDAPRO::ToKiCadLibID( fpLibName, itemName ) );
296 footprints.emplace( uuid, std::move( footprint ) );
297 }
298
299 const EASYEDAPRO::V3_DOC_RAW* pcbRawDoc = adapter.FindRawDoc( wxS( "PCB" ), pcbToLoad );
300
301 if( !pcbRawDoc )
302 THROW_IO_ERRORF( _( "PCB document '%s' not found in '%s'" ), pcbToLoad, aFileName );
303
304 std::multimap<wxString, EASYEDAPRO::POURED> poured; // Empty - v3 parser extracts from raw doc
305
306 parser.ParseBoard( m_board, project, footprints, blobs, poured, *pcbRawDoc, fpLibName );
307
308 // Loading a board must not write to disk, so the project library is left to the reconciler
310
311 for( auto& [uuid, footprint] : footprints )
312 {
313 if( footprint )
314 m_importedLibFootprints.emplace_back( std::move( footprint ) );
315 }
316
317 if( adapter.GetSkippedCount() > 0 )
318 {
319 Report( wxString::Format( _( "EasyEDA (JLCEDA) Pro v3 import skipped %d unsupported object(s)." ),
321 }
322}
int index
const char * name
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
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
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.
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::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:368
PCB_IO(const wxString &aName)
Definition pcb_io.h:351
const std::map< std::string, UTF8 > * m_props
Properties passed via Save() or Load(), no ownership, may be NULL.
Definition pcb_io.h:371
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)
@ 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)