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
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
22
23#include <algorithm>
24
25#include <wx/filedlg.h>
26
27#include <kiplatform/ui.h>
28
29#include <bitmaps.h>
30#include <confirm.h>
31#include <pcb_edit_frame.h>
32#include <pcbnew_settings.h>
33#include <pgm_base.h>
34#include <project.h>
35#include <string_utils.h>
38#include <board.h>
39#include <reporter.h>
44#include <tool/tool_manager.h>
45#include <grid_tricks.h>
46#include <widgets/wx_grid.h>
48
49
50namespace
51{
52enum LAYER_GRID_COL
53{
54 COL_INCLUDE,
55 COL_LAYER,
58 COL_CONTEXT,
59 COL_SPAN
60};
61
62
63wxArrayString overridableTypeNames( PCB_LAYER_ID aLayer )
64{
65 wxArrayString names;
66
67 for( ODB_TYPE type : ODB::OverridableLayerTypes( aLayer ) )
68 names.Add( wxString::FromUTF8( ODB::Enum2String( type ) ) );
69
70 return names;
71}
72
73} // namespace
74
75
77 DIALOG_EXPORT_ODBPP_BASE( aParent ),
78 m_parent( aParent ),
79 m_job( nullptr )
80{
82 SetupStandardButtons( { { wxID_OK, _( "Export" ) }, { wxID_CANCEL, _( "Close" ) } } );
83
84 // DIALOG_SHIM needs a unique hash_key because classname will be the same for both job and
85 // non-job versions.
86 m_hash_key = TO_UTF8( GetTitle() );
87
89 // Now all widgets have the size fixed, call FinishDialogSettings
91}
92
93
95 wxWindow* aParent ) :
96 DIALOG_EXPORT_ODBPP_BASE( aParent ),
97 m_parent( aEditFrame ),
98 m_job( aJob )
99{
100 m_browseButton->Hide();
101 m_messagesPanel->Hide();
102
104
105 // DIALOG_SHIM needs a unique hash_key because classname will be the same for both job and
106 // non-job versions.
107 m_hash_key = TO_UTF8( GetTitle() );
108
110 // Now all widgets have the size fixed, call FinishDialogSettings
112}
113
114
116{
117 m_choiceUnits->Bind( wxEVT_CHOICE,
118 [this]( wxCommandEvent& )
119 {
121 } );
122 m_contentPanel->Configure( FAB_CONTENT_FORMAT::ODBPP, m_parent->GetBoard() );
123 m_contentPanel->SetContentChanged(
124 [this]
125 {
128 } );
129
130 m_layers->PushEventHandler( new GRID_TRICKS( m_layers ) );
131
132 wxGridCellAttr* includeAttr = new wxGridCellAttr;
133 includeAttr->SetRenderer( new wxGridCellBoolRenderer );
134 includeAttr->SetEditor( new wxGridCellBoolEditor );
135 includeAttr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
136 m_layers->SetColAttr( COL_INCLUDE, includeAttr );
137
138 for( int col : { COL_LAYER, COL_CONTEXT, COL_SPAN } )
139 {
140 wxGridCellAttr* readOnlyAttr = new wxGridCellAttr;
141 readOnlyAttr->SetReadOnly();
142 m_layers->SetColAttr( col, readOnlyAttr );
143 }
144}
145
146
148{
149 m_layers->PopEventHandler( true );
150}
151
152
158
159
161{
162 JOB_EXPORT_PCB_ODB defaults;
163 const JOB_EXPORT_PCB_ODB& settings = m_job ? *m_job : defaults;
164
165 m_choiceUnits->SetSelection( settings.m_units == JOB_EXPORT_PCB_FAB::UNITS::MM ? 0 : 1 );
167 m_precision->SetValue( settings.m_precision );
168 m_choiceCompress->SetSelection( static_cast<int>( settings.m_compressionMode ) );
169 m_choiceOrigin->SetSelection( static_cast<int>( settings.m_origin ) );
170 m_productName->SetValue( settings.m_productName );
171 m_refillZones->SetValue( m_job ? settings.m_checkZonesBeforeExport : true );
172 m_contentPanel->SetDataSet( settings.m_dataSet );
173 m_contentPanel->SetSectionKey( settings.m_sections );
174 m_contentPanel->SetBoardMetadata( settings.m_boardMetadata );
175 m_contentPanel->SetNetNames( settings.m_netNames );
176 m_contentPanel->SetVariantNames( settings.m_variantNames );
177 m_contentPanel->SetCombinedVariantOutput( settings.m_variantPackaging
179 IPC2581_BOM_FIELDS fields;
180 fields.m_mfgPn = settings.m_colMfgPn;
181 m_contentPanel->SetBomFields( fields );
183
184 if( !m_job )
185 {
186 if( m_outputFileName->GetValue().IsEmpty() )
187 {
188 wxFileName brdFile( m_parent->GetBoard()->GetFileName() );
189 wxFileName odbFile( brdFile.GetPath(), wxString::Format( wxS( "%s-odb" ), brdFile.GetName() ),
191
192 m_outputFileName->SetValue( odbFile.GetFullPath() );
194 }
195 }
196 else
197 {
198 SetTitle( m_job->GetSettingsDialogTitle() );
199 m_outputFileName->SetValue( m_job->GetConfiguredOutputPath() );
200 }
201
204 return true;
205}
206
207
209{
210 aJob.SetConfiguredOutputPath( m_outputFileName->GetValue() );
211
212 if( &aJob != m_job )
213 aJob.m_filename = m_parent->GetBoard()->GetFileName();
214
215 aJob.m_precision = m_precision->GetValue();
217 aJob.m_compressionMode = static_cast<JOB_EXPORT_PCB_ODB::ODB_COMPRESSION>( m_choiceCompress->GetSelection() );
218 aJob.m_origin = static_cast<JOB_EXPORT_PCB_ODB::ORIGIN>( m_choiceOrigin->GetSelection() );
219 aJob.m_productName = m_productName->GetValue();
220 aJob.m_checkZonesBeforeExport = m_refillZones->GetValue();
221 aJob.m_dataSet = m_contentPanel->GetDataSet();
222 aJob.m_sections = m_contentPanel->GetSectionKey();
223 aJob.m_boardMetadata = m_contentPanel->GetBoardMetadata();
224 aJob.m_netNames = m_contentPanel->GetNetNames();
225 aJob.m_variantNames = m_contentPanel->GetVariantNames();
226 aJob.m_variantPackaging = m_contentPanel->IsCombinedVariantOutput()
229 aJob.m_colMfgPn = m_contentPanel->GetBomFields().m_mfgPn;
231}
232
233
235{
236 JOB_EXPORT_PCB_ODB previewJob;
237 populateJob( previewJob );
238 previewJob.m_layerOverrides.clear();
240 m_previewRows = PreviewOdbMatrix( m_parent->GetBoard(), previewJob );
241 m_layers->ClearRows();
242 m_layers->AppendRows( static_cast<int>( m_previewRows.size() ) );
243 int gridRow = 0;
244
245 for( const ODB_MATRIX_PREVIEW_ROW& row : m_previewRows )
246 {
247 auto it = std::find_if( m_layerOverrides.begin(), m_layerOverrides.end(),
248 [&]( const ODB_LAYER_OVERRIDE& aCandidate )
249 {
250 return row.m_editable && aCandidate.m_layer == row.m_boardLayer;
251 } );
252 const ODB_LAYER_OVERRIDE* override = it == m_layerOverrides.end() ? nullptr : &*it;
253
254 wxString name = override && !override->m_odbName.IsEmpty() ? override->m_odbName : row.m_matrix.m_name;
255 wxString type = override && !override->m_odbType.IsEmpty() ? override->m_odbType : row.m_matrix.m_type;
256 wxString span = row.m_matrix.m_startName;
257
258 if( !row.m_matrix.m_endName.IsEmpty() )
259 span += wxS( " – " ) + row.m_matrix.m_endName;
260
261 // The preview omits overrides, and the exporter only references copper from mask, paste and silk
262 bool keepsRef = type == wxS( "SOLDER_MASK" ) || type == wxS( "SOLDER_PASTE" ) || type == wxS( "SILK_SCREEN" );
263
264 if( !row.m_matrix.m_ref.IsEmpty() && keepsRef )
265 {
266 span += wxS( " REF " ) + OdbPreviewReferenceName( row.m_matrix.m_ref, m_previewRows );
267 }
268
269 bool include = !override || override->m_include;
270 m_layers->SetCellValue( gridRow, COL_INCLUDE, include ? wxString( wxS( "1" ) ) : wxString() );
271 m_layers->SetCellValue( gridRow, COL_LAYER, row.m_displayLayer );
272 m_layers->SetCellValue( gridRow, COL_NAME, name );
273 m_layers->SetCellValue( gridRow, COL_TYPE, type );
274 m_layers->SetCellValue( gridRow, COL_CONTEXT, row.m_matrix.m_context );
275 m_layers->SetCellValue( gridRow, COL_SPAN, span );
276
277 if( row.m_editable )
278 {
279 m_layers->SetCellEditor( gridRow, COL_TYPE,
280 new wxGridCellChoiceEditor( overridableTypeNames( row.m_boardLayer ) ) );
281 }
282 else
283 {
284 for( int col : { COL_INCLUDE, COL_NAME, COL_TYPE } )
285 m_layers->SetReadOnly( gridRow, col );
286 }
287
288 ++gridRow;
289 }
290}
291
292
294{
295 m_layers->CommitPendingChanges( true );
296
297 for( size_t index = 0; index < m_previewRows.size() && index < static_cast<size_t>( m_layers->GetNumberRows() );
298 ++index )
299 {
301
302 if( !row.m_editable )
303 continue;
304
305 int gridRow = static_cast<int>( index );
306 wxString name = m_layers->GetCellValue( gridRow, COL_NAME );
307 wxString type = m_layers->GetCellValue( gridRow, COL_TYPE );
308
309 // Pasted text bypasses the choice editor
310 if( overridableTypeNames( row.m_boardLayer ).Index( type ) == wxNOT_FOUND )
311 type = row.m_matrix.m_type;
312
313 ODB_LAYER_OVERRIDE override;
314 override.m_layer = row.m_boardLayer;
315 override.m_include = !m_layers->GetCellValue( gridRow, COL_INCLUDE ).IsEmpty();
316 override.m_odbName = name == row.m_matrix.m_name ? wxString() : name;
317 override.m_odbType = type == row.m_matrix.m_type ? wxString() : type;
318
319 std::erase_if( m_layerOverrides,
320 [&]( const ODB_LAYER_OVERRIDE& aCandidate )
321 {
322 return aCandidate.m_layer == row.m_boardLayer;
323 } );
324
325 if( !override.m_include || !override.m_odbName.IsEmpty() || !override.m_odbType.IsEmpty() )
326 m_layerOverrides.push_back( override );
327 }
328}
329
330
332{
333 bool separate = m_contentPanel->GetVariantNames().size() > 1 && !m_contentPanel->IsCombinedVariantOutput();
334 bool directory = m_choiceCompress->GetSelection() == static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE );
335 wxString path =
337 m_autoVariantSuffix = separate;
338
339 if( !path.IsEmpty() )
340 m_outputFileName->SetValue( path );
341}
342
343
344void DIALOG_EXPORT_ODBPP::onBrowseClicked( wxCommandEvent& event )
345{
346 // clang-format off
347 wxString filter = _( "zip files" )
349 + _( "tgz files" )
350 + AddFileExtListToFilter( { "tgz" } );
351 // clang-format on
352
353 // Build the absolute path of current output directory to preselect it in the file browser.
354 wxFileName fn( ResolveProjectPath( m_outputFileName->GetValue(), &Prj() ) );
355
356 wxFileName brdFile( m_parent->GetBoard()->GetFileName() );
357
358 wxString fileDialogName( wxString::Format( wxS( "%s-odb" ), brdFile.GetName() ) );
359
360 wxFileDialog dlg( this, _( "Export ODB++ File" ), fn.GetPath(), fileDialogName, filter, wxFD_SAVE );
361
363
364 if( dlg.ShowModal() == wxID_CANCEL )
365 return;
366
367 wxString path = dlg.GetPath();
368
369 fn = wxFileName( path );
370
371 if( fn.GetExt().Lower() == "zip" )
372 {
373 m_choiceCompress->SetSelection( static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::ZIP ) );
374 }
375 else if( fn.GetExt().Lower() == "tgz" )
376 {
377 m_choiceCompress->SetSelection( static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ ) );
378 }
379 else if( path.EndsWith( "/" ) || path.EndsWith( "\\" ) )
380 {
381 m_choiceCompress->SetSelection( static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE ) );
382 }
383 else
384 {
385 DisplayErrorMessage( this, _( "The selected output file name is not a supported archive format." ) );
386 return;
387 }
388
389 m_outputFileName->SetValue( path );
390}
391
392
393void DIALOG_EXPORT_ODBPP::onFormatChoice( wxCommandEvent& event )
394{
396}
397
398
400{
401 wxString fn = m_outputFileName->GetValue();
402
403 wxFileName fileName( fn );
404
405 auto compressionMode = static_cast<JOB_EXPORT_PCB_ODB::ODB_COMPRESSION>( m_choiceCompress->GetSelection() );
406
407 int sepIdx = std::max( fn.Find( '/', true ), fn.Find( '\\', true ) );
408 int dotIdx = fn.Find( '.', true );
409
410 if( fileName.IsDir() )
411 fn = fn.Mid( 0, sepIdx );
412 else if( sepIdx < dotIdx )
413 fn = fn.Mid( 0, dotIdx );
414
415 switch( compressionMode )
416 {
418 fn = fn + '.' + FILEEXT::ArchiveFileExtension;
419 break;
421 fn += ".tgz";
422 break;
424 fn = wxFileName( fn, "" ).GetFullPath();
425 break;
426 default:
427 break;
428 };
429
430 m_outputFileName->SetValue( fn );
432}
433
434
435void DIALOG_EXPORT_ODBPP::onOKClick( wxCommandEvent& event )
436{
437 if( !m_job )
438 {
439 wxString fn = m_outputFileName->GetValue();
440
441 if( fn.IsEmpty() )
442 {
443 DisplayErrorMessage( this, _( "Output file name cannot be empty." ) );
444 return;
445 }
446
447 auto compressionMode = static_cast<JOB_EXPORT_PCB_ODB::ODB_COMPRESSION>( m_choiceCompress->GetSelection() );
448
449 wxFileName fileName( fn );
450 bool isDirectory = fileName.IsDir();
451 wxString extension = fileName.GetExt();
452
453 if( ( compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE && !isDirectory )
454 || ( compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::ZIP && extension != "zip" )
455 || ( compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ && extension != "tgz" ) )
456 {
457 DisplayErrorMessage( this, _( "The output file name conflicts with the selected compression format." ) );
458 return;
459 }
460 }
461
462 if( m_job )
463 {
464 event.Skip();
465 return;
466 }
467
470 populateJob( job );
471 m_messagesPanel->Clear();
472 REPORTER& reporter = m_messagesPanel->Reporter();
473
475 m_parent->GetToolManager()->GetTool<ZONE_FILLER_TOOL>()->CheckAllZones( this );
476
477 WX_PROGRESS_REPORTER progress( this, _( "Generate ODB++ Files" ), 3, PR_CAN_ABORT );
478 ODB_EXPORT_RESULT result = GenerateODBPPFiles( job, m_parent->GetBoard(), m_parent, &progress, &reporter );
479
480 for( const wxString& output : result.m_outputs )
481 reporter.Report( wxString::Format( _( "Generated %s" ), output ), RPT_SEVERITY_ACTION );
482}
483
484
486{
488
489 if( m_job )
490 populateJob( *m_job );
491
492 return true;
493}
int index
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
PANEL_FAB_EXPORT_CONTENT * m_contentPanel
WX_HTML_REPORT_PANEL * m_messagesPanel
DIALOG_EXPORT_ODBPP_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Export ODB++"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
bool TransferDataFromWindow() override
std::vector< ODB_MATRIX_PREVIEW_ROW > m_previewRows
void onFormatChoice(wxCommandEvent &event) override
std::vector< ODB_LAYER_OVERRIDE > m_layerOverrides
bool TransferDataToWindow() override
void populateJob(JOB_EXPORT_PCB_ODB &aJob) const
void onBrowseClicked(wxCommandEvent &event) override
JOB_EXPORT_PCB_ODB * m_job
DIALOG_EXPORT_ODBPP(PCB_EDIT_FRAME *aParent)
PCB_EDIT_FRAME * m_parent
void harvestLayerOverrides()
Fold the grid's edits for the rows on screen into the layer overrides.
void onOKClick(wxCommandEvent &event) override
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...
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition grid_tricks.h:57
std::optional< wxString > m_sections
Section key that replaces the optional sections of the data set Unset lets the data set select them a...
std::vector< wxString > m_variantNames
VARIANT_PACKAGING m_variantPackaging
ODB_COMPRESSION m_compressionMode
std::vector< ODB_LAYER_OVERRIDE > m_layerOverrides
void SetConfiguredOutputPath(const wxString &aPath)
Sets the configured output path for the job, this path is always saved to file.
Definition job.cpp:156
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
The main frame for Pcbnew.
static int MinPrecision(bool aInches)
Lowest sigfig that still resolves a 0.1 mil grid: 3 decimal places in mm, 4 in inches.
static int MaxPrecision()
Highest sigfig accepted by the exporter, matching the export dialog's spin control.
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
Multi-thread safe progress reporter dialog, intended for use of tasks that parallel reporting back of...
Handle actions specific to filling copper zones.
wxString ResolveProjectPath(const wxString &aPath, const PROJECT *aProject, const std::function< bool(wxString *)> *aTextResolver)
Expand a user-entered file path as ExpandPathVariables() does, then anchor a relative result at the p...
Definition common.cpp:817
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
#define _(s)
static const std::string ArchiveFileExtension
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
void AllowNetworkFileSystems(wxDialog *aDialog)
Configure a file dialog to show network and virtual file systems.
Definition wxgtk/ui.cpp:640
std::string Enum2String(T value)
Definition odb_util.h:279
std::vector< ODB_TYPE > OverridableLayerTypes(PCB_LAYER_ID aLayer)
Matrix types a layer override may assign to aLayer.
Definition odb_util.cpp:110
wxString OdbPreviewReferenceName(const wxString &aReference, const std::vector< ODB_MATRIX_PREVIEW_ROW > &aRows)
Matrix name of the row whose ID is aReference.
wxString UpdateOdbVariantOutputPath(const wxString &aPath, bool aDirectory, bool aSeparate, bool aRemoveAutomaticSuffix)
Add or remove the automatic variant suffix on the file stem or final directory.
std::vector< ODB_MATRIX_PREVIEW_ROW > PreviewOdbMatrix(BOARD *aBoard, const JOB_EXPORT_PCB_ODB &aJob)
Build export matrix rows without layer overrides or writing files.
ODB_EXPORT_RESULT GenerateODBPPFiles(const JOB_EXPORT_PCB_ODB &aJob, BOARD *aBoard, PCB_EDIT_FRAME *aParentFrame, PROGRESS_REPORTER *aProgressReporter, REPORTER *aReporter)
Generate the ODB++ tree or archive and return the write status and the paths written.
ODB_TYPE
Definition odb_util.h:105
see class PGM_BASE
@ RPT_SEVERITY_ACTION
Fields that supply the IPC-2581 BOM section.
One matrix row with its KiCad layer for the dialog's layer grid.
ODB_MATRIX_ROW m_matrix
std::string path
IbisParser parser & reporter
wxString result
Test unit parsing edge cases and error handling.
wxString AddFileExtListToFilter(const std::vector< std::string > &aExts)
Build the wildcard extension file dialog wildcard filter to add to the base message dialog.
#define PR_CAN_ABORT