KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_import_graphics.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) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
22
24#include <dialog_map_layers.h>
27#include <base_units.h>
28#include <bitmap_tracing.h>
29#include <cmath>
30#include <board.h>
31#include <kiface_base.h>
32#include <locale_io.h>
35#include <bitmaps.h>
37#include <footprint.h>
38#include <wx/filedlg.h>
39#include <wx/msgdlg.h>
40#include <kiplatform/ui.h>
41
42#include <memory>
43
45
46
47static PCB_LAYER_ID getAutoMappedLayer( const wxString& aSourceLayer, const LSET& aPermittedLayers )
48{
49 for( PCB_LAYER_ID layer : aPermittedLayers.UIOrder() )
50 {
51 if( aSourceLayer == LayerName( layer ) || aSourceLayer == LSET::Name( layer ) )
52 return layer;
53 }
54
56}
57
58
59static std::vector<INPUT_LAYER_DESC> buildDxfLayerDescriptions( const std::vector<wxString>& aSourceLayers,
60 const LSET& aPermittedLayers )
61{
62 std::vector<INPUT_LAYER_DESC> layerDescriptions;
63
64 for( const wxString& sourceLayer : aSourceLayers )
65 {
67 desc.Name = sourceLayer;
68 desc.PermittedLayers = aPermittedLayers;
69 desc.AutoMapLayer = getAutoMappedLayer( sourceLayer, aPermittedLayers );
70 desc.Required = false;
71
72 layerDescriptions.push_back( desc );
73 }
74
75 return layerDescriptions;
76}
77
78
80{
81 if( aFrame && aFrame->GetBoard() )
82 return aFrame->GetBoard()->GetEnabledLayers();
83
84 return LSET::AllLayersMask();
85}
86
87
90 m_parent( aParent ),
91 m_xOrigin( aParent, nullptr, m_xCtrl, nullptr ),
92 m_yOrigin( aParent, m_yLabel, m_yCtrl, m_yUnits ),
97 m_sourceSizeMM( 0, 0 ),
98 m_xScale( 1.0 ),
99 m_yScale( 1.0 )
100{
101 // The SVG import has currently a flaw: all SVG shapes are imported as curves and
102 // converted to a lot of segments. A better approach is to convert to polylines
103 // (not yet existing in Pcbnew) and keep arcs and circles as primitives (not yet
104 // possible with tinysvg library).
105
106 m_importer = std::make_unique<GRAPHICS_IMPORTER_PCBNEW>( aParent->GetModel() );
107 m_gfxImportMgr = std::make_unique<GRAPHICS_IMPORT_MGR>();
108
109 // Keyed apart from the schematic dialog, which has the same title and another layout.
110 m_hash_key = "DIALOG_IMPORT_GRAPHICS";
111
112 // Configure the layers list selector
113 m_SelLayerBox->SetLayersHotkeys( false ); // Do not display hotkeys
114 m_SelLayerBox->SetBoardFrame( m_parent );
115 m_SelLayerBox->Resync();
116
118
119 // The chosen unit decides what a DXF file's numbers mean, so its size has to be read again.
120 m_dxfUnitsChoice->Bind( wxEVT_CHOICE,
121 [this]( wxCommandEvent& aEvent )
122 {
123 m_sourcePath.Clear();
124
125 wxCommandEvent dummy;
127 aEvent.Skip();
128 } );
129
131
132 wxCommandEvent dummy;
134
137
138 GetSizer()->Fit( this );
139 GetSizer()->SetSizeHints( this );
140 Centre();
141}
142
143
144void DIALOG_IMPORT_GRAPHICS::SetFilenameOverride( const wxString& aFilenameOverride )
145{
146 m_filenameOverride = aFilenameOverride;
147}
148
149
151{
152 DIALOG_SHIM::TransferDataToWindow();
153
154 // We have to set the filename field value here, otherwise it gets overwritten by state loading
155 if( !m_filenameOverride.IsEmpty() )
157
158 // The scale comes back from the last run without an event too, so the size fields have
159 // to be told what it is before they are filled in.
161
162 if( scale > 0.0 )
163 {
164 m_xScale = scale;
165 m_yScale = scale;
166 }
167
168 // A filename restored from the last run arrives without an event, so sync to it here.
169 wxCommandEvent dummy;
171
172 return true;
173}
174
175
176void DIALOG_IMPORT_GRAPHICS::onFilenameChanged( wxCommandEvent& event )
177{
179
180 bool enableDXFControls = true;
181 wxString ext = wxFileName( m_textCtrlFileName->GetValue() ).GetExt();
182
183 if( std::unique_ptr<GRAPHICS_IMPORT_PLUGIN> plugin = m_gfxImportMgr->GetPluginByExt( ext ) )
184 enableDXFControls = dynamic_cast<DXF_IMPORT_PLUGIN*>( plugin.get() ) != nullptr;
185
186 m_defaultLineWidth.Enable( enableDXFControls );
187
188 m_dxfUnitsLabel->Enable( enableDXFControls );
189 m_dxfUnitsChoice->Enable( enableDXFControls );
190
191 m_radioBtnMapLayers->Enable( enableDXFControls );
192
193 if( !enableDXFControls && m_radioBtnMapLayers->GetValue() )
194 m_radioBtnSingleLayer->SetValue( true );
195
198}
199
200
202{
203 const wxString path = m_textCtrlFileName->GetValue();
204
205 if( path == m_sourcePath )
206 return;
207
209 m_previewSource.Destroy();
210 m_sourceSizeMM = VECTOR2D( 0, 0 );
211
212 std::unique_ptr<GRAPHICS_IMPORT_PLUGIN> plugin = m_gfxImportMgr->GetPluginByExt( wxFileName( path ).GetExt() );
213
214 if( !plugin )
215 return;
216
217 if( DXF_IMPORT_PLUGIN* dxfPlugin = dynamic_cast<DXF_IMPORT_PLUGIN*>( plugin.get() ) )
218 dxfPlugin->SetUnit( DxfImportUnitFromChoice( m_dxfUnitsChoice->GetSelection() ) );
219
220 wxLogNull doNotLog;
221 LOCALE_IO dummy; // Ensure floats can be read.
222
223 if( !plugin->Load( path ) )
224 return;
225
226 const VECTOR2D size( plugin->GetImageWidth(), plugin->GetImageHeight() );
227
228 // A file holding no geometry leaves its bounding box unset.
229 if( std::isfinite( size.x ) && std::isfinite( size.y ) && size.x > 0 && size.y > 0 )
230 m_sourceSizeMM = size;
231
232 // The image data is shared, so this keeps the pixels without reading the file again.
233 if( BITMAP_IMPORT_PLUGIN* bitmapPlugin = dynamic_cast<BITMAP_IMPORT_PLUGIN*>( plugin.get() ) )
234 m_previewSource = bitmapPlugin->GetImage();
235}
236
237
239{
240 const bool known = m_sourceSizeMM.x > 0 && m_sourceSizeMM.y > 0;
241
242 // Without a source size there is nothing to turn a size into a scale.
243 m_importWidth.Enable( known );
244 m_importHeight.Enable( known );
245
246 // Scaling the axes apart would turn an imported circle into an ellipse, which the
247 // importer has no way to report, so only a traced image may be stretched.
248 const bool stretchable = known && m_previewSource.IsOk();
249
250 m_aspectRatioCheckbox->Enable( stretchable );
251
252 if( !stretchable )
253 m_aspectRatioCheckbox->SetValue( true );
254
255 if( m_aspectRatioCheckbox->IsChecked() )
257
259}
260
261
263{
264 if( m_sourceSizeMM.x <= 0 || m_sourceSizeMM.y <= 0 )
265 {
266 m_importWidth.ChangeValue( wxEmptyString );
267 m_importHeight.ChangeValue( wxEmptyString );
268 return;
269 }
270
271 // ChangeValue(), so that writing one field does not look like the user typing in it.
272 m_importWidth.ChangeValue( pcbIUScale.mmToIU( m_sourceSizeMM.x * m_xScale ) );
273 m_importHeight.ChangeValue( pcbIUScale.mmToIU( m_sourceSizeMM.y * m_yScale ) );
274}
275
276
278{
279 if( m_sourceSizeMM.x <= 0 || m_sourceSizeMM.y <= 0 )
280 return;
281
282 const double width = pcbIUScale.IUTomm( m_importWidth.GetIntValue() );
283 const double height = pcbIUScale.IUTomm( m_importHeight.GetIntValue() );
284 const bool locked = m_aspectRatioCheckbox->IsChecked();
285
286 // A half typed number is not a size to work from.
287 if( aFromWidth && width > 0 )
288 m_xScale = width / m_sourceSizeMM.x;
289 else if( !aFromWidth && height > 0 )
290 m_yScale = height / m_sourceSizeMM.y;
291 else
292 return;
293
294 if( locked )
295 {
296 if( aFromWidth )
298 else
300 }
301
302 m_importScaleCtrl->ChangeValue( wxString::Format( wxT( "%g" ), m_xScale ) );
303
304 // The field the user is typing in is left alone; only the one that follows is written.
305 if( locked && aFromWidth )
306 m_importHeight.ChangeValue( pcbIUScale.mmToIU( m_sourceSizeMM.y * m_yScale ) );
307 else if( locked )
308 m_importWidth.ChangeValue( pcbIUScale.mmToIU( m_sourceSizeMM.x * m_xScale ) );
309}
310
311
312void DIALOG_IMPORT_GRAPHICS::onScaleChanged( wxCommandEvent& event )
313{
315
316 if( scale <= 0.0 )
317 return;
318
319 m_xScale = scale;
320 m_yScale = scale;
321
323}
324
325
326void DIALOG_IMPORT_GRAPHICS::onWidthChanged( wxCommandEvent& event )
327{
328 scaleFromSize( true );
329}
330
331
332void DIALOG_IMPORT_GRAPHICS::onHeightChanged( wxCommandEvent& event )
333{
334 scaleFromSize( false );
335}
336
337
339{
340 // Locking the ratio again keeps the width and brings the height back into step.
341 if( m_aspectRatioCheckbox->IsChecked() )
342 {
345 }
346}
347
348
350{
351 wxString ext = wxFileName( m_textCtrlFileName->GetValue() ).GetExt();
352 bool isImage = false;
353
354 if( std::unique_ptr<GRAPHICS_IMPORT_PLUGIN> plugin = m_gfxImportMgr->GetPluginByExt( ext ) )
355 isImage = dynamic_cast<BITMAP_IMPORT_PLUGIN*>( plugin.get() ) != nullptr;
356
357 // The whole panel, since a hidden wxSlider still shows its tick labels on macOS.
358 m_imagePanel->Show( isImage );
359
361
362 Layout();
363 GetSizer()->Fit( this );
364}
365
366
368{
369 if( !m_previewSource.IsOk() )
370 {
371 m_previewBitmap->SetBitmap( wxNullBitmap );
372 return;
373 }
374
375 double threshold = (double) m_thresholdSlider->GetValue() / m_thresholdSlider->GetMax();
376
377 m_previewBitmap->SetBitmap( wxBitmap( MakeBilevelPreview( m_previewSource, threshold, m_cbInvertColors->IsChecked(),
378 m_previewBitmap->GetMinSize() ) ) );
379}
380
381
383{
385}
386
387
388void DIALOG_IMPORT_GRAPHICS::onInvertChanged( wxCommandEvent& event )
389{
391}
392
393
394void DIALOG_IMPORT_GRAPHICS::onBrowseFiles( wxCommandEvent& event )
395{
396 wxString path;
397 wxString filename = m_textCtrlFileName->GetValue();
398
399 if( !filename.IsEmpty() )
400 {
401 wxFileName fn( filename );
402 path = fn.GetPath();
403 filename = fn.GetFullName();
404 }
405
406 // Generate the list of handled file formats
407 wxString wildcardsDesc;
408 wxString allWildcards;
409
410 for( GRAPHICS_IMPORT_MGR::GFX_FILE_T pluginType : m_gfxImportMgr->GetImportableFileTypes() )
411 {
412 std::unique_ptr<GRAPHICS_IMPORT_PLUGIN> plugin = m_gfxImportMgr->GetPlugin( pluginType );
413 const std::vector<std::string> extensions = plugin->GetFileExtensions();
414
415 wildcardsDesc += wxT( "|" ) + plugin->GetName() + AddFileExtListToFilter( extensions );
416 allWildcards += plugin->GetWildcards() + wxT( ";" );
417 }
418
419 wildcardsDesc = _( "All supported formats" ) + wxT( "|" ) + allWildcards + wildcardsDesc;
420
421 wxFileDialog dlg( m_parent, _( "Import Graphics" ), path, filename, wildcardsDesc,
422 wxFD_OPEN | wxFD_FILE_MUST_EXIST );
423
425
426 if( dlg.ShowModal() == wxID_OK && !dlg.GetPath().IsEmpty() )
427 m_textCtrlFileName->SetValue( dlg.GetPath() );
428}
429
430
432{
433 if( !wxDialog::TransferDataFromWindow() )
434 return false;
435
436 if( m_textCtrlFileName->GetValue().IsEmpty() )
437 {
438 wxMessageBox( _( "Please select a file to import." ) );
439 return false;
440 }
441
442 bool useDxfLayerMapping = m_radioBtnMapLayers->IsEnabled() && m_radioBtnMapLayers->GetValue();
443 bool useSingleLayer = m_radioBtnSingleLayer->GetValue();
444
445 if( useSingleLayer && m_SelLayerBox->GetLayerSelection() < 0 )
446 {
447 wxMessageBox( _( "Please select a valid layer." ) );
448 return false;
449 }
450
451 PCBNEW_SETTINGS* cfg = m_parent->GetPcbNewSettings();
452 wxString ext = wxFileName( m_textCtrlFileName->GetValue() ).GetExt();
454
455 if( scale <= 0.0 )
456 {
457 wxMessageBox( _( "Import scale must be a positive number." ) );
458 return false;
459 }
460
461 double xscale = m_xScale;
462 double yscale = m_yScale;
463
465 xscale *= -1.0;
466
468 yscale *= -1.0;
469
470 VECTOR2D origin( m_xOrigin.GetDoubleValue() / xscale, m_yOrigin.GetDoubleValue() / yscale );
471
472 switch( m_originCtrl->GetSelection() )
473 {
474 case 0: break;
475 case 1: origin += m_parent->GetAuxOrigin(); break; // Drill/place file origin
476 case 2: origin += m_parent->GetGridOrigin(); break;
477 case 3: origin += m_parent->GetUserOrigin(); break;
478 }
479
480 if( std::unique_ptr<GRAPHICS_IMPORT_PLUGIN> plugin = m_gfxImportMgr->GetPluginByExt( ext ) )
481 {
482 DXF_IMPORT_PLUGIN* dxfPlugin = dynamic_cast<DXF_IMPORT_PLUGIN*>( plugin.get() );
483
484 if( dxfPlugin )
485 {
486 dxfPlugin->SetUnit( DxfImportUnitFromChoice( m_dxfUnitsChoice->GetSelection() ) );
487 m_importer->SetLineWidthMM( pcbIUScale.IUTomm( m_defaultLineWidth.GetIntValue() ) );
488 }
489 else
490 {
491 m_importer->SetLineWidthMM( 0.0 );
492 }
493
494 if( BITMAP_IMPORT_PLUGIN* bitmapPlugin = dynamic_cast<BITMAP_IMPORT_PLUGIN*>( plugin.get() ) )
495 {
496 bitmapPlugin->SetThreshold( (double) m_thresholdSlider->GetValue() / m_thresholdSlider->GetMax() );
497 bitmapPlugin->SetInvert( m_cbInvertColors->IsChecked() );
498 }
499
500 m_importer->SetPlugin( std::move( plugin ) );
501 m_importer->ClearLayerMap();
502
503 if( useSingleLayer )
504 m_importer->SetLayer( PCB_LAYER_ID( m_SelLayerBox->GetLayerSelection() ) );
505 else
506 m_importer->SetLayer( m_parent->GetActiveLayer() );
507
508 m_importer->SetImportOffsetMM( origin * pcbIUScale.IUTomm( 1 ) );
509
510 LOCALE_IO dummy; // Ensure floats can be read.
511
512 if( m_importer->Load( m_textCtrlFileName->GetValue() ) )
513 {
514 if( useDxfLayerMapping && dxfPlugin )
515 {
516 std::vector<wxString> sourceLayers = dxfPlugin->GetSourceLayers();
517
518 if( !sourceLayers.empty() )
519 {
520 LSET permittedLayers = getPermittedImportLayers( m_parent );
521 std::map<wxString, PCB_LAYER_ID> layerMap = DIALOG_MAP_LAYERS::RunModal(
522 this, buildDxfLayerDescriptions( sourceLayers, permittedLayers ) );
523
524 if( layerMap.empty() )
525 return false;
526
527 m_importer->SetLayerMap( layerMap );
528 }
529 }
530
531 m_importer->Import( VECTOR2D( m_xScale, m_yScale ) );
532 }
533
534 // Get warning messages:
535 wxString warnings = m_importer->GetMessages();
536
537 // This isn't a fatal error so allow the dialog to close with wxID_OK.
538 if( !warnings.empty() )
539 {
540 HTML_MESSAGE_BOX dlg( this, _( "Warning" ) );
541 dlg.MessageSet( _( "Items in the imported file could not be handled properly." ) );
542 warnings.Replace( wxT( "\n" ), wxT( "<br/>" ) );
543 dlg.AddHTML_Text( warnings );
544 dlg.ShowModal();
545 }
546
547 return true;
548 }
549 else
550 {
551 wxMessageBox( _( "There is no plugin to handle this file type." ) );
552 return false;
553 }
554}
555
556
557void DIALOG_IMPORT_GRAPHICS::onUpdateUI( wxUpdateUIEvent& event )
558{
559 m_xOrigin.Enable( m_placeAtCheckbox->GetValue() );
560 m_yOrigin.Enable( m_placeAtCheckbox->GetValue() );
561 m_originLabel->Enable( m_placeAtCheckbox->GetValue() );
562 m_originCtrl->Enable( m_placeAtCheckbox->GetValue() );
563
564 m_tolerance.Enable( m_rbFixDiscontinuities->GetValue() );
565
566 m_SelLayerBox->Enable( m_radioBtnSingleLayer->GetValue() );
567}
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
wxImage MakeBilevelPreview(const wxImage &aImage, double aThreshold, bool aInvert, const wxSize &aRoom)
Produce the black and white view of an image, shrunk to fit a preview area.
Import a raster image by tracing its dark areas into filled polygons.
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1174
DIALOG_IMPORT_GRAPHICS_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Import Graphics File"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
void onHeightChanged(wxCommandEvent &event) override
std::unique_ptr< GRAPHICS_IMPORT_MGR > m_gfxImportMgr
void updateImageControls()
Show the image controls only when the chosen file is a raster image.
std::unique_ptr< GRAPHICS_IMPORTER_PCBNEW > m_importer
void onInvertChanged(wxCommandEvent &event) override
void onLockAspectChanged(wxCommandEvent &event) override
void onThresholdChanged(wxScrollEvent &event) override
void updateSizeControls()
Offer the size fields only when the source has a size to scale.
void scaleFromSize(bool aFromWidth)
Work the scale out from a size the user typed.
void loadSourceFile()
Read the chosen file, which is where the preview and the source size come from.
void onBrowseFiles(wxCommandEvent &event) override
void onFilenameChanged(wxCommandEvent &event) override
wxString m_sourcePath
The file the source size and the preview were taken from.
void SetFilenameOverride(const wxString &aFilenameOverride)
Set the filename override to be applied in TransferDataToWindow.
void sizeFromScale()
Write the scale into the size fields.
void onWidthChanged(wxCommandEvent &event) override
void onScaleChanged(wxCommandEvent &event) override
VECTOR2D m_sourceSizeMM
Size of the source file's contents in mm, (0,0) when it has none to report.
void onUpdateUI(wxUpdateUIEvent &event) override
DIALOG_IMPORT_GRAPHICS(PCB_BASE_FRAME *aParent)
static std::map< wxString, PCB_LAYER_ID > RunModal(wxWindow *aParent, const std::vector< INPUT_LAYER_DESC > &aLayerDesc)
Create and show a dialog (modal) and returns the data from it after completion.
void SetInitialFocus(wxWindow *aWindow)
Sets the window (usually a wxTextCtrl) that should be focused when the dialog is shown.
Definition dialog_shim.h:94
void SetupStandardButtons(std::map< int, wxString > aLabels={})
std::string m_hash_key
int ShowModal() override
std::vector< wxString > GetSourceLayers() const
void SetUnit(DXF_IMPORT_UNITS aUnit)
Set the default units when importing DXFs.
GFX_FILE_T
List of handled file types.
void MessageSet(const wxString &message)
Add a message (in bold) to message list.
void AddHTML_Text(const wxString &message)
Add HTML text (without any change) to message list.
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
LSEQ UIOrder() const
Return the copper, technical and user layers in the order shown in layer widget.
Definition lset.cpp:739
static const LSET & AllLayersMask()
Definition lset.cpp:637
static wxString Name(PCB_LAYER_ID aLayerId)
Return the fixed name association with aLayerId.
Definition lset.cpp:184
DISPLAY_OPTIONS m_Display
Base PCB main window class for Pcbnew, Gerbview, and CvPcb footprint viewer.
BOARD * GetBoard() const
virtual BOARD_ITEM_CONTAINER * GetModel() const =0
static LSET getPermittedImportLayers(PCB_BASE_FRAME *aFrame)
static PCB_LAYER_ID getAutoMappedLayer(const wxString &aSourceLayer, const LSET &aPermittedLayers)
static std::vector< INPUT_LAYER_DESC > buildDxfLayerDescriptions(const std::vector< wxString > &aSourceLayers, const LSET &aPermittedLayers)
wxArrayString GetDxfImportUnitChoices()
DXF_IMPORT_UNITS DxfImportUnitFromChoice(int aSelection)
#define _(s)
wxString LayerName(int aLayer)
Returns the default display name for a given layer.
Definition layer_id.cpp:31
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ UNDEFINED_LAYER
Definition layer_ids.h:57
KICOMMON_API double DoubleValueFromString(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, const wxString &aTextValue, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
Convert aTextValue to a double.
void AllowNetworkFileSystems(wxDialog *aDialog)
Configure a file dialog to show network and virtual file systems.
Definition wxgtk/ui.cpp:640
static const std::vector< std::string > extensions
Definition plugin.cpp:63
const int scale
std::vector< FAB_LAYER_COLOR > dummy
Describes an imported layer and how it could be mapped to KiCad Layers.
PCB_LAYER_ID AutoMapLayer
Best guess as to what the equivalent KiCad layer might be.
bool Required
Should we require the layer to be assigned?
LSET PermittedLayers
KiCad layers that the imported layer can be mapped onto.
wxString Name
Imported layer name as displayed in original application.
std::string path
VECTOR2< double > VECTOR2D
Definition vector2d.h:707
wxString AddFileExtListToFilter(const std::vector< std::string > &aExts)
Build the wildcard extension file dialog wildcard filter to add to the base message dialog.
Definition of file extensions used in Kicad.