KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_import_gfx.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
23#include <eda_item.h>
26#include <bitmap_tracing.h>
27#include <cmath>
28
29#include <base_units.h>
30#include <kiface_base.h>
31#include <locale_io.h>
32#include <bitmaps.h>
33#include <string_utils.h>
35
36#include <eda_draw_frame.h>
37
38#include <wx/filedlg.h>
39#include <wx/msgdlg.h>
40#include <kiplatform/ui.h>
41
44
45#include <memory>
46
47// Static members of DIALOG_IMPORT_GFX, to remember the user's choices during the session
49
50
51DIALOG_IMPORT_GFX::DIALOG_IMPORT_GFX( EDA_DRAW_FRAME* aParent, std::unique_ptr<GRAPHICS_IMPORTER> aImporter,
52 bool aAllowGrouping ) :
53 DIALOG_IMPORT_GFX_BASE( aParent ),
54 m_parent( aParent ),
55 m_importer( std::move( aImporter ) ),
56 m_allowGrouping( aAllowGrouping ),
57 m_xOrigin( aParent, m_xLabel, m_xCtrl, m_xUnits ),
58 m_yOrigin( aParent, m_yLabel, m_yCtrl, m_yUnits ),
62 m_sourceSizeMM( 0, 0 ),
63 m_xScale( 1.0 ),
64 m_yScale( 1.0 )
65{
67
68 // The chosen unit decides what a DXF file's numbers mean, so its size has to be read again.
69 m_choiceDxfUnits->Bind( wxEVT_CHOICE,
70 [this]( wxCommandEvent& aEvent )
71 {
72 m_sourcePath.Clear();
73
74 wxCommandEvent dummy;
76 aEvent.Skip();
77 } );
79
80 // An editor with nothing to group into has no use for the option.
81 if( !m_allowGrouping )
82 m_cbGroupItems->Hide();
83
84 m_gfxImportMgr = std::make_unique<GRAPHICS_IMPORT_MGR>();
85
86 // Keyed apart from the board dialog, which has the same title and another layout, and keyed
87 // per editor on top of that: the origin and the line width are remembered as internal units,
88 // which are not the same size in a schematic as in a drawing sheet.
89 m_hash_key = std::string( "DIALOG_IMPORT_GFX_" ) + TO_UTF8( aParent->GetName() );
90
91 wxCommandEvent dummy;
93
96
97 GetSizer()->Fit( this );
98 GetSizer()->SetSizeHints( this );
99 Centre();
100}
101
102
103void DIALOG_IMPORT_GFX::SetFilenameOverride( const wxString& aFilenameOverride )
104{
105 m_filenameOverride = aFilenameOverride;
106}
107
108
110{
111 DIALOG_SHIM::TransferDataToWindow();
112
113 // We have to set the filename field value here, otherwise it gets overwritten by state loading
114 if( !m_filenameOverride.IsEmpty() )
116
117 // The scale comes back from the last run without an event too, so the size fields have
118 // to be told what it is before they are filled in.
120
121 if( scale > 0.0 )
122 {
123 m_xScale = scale;
124 m_yScale = scale;
125 }
126
127 // A filename restored from the last run arrives without an event, so sync to it here.
128 wxCommandEvent dummy;
130
131 return true;
132}
133
134
135void DIALOG_IMPORT_GFX::onFilenameChanged( wxCommandEvent& event )
136{
138
139 bool enableDXFControls = true;
140 wxString ext = wxFileName( m_textCtrlFileName->GetValue() ).GetExt();
141
142 if( std::unique_ptr<GRAPHICS_IMPORT_PLUGIN> plugin = m_gfxImportMgr->GetPluginByExt( ext ) )
143 enableDXFControls = dynamic_cast<DXF_IMPORT_PLUGIN*>( plugin.get() ) != nullptr;
144
145 m_defaultLineWidth.Enable( enableDXFControls );
146
147 m_staticTextLineWidth1->Enable( enableDXFControls );
148 m_choiceDxfUnits->Enable( enableDXFControls );
149
152}
153
154
156{
157 const wxString path = m_textCtrlFileName->GetValue();
158
159 if( path == m_sourcePath )
160 return;
161
163 m_previewSource.Destroy();
164 m_sourceSizeMM = VECTOR2D( 0, 0 );
165
166 std::unique_ptr<GRAPHICS_IMPORT_PLUGIN> plugin = m_gfxImportMgr->GetPluginByExt( wxFileName( path ).GetExt() );
167
168 if( !plugin )
169 return;
170
171 if( DXF_IMPORT_PLUGIN* dxfPlugin = dynamic_cast<DXF_IMPORT_PLUGIN*>( plugin.get() ) )
172 dxfPlugin->SetUnit( DxfImportUnitFromChoice( m_choiceDxfUnits->GetSelection() ) );
173
174 wxLogNull doNotLog;
175 LOCALE_IO dummy; // Ensure floats can be read.
176
177 if( !plugin->Load( path ) )
178 return;
179
180 const VECTOR2D size( plugin->GetImageWidth(), plugin->GetImageHeight() );
181
182 // A file holding no geometry leaves its bounding box unset.
183 if( std::isfinite( size.x ) && std::isfinite( size.y ) && size.x > 0 && size.y > 0 )
184 m_sourceSizeMM = size;
185
186 // The image data is shared, so this keeps the pixels without reading the file again.
187 if( BITMAP_IMPORT_PLUGIN* bitmapPlugin = dynamic_cast<BITMAP_IMPORT_PLUGIN*>( plugin.get() ) )
188 m_previewSource = bitmapPlugin->GetImage();
189}
190
191
193{
194 const bool known = m_sourceSizeMM.x > 0 && m_sourceSizeMM.y > 0;
195
196 // Without a source size there is nothing to turn a size into a scale.
197 m_importWidth.Enable( known );
198 m_importHeight.Enable( known );
199
200 // Scaling the axes apart would turn an imported circle into an ellipse, which the
201 // importer has no way to report, so only a traced image may be stretched.
202 const bool stretchable = known && m_previewSource.IsOk();
203
204 m_aspectRatioCheckbox->Enable( stretchable );
205
206 if( !stretchable )
207 m_aspectRatioCheckbox->SetValue( true );
208
209 if( m_aspectRatioCheckbox->IsChecked() )
211
213}
214
215
217{
218 if( m_sourceSizeMM.x <= 0 || m_sourceSizeMM.y <= 0 )
219 {
220 m_importWidth.ChangeValue( wxEmptyString );
221 m_importHeight.ChangeValue( wxEmptyString );
222 return;
223 }
224
225 // ChangeValue(), so that writing one field does not look like the user typing in it.
226 m_importWidth.ChangeValue( m_parent->GetIuScale().mmToIU( m_sourceSizeMM.x * m_xScale ) );
227 m_importHeight.ChangeValue( m_parent->GetIuScale().mmToIU( m_sourceSizeMM.y * m_yScale ) );
228}
229
230
231void DIALOG_IMPORT_GFX::scaleFromSize( bool aFromWidth )
232{
233 if( m_sourceSizeMM.x <= 0 || m_sourceSizeMM.y <= 0 )
234 return;
235
236 const double width = m_parent->GetIuScale().IUTomm( m_importWidth.GetValue() );
237 const double height = m_parent->GetIuScale().IUTomm( m_importHeight.GetValue() );
238 const bool locked = m_aspectRatioCheckbox->IsChecked();
239
240 // A half typed number is not a size to work from.
241 if( aFromWidth && width > 0 )
242 m_xScale = width / m_sourceSizeMM.x;
243 else if( !aFromWidth && height > 0 )
244 m_yScale = height / m_sourceSizeMM.y;
245 else
246 return;
247
248 if( locked )
249 {
250 if( aFromWidth )
252 else
254 }
255
256 m_importScaleCtrl->ChangeValue( wxString::Format( wxT( "%g" ), m_xScale ) );
257
258 // The field the user is typing in is left alone; only the one that follows is written.
259 if( locked && aFromWidth )
260 m_importHeight.ChangeValue( m_parent->GetIuScale().mmToIU( m_sourceSizeMM.y * m_yScale ) );
261 else if( locked )
262 m_importWidth.ChangeValue( m_parent->GetIuScale().mmToIU( m_sourceSizeMM.x * m_xScale ) );
263}
264
265
266void DIALOG_IMPORT_GFX::onScaleChanged( wxCommandEvent& event )
267{
269
270 if( scale <= 0.0 )
271 return;
272
273 m_xScale = scale;
274 m_yScale = scale;
275
277}
278
279
280void DIALOG_IMPORT_GFX::onWidthChanged( wxCommandEvent& event )
281{
282 scaleFromSize( true );
283}
284
285
286void DIALOG_IMPORT_GFX::onHeightChanged( wxCommandEvent& event )
287{
288 scaleFromSize( false );
289}
290
291
292void DIALOG_IMPORT_GFX::onLockAspectChanged( wxCommandEvent& event )
293{
294 // Locking the ratio again keeps the width and brings the height back into step.
295 if( m_aspectRatioCheckbox->IsChecked() )
296 {
299 }
300}
301
302
304{
305 wxString ext = wxFileName( m_textCtrlFileName->GetValue() ).GetExt();
306 bool isImage = false;
307
308 if( std::unique_ptr<GRAPHICS_IMPORT_PLUGIN> plugin = m_gfxImportMgr->GetPluginByExt( ext ) )
309 isImage = dynamic_cast<BITMAP_IMPORT_PLUGIN*>( plugin.get() ) != nullptr;
310
311 // The whole panel, since a hidden wxSlider still shows its tick labels on macOS.
312 m_imagePanel->Show( isImage );
313
315
316 Layout();
317 GetSizer()->Fit( this );
318}
319
320
322{
323 if( !m_previewSource.IsOk() )
324 {
325 m_previewBitmap->SetBitmap( wxNullBitmap );
326 return;
327 }
328
329 double threshold = (double) m_thresholdSlider->GetValue() / m_thresholdSlider->GetMax();
330
331 m_previewBitmap->SetBitmap( wxBitmap( MakeBilevelPreview( m_previewSource, threshold, m_cbInvertColors->IsChecked(),
332 m_previewBitmap->GetMinSize() ) ) );
333}
334
335
336void DIALOG_IMPORT_GFX::onThresholdChanged( wxScrollEvent& event )
337{
339}
340
341
342void DIALOG_IMPORT_GFX::onInvertChanged( wxCommandEvent& event )
343{
345}
346
347
348void DIALOG_IMPORT_GFX::onBrowseFiles( wxCommandEvent& event )
349{
350 wxString path;
351 wxString filename = m_textCtrlFileName->GetValue();
352
353 if( !filename.IsEmpty() )
354 {
355 wxFileName fn( filename );
356 path = fn.GetPath();
357 filename = fn.GetFullName();
358 }
359
360 // Generate the list of handled file formats
361 wxString wildcardsDesc;
362 wxString allWildcards;
363
364 for( GRAPHICS_IMPORT_MGR::GFX_FILE_T pluginType : m_gfxImportMgr->GetImportableFileTypes() )
365 {
366 std::unique_ptr<GRAPHICS_IMPORT_PLUGIN> plugin = m_gfxImportMgr->GetPlugin( pluginType );
367 const std::vector<std::string> extensions = plugin->GetFileExtensions();
368
369 wildcardsDesc += wxT( "|" ) + plugin->GetName() + AddFileExtListToFilter( extensions );
370 allWildcards += plugin->GetWildcards() + wxT( ";" );
371 }
372
373 wildcardsDesc = _( "All supported formats" ) + wxT( "|" ) + allWildcards + wildcardsDesc;
374
375 wxFileDialog dlg( m_parent, _( "Import Graphics" ), path, filename, wildcardsDesc,
376 wxFD_OPEN | wxFD_FILE_MUST_EXIST );
377
379
380 if( dlg.ShowModal() == wxID_OK && !dlg.GetPath().IsEmpty() )
381 m_textCtrlFileName->SetValue( dlg.GetPath() );
382}
383
384
386{
387 if( !wxDialog::TransferDataFromWindow() )
388 return false;
389
390 if( m_textCtrlFileName->GetValue().IsEmpty() )
391 {
392 wxMessageBox( _( "No file selected!" ) );
393 return false;
394 }
395
396 wxString ext = wxFileName( m_textCtrlFileName->GetValue() ).GetExt();
398
399 if( scale <= 0.0 )
400 {
401 wxMessageBox( _( "Import scale must be a positive number." ) );
402 return false;
403 }
404
405 double xscale = m_xScale;
406 double yscale = m_yScale;
407
408 VECTOR2D origin( m_xOrigin.GetValue() / xscale, m_yOrigin.GetValue() / yscale );
409
410 if( std::unique_ptr<GRAPHICS_IMPORT_PLUGIN> plugin = m_gfxImportMgr->GetPluginByExt( ext ) )
411 {
412 if( DXF_IMPORT_PLUGIN* dxfPlugin = dynamic_cast<DXF_IMPORT_PLUGIN*>( plugin.get() ) )
413 {
414 dxfPlugin->SetUnit( DxfImportUnitFromChoice( m_choiceDxfUnits->GetSelection() ) );
415 m_importer->SetLineWidthMM( m_parent->GetIuScale().IUTomm( m_defaultLineWidth.GetValue() ) );
416 }
417 else
418 {
419 m_importer->SetLineWidthMM( 0.0 );
420 }
421
422 if( BITMAP_IMPORT_PLUGIN* bitmapPlugin = dynamic_cast<BITMAP_IMPORT_PLUGIN*>( plugin.get() ) )
423 {
424 bitmapPlugin->SetThreshold( (double) m_thresholdSlider->GetValue() / m_thresholdSlider->GetMax() );
425 bitmapPlugin->SetInvert( m_cbInvertColors->IsChecked() );
426 }
427
428 m_importer->SetPlugin( std::move( plugin ) );
429 m_importer->SetImportOffsetMM(
430 { m_parent->GetIuScale().IUTomm( origin.x ), m_parent->GetIuScale().IUTomm( origin.y ) } );
431
432 LOCALE_IO dummy; // Ensure floats can be read.
433
434 if( m_importer->Load( m_textCtrlFileName->GetValue() ) )
435 m_importer->Import( VECTOR2D( xscale, yscale ) );
436
437 // Get warning messages:
438 wxString warnings = m_importer->GetMessages();
439
440 // This isn't a fatal error so allow the dialog to close with wxID_OK.
441 if( !warnings.empty() )
442 {
443 HTML_MESSAGE_BOX dlg( this, _( "Warning" ) );
444 dlg.MessageSet( _( "Items in the imported file could not be handled properly." ) );
445 warnings.Replace( wxT( "\n" ), wxT( "<br/>" ) );
446 dlg.AddHTML_Text( warnings );
447 dlg.ShowModal();
448 }
449
450 return true;
451 }
452 else
453 {
454 wxMessageBox( _( "There is no plugin to handle this file type." ) );
455 return false;
456 }
457}
458
459
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.
wxRadioButton * m_rbInteractivePlacement
DIALOG_IMPORT_GFX_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)
wxRadioButton * m_rbAbsolutePlacement
STD_BITMAP_BUTTON * m_browseButton
DIALOG_IMPORT_GFX(EDA_DRAW_FRAME *aParent, std::unique_ptr< GRAPHICS_IMPORTER > aImporter, bool aAllowGrouping)
void sizeFromScale()
Write the scale into the size fields.
bool TransferDataFromWindow() override
void originOptionOnUpdateUI(wxUpdateUIEvent &event) override
void scaleFromSize(bool aFromWidth)
Work the scale out from a size the user typed.
void updateSizeControls()
Offer the size fields only when the source has a size to scale.
void updateImageControls()
Show the image controls only when the chosen file is a raster image.
void onFilenameChanged(wxCommandEvent &event) override
void loadSourceFile()
Read the chosen file, which is where the preview and the source size come from.
EDA_DRAW_FRAME * m_parent
void onLockAspectChanged(wxCommandEvent &event) override
VECTOR2D m_sourceSizeMM
Size of the source file's contents in mm, (0,0) when it has none to report.
std::unique_ptr< GRAPHICS_IMPORT_MGR > m_gfxImportMgr
bool TransferDataToWindow() override
static bool m_placementInteractive
UNIT_BINDER m_defaultLineWidth
void onInvertChanged(wxCommandEvent &event) override
void onThresholdChanged(wxScrollEvent &event) override
void onScaleChanged(wxCommandEvent &event) override
void onBrowseFiles(wxCommandEvent &event) override
wxString m_sourcePath
The file the source size and the preview were taken from.
std::unique_ptr< GRAPHICS_IMPORTER > m_importer
void onWidthChanged(wxCommandEvent &event) override
void onHeightChanged(wxCommandEvent &event) override
void SetFilenameOverride(const wxString &aFilenameOverride)
Set the filename override to be applied in TransferDataToWindow.
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
The base class for create windows for drawing purpose.
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
wxArrayString GetDxfImportUnitChoices()
DXF_IMPORT_UNITS DxfImportUnitFromChoice(int aSelection)
#define _(s)
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
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
STL namespace.
static const std::vector< std::string > extensions
Definition plugin.cpp:63
const int scale
std::vector< FAB_LAYER_COLOR > dummy
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.