KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_export_2581.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 <set>
23#include <map>
24#include <vector>
25
26#include <wx/filedlg.h>
27#include <wx/filefn.h>
28#include <kiplatform/ui.h>
29
30#include <board.h>
31#include <footprint.h>
32#include <kiway_holder.h>
33#include <paths.h>
34#include <pcb_edit_frame.h>
35#include <pcbnew_settings.h>
36#include <pgm_base.h>
37#include <project.h>
39#include <pcb_io/pcb_io_mgr.h>
43#include <string_utils.h>
46#include <kiplatform/io.h>
47#include <wx/wfstream.h>
48#include <wx/zipstrm.h>
49#include <wx_filename.h>
50
51
53 DIALOG_EXPORT_2581_BASE( aParent ),
54 m_parent( aParent ),
55 m_job( nullptr )
56{
58
59 SetupStandardButtons( { { wxID_OK, _( "Export" ) },
60 { wxID_CANCEL, _( "Close" ) } } );
61
62 // DIALOG_SHIM needs a unique hash_key because classname will be the same for both job and
63 // non-job versions.
64 m_hash_key = TO_UTF8( GetTitle() );
65
66 init();
67
68 // Now all widgets have the size fixed, call FinishDialogSettings
70
71 // The messages panel uses a negative min width so it doesn't drive the dialog width.
72 // Ensure the dialog is at least wide enough for the standard buttons and the messages
73 // panel's internal controls (filter checkboxes and Save button).
74 int btnWidth = m_stdButtons->GetMinSize().GetWidth() + 10;
75 int panelWidth = m_messagesPanel->GetBestSize().GetWidth() + 10;
76 int minWidth = std::max( btnWidth, panelWidth );
77 wxSize dialogMin = GetMinSize();
78
79 if( dialogMin.GetWidth() < minWidth )
80 {
81 SetMinSize( wxSize( minWidth, dialogMin.GetHeight() ) );
82 SetSize( wxSize( std::max( GetSize().GetWidth(), minWidth ), GetSize().GetHeight() ) );
83 }
84}
85
86
88 wxWindow* aParent ) :
89 DIALOG_EXPORT_2581_BASE( aParent ),
90 m_parent( aEditFrame ),
91 m_job( aJob )
92{
93 m_browseButton->Hide();
94
96
97 SetTitle( m_job->GetSettingsDialogTitle() );
98
99 // DIALOG_SHIM needs a unique hash_key because classname will be the same for both job and
100 // non-job versions.
101 m_hash_key = TO_UTF8( GetTitle() );
102
103 init();
104
105 // Now all widgets have the size fixed, call FinishDialogSettings
107
108 // The messages panel uses a negative min width so it doesn't drive the dialog width.
109 // Ensure the dialog is at least wide enough for the standard buttons and the messages
110 // panel's internal controls (filter checkboxes and Save button).
111 int btnWidth = m_stdButtons->GetMinSize().GetWidth() + 10;
112 int panelWidth = m_messagesPanel->GetBestSize().GetWidth() + 10;
113 int minWidth = std::max( btnWidth, panelWidth );
114 wxSize dialogMin = GetMinSize();
115
116 if( dialogMin.GetWidth() < minWidth )
117 {
118 SetMinSize( wxSize( minWidth, dialogMin.GetHeight() ) );
119 SetSize( wxSize( std::max( GetSize().GetWidth(), minWidth ), GetSize().GetHeight() ) );
120 }
121}
122
123
124void DIALOG_EXPORT_2581::onBrowseClicked( wxCommandEvent& event )
125{
126 // Build the absolute path of current output directory to preselect it in the file browser.
127 wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() );
128 wxFileName fn( Prj().AbsolutePath( path ) );
129 wxString ipc_files = _( "IPC-2581 Files (*.xml)|*.xml" );
130 wxString compressed_files = _( "IPC-2581 Compressed Files (*.zip)|*.zip" );
131
132 wxFileDialog dlg( this, _( "Export IPC-2581 File" ), fn.GetPath(), fn.GetFullName(),
133 m_cbCompress->IsChecked() ? compressed_files : ipc_files,
134 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
135
137
138 if( dlg.ShowModal() == wxID_CANCEL )
139 return;
140
141 m_outputFileName->SetValue( dlg.GetPath() );
142
143}
144
145void DIALOG_EXPORT_2581::onCompressCheck( wxCommandEvent& event )
146{
147 if( m_cbCompress->GetValue() )
148 {
149 wxFileName fn = m_outputFileName->GetValue();
150
151 fn.SetExt( "zip" );
152 m_outputFileName->SetValue( fn.GetFullPath() );
153 }
154 else
155 {
156 wxFileName fn = m_outputFileName->GetValue();
157
158 fn.SetExt( "xml" );
159 m_outputFileName->SetValue( fn.GetFullPath() );
160 }
161}
162
163
164void DIALOG_EXPORT_2581::onMfgPNChange( wxCommandEvent& event )
165{
166 if( event.GetSelection() == 0 )
167 {
168 m_choiceMfg->Enable( false );
169 }
170 else
171 {
172 m_choiceMfg->Enable( true );
173
174 // Don't try to guess the manufacturer if the user has already selected one
175 if( m_choiceMfg->GetSelection() > 0 )
176 return;
177
178 int it = 0;
179
180 if( it = m_choiceMfg->FindString( wxT( "manufacturer" ) ); it != wxNOT_FOUND )
181 m_choiceMfg->Select( it );
182 else if( it = m_choiceMfg->FindString( _( "manufacturer" ) ); it != wxNOT_FOUND )
183 m_choiceMfg->Select( it );
184 else if( it = m_choiceMfg->FindString( wxT( "mfg" ) ); it != wxNOT_FOUND )
185 m_choiceMfg->Select( it );
186 else if( it = m_choiceMfg->FindString( _( "mfg" ) ); it != wxNOT_FOUND )
187 m_choiceMfg->Select( it );
188 }
189}
190
191
192void DIALOG_EXPORT_2581::onDistPNChange( wxCommandEvent& event )
193{
194 if( event.GetSelection() == 0 )
195 {
196 m_textDistributor->Enable( false );
197 m_textDistributor->SetValue( _( "N/A" ) );
198 }
199 else
200 {
201 m_textDistributor->Enable( true );
202
203 // Don't try to guess the distributor if the user has already selected one
204 if( m_textDistributor->GetValue() != _( "N/A" ) )
205 return;
206
207 wxString dist = m_choiceDistPN->GetStringSelection();
208 dist.MakeUpper();
209
210 // Try to guess the distributor from the part number column
211
212 if( dist.Contains( wxT( "DIGIKEY" ) ) )
213 {
214 m_textDistributor->SetValue( wxT( "Digi-Key" ) );
215 }
216 else if( dist.Contains( wxT( "DIGI-KEY" ) ) )
217 {
218 m_textDistributor->SetValue( wxT( "Digi-Key" ) );
219 }
220 else if( dist.Contains( wxT( "MOUSER" ) ) )
221 {
222 m_textDistributor->SetValue( wxT( "Mouser" ) );
223 }
224 else if( dist.Contains( wxT( "NEWARK" ) ) )
225 {
226 m_textDistributor->SetValue( wxT( "Newark" ) );
227 }
228 else if( dist.Contains( wxT( "RS COMPONENTS" ) ) )
229 {
230 m_textDistributor->SetValue( wxT( "RS Components" ) );
231 }
232 else if( dist.Contains( wxT( "FARNELL" ) ) )
233 {
234 m_textDistributor->SetValue( wxT( "Farnell" ) );
235 }
236 else if( dist.Contains( wxT( "ARROW" ) ) )
237 {
238 m_textDistributor->SetValue( wxT( "Arrow" ) );
239 }
240 else if( dist.Contains( wxT( "AVNET" ) ) )
241 {
242 m_textDistributor->SetValue( wxT( "Avnet" ) );
243 }
244 else if( dist.Contains( wxT( "TME" ) ) )
245 {
246 m_textDistributor->SetValue( wxT( "TME" ) );
247 }
248 else if( dist.Contains( wxT( "LCSC" ) ) )
249 {
250 m_textDistributor->SetValue( wxT( "LCSC" ) );
251 }
252 }
253}
254
255
256void DIALOG_EXPORT_2581::onOKClick( wxCommandEvent& event )
257{
258 if( m_job )
259 {
261 EndModal( wxID_OK );
262
263 return;
264 }
265
267 m_job = &job;
268
270
271 m_job = nullptr;
272
273 m_messagesPanel->Clear();
274
275 REPORTER& reporter = m_messagesPanel->Reporter();
276
277 wxFileName pcbFileName = GetOutputPath();
279
280 if( pcbFileName.GetName().empty() )
281 {
282 reporter.Report( _( "The board must be saved before generating IPC-2581 file." ),
284 return;
285 }
286
287 if( !m_parent->IsWritable( pcbFileName ) )
288 {
289 reporter.Report( wxString::Format( _( "Insufficient permissions to write file '%s'." ),
290 pcbFileName.GetFullPath() ),
292 return;
293 }
294
295 WX_PROGRESS_REPORTER progress( this, _( "Generate IPC-2581 File" ), 5, PR_CAN_ABORT );
296
297 if( !GenerateFile( job, m_parent->GetBoard(), &progress, &reporter ) )
298 return;
299
300 reporter.Report( _( "IPC-2581 file generated successfully." ), RPT_SEVERITY_ACTION );
301}
302
303
305 PROGRESS_REPORTER* aProgressReporter, REPORTER* aReporter )
306{
307 wxCHECK( aBoard, false );
308 wxString outPath = aJob.GetFullOutputPath( aBoard->GetProject() );
309
310 if( !PATHS::EnsurePathExists( outPath, true ) )
311 {
312 if( aReporter )
313 aReporter->Report( _( "Failed to create output directory\n" ), RPT_SEVERITY_ERROR );
314
315 return false;
316 }
317
318 std::map<std::string, UTF8> props;
319 props["units"] = aJob.m_units == JOB_EXPORT_PCB_IPC2581::IPC2581_UNITS::MM ? "mm" : "inch";
320 props["sigfig"] = wxString::Format( "%d", aJob.m_precision );
321 props["version"] = aJob.m_version == JOB_EXPORT_PCB_IPC2581::IPC2581_VERSION::C ? "C" : "B";
322 props["OEMRef"] = aJob.m_colInternalId;
323 props["mpn"] = aJob.m_colMfgPn;
324 props["mfg"] = aJob.m_colMfg;
325 props["dist"] = aJob.m_colDist;
326 props["distpn"] = aJob.m_colDistPn;
327
328 wxString bomRev = aJob.m_bomRev;
329
330 if( bomRev.IsEmpty() && aBoard->GetProject() )
331 {
332 const IP2581_BOM& bomSettings = aBoard->GetProject()->GetProjectFile().m_IP2581Bom;
333 bomRev = bomSettings.bomRev;
334
335 if( bomRev.IsEmpty() )
336 bomRev = bomSettings.schRevision;
337 }
338
339 if( !bomRev.IsEmpty() )
340 props["bomrev"] = bomRev;
341
342 wxString tempFile = wxFileName::CreateTempFileName( wxS( "pcbnew_ipc" ) );
343
344 try
345 {
347 pi->SetProgressReporter( aProgressReporter );
348 pi->SaveBoard( tempFile, aBoard, &props );
349 }
350 catch( const IO_ERROR& ioe )
351 {
352 if( aReporter )
353 {
354 aReporter->Report( wxString::Format( _( "Error generating IPC-2581 file '%s'.\n%s" ),
355 aJob.m_filename,
356 ioe.What() ),
358 }
359
360 wxRemoveFile( tempFile );
361
362 return false;
363 }
364
365 if( aJob.m_compress )
366 {
367 wxFileName tempfn = outPath;
368 tempfn.SetExt( FILEEXT::Ipc2581FileExtension );
369 wxFileName zipfn = tempFile;
370 zipfn.SetExt( "zip" );
371
372 {
373 wxFFileOutputStream fnout( zipfn.GetFullPath() );
374
375 // Use a large I/O buffer to improve compatibility with cloud-synced folders.
376 // See KIPLATFORM::IO::CLOUD_SYNC_BUFFER_SIZE comment for details.
377 if( FILE* fp = fnout.GetFile()->fp() )
378 setvbuf( fp, nullptr, _IOFBF, KIPLATFORM::IO::CLOUD_SYNC_BUFFER_SIZE );
379
380 wxZipOutputStream zip( fnout );
381 wxFFileInputStream fnin( tempFile );
382
383 zip.PutNextEntry( tempfn.GetFullName() );
384 fnin.Read( zip );
385 }
386
387 wxRemoveFile( tempFile );
388 tempFile = zipfn.GetFullPath();
389 }
390
391 // If save succeeded, replace the original with what we just wrote
392 if( !wxRenameFile( tempFile, outPath ) )
393 {
394 if( aReporter )
395 {
396 aReporter->Report( wxString::Format( _( "Error generating IPC-2581 file '%s'.\n"
397 "Failed to rename temporary file '%s." ),
398 outPath,
399 tempFile ),
401 }
402
403 return false;
404 }
405
406 aJob.AddOutput( outPath );
407 return true;
408}
409
410
412{
413 m_textDistributor->SetSize( m_choiceDistPN->GetSize() );
414
415 std::set<wxString> options;
416
417 for( FOOTPRINT* fp : m_parent->GetBoard()->Footprints() )
418 {
419 for( PCB_FIELD* field : fp->GetFields() )
420 {
421 wxCHECK2( field, continue );
422
423 options.insert( field->GetName() );
424 }
425 }
426
427 std::vector<wxString> items( options.begin(), options.end() );
428 m_oemRef->Append( items );
429 m_choiceMPN->Append( items );
430 m_choiceMfg->Append( items );
431 m_choiceDistPN->Append( items );
432}
433
434
436{
437 if( !m_job )
438 {
439 wxString path = m_outputFileName->GetValue();
440
441 if( path.IsEmpty() )
442 {
443 wxFileName brdFile( m_parent->GetBoard()->GetFileName() );
444 brdFile.SetExt( wxT( "xml" ) );
445 path = brdFile.GetFullPath();
446 m_outputFileName->SetValue( path );
447 }
448 }
449 else
450 {
451 m_choiceUnits->SetSelection( m_job->m_units == JOB_EXPORT_PCB_IPC2581::IPC2581_UNITS::MM ? 0 : 1 );
452 m_precision->SetValue( static_cast<int>( m_job->m_precision ) );
453 m_versionChoice->SetSelection( m_job->m_version == JOB_EXPORT_PCB_IPC2581::IPC2581_VERSION::B ? 0 : 1 );
454 m_cbCompress->SetValue( m_job->m_compress );
455 m_outputFileName->SetValue( m_job->GetConfiguredOutputPath() );
456 }
457
458 wxCommandEvent dummy;
460
462
463 wxString internalIdCol;
464 wxString mpnCol;
465 wxString distPnCol;
466 wxString mfgCol;
467 wxString distCol;
468
469 if( !m_job )
470 {
471 internalIdCol = prj.m_IP2581Bom.id;
472 mpnCol = prj.m_IP2581Bom.MPN;
473 distPnCol = prj.m_IP2581Bom.distPN;
474 mfgCol = prj.m_IP2581Bom.mfg;
475 distCol = prj.m_IP2581Bom.dist;
476 wxString bomRev = prj.m_IP2581Bom.bomRev;
477
478 if( bomRev.IsEmpty() )
479 bomRev = prj.m_IP2581Bom.schRevision;
480
481 m_textBomRev->SetValue( bomRev );
482 }
483 else
484 {
485 internalIdCol = m_job->m_colInternalId;
486 mpnCol = m_job->m_colMfgPn;
487 distPnCol = m_job->m_colDistPn;
488 mfgCol = m_job->m_colMfg;
489 distCol = m_job->m_colDist;
490 m_textBomRev->SetValue( m_job->m_bomRev );
491 }
492
493 if( !m_choiceMPN->SetStringSelection( internalIdCol ) )
494 m_choiceMPN->SetSelection( 0 );
495
496 if( m_choiceMPN->SetStringSelection( mpnCol ) )
497 {
498 m_choiceMfg->Enable( true );
499
500 if( !m_choiceMfg->SetStringSelection( mfgCol ) )
501 m_choiceMfg->SetSelection( 0 );
502 }
503 else
504 {
505 m_choiceMPN->SetSelection( 0 );
506 m_choiceMfg->SetSelection( 0 );
507 m_choiceMfg->Enable( false );
508 }
509
510 if( m_choiceDistPN->SetStringSelection( distPnCol ) )
511 {
512 m_textDistributor->Enable( true );
513
514 // The combo box selection can be fixed, so any value can be entered
515 if( !prj.m_IP2581Bom.distPN.empty() )
516 {
517 m_textDistributor->SetValue( distCol );
518 }
519 else
520 {
521 wxCommandEvent evt;
522 onDistPNChange( evt );
523 }
524 }
525 else
526 {
527 m_choiceDistPN->SetSelection( 0 );
528 m_textDistributor->SetValue( _( "N/A" ) );
529 m_textDistributor->Enable( false );
530 }
531
532 return true;
533}
534
535
537{
538 if( !m_job )
539 {
541
542 prj.m_IP2581Bom.id = GetOEM();
543 prj.m_IP2581Bom.mfg = GetMfg();
544 prj.m_IP2581Bom.MPN = GetMPN();
546 prj.m_IP2581Bom.dist = GetDist();
547 prj.m_IP2581Bom.bomRev = m_textBomRev->GetValue();
548 }
549 else
550 {
551 m_job->SetConfiguredOutputPath( m_outputFileName->GetValue() );
552
553 m_job->m_colInternalId = GetOEM();
554 m_job->m_colDist = GetDist();
555 m_job->m_colDistPn = GetDistPN();
556 m_job->m_colMfg = GetMfg();
557 m_job->m_colMfgPn = GetMPN();
558 m_job->m_bomRev = m_textBomRev->GetValue();
559
564 m_job->m_precision = m_precision->GetValue();
565 m_job->m_compress = GetCompress();
566 }
567
568 return true;
569}
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:323
PROJECT * GetProject() const
Definition board.h:587
wxStdDialogButtonSizer * m_stdButtons
DIALOG_EXPORT_2581_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Export IPC-2581"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
WX_HTML_REPORT_PANEL * m_messagesPanel
STD_BITMAP_BUTTON * m_browseButton
void onMfgPNChange(wxCommandEvent &event) override
wxString GetOEM() const
static bool GenerateFile(JOB_EXPORT_PCB_IPC2581 &aJob, BOARD *aBoard, PROGRESS_REPORTER *aProgressReporter, REPORTER *aReporter)
bool TransferDataToWindow() override
void onCompressCheck(wxCommandEvent &event) override
void onOKClick(wxCommandEvent &event) override
wxString GetMPN() const
void onDistPNChange(wxCommandEvent &event) override
bool TransferDataFromWindow() override
wxString GetDistPN() const
DIALOG_EXPORT_2581(PCB_EDIT_FRAME *aParent)
PCB_EDIT_FRAME * m_parent
wxString GetDist() const
wxString GetMfg() const
void onBrowseClicked(wxCommandEvent &event) override
wxString GetUnitsString() const
JOB_EXPORT_PCB_IPC2581 * m_job
wxString GetOutputPath() const
void SetupStandardButtons(std::map< int, wxString > aLabels={})
std::string m_hash_key
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.
virtual const wxString What() const
A composite of Problem() and Where()
void AddOutput(wxString aOutputPath)
Definition job.h:216
wxString GetFullOutputPath(PROJECT *aProject) const
Returns the full output path for the job, taking into account the configured output path,...
Definition job.cpp:150
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:518
The main frame for Pcbnew.
static PCB_IO * FindPlugin(PCB_FILE_T aFileType)
Return a #PLUGIN which the caller can use to import, export, save, or load design documents.
A progress reporter interface for use in multi-threaded environments.
The backing store for a PROJECT, in JSON format.
struct IP2581_BOM m_IP2581Bom
Layer pair list for the board.
virtual PROJECT_FILE & GetProjectFile() const
Definition project.h:204
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
static void ResolvePossibleSymlinks(wxFileName &aFilename)
Multi-thread safe progress reporter dialog, intended for use of tasks that parallel reporting back of...
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:558
#define _(s)
static const std::string Ipc2581FileExtension
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
static constexpr size_t CLOUD_SYNC_BUFFER_SIZE
Buffer size for file I/O operations on cloud-synced folders.
Definition io.h:77
void AllowNetworkFileSystems(wxDialog *aDialog)
Configure a file dialog to show network and virtual file systems.
Definition wxgtk/ui.cpp:435
see class PGM_BASE
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_ACTION
std::vector< FAB_LAYER_COLOR > dummy
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
wxString mfg
Manufacturer name column.
wxString bomRev
Explicit BOM revision override set by user.
wxString schRevision
Auto-propagated schematic title block revision.
wxString MPN
Manufacturer part number column.
wxString id
Internal ID column.
wxString dist
Distributor name column.
wxString distPN
Distributor part number column.
std::string path
#define PR_CAN_ABORT