KiCad PCB EDA Suite
Loading...
Searching...
No Matches
command_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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "command_import.h"
21#include <cli/exit_codes.h>
22#include <jobs/job_pcb_import.h>
23#include <jobs/job_sch_import.h>
25#include <ki_exception.h>
26#include <pgm_base.h>
28#include <project.h>
30#include <reporter.h>
31#include <kiid.h>
32#include <string_utils.h>
33#include <macros.h>
35#include <wx/crt.h>
36#include <wx/filename.h>
37
38#include <string>
39#include <set>
40#include <vector>
41
42
43#define ARG_LAYER_MAP "--layer-map"
44
45
47{
48 m_argParser.add_description( UTF8STDSTR(
49 _( "Import non-KiCad board and/or schematic files into a new KiCad project" ) ) );
50
51 m_argParser.add_argument( ARG_INPUT )
52 .help( UTF8STDSTR( _( "Input file(s) to import; each is autodetected as a board or "
53 "schematic" ) ) )
54 .nargs( argparse::nargs_pattern::at_least_one )
55 .metavar( "INPUT_FILES" );
56
57 m_argParser.add_argument( "-o", ARG_OUTPUT )
58 .default_value( std::string() )
59 .help( UTF8STDSTR( _( "Output project path stem; produces <stem>.kicad_pro plus "
60 "imported board and/or schematic files beside it. Defaults to the first "
61 "input's name in the current directory." ) ) )
62 .metavar( "PROJECT" );
63
64 m_argParser.add_argument( ARG_LAYER_MAP )
65 .default_value( std::string( "" ) )
66 .help( UTF8STDSTR( _( "JSON file mapping source layer names to KiCad layer names for "
67 "imported boards; unmapped layers use the automatic best-guess" ) ) )
68 .metavar( "FILE" );
69}
70
71
73{
74 std::vector<std::string> rawInputs = m_argParser.get<std::vector<std::string>>( ARG_INPUT );
75 wxString outputArg = From_UTF8( m_argParser.get<std::string>( ARG_OUTPUT ).c_str() );
76
77 if( rawInputs.empty() )
78 {
79 wxFprintf( stderr, _( "At least one input file is required\n" ) );
81 }
82
83 std::vector<wxString> inputFiles;
84
85 for( const std::string& raw : rawInputs )
86 inputFiles.push_back( From_UTF8( raw.c_str() ) );
87
88 std::map<wxString, wxString> layerMap;
89 wxString layerMapFile = From_UTF8( m_argParser.get<std::string>( ARG_LAYER_MAP ).c_str() );
90
91 if( !layerMapFile.IsEmpty() )
92 {
93 wxString error;
94
95 if( !LoadLayerMapFile( layerMapFile, layerMap, error ) )
96 {
97 wxFprintf( stderr, wxS( "%s\n" ), error );
99 }
100 }
101
102 wxFileName projectFn;
103
104 if( !outputArg.IsEmpty() )
105 {
106 projectFn.Assign( outputArg );
107
108 // The argument is a stem, so a dot in it is part of the name unless it names a project file
109 if( projectFn.HasExt() && projectFn.GetExt() != FILEEXT::ProjectFileExtension )
110 projectFn.SetName( projectFn.GetFullName() );
111 }
112 else
113 {
114 wxFileName firstInput( inputFiles.front() );
115 projectFn.AssignDir( wxFileName::GetCwd() );
116 projectFn.SetName( firstInput.GetName() );
117 }
118
119 projectFn.SetExt( FILEEXT::ProjectFileExtension );
120 projectFn.MakeAbsolute();
121
122 if( !projectFn.DirExists()
123 && !wxFileName::Mkdir( projectFn.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
124 {
125 wxFprintf( stderr, _( "Could not create output directory: %s\n" ), projectFn.GetPath() );
127 }
128
129 wxString stem = projectFn.GetName();
130 wxString dir = projectFn.GetPath();
131
132 wxFileName boardFn( dir, stem, FILEEXT::KiCadPcbFileExtension );
133 wxFileName schFn( dir, stem, FILEEXT::KiCadSchematicFileExtension );
134
135 // LoadProject returns false for a not-yet-existing file but still registers a fully-defaulted
136 // active project, so success is signalled by GetProject() returning it.
138
139 mgr.LoadProject( projectFn.GetFullPath(), true );
140
141 PROJECT* project = mgr.GetProject( projectFn.GetFullPath() );
142
143 if( !project )
144 {
145 wxFprintf( stderr, _( "Could not create project: %s\n" ), projectFn.GetFullPath() );
147 }
149
150 bool haveBoard = false;
151 bool haveSch = false;
152 std::vector<FILE_INFO_PAIR> boards;
153 int retCode = EXIT_CODES::SUCCESS;
154
155 std::map<wxString, wxString> netNameMap;
156 wxString boardInput;
157
158 // Preserve board-first format detection, but defer loading until the schematic supplies its names.
159 for( const wxString& input : inputFiles )
160 {
161 bool accepted = false;
162
163 if( !haveBoard )
164 {
165 JOB_PCB_IMPORT probe;
166 probe.m_inputFile = input;
167 probe.m_probeOnly = true;
168
169 int pcbResult = aKiway.ProcessJob( KIWAY::FACE_PCB, &probe, &reporter );
170
171 if( pcbResult == EXIT_CODES::SUCCESS )
172 {
173 const auto& sourcePcbs = probe.m_projectBoards;
174
175 if( !sourcePcbs.empty() )
176 {
177 const bool hasMultipleBoards = sourcePcbs.size() > 1;
178 std::set<wxString> usedBoardNames;
179
180 for( const auto& [pcbId, sourceName] : sourcePcbs )
181 {
182 wxFileName outputFn = boardFn;
183
184 if( hasMultipleBoards )
185 {
186 wxString boardName = EscapeString( sourceName, CTX_FILENAME );
187
188 if( boardName.empty() )
189 boardName = pcbId;
190
191 wxString uniqueBoardName = boardName;
192 unsigned duplicate = 2;
193
194 while( !usedBoardNames.insert( uniqueBoardName ).second )
195 {
196 uniqueBoardName = wxString::Format( wxS( "%s-%u" ), boardName, duplicate );
197 ++duplicate;
198 }
199
200 outputFn.SetName( uniqueBoardName );
201 }
202
203 JOB_PCB_IMPORT pcbJob;
204 pcbJob.m_inputFile = input;
205 pcbJob.m_importPcbId = pcbId;
206 pcbJob.m_isPartOfMultiBoardProject = hasMultipleBoards;
207 pcbJob.SetConfiguredOutputPath( outputFn.GetFullPath() );
208
209 pcbResult = aKiway.ProcessJob( KIWAY::FACE_PCB, &pcbJob, &reporter );
210
211 if( pcbResult != EXIT_CODES::SUCCESS )
212 {
213 retCode = pcbResult;
214 break;
215 }
216
217 boards.emplace_back( KIID(), outputFn.GetFullName() );
218 haveBoard = true;
219 }
220
221 if( retCode != EXIT_CODES::SUCCESS )
222 break;
223 }
224 else
225 {
226 boards.emplace_back( KIID(), boardFn.GetFullName() );
227 haveBoard = true;
228 boardInput = input;
229 }
230
231 accepted = true;
232 }
233 else if( pcbResult != EXIT_CODES::ERR_UNKNOWN_FILE_FORMAT )
234 {
235 retCode = pcbResult;
236 break;
237 }
238 }
239
240 // Some formats keep the board and the schematic in one file, so a board input is offered to both faces;
241 // a board file without a schematic is still a good board
242 if( !haveSch )
243 {
244 JOB_SCH_IMPORT schJob;
245 schJob.m_inputFile = input;
247 schJob.SetConfiguredOutputPath( schFn.GetFullPath() );
248
249 int schResult = aKiway.ProcessJob( KIWAY::FACE_SCH, &schJob, &reporter );
250
251 if( schResult == EXIT_CODES::SUCCESS )
252 {
253 haveSch = true;
254 netNameMap = std::move( schJob.m_netNameMap );
255 accepted = true;
256 }
257 else if( schResult != EXIT_CODES::ERR_UNKNOWN_FILE_FORMAT && !accepted )
258 {
259 retCode = schResult;
260 break;
261 }
262 }
263
264 if( accepted )
265 continue;
266
267 if( haveBoard && haveSch )
268 {
269 wxFprintf( stderr, _( "Could not import '%s': a board and a schematic have already "
270 "been imported for this project\n" ), input );
271 }
272 else
273 {
274 wxFprintf( stderr, _( "No board or schematic importer recognizes the file: %s\n" ), input );
275 }
276
278 break;
279 }
280
281 if( retCode == EXIT_CODES::SUCCESS && !boardInput.empty() )
282 {
283 JOB_PCB_IMPORT pcbJob;
284 pcbJob.m_inputFile = boardInput;
286 pcbJob.m_layerMap = layerMap;
287 pcbJob.m_netNameMap = std::move( netNameMap );
288 pcbJob.SetConfiguredOutputPath( boardFn.GetFullPath() );
289 retCode = aKiway.ProcessJob( KIWAY::FACE_PCB, &pcbJob, &reporter );
290 }
291
292 if( retCode == EXIT_CODES::SUCCESS && !haveBoard && !haveSch )
293 {
294 wxFprintf( stderr, _( "No inputs could be imported\n" ) );
296 }
297
298 if( retCode == EXIT_CODES::SUCCESS && project )
299 {
300 // The schematic handler already registered any sheets; link the board here.
301 if( haveBoard )
302 {
303 std::vector<FILE_INFO_PAIR>& projectBoards = project->GetProjectFile().GetBoards();
304 projectBoards = boards;
305 }
306
307 if( mgr.SaveProject( projectFn.GetFullPath() ) )
308 {
309 reporter.Report( wxString::Format( _( "Created project '%s'\n" ),
310 projectFn.GetFullPath() ),
312 }
313 else
314 {
315 wxFprintf( stderr, _( "Failed to write project file: %s\n" ),
316 projectFn.GetFullPath() );
318 }
319 }
320
321 mgr.UnloadProject( project, false );
322
323 return retCode;
324}
argparse::ArgumentParser m_argParser
Definition command.h:113
COMMAND(const std::string &aName)
Define a new COMMAND instance.
Definition command.cpp:30
int doPerform(KIWAY &aKiway) override
The internal handler that should be overloaded to implement command specific processing and work.
static CLI_REPORTER & GetInstance()
Definition reporter.cpp:261
Job to import a non-KiCad PCB file to KiCad format.
wxString m_importPcbId
EasyEDA Pro PCB document identifier selected by project-level import.
std::map< wxString, wxString > m_layerMap
Explicit overrides from source layer name to KiCad layer name (canonical board-file name,...
std::map< wxString, wxString > m_netNameMap
bool m_isPartOfMultiBoardProject
Avoid promoting an individual board's source settings into shared project settings.
std::vector< std::pair< wxString, wxString > > m_projectBoards
PCB document identifier and source name pairs found during a probe.
wxString m_inputFile
Job to import a non-KiCad schematic file to KiCad format.
wxString m_inputFile
std::map< wxString, wxString > m_netNameMap
void SetConfiguredOutputPath(const wxString &aPath)
Sets the configured output path for the job, this path is always saved to file.
Definition job.cpp:156
Definition kiid.h:46
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:348
int ProcessJob(KIWAY::FACE_T aFace, JOB *aJob, REPORTER *aReporter=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Definition kiway.cpp:753
@ FACE_SCH
eeschema DSO
Definition kiway.h:355
@ FACE_PCB
pcbnew DSO
Definition kiway.h:356
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
Container for project specific data.
Definition project.h:63
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
bool SaveProject(const wxString &aFullPath=wxEmptyString, PROJECT *aProject=nullptr)
Save a loaded project.
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT * GetProject(const wxString &aFullPath) const
Retrieve a loaded project by name.
bool UnloadProject(PROJECT *aProject, bool aSave=true)
Save, unload and unregister the given PROJECT.
#define ARG_OUTPUT
Definition command.h:33
#define ARG_INPUT
Definition command.h:34
#define UTF8STDSTR(s)
Definition command.h:27
#define ARG_LAYER_MAP
#define _(s)
static const std::string ProjectFileExtension
static const std::string KiCadSchematicFileExtension
static const std::string KiCadPcbFileExtension
static wxString From_UTF8(const char *cstring)
Convert a UTF8 encoded C string to a wxString for all wxWidgets build modes.
Definition idf_helpers.h:44
bool LoadLayerMapFile(const wxString &aFile, std::map< wxString, wxString > &aMap, wxString &aError)
Load an explicit layer-mapping file into aMap.
This file contains miscellaneous commonly used macros and functions.
static const int ERR_ARGS
Definition exit_codes.h:31
static const int ERR_INVALID_INPUT_FILE
Definition exit_codes.h:33
static const int SUCCESS
Definition exit_codes.h:29
static const int ERR_INVALID_OUTPUT_CONFLICT
Definition exit_codes.h:34
static const int ERR_UNKNOWN_FILE_FORMAT
No plugin for the requested face recognized the input file format.
Definition exit_codes.h:42
static const int ERR_UNKNOWN
Definition exit_codes.h:32
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
@ RPT_SEVERITY_INFO
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
@ CTX_FILENAME
IbisParser parser & reporter
Definition of file extensions used in Kicad.