KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_export_odbpp.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 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
21
22#include <board.h>
23#include <confirm.h>
24#include <footprint.h>
25#include <kidialog.h>
26#include <kiway_holder.h>
27#include <pcb_edit_frame.h>
28#include <pcbnew_settings.h>
29#include <pgm_base.h>
30#include <progress_reporter.h>
31#include <project.h>
37
38#include <set>
39#include <vector>
40#include <core/thread_pool.h>
41#include <io/io_mgr.h>
43#include <pcb_io/pcb_io_mgr.h>
44#include <wx/dir.h>
45#include <wx/dirdlg.h>
46#include <wx/wfstream.h>
47#include <wx/zipstrm.h>
48
49static wxString s_oemColumn = wxEmptyString;
50
52 DIALOG_EXPORT_ODBPP_BASE( aParent ),
53 m_parent( aParent ),
54 m_job( nullptr )
55{
56 m_browseButton->SetBitmap( KiBitmapBundle( BITMAPS::small_folder ) );
57
58 SetupStandardButtons( { { wxID_OK, _( "Export" ) }, { wxID_CANCEL, _( "Close" ) } } );
59
61
62 if( path.IsEmpty() )
63 {
64 wxFileName brdFile( m_parent->GetBoard()->GetFileName() );
65 path = brdFile.GetPath();
66 }
67
68 m_outputFileName->SetValue( path );
69
70 // Fill wxChoice (and others) items with data before calling finishDialogSettings()
71 // to calculate suitable widgets sizes
72 Init();
73
74 // Now all widgets have the size fixed, call FinishDialogSettings
76}
77
78
80 wxWindow* aParent ) :
81 DIALOG_EXPORT_ODBPP_BASE( aParent ),
82 m_parent( aEditFrame ),
83 m_job( aJob )
84{
85 m_browseButton->Hide();
86
87 SetupStandardButtons( { { wxID_OK, _( "Save" ) }, { wxID_CANCEL, _( "Close" ) } } );
88
89 m_outputFileName->SetValue( m_job->GetOutputPath() );
90
91 // Fill wxChoice (and others) items with data before calling finishDialogSettings()
92 // to calculate suitable widgets sizes
93 Init();
94
95 // Now all widgets have the size fixed, call FinishDialogSettings
97}
98
99
100void DIALOG_EXPORT_ODBPP::onBrowseClicked( wxCommandEvent& event )
101{
102 // Build the absolute path of current output directory to preselect it in the file browser.
103 wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() );
104 wxFileName fn( Prj().AbsolutePath( path ) );
105
106 wxDirDialog dlg( this, _( "Export ODB++ File" ), fn.GetPath() );
107
108 if( dlg.ShowModal() == wxID_CANCEL )
109 return;
110
111 m_outputFileName->SetValue( dlg.GetPath() );
112}
113
114
115void DIALOG_EXPORT_ODBPP::onOKClick( wxCommandEvent& event )
116{
117 if( !m_job )
119
120 event.Skip();
121}
122
123
125{
127 PCBNEW_SETTINGS* cfg = mgr.GetAppSettings<PCBNEW_SETTINGS>( "pcbnew" );
128
129 if( !m_job )
130 {
131 m_choiceUnits->SetSelection( cfg->m_ExportODBPP.units );
132 m_precision->SetValue( cfg->m_ExportODBPP.precision );
133 m_cbCompress->SetValue( cfg->m_ExportODBPP.compress );
134 }
135 else
136 {
137 m_choiceUnits->SetSelection( static_cast<int>( m_job->m_units ) );
138 m_precision->SetValue( m_job->m_precision );
140 }
141
142 return true;
143}
144
145
147{
148 if( !m_job )
149 {
151 PCBNEW_SETTINGS* cfg = mgr.GetAppSettings<PCBNEW_SETTINGS>( "pcbnew" );
152
153 cfg->m_ExportODBPP.units = m_choiceUnits->GetSelection();
154 cfg->m_ExportODBPP.precision = m_precision->GetValue();
155 cfg->m_ExportODBPP.compress = m_cbCompress->GetValue();
156 }
157 else
158 {
159 m_job->SetOutputPath( m_outputFileName->GetValue() );
160
161 m_job->m_precision = m_precision->GetValue();
162 m_job->m_units = static_cast<JOB_EXPORT_PCB_ODB::ODB_UNITS>( m_choiceUnits->GetSelection() );
166 }
167
168 return true;
169}
170
171
173 PCB_EDIT_FRAME* aParentFrame,
174 PROGRESS_REPORTER* aProgressReporter,
175 REPORTER* aReporter )
176{
177 wxCHECK( aBoard, /* void */ );
178 wxString outputPath = aJob.GetOutputPath();
179
180 if( outputPath.IsEmpty() )
181 outputPath = wxFileName( aJob.m_filename ).GetPath();
182
183 wxFileName pcbFileName( outputPath );
184
185 // Write through symlinks, don't replace them
187
188 if( pcbFileName.GetPath().IsEmpty() && pcbFileName.HasName() )
189 pcbFileName.MakeAbsolute();
190
191 wxString msg;
192
193 if( pcbFileName.IsDir() && !pcbFileName.IsDirWritable() )
194 {
195 msg.Printf( _( "Insufficient permissions to folder '%s'." ), pcbFileName.GetPath() );
196 }
197 else if( !pcbFileName.FileExists() && !pcbFileName.IsDirWritable() )
198 {
199 msg.Printf( _( "Insufficient permissions to save file '%s'." ), pcbFileName.GetFullPath() );
200 }
201 else if( pcbFileName.FileExists() && !pcbFileName.IsFileWritable() )
202 {
203 msg.Printf( _( "Insufficient permissions to save file '%s'." ), pcbFileName.GetFullPath() );
204 }
205
206 if( !msg.IsEmpty() )
207 {
208 if( aReporter )
209 aReporter->Report( msg, RPT_SEVERITY_ERROR );
210
211 return;
212 }
213
214 if( !wxFileName::DirExists( pcbFileName.GetFullPath() ) )
215 {
216 // Make every directory provided when the provided path doesn't exist
217 if( !wxFileName::Mkdir( pcbFileName.GetFullPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
218 {
219 msg.Printf( _( "Cannot create output directory '%s'." ), pcbFileName.GetFullPath() );
220
221 if( aReporter )
222 aReporter->Report( msg, RPT_SEVERITY_ERROR );
223
224 return;
225 }
226 }
227
228 wxFileName zipFileName( pcbFileName.GetFullPath(),
229 wxString::Format( wxS( "%s-odb.zip" ),
230 aBoard->GetProject()->GetProjectName() ) );
231
232 wxFileName tempFile( pcbFileName.GetFullPath(), "" );
233
235 {
236 if( zipFileName.Exists() )
237 {
238 if( aParentFrame )
239 {
240 msg = wxString::Format( _( "Output files '%s' already exists. "
241 "Do you want to overwrite it?" ),
242 zipFileName.GetFullPath() );
243
244 KIDIALOG errorDlg( aParentFrame, msg, _( "Confirmation" ),
245 wxOK | wxCANCEL | wxICON_WARNING );
246 errorDlg.SetOKLabel( _( "Overwrite" ) );
247
248 if( errorDlg.ShowModal() != wxID_OK )
249 return;
250
251 if( !wxRemoveFile( zipFileName.GetFullPath() ) )
252 {
253 msg.Printf( _( "Cannot remove existing output file '%s'." ),
254 zipFileName.GetFullPath() );
255 DisplayErrorMessage( aParentFrame, msg );
256 return;
257 }
258 }
259 else
260 {
261 msg = wxString::Format( _( "Output file '%s' already exists." ),
262 zipFileName.GetFullPath() );
263
264 if( aReporter )
265 aReporter->Report( msg, RPT_SEVERITY_ERROR );
266
267 return;
268 }
269 }
270
271 tempFile.AssignDir( wxFileName::GetTempDir() );
272 tempFile.AppendDir( "kicad" );
273 tempFile.AppendDir( "odb" );
274
275 if( !wxFileName::Mkdir( tempFile.GetFullPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
276 {
277 msg.Printf( _( "Cannot create temporary output directory." ) );
278
279 if( aReporter )
280 aReporter->Report( msg, RPT_SEVERITY_ERROR );
281
282 return;
283 }
284 }
285 else
286 {
287 // Plugin will create the 'odb' subdirectory for us, so test for it here
288 wxFileName odbDir( tempFile );
289 odbDir.AppendDir( "odb" );
290 wxDir testDir( odbDir.GetFullPath() );
291
292 if( testDir.IsOpened() && ( testDir.HasFiles() || testDir.HasSubDirs() ) )
293 {
294 if( aParentFrame )
295 {
296 msg = wxString::Format( _( "Output directory '%s' already exists and is not empty. "
297 "Do you want to overwrite it?" ),
298 odbDir.GetFullPath() );
299
300 KIDIALOG errorDlg( aParentFrame, msg, _( "Confirmation" ),
301 wxOK | wxCANCEL | wxICON_WARNING );
302 errorDlg.SetOKLabel( _( "Overwrite" ) );
303
304 if( errorDlg.ShowModal() != wxID_OK )
305 return;
306
307 if( !odbDir.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
308 {
309 msg.Printf( _( "Cannot remove existing output directory '%s'." ),
310 odbDir.GetFullPath() );
311 DisplayErrorMessage( aParentFrame, msg );
312 return;
313 }
314 }
315 else
316 {
317 msg = wxString::Format( _( "Output directory '%s' already exists." ),
318 odbDir.GetFullPath() );
319
320 if( aReporter )
321 aReporter->Report( msg, RPT_SEVERITY_ERROR );
322
323 return;
324 }
325 }
326 }
327
328 wxString upperTxt;
329 wxString lowerTxt;
330 std::map<std::string, UTF8> props;
331
332 props["units"] = aJob.m_units == JOB_EXPORT_PCB_ODB::ODB_UNITS::MILLIMETERS ? "mm" : "inch";
333 props["sigfig"] = wxString::Format( "%d", aJob.m_precision );
334
335 auto saveFile =
336 [&]() -> bool
337 {
338 try
339 {
341 pi->SetReporter( aReporter );
342 pi->SetProgressReporter( aProgressReporter );
343 pi->SaveBoard( tempFile.GetFullPath(), aBoard, &props );
344 return true;
345 }
346 catch( const IO_ERROR& ioe )
347 {
348 if( aReporter )
349 {
350 msg = wxString::Format( _( "Error generating ODBPP files '%s'.\n%s" ),
351 tempFile.GetFullPath(), ioe.What() );
352 aReporter->Report( msg, RPT_SEVERITY_ERROR );
353 }
354
355 // In case we started a file but didn't fully write it, clean up
356 wxFileName::Rmdir( tempFile.GetFullPath() );
357 return false;
358 }
359 };
360
362 auto ret = tp.submit( saveFile );
363
364 std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
365
366 while( status != std::future_status::ready )
367 {
368 if( aProgressReporter)
369 aProgressReporter->KeepRefreshing();
370
371 status = ret.wait_for( std::chrono::milliseconds( 250 ) );
372 }
373
374 try
375 {
376 if( !ret.get() )
377 return;
378 }
379 catch( const std::exception& e )
380 {
381 if( aReporter )
382 {
383 aReporter->Report( wxString::Format( "Exception in ODB++ generation: %s", e.what() ),
385 }
386
387 return;
388 }
389
391 {
392 if( aProgressReporter )
393 aProgressReporter->AdvancePhase( _( "Compressing output" ) );
394
395 wxFFileOutputStream fnout( zipFileName.GetFullPath() );
396 wxZipOutputStream zipStream( fnout );
397
398 std::function<void( const wxString&, const wxString& )> addDirToZip =
399 [&]( const wxString& dirPath, const wxString& parentPath )
400 {
401 wxDir dir( dirPath );
402 wxString fileName;
403
404 bool cont = dir.GetFirst( &fileName, wxEmptyString, wxDIR_DEFAULT );
405
406 while( cont )
407 {
408 wxFileName fileInZip( dirPath, fileName );
409 wxString relativePath =
410 parentPath.IsEmpty()
411 ? fileName
412 : parentPath + wxString( wxFileName::GetPathSeparator() )
413 + fileName;
414
415 if( wxFileName::DirExists( fileInZip.GetFullPath() ) )
416 {
417 zipStream.PutNextDirEntry( relativePath );
418 addDirToZip( fileInZip.GetFullPath(), relativePath );
419 }
420 else
421 {
422 wxFFileInputStream fileStream( fileInZip.GetFullPath() );
423 zipStream.PutNextEntry( relativePath );
424 fileStream.Read( zipStream );
425 }
426 cont = dir.GetNext( &fileName );
427 }
428 };
429
430 addDirToZip( tempFile.GetFullPath(), wxEmptyString );
431
432 zipStream.Close();
433 fnout.Close();
434
435 tempFile.Rmdir( wxPATH_RMDIR_RECURSIVE );
436 }
437
438 if( aProgressReporter )
439 aProgressReporter->SetCurrentProgress( 1 );
440}
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap)
Definition: bitmap.cpp:110
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:290
const wxString & GetFileName() const
Definition: board.h:327
PROJECT * GetProject() const
Definition: board.h:491
Class DIALOG_EXPORT_ODBPP_BASE.
STD_BITMAP_BUTTON * m_browseButton
bool TransferDataFromWindow() override
static void GenerateODBPPFiles(const JOB_EXPORT_PCB_ODB &aJob, BOARD *aBoard, PCB_EDIT_FRAME *aParentFrame=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr, REPORTER *aErrorReporter=nullptr)
void onBrowseClicked(wxCommandEvent &event) override
JOB_EXPORT_PCB_ODB * m_job
DIALOG_EXPORT_ODBPP(PCB_EDIT_FRAME *aParent)
PCB_EDIT_FRAME * m_parent
void onOKClick(wxCommandEvent &event) override
void SetupStandardButtons(std::map< int, wxString > aLabels={})
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
Definition: ki_exception.h:77
virtual const wxString What() const
A composite of Problem() and Where()
Definition: exceptions.cpp:30
ODB_COMPRESSION m_compressionMode
void SetOutputPath(const wxString &aPath)
Definition: job.cpp:129
wxString GetOutputPath() const
Definition: job.h:119
Helper class to create more flexible dialogs, including 'do not show again' checkbox handling.
Definition: kidialog.h:43
int ShowModal() override
Definition: kidialog.cpp:95
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
DIALOG_EXPORT_ODBPP m_ExportODBPP
BOARD * GetBoard() const
The main frame for Pcbnew.
void SetLastPath(LAST_PATH_TYPE aType, const wxString &aLastPath)
Set the path of the last file successfully read.
wxString GetLastPath(LAST_PATH_TYPE aType)
Get the last path for a particular type.
static PCB_IO * PluginFind(PCB_FILE_T aFileType)
Return a #PLUGIN which the caller can use to import, export, save, or load design documents.
Definition: pcb_io_mgr.cpp:69
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition: pgm_base.h:142
A progress reporter interface for use in multi-threaded environments.
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void AdvancePhase()=0
Use the next available virtual zone of the dialog progress bar.
virtual void SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
virtual const wxString GetProjectName() const
Return the short name of the project.
Definition: project.cpp:147
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:72
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
T * GetAppSettings(const wxString &aFilename)
Returns a handle to the a given settings by type If the settings have already been loaded,...
void SetBitmap(const wxBitmapBundle &aBmp)
static void ResolvePossibleSymlinks(wxFileName &aFilename)
Definition: wx_filename.cpp:92
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:348
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:195
This file is part of the common library.
static wxString s_oemColumn
#define _(s)
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition: io_mgr.h:33
This file is part of the common library.
PGM_BASE & Pgm()
The global Program "get" accessor.
Definition: pgm_base.cpp:1060
see class PGM_BASE
@ LAST_PATH_ODBPP
Definition: project_file.h:60
@ RPT_SEVERITY_ERROR
static thread_pool * tp
Definition: thread_pool.cpp:30
BS::thread_pool thread_pool
Definition: thread_pool.h:30
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
Definition: thread_pool.cpp:32