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 <wx/filedlg.h>
24#include <kiplatform/ui.h>
25
26#include <confirm.h>
27#include <pcb_edit_frame.h>
28#include <pcbnew_settings.h>
29#include <pgm_base.h>
30#include <project.h>
31#include <string_utils.h>
34#include <board.h>
35#include <reporter.h>
40#include <tool/tool_manager.h>
41#include <grid_tricks.h>
42#include <widgets/wx_grid.h>
44
45#include <algorithm>
46
47
48namespace
49{
50enum LAYER_GRID_COL
51{
52 COL_INCLUDE,
53 COL_LAYER,
56 COL_CONTEXT,
57 COL_SPAN
58};
59
60
61wxArrayString overridableTypeNames( PCB_LAYER_ID aLayer )
62{
63 wxArrayString names;
64
65 for( ODB_TYPE type : ODB::OverridableLayerTypes( aLayer ) )
66 names.Add( wxString::FromUTF8( ODB::Enum2String( type ) ) );
67
68 return names;
69}
70
71} // namespace
72
73
75 DIALOG_EXPORT_ODBPP_BASE( aParent ),
76 m_parent( aParent ),
77 m_job( nullptr )
78{
80 SetupStandardButtons( { { wxID_OK, _( "Export" ) }, { wxID_CANCEL, _( "Close" ) } } );
81
82 // DIALOG_SHIM needs a unique hash_key because classname will be the same for both job and
83 // non-job versions.
84 m_hash_key = TO_UTF8( GetTitle() );
85
87 // Now all widgets have the size fixed, call FinishDialogSettings
89}
90
91
93 wxWindow* aParent ) :
94 DIALOG_EXPORT_ODBPP_BASE( aParent ),
95 m_parent( aEditFrame ),
96 m_job( aJob )
97{
98 m_browseButton->Hide();
99 m_messagesPanel->Hide();
100
102
103 // DIALOG_SHIM needs a unique hash_key because classname will be the same for both job and
104 // non-job versions.
105 m_hash_key = TO_UTF8( GetTitle() );
106
108 // Now all widgets have the size fixed, call FinishDialogSettings
110}
111
112
114{
115 m_choiceUnits->Bind( wxEVT_CHOICE,
116 [this]( wxCommandEvent& )
117 {
119 } );
120 m_contentPanel->Configure( FAB_CONTENT_FORMAT::ODBPP, m_parent->GetBoard() );
121 m_contentPanel->SetContentChanged(
122 [this]
123 {
126 } );
127
128 m_layers->PushEventHandler( new GRID_TRICKS( m_layers ) );
129
130 wxGridCellAttr* includeAttr = new wxGridCellAttr;
131 includeAttr->SetRenderer( new wxGridCellBoolRenderer );
132 includeAttr->SetEditor( new wxGridCellBoolEditor );
133 includeAttr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
134 m_layers->SetColAttr( COL_INCLUDE, includeAttr );
135
136 for( int col : { COL_LAYER, COL_CONTEXT, COL_SPAN } )
137 {
138 wxGridCellAttr* readOnlyAttr = new wxGridCellAttr;
139 readOnlyAttr->SetReadOnly();
140 m_layers->SetColAttr( col, readOnlyAttr );
141 }
142}
143
144
146{
147 m_layers->PopEventHandler( true );
148}
149
150
156
157
159{
160 JOB_EXPORT_PCB_ODB defaults;
161 const JOB_EXPORT_PCB_ODB& settings = m_job ? *m_job : defaults;
162
163 m_choiceUnits->SetSelection( settings.m_units == JOB_EXPORT_PCB_FAB::UNITS::MM ? 0 : 1 );
165 m_precision->SetValue( settings.m_precision );
166 m_choiceCompress->SetSelection( static_cast<int>( settings.m_compressionMode ) );
167 m_choiceOrigin->SetSelection( static_cast<int>( settings.m_origin ) );
168 m_productName->SetValue( settings.m_productName );
169 m_refillZones->SetValue( m_job ? settings.m_checkZonesBeforeExport : true );
170 m_contentPanel->SetDataSet( settings.m_dataSet );
171 m_contentPanel->SetSectionKey( settings.m_sections );
172 m_contentPanel->SetBoardMetadata( settings.m_boardMetadata );
173 m_contentPanel->SetNetNames( settings.m_netNames );
174 m_contentPanel->SetVariantNames( settings.m_variantNames );
175 m_contentPanel->SetCombinedVariantOutput( settings.m_variantPackaging
177 IPC2581_BOM_FIELDS fields;
178 fields.m_mfgPn = settings.m_colMfgPn;
179 m_contentPanel->SetBomFields( fields );
181
182 if( !m_job )
183 {
184 if( m_outputFileName->GetValue().IsEmpty() )
185 {
186 wxFileName brdFile( m_parent->GetBoard()->GetFileName() );
187 wxFileName odbFile( brdFile.GetPath(), wxString::Format( wxS( "%s-odb" ), brdFile.GetName() ),
189
190 m_outputFileName->SetValue( odbFile.GetFullPath() );
192 }
193 }
194 else
195 {
196 SetTitle( m_job->GetSettingsDialogTitle() );
197 m_outputFileName->SetValue( m_job->GetConfiguredOutputPath() );
198 }
199
202 return true;
203}
204
205
207{
208 aJob.SetConfiguredOutputPath( m_outputFileName->GetValue() );
209
210 if( &aJob != m_job )
211 aJob.m_filename = m_parent->GetBoard()->GetFileName();
212
213 aJob.m_precision = m_precision->GetValue();
215 aJob.m_compressionMode = static_cast<JOB_EXPORT_PCB_ODB::ODB_COMPRESSION>( m_choiceCompress->GetSelection() );
216 aJob.m_origin = static_cast<JOB_EXPORT_PCB_ODB::ORIGIN>( m_choiceOrigin->GetSelection() );
217 aJob.m_productName = m_productName->GetValue();
218 aJob.m_checkZonesBeforeExport = m_refillZones->GetValue();
219 aJob.m_dataSet = m_contentPanel->GetDataSet();
220 aJob.m_sections = m_contentPanel->GetSectionKey();
221 aJob.m_boardMetadata = m_contentPanel->GetBoardMetadata();
222 aJob.m_netNames = m_contentPanel->GetNetNames();
223 aJob.m_variantNames = m_contentPanel->GetVariantNames();
224 aJob.m_variantPackaging = m_contentPanel->IsCombinedVariantOutput()
227 aJob.m_colMfgPn = m_contentPanel->GetBomFields().m_mfgPn;
229}
230
231
233{
234 JOB_EXPORT_PCB_ODB previewJob;
235 populateJob( previewJob );
236 previewJob.m_layerOverrides.clear();
238 m_previewRows = PreviewOdbMatrix( m_parent->GetBoard(), previewJob );
239 m_layers->ClearRows();
240 m_layers->AppendRows( static_cast<int>( m_previewRows.size() ) );
241 int gridRow = 0;
242
243 for( const ODB_MATRIX_PREVIEW_ROW& row : m_previewRows )
244 {
245 auto it = std::find_if( m_layerOverrides.begin(), m_layerOverrides.end(),
246 [&]( const ODB_LAYER_OVERRIDE& aCandidate )
247 {
248 return row.m_editable && aCandidate.m_layer == row.m_boardLayer;
249 } );
250 const ODB_LAYER_OVERRIDE* override = it == m_layerOverrides.end() ? nullptr : &*it;
251
252 wxString name = override && !override->m_odbName.IsEmpty() ? override->m_odbName : row.m_matrix.m_name;
253 wxString type = override && !override->m_odbType.IsEmpty() ? override->m_odbType : row.m_matrix.m_type;
254 wxString span = row.m_matrix.m_startName;
255
256 if( !row.m_matrix.m_endName.IsEmpty() )
257 span += wxS( " – " ) + row.m_matrix.m_endName;
258
259 // The preview omits overrides, and the exporter only references copper from mask, paste and silk
260 bool keepsRef = type == wxS( "SOLDER_MASK" ) || type == wxS( "SOLDER_PASTE" ) || type == wxS( "SILK_SCREEN" );
261
262 if( !row.m_matrix.m_ref.IsEmpty() && keepsRef )
263 {
264 span += wxS( " REF " ) + OdbPreviewReferenceName( row.m_matrix.m_ref, m_previewRows );
265 }
266
267 bool include = !override || override->m_include;
268 m_layers->SetCellValue( gridRow, COL_INCLUDE, include ? wxString( wxS( "1" ) ) : wxString() );
269 m_layers->SetCellValue( gridRow, COL_LAYER, row.m_displayLayer );
270 m_layers->SetCellValue( gridRow, COL_NAME, name );
271 m_layers->SetCellValue( gridRow, COL_TYPE, type );
272 m_layers->SetCellValue( gridRow, COL_CONTEXT, row.m_matrix.m_context );
273 m_layers->SetCellValue( gridRow, COL_SPAN, span );
274
275 if( row.m_editable )
276 {
277 m_layers->SetCellEditor( gridRow, COL_TYPE,
278 new wxGridCellChoiceEditor( overridableTypeNames( row.m_boardLayer ) ) );
279 }
280 else
281 {
282 for( int col : { COL_INCLUDE, COL_NAME, COL_TYPE } )
283 m_layers->SetReadOnly( gridRow, col );
284 }
285
286 ++gridRow;
287 }
288}
289
290
292{
293 m_layers->CommitPendingChanges( true );
294
295 for( size_t index = 0; index < m_previewRows.size() && index < static_cast<size_t>( m_layers->GetNumberRows() );
296 ++index )
297 {
299
300 if( !row.m_editable )
301 continue;
302
303 int gridRow = static_cast<int>( index );
304 wxString name = m_layers->GetCellValue( gridRow, COL_NAME );
305 wxString type = m_layers->GetCellValue( gridRow, COL_TYPE );
306
307 // Pasted text bypasses the choice editor
308 if( overridableTypeNames( row.m_boardLayer ).Index( type ) == wxNOT_FOUND )
309 type = row.m_matrix.m_type;
310
311 ODB_LAYER_OVERRIDE override;
312 override.m_layer = row.m_boardLayer;
313 override.m_include = !m_layers->GetCellValue( gridRow, COL_INCLUDE ).IsEmpty();
314 override.m_odbName = name == row.m_matrix.m_name ? wxString() : name;
315 override.m_odbType = type == row.m_matrix.m_type ? wxString() : type;
316
317 std::erase_if( m_layerOverrides,
318 [&]( const ODB_LAYER_OVERRIDE& aCandidate )
319 {
320 return aCandidate.m_layer == row.m_boardLayer;
321 } );
322
323 if( !override.m_include || !override.m_odbName.IsEmpty() || !override.m_odbType.IsEmpty() )
324 m_layerOverrides.push_back( override );
325 }
326}
327
328
330{
331 bool separate = m_contentPanel->GetVariantNames().size() > 1 && !m_contentPanel->IsCombinedVariantOutput();
332 bool directory = m_choiceCompress->GetSelection() == static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE );
333 wxString path =
335 m_autoVariantSuffix = separate;
336
337 if( !path.IsEmpty() )
338 m_outputFileName->SetValue( path );
339}
340
341
342void DIALOG_EXPORT_ODBPP::onBrowseClicked( wxCommandEvent& event )
343{
344 // clang-format off
345 wxString filter = _( "zip files" )
347 + _( "tgz files" )
348 + AddFileExtListToFilter( { "tgz" } );
349 // clang-format on
350
351 // Build the absolute path of current output directory to preselect it in the file browser.
352 wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() );
353 wxFileName fn( Prj().AbsolutePath( path ) );
354
355 wxFileName brdFile( m_parent->GetBoard()->GetFileName() );
356
357 wxString fileDialogName( wxString::Format( wxS( "%s-odb" ), brdFile.GetName() ) );
358
359 wxFileDialog dlg( this, _( "Export ODB++ File" ), fn.GetPath(), fileDialogName, filter, wxFD_SAVE );
360
362
363 if( dlg.ShowModal() == wxID_CANCEL )
364 return;
365
366 path = dlg.GetPath();
367
368 fn = wxFileName( path );
369
370 if( fn.GetExt().Lower() == "zip" )
371 {
372 m_choiceCompress->SetSelection( static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::ZIP ) );
373 }
374 else if( fn.GetExt().Lower() == "tgz" )
375 {
376 m_choiceCompress->SetSelection( static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ ) );
377 }
378 else if( path.EndsWith( "/" ) || path.EndsWith( "\\" ) )
379 {
380 m_choiceCompress->SetSelection( static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE ) );
381 }
382 else
383 {
384 DisplayErrorMessage( this, _( "The selected output file name is not a supported archive format." ) );
385 return;
386 }
387
388 m_outputFileName->SetValue( path );
389}
390
391
392void DIALOG_EXPORT_ODBPP::onFormatChoice( wxCommandEvent& event )
393{
395}
396
397
399{
400 wxString fn = m_outputFileName->GetValue();
401
402 wxFileName fileName( fn );
403
404 auto compressionMode = static_cast<JOB_EXPORT_PCB_ODB::ODB_COMPRESSION>( m_choiceCompress->GetSelection() );
405
406 int sepIdx = std::max( fn.Find( '/', true ), fn.Find( '\\', true ) );
407 int dotIdx = fn.Find( '.', true );
408
409 if( fileName.IsDir() )
410 fn = fn.Mid( 0, sepIdx );
411 else if( sepIdx < dotIdx )
412 fn = fn.Mid( 0, dotIdx );
413
414 switch( compressionMode )
415 {
417 fn = fn + '.' + FILEEXT::ArchiveFileExtension;
418 break;
420 fn += ".tgz";
421 break;
423 fn = wxFileName( fn, "" ).GetFullPath();
424 break;
425 default:
426 break;
427 };
428
429 m_outputFileName->SetValue( fn );
431}
432
433
434void DIALOG_EXPORT_ODBPP::onOKClick( wxCommandEvent& event )
435{
436 if( !m_job )
437 {
438 wxString fn = m_outputFileName->GetValue();
439
440 if( fn.IsEmpty() )
441 {
442 DisplayErrorMessage( this, _( "Output file name cannot be empty." ) );
443 return;
444 }
445
446 auto compressionMode = static_cast<JOB_EXPORT_PCB_ODB::ODB_COMPRESSION>( m_choiceCompress->GetSelection() );
447
448 wxFileName fileName( fn );
449 bool isDirectory = fileName.IsDir();
450 wxString extension = fileName.GetExt();
451
452 if( ( compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE && !isDirectory )
453 || ( compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::ZIP && extension != "zip" )
454 || ( compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ && extension != "tgz" ) )
455 {
456 DisplayErrorMessage( this, _( "The output file name conflicts with the selected compression format." ) );
457 return;
458 }
459 }
460
461 if( m_job )
462 {
463 event.Skip();
464 return;
465 }
466
469 populateJob( job );
470 m_messagesPanel->Clear();
471 REPORTER& reporter = m_messagesPanel->Reporter();
472
474 m_parent->GetToolManager()->GetTool<ZONE_FILLER_TOOL>()->CheckAllZones( this );
475
476 WX_PROGRESS_REPORTER progress( this, _( "Generate ODB++ Files" ), 3, PR_CAN_ABORT );
477 ODB_EXPORT_RESULT result = GenerateODBPPFiles( job, m_parent->GetBoard(), m_parent, &progress, &reporter );
478
479 for( const wxString& output : result.m_outputs )
480 reporter.Report( wxString::Format( _( "Generated %s" ), output ), RPT_SEVERITY_ACTION );
481}
482
483
485{
487
488 if( m_job )
489 populateJob( *m_job );
490
491 return true;
492}
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:157
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.
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:776
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:533
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