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 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
21
22#include <board.h>
23#include <confirm.h>
24#include <footprint.h>
25#include <kidialog.h>
26#include <kiway_holder.h>
27#include <paths.h>
28#include <pcb_edit_frame.h>
29#include <pcbnew_settings.h>
30#include <pgm_base.h>
31#include <progress_reporter.h>
32#include <project.h>
38
39#include <set>
40#include <vector>
41#include <thread_pool.h>
42#include <io/io_mgr.h>
44#include <pcb_io/pcb_io_mgr.h>
45#include <wx/dir.h>
46#include <wx/filedlg.h>
47#include <wx/wfstream.h>
48#include <wx/zipstrm.h>
49#include <wx/tarstrm.h>
50#include <wx/zstream.h>
51
52static wxString s_oemColumn = wxEmptyString;
53
55 DIALOG_EXPORT_ODBPP_BASE( aParent ),
56 m_parent( aParent ),
57 m_job( nullptr )
58{
59 m_browseButton->SetBitmap( KiBitmapBundle( BITMAPS::small_folder ) );
60
62
64
65 if( path.IsEmpty() )
66 {
67 wxFileName brdFile( m_parent->GetBoard()->GetFileName() );
68 wxFileName odbFile( brdFile.GetPath(),
69 wxString::Format( wxS( "%s-odb" ), brdFile.GetName() ),
71 path = odbFile.GetFullPath();
72 }
73
74 m_outputFileName->SetValue( path );
75
76 // Fill wxChoice (and others) items with data before calling finishDialogSettings()
77 // to calculate suitable widgets sizes
78 Init();
79
80 // Now all widgets have the size fixed, call FinishDialogSettings
82}
83
84
86 wxWindow* aParent ) :
87 DIALOG_EXPORT_ODBPP_BASE( aParent ),
88 m_parent( aEditFrame ),
89 m_job( aJob )
90{
91 m_browseButton->Hide();
92
94
96
97 // Fill wxChoice (and others) items with data before calling finishDialogSettings()
98 // to calculate suitable widgets sizes
99 Init();
100
101 // Now all widgets have the size fixed, call FinishDialogSettings
103}
104
105
106void DIALOG_EXPORT_ODBPP::onBrowseClicked( wxCommandEvent& event )
107{
108 // clang-format off
109 wxString filter = _( "zip files" )
111 + _( "tgz files" )
112 + AddFileExtListToFilter( { "tgz" } );
113 // clang-format on
114
115 // Build the absolute path of current output directory to preselect it in the file browser.
116 wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() );
117 wxFileName fn( Prj().AbsolutePath( path ) );
118
119 wxFileName brdFile( m_parent->GetBoard()->GetFileName() );
120
121 wxString fileDialogName( wxString::Format( wxS( "%s-odb" ), brdFile.GetName() ) );
122
123 wxFileDialog dlg( this, _( "Export ODB++ File" ), fn.GetPath(), fileDialogName, filter,
124 wxFD_SAVE );
125
126 if( dlg.ShowModal() == wxID_CANCEL )
127 return;
128
129 path = dlg.GetPath();
130
131 fn = wxFileName( path );
132
133 if( fn.GetExt().Lower() == "zip" )
134 {
135 m_choiceCompress->SetSelection(
136 static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::ZIP ) );
137 }
138 else if( fn.GetExt().Lower() == "tgz" )
139 {
140 m_choiceCompress->SetSelection(
141 static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ ) );
142 }
143 else if( path.EndsWith( "/" ) || path.EndsWith( "\\" ) )
144 {
145 m_choiceCompress->SetSelection(
146 static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE ) );
147 }
148 else
149 {
150 wxString msg;
151 msg.Printf( _( "The selected output file name is not a supported archive format." ) );
152 DisplayErrorMessage( this, msg );
153 return;
154 }
155
156 m_outputFileName->SetValue( path );
157}
158
159void DIALOG_EXPORT_ODBPP::onFormatChoice( wxCommandEvent& event )
160{
162}
163
164
166{
167 wxString fn = m_outputFileName->GetValue();
168
169 wxFileName fileName( fn );
170
171 auto compressionMode =
172 static_cast<JOB_EXPORT_PCB_ODB::ODB_COMPRESSION>( m_choiceCompress->GetSelection() );
173
174 int sepIdx = std::max( fn.Find( '/', true ), fn.Find( '\\', true ) );
175 int dotIdx = fn.Find( '.', true );
176
177 if( fileName.IsDir() )
178 fn = fn.Mid( 0, sepIdx );
179 else if( sepIdx < dotIdx )
180 fn = fn.Mid( 0, dotIdx );
181
182 switch( compressionMode )
183 {
185 fn = fn + '.' + FILEEXT::ArchiveFileExtension;
186 break;
187 case JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ: fn += ".tgz"; break;
188 case JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE: fn = wxFileName( fn, "" ).GetFullPath(); break;
189 default: break;
190 };
191
192 m_outputFileName->SetValue( fn );
193}
194
195void DIALOG_EXPORT_ODBPP::onOKClick( wxCommandEvent& event )
196{
197 if( !m_job )
198 {
199 wxString fn = m_outputFileName->GetValue();
200
201 if( fn.IsEmpty() )
202 {
203 wxString msg;
204 msg.Printf( _( "Output file name cannot be empty." ) );
205 DisplayErrorMessage( this, msg );
206 return;
207 }
208
209 auto compressionMode = static_cast<JOB_EXPORT_PCB_ODB::ODB_COMPRESSION>(
210 m_choiceCompress->GetSelection() );
211
212 wxFileName fileName( fn );
213 bool isDirectory = fileName.IsDir();
214 wxString extension = fileName.GetExt();
215
216 if( ( compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE && !isDirectory )
217 || ( compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::ZIP && extension != "zip" )
218 || ( compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ
219 && extension != "tgz" ) )
220 {
221 wxString msg;
222 msg.Printf(
223 _( "The output file name conflicts with the selected compression format." ) );
224 DisplayErrorMessage( this, msg );
225 return;
226 }
227
229 }
230
231 event.Skip();
232}
233
234
236{
238 PCBNEW_SETTINGS* cfg = mgr.GetAppSettings<PCBNEW_SETTINGS>( "pcbnew" );
239
240 if( !m_job )
241 {
242 m_choiceUnits->SetSelection( cfg->m_ExportODBPP.units );
243 m_precision->SetValue( cfg->m_ExportODBPP.precision );
244 m_choiceCompress->SetSelection( cfg->m_ExportODBPP.compressFormat );
246 }
247 else
248 {
249 SetTitle( m_job->GetSettingsDialogTitle() );
250
251 m_choiceUnits->SetSelection( static_cast<int>( m_job->m_units ) );
252 m_precision->SetValue( m_job->m_precision );
253 m_choiceCompress->SetSelection( static_cast<int>( m_job->m_compressionMode ) );
255 }
256
257 // DIALOG_SHIM needs a unique hash_key because classname will be the same for both job and
258 // non-job versions (which have different sizes).
259 m_hash_key = TO_UTF8( GetTitle() );
260
261 return true;
262}
263
264
266{
267 if( !m_job )
268 {
270 PCBNEW_SETTINGS* cfg = mgr.GetAppSettings<PCBNEW_SETTINGS>( "pcbnew" );
271
272 cfg->m_ExportODBPP.units = m_choiceUnits->GetSelection();
273 cfg->m_ExportODBPP.precision = m_precision->GetValue();
274 cfg->m_ExportODBPP.compressFormat = m_choiceCompress->GetSelection();
275 }
276 else
277 {
279
280 m_job->m_precision = m_precision->GetValue();
281 m_job->m_units =
282 static_cast<JOB_EXPORT_PCB_ODB::ODB_UNITS>( m_choiceUnits->GetSelection() );
284 m_choiceCompress->GetSelection() );
285 }
286
287 return true;
288}
289
290
292 PCB_EDIT_FRAME* aParentFrame,
293 PROGRESS_REPORTER* aProgressReporter,
294 REPORTER* aReporter )
295{
296 wxCHECK( aBoard, /* void */ );
297 wxString outputPath = aJob.GetFullOutputPath( NULL );
298
299 if( outputPath.IsEmpty() )
300 outputPath = wxFileName( aJob.m_filename ).GetPath();
301
302 wxFileName outputFn( outputPath );
303
304 // Write through symlinks, don't replace them
306
307 if( outputFn.GetPath().IsEmpty() && outputFn.HasName() )
308 outputFn.MakeAbsolute();
309
310 wxString msg;
311
312 if( !PATHS::EnsurePathExists( outputFn.GetPath(),
314 {
315 msg.Printf( _( "Cannot create output directory '%s'." ), outputFn.GetFullPath() );
316
317 if( aReporter )
318 aReporter->Report( msg, RPT_SEVERITY_ERROR );
319 return;
320 }
321
322 if( outputFn.IsDir() && !outputFn.IsDirWritable() )
323 {
324 msg.Printf( _( "Insufficient permissions to folder '%s'." ), outputFn.GetPath() );
325 }
326 else if( !outputFn.FileExists() && !outputFn.IsDirWritable() )
327 {
328 msg.Printf( _( "Insufficient permissions to save file '%s'." ), outputFn.GetFullPath() );
329 }
330 else if( outputFn.FileExists() && !outputFn.IsFileWritable() )
331 {
332 msg.Printf( _( "Insufficient permissions to save file '%s'." ), outputFn.GetFullPath() );
333 }
334
335 if( !msg.IsEmpty() )
336 {
337 if( aReporter )
338 aReporter->Report( msg, RPT_SEVERITY_ERROR );
339
340 return;
341 }
342
343 wxFileName tempFile( outputFn.GetFullPath() );
344
346 {
347 if( outputFn.Exists() )
348 {
349 if( aParentFrame )
350 {
351 msg = wxString::Format( _( "Output files '%s' already exists. "
352 "Do you want to overwrite it?" ),
353 outputFn.GetFullPath() );
354
355 KIDIALOG errorDlg( aParentFrame, msg, _( "Confirmation" ),
356 wxOK | wxCANCEL | wxICON_WARNING );
357 errorDlg.SetOKLabel( _( "Overwrite" ) );
358
359 if( errorDlg.ShowModal() != wxID_OK )
360 return;
361
362 if( !wxRemoveFile( outputFn.GetFullPath() ) )
363 {
364 msg.Printf( _( "Cannot remove existing output file '%s'." ),
365 outputFn.GetFullPath() );
366 DisplayErrorMessage( aParentFrame, msg );
367 return;
368 }
369 }
370 else
371 {
372 msg = wxString::Format( _( "Output file '%s' already exists." ),
373 outputFn.GetFullPath() );
374
375 if( aReporter )
376 aReporter->Report( msg, RPT_SEVERITY_ERROR );
377
378 return;
379 }
380 }
381
382 tempFile.AssignDir( wxFileName::GetTempDir() );
383 tempFile.AppendDir( "kicad" );
384 tempFile.AppendDir( "odb" );
385
386 if( !wxFileName::Mkdir( tempFile.GetFullPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
387 {
388 msg.Printf( _( "Cannot create temporary output directory." ) );
389
390 if( aReporter )
391 aReporter->Report( msg, RPT_SEVERITY_ERROR );
392
393 return;
394 }
395 }
396 else
397 {
398 // Test for the output directory of tempFile
399 wxDir testDir( tempFile.GetFullPath() );
400
401 if( testDir.IsOpened() && ( testDir.HasFiles() || testDir.HasSubDirs() ) )
402 {
403 if( aParentFrame )
404 {
405 msg = wxString::Format( _( "Output directory '%s' already exists and is not empty. "
406 "Do you want to overwrite it?" ),
407 tempFile.GetFullPath() );
408
409 KIDIALOG errorDlg( aParentFrame, msg, _( "Confirmation" ),
410 wxOK | wxCANCEL | wxICON_WARNING );
411 errorDlg.SetOKLabel( _( "Overwrite" ) );
412
413 if( errorDlg.ShowModal() != wxID_OK )
414 return;
415
416 if( !tempFile.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
417 {
418 msg.Printf( _( "Cannot remove existing output directory '%s'." ),
419 tempFile.GetFullPath() );
420 DisplayErrorMessage( aParentFrame, msg );
421 return;
422 }
423 }
424 else
425 {
426 msg = wxString::Format( _( "Output directory '%s' already exists." ),
427 tempFile.GetFullPath() );
428
429 if( aReporter )
430 aReporter->Report( msg, RPT_SEVERITY_ERROR );
431
432 return;
433 }
434 }
435 }
436
437 wxString upperTxt;
438 wxString lowerTxt;
439 std::map<std::string, UTF8> props;
440
441 props["units"] = aJob.m_units == JOB_EXPORT_PCB_ODB::ODB_UNITS::MILLIMETERS ? "mm" : "inch";
442 props["sigfig"] = wxString::Format( "%d", aJob.m_precision );
443
444 auto saveFile = [&]() -> bool
445 {
446 try
447 {
449 pi->SetReporter( aReporter );
450 pi->SetProgressReporter( aProgressReporter );
451 pi->SaveBoard( tempFile.GetFullPath(), aBoard, &props );
452 return true;
453 }
454 catch( const IO_ERROR& ioe )
455 {
456 if( aReporter )
457 {
458 msg = wxString::Format( _( "Error generating ODBPP files '%s'.\n%s" ),
459 tempFile.GetFullPath(), ioe.What() );
460 aReporter->Report( msg, RPT_SEVERITY_ERROR );
461 }
462
463 // In case we started a file but didn't fully write it, clean up
464 wxFileName::Rmdir( tempFile.GetFullPath() );
465 return false;
466 }
467 };
468
470 auto ret = tp.submit( saveFile );
471
472 std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
473
474 while( status != std::future_status::ready )
475 {
476 if( aProgressReporter )
477 aProgressReporter->KeepRefreshing();
478
479 status = ret.wait_for( std::chrono::milliseconds( 250 ) );
480 }
481
482 try
483 {
484 if( !ret.get() )
485 return;
486 }
487 catch( const std::exception& e )
488 {
489 if( aReporter )
490 {
491 aReporter->Report( wxString::Format( "Exception in ODB++ generation: %s", e.what() ),
493 }
494
495 return;
496 }
497
499 {
500 if( aProgressReporter )
501 aProgressReporter->AdvancePhase( _( "Compressing output" ) );
502
503 wxFFileOutputStream fnout( outputFn.GetFullPath() );
504 wxZipOutputStream zipStream( fnout );
505
506 std::function<void( const wxString&, const wxString& )> addDirToZip =
507 [&]( const wxString& dirPath, const wxString& parentPath )
508 {
509 wxDir dir( dirPath );
510 wxString fileName;
511
512 bool cont = dir.GetFirst( &fileName, wxEmptyString, wxDIR_DEFAULT );
513
514 while( cont )
515 {
516 wxFileName fileInZip( dirPath, fileName );
517 wxString relativePath =
518 parentPath.IsEmpty()
519 ? fileName
520 : parentPath + wxString( wxFileName::GetPathSeparator() )
521 + fileName;
522
523 if( wxFileName::DirExists( fileInZip.GetFullPath() ) )
524 {
525 zipStream.PutNextDirEntry( relativePath );
526 addDirToZip( fileInZip.GetFullPath(), relativePath );
527 }
528 else
529 {
530 wxFFileInputStream fileStream( fileInZip.GetFullPath() );
531 zipStream.PutNextEntry( relativePath );
532 fileStream.Read( zipStream );
533 }
534 cont = dir.GetNext( &fileName );
535 }
536 };
537
538 addDirToZip( tempFile.GetFullPath(), wxEmptyString );
539
540 zipStream.Close();
541 fnout.Close();
542
543 tempFile.Rmdir( wxPATH_RMDIR_RECURSIVE );
544 }
546 {
547 wxFFileOutputStream fnout( outputFn.GetFullPath() );
548 wxZlibOutputStream zlibStream( fnout, -1, wxZLIB_GZIP );
549 wxTarOutputStream tarStream( zlibStream );
550
551 std::function<void( const wxString&, const wxString& )> addDirToTar =
552 [&]( const wxString& dirPath, const wxString& parentPath )
553 {
554 wxDir dir( dirPath );
555 wxString fileName;
556
557 bool cont = dir.GetFirst( &fileName, wxEmptyString, wxDIR_DEFAULT );
558 while( cont )
559 {
560 wxFileName fileInTar( dirPath, fileName );
561 wxString relativePath =
562 parentPath.IsEmpty()
563 ? fileName
564 : parentPath + wxString( wxFileName::GetPathSeparator() )
565 + fileName;
566
567 if( wxFileName::DirExists( fileInTar.GetFullPath() ) )
568 {
569 tarStream.PutNextDirEntry( relativePath );
570 addDirToTar( fileInTar.GetFullPath(), relativePath );
571 }
572 else
573 {
574 wxFFileInputStream fileStream( fileInTar.GetFullPath() );
575 tarStream.PutNextEntry( relativePath, wxDateTime::Now(),
576 fileStream.GetLength() );
577 fileStream.Read( tarStream );
578 }
579 cont = dir.GetNext( &fileName );
580 }
581 };
582
583 addDirToTar(
584 tempFile.GetFullPath(),
585 tempFile.GetPath( wxPATH_NO_SEPARATOR ).AfterLast( tempFile.GetPathSeparator() ) );
586
587 tarStream.Close();
588 zlibStream.Close();
589 fnout.Close();
590
591 tempFile.Rmdir( wxPATH_RMDIR_RECURSIVE );
592 }
593
594 if( aProgressReporter )
595 aProgressReporter->SetCurrentProgress( 1 );
596}
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap)
Definition: bitmap.cpp:110
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:295
const wxString & GetFileName() const
Definition: board.h:332
Class DIALOG_EXPORT_ODBPP_BASE.
STD_BITMAP_BUTTON * m_browseButton
bool TransferDataFromWindow() override
void onFormatChoice(wxCommandEvent &event) 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={})
std::string m_hash_key
Definition: dialog_shim.h:230
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
wxString GetSettingsDialogTitle() const override
ODB_COMPRESSION m_compressionMode
void SetConfiguredOutputPath(const wxString &aPath)
Sets the configured output path for the job, this path is always saved to file.
Definition: job.cpp:153
wxString GetFullOutputPath(PROJECT *aProject) const
Returns the full output path for the job, taking into account the configured output path,...
Definition: job.cpp:100
wxString GetConfiguredOutputPath() const
Returns the configured output path for the job.
Definition: job.h:226
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.
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
Definition: paths.cpp:467
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:125
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).
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)
Return a handle to the a given settings by type.
void SetBitmap(const wxBitmapBundle &aBmp)
static void ResolvePossibleSymlinks(wxFileName &aFilename)
Definition: wx_filename.cpp:91
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:351
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)
static const std::string ArchiveFileExtension
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:1073
see class PGM_BASE
@ LAST_PATH_ODBPP
Definition: project_file.h:60
@ RPT_SEVERITY_ERROR
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: string_utils.h:398
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
Definition: thread_pool.cpp:30
static thread_pool * tp
Definition: thread_pool.cpp:28
BS::thread_pool thread_pool
Definition: thread_pool.h:31
wxString AddFileExtListToFilter(const std::vector< std::string > &aExts)
Build the wildcard extension file dialog wildcard filter to add to the base message dialog.