KiCad PCB EDA Suite
dialog_export_step.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) 2016 Cirilo Bernardo
5  * Copyright (C) 2016-2020 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, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 #include <wx/choicdlg.h>
26 #include <wx/stdpaths.h>
27 #include <wx/process.h>
28 #include <wx/string.h>
29 
30 #include <pgm_base.h>
31 #include <board.h>
32 #include <confirm.h>
34 #include <footprint.h>
35 #include <kiface_base.h>
36 #include <locale_io.h>
37 #include <math/vector3.h>
38 #include <pcb_edit_frame.h>
39 #include <pcbnew_settings.h>
40 #include <project/project_file.h> // LAST_PATH_TYPE
41 #include <reporter.h>
42 #include <widgets/text_ctrl_eval.h>
44 #include <filename_resolver.h>
45 
46 #ifdef KICAD_STEP_EXPORT_LIB
47 #include <kicad2step.h>
48 #endif
49 
51 {
52 public:
54  {
55  STEP_ORG_0, // absolute coordinates
56  STEP_ORG_PLOT_AXIS, // origin is plot/drill axis origin
57  STEP_ORG_GRID_AXIS, // origin is grid origin
58  STEP_ORG_BOARD_CENTER, // origin is board center
59  STEP_ORG_USER, // origin is entered by user
60  };
61 
62 private:
64  // The last preference for STEP Origin:
66  bool m_noVirtual; // remember last preference for No Virtual Component
67  int m_OrgUnits; // remember last units for User Origin
68  double m_XOrg; // remember last User Origin X value
69  double m_YOrg; // remember last User Origin Y value
70  wxString m_boardPath; // path to the exported board file
71 
72 protected:
73  void onUpdateUnits( wxUpdateUIEvent& aEvent ) override;
74  void onUpdateXPos( wxUpdateUIEvent& aEvent ) override;
75  void onUpdateYPos( wxUpdateUIEvent& aEvent ) override;
76  void onExportButton( wxCommandEvent& aEvent ) override;
77 
78  int GetOrgUnitsChoice() const
79  {
80  return m_STEP_OrgUnitChoice->GetSelection();
81  }
82 
83  double GetXOrg() const
84  {
86  }
87 
88  double GetYOrg()
89  {
91  }
92 
94 
96  {
97  return m_cbRemoveVirtual->GetValue();
98  }
99 
101  {
102  return m_cbSubstModels->GetValue();
103  }
104 
106  {
107  return m_cbOverwriteFile->GetValue();
108  }
109 
110 public:
111  DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString& aBoardPath );
113 };
114 
115 
116 DIALOG_EXPORT_STEP::DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString& aBoardPath ) :
117  DIALOG_EXPORT_STEP_BASE( aParent )
118 {
119  m_parent = aParent;
120  m_boardPath = aBoardPath;
121  m_sdbSizerCancel->SetLabel( _( "Close" ) );
122  m_sdbSizerOK->SetLabel( _( "Export" ) );
123  m_sdbSizer->Layout();
124 
125  // Build default output file name
126  wxString path = m_parent->GetLastPath( LAST_PATH_STEP );
127 
128  if( path.IsEmpty() )
129  {
130  wxFileName brdFile = m_parent->GetBoard()->GetFileName();
131  brdFile.SetExt( "step" );
132  path = brdFile.GetFullPath();
133  }
134 
135  // Reset this picker bc wxFormBuilder doesn't allow untranslated strings
136  wxSizerItem* sizer_item = bSizerTop->GetItem( 1UL );
137  wxWindow* widget = sizer_item->GetWindow();
138  bSizerTop->Hide( widget );
139  widget->Destroy();
140 
141  m_filePickerSTEP = new wxFilePickerCtrl( this, wxID_ANY, wxEmptyString,
142  _( "Select a STEP export filename" ),
143  _( "STEP files" ) + AddFileExtListToFilter( { "STEP", "STP" } ),
144  wxDefaultPosition,
145  wxSize( -1, -1 ), wxFLP_SAVE | wxFLP_USE_TEXTCTRL );
146  bSizerTop->Add( m_filePickerSTEP, 1, wxTOP|wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 );
147 
148  m_filePickerSTEP->SetPath( path );
149 
150  Layout();
151  bSizerSTEPFile->Fit( this );
152 
153  SetFocus();
154 
155  auto cfg = m_parent->GetPcbNewSettings();
156 
157  m_STEP_org_opt = static_cast<STEP_ORG_OPT>( cfg->m_ExportStep.origin_mode );
158 
159  switch( m_STEP_org_opt )
160  {
161  default: break;
162  case STEP_ORG_PLOT_AXIS: m_rbDrillAndPlotOrigin->SetValue( true ); break;
163  case STEP_ORG_GRID_AXIS: m_rbGridOrigin->SetValue( true ); break;
164  case STEP_ORG_USER: m_rbUserDefinedOrigin->SetValue( true ); break;
165  case STEP_ORG_BOARD_CENTER: m_rbBoardCenterOrigin->SetValue( true ); break;
166  }
167 
168  m_OrgUnits = cfg->m_ExportStep.origin_units;
169  m_XOrg = cfg->m_ExportStep.origin_x;
170  m_YOrg = cfg->m_ExportStep.origin_y;
171  m_noVirtual = cfg->m_ExportStep.no_virtual;
172 
173  m_cbRemoveVirtual->SetValue( m_noVirtual );
174  m_cbSubstModels->SetValue( cfg->m_ExportStep.replace_models );
175  m_cbOverwriteFile->SetValue( cfg->m_ExportStep.overwrite_file );
176 
177  m_STEP_OrgUnitChoice->SetSelection( m_OrgUnits );
178  wxString tmpStr;
179  tmpStr << m_XOrg;
180  m_STEP_Xorg->SetValue( tmpStr );
181  tmpStr = "";
182  tmpStr << m_YOrg;
183  m_STEP_Yorg->SetValue( tmpStr );
184 
185  wxString bad_scales;
186  size_t bad_count = 0;
187 
188  for( auto& fp : aParent->GetBoard()->Footprints() )
189  {
190  for( auto& model : fp->Models() )
191  {
192 
193  if( model.m_Scale.x != 1.0 ||
194  model.m_Scale.y != 1.0 ||
195  model.m_Scale.z != 1.0 )
196  {
197  bad_scales.Append( wxS("\n") );
198  bad_scales.Append( model.m_Filename );
199  bad_count++;
200  }
201  }
202 
203  if( bad_count >= 5 )
204  break;
205  }
206 
207  if( !bad_scales.empty()
208  && !Pgm().GetCommonSettings()->m_DoNotShowAgain.scaled_3d_models_warning )
209  {
210  wxString extendedMsg = _( "Non-unity scaled models:" ) + "\n" + bad_scales;
211 
212  KIDIALOG msgDlg( m_parent, _( "Scaled models detected. "
213  "Model scaling is not reliable for mechanical export." ),
214  _( "Model Scale Warning" ), wxOK | wxICON_WARNING );
215  msgDlg.SetExtendedMessage( extendedMsg );
216  msgDlg.DoNotShowCheckbox( __FILE__, __LINE__ );
217 
218  msgDlg.ShowModal();
219 
220  if( msgDlg.DoNotShowAgain() )
221  Pgm().GetCommonSettings()->m_DoNotShowAgain.scaled_3d_models_warning = true;
222  }
223  // Now all widgets have the size fixed, call FinishDialogSettings
225 }
226 
227 
229 {
230  GetOriginOption(); // Update m_STEP_org_opt member.
231 
232  auto cfg = m_parent->GetPcbNewSettings();
233 
234  cfg->m_ExportStep.origin_mode = static_cast<int>( m_STEP_org_opt );
235  cfg->m_ExportStep.origin_units = m_STEP_OrgUnitChoice->GetSelection();
236  cfg->m_ExportStep.replace_models = m_cbSubstModels->GetValue();
237  cfg->m_ExportStep.overwrite_file = m_cbOverwriteFile->GetValue();
238 
239  double val = 0.0;
240 
241  m_STEP_Xorg->GetValue().ToDouble( &val );
242  cfg->m_ExportStep.origin_x = val;
243 
244  m_STEP_Yorg->GetValue().ToDouble( &val );
245  cfg->m_ExportStep.origin_y = val;
246 
247  cfg->m_ExportStep.no_virtual = m_cbRemoveVirtual->GetValue();
248 }
249 
250 
252 {
254 
255  if( m_rbDrillAndPlotOrigin->GetValue() )
257  else if( m_rbGridOrigin->GetValue() )
259  else if( m_rbUserDefinedOrigin->GetValue() )
261  else if( m_rbBoardCenterOrigin->GetValue() )
263 
264  return m_STEP_org_opt;
265 }
266 
267 
268 void PCB_EDIT_FRAME::OnExportSTEP( wxCommandEvent& event )
269 {
270  wxFileName brdFile = GetBoard()->GetFileName();
271 
272  if( GetScreen()->IsContentModified() || brdFile.GetFullPath().empty() )
273  {
274  if( !doAutoSave() )
275  {
276  DisplayErrorMessage( this, _( "STEP export failed! "
277  "Please save the PCB and try again" ) );
278  return;
279  }
280 
281  // Use auto-saved board for export
282  brdFile.SetName( GetAutoSaveFilePrefix() + brdFile.GetName() );
283  }
284 
285  DIALOG_EXPORT_STEP dlg( this, brdFile.GetFullPath() );
286  dlg.ShowModal();
287 }
288 
289 
290 void DIALOG_EXPORT_STEP::onUpdateUnits( wxUpdateUIEvent& aEvent )
291 {
292  aEvent.Enable( m_rbUserDefinedOrigin->GetValue() );
293 }
294 
295 
296 void DIALOG_EXPORT_STEP::onUpdateXPos( wxUpdateUIEvent& aEvent )
297 {
298  aEvent.Enable( m_rbUserDefinedOrigin->GetValue() );
299 }
300 
301 
302 void DIALOG_EXPORT_STEP::onUpdateYPos( wxUpdateUIEvent& aEvent )
303 {
304  aEvent.Enable( m_rbUserDefinedOrigin->GetValue() );
305 }
306 
307 
308 void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent )
309 {
311 
312  double tolerance = 0.01; // default value in mm
313 
314  switch( m_tolerance->GetSelection() )
315  {
316  case 0: // small
317  tolerance = 0.001;
318  break;
319 
320  default:
321  case 1: break; // Normal
322 
323  case 2: // large
324  tolerance = 0.1;
325  break;
326  }
327 
328  SHAPE_POLY_SET outline;
329  wxString msg;
330 
331  // Check if the board outline is continuous
332  // max dist from one endPt to next startPt to build a closed shape:
333  int chainingEpsilon = Millimeter2iu( tolerance );
334  // Arc to segment approx error (not critical here: we do not use the outline shape):
335  int maxError = Millimeter2iu( 0.005 );
336  bool success = BuildBoardPolygonOutlines( m_parent->GetBoard(), outline, maxError,
337  chainingEpsilon, nullptr );
338  if( !success )
339  {
341  _( "Board outline is missing or not closed using %.3f mm tolerance.\n"
342  "Run DRC for a full analysis." ), tolerance ) );
343  return;
344  }
345 
346  wxFileName fn = m_filePickerSTEP->GetFileName();
347 
348  if( fn.FileExists() && !GetOverwriteFile() )
349  {
350  msg.Printf( _( "File '%s' already exists. Do you want overwrite this file?" ),
351  fn.GetFullPath() );
352 
353  if( wxMessageBox( msg, _( "STEP Export" ), wxYES_NO | wxICON_QUESTION, this ) == wxNO )
354  return;
355  }
356 
357  FILENAME_RESOLVER* fnResolver = m_parent->Prj().Get3DFilenameResolver();
358 
359  fnResolver->WritePathList( wxStandardPaths::Get().GetTempDir(), "ExportPaths.cfg", true );
360 
362  double xOrg = 0.0;
363  double yOrg = 0.0;
364 
365 #ifndef KICAD_STEP_EXPORT_LIB
366  wxFileName appK2S( wxStandardPaths::Get().GetExecutablePath() );
367 
368 #ifdef __WXMAC__
369  // On macOS, we have standalone applications inside the main bundle, so we handle that here:
370  if( appK2S.GetPath().Find( "/Contents/Applications/pcbnew.app/Contents/MacOS" ) != wxNOT_FOUND )
371  {
372  appK2S.AppendDir( ".." );
373  appK2S.AppendDir( ".." );
374  appK2S.AppendDir( ".." );
375  appK2S.AppendDir( ".." );
376  appK2S.AppendDir( "MacOS" );
377  }
378 #endif
379 
380  appK2S.SetName( "kicad2step" );
381 
382  wxString cmdK2S = "\"";
383  cmdK2S.Append( appK2S.GetFullPath() );
384  cmdK2S.Append( "\"" );
385 
386  if( GetNoVirtOption() )
387  cmdK2S.Append( " --no-virtual" );
388 
389  if( GetSubstOption() )
390  cmdK2S.Append( " --subst-models" );
391 
392  switch( orgOpt )
393  {
395  break;
396 
398  cmdK2S.Append( " --drill-origin" );
399  break;
400 
402  cmdK2S.Append( " --grid-origin" );
403  break;
404 
406  {
407  xOrg = GetXOrg();
408  yOrg = GetYOrg();
409 
410  if( GetOrgUnitsChoice() == 1 )
411  {
412  // selected reference unit is in inches, and STEP units are mm
413  xOrg *= 25.4;
414  yOrg *= 25.4;
415  }
416 
418  cmdK2S.Append( wxString::Format( " --user-origin=\"%.6f x %.6f\"", xOrg, yOrg ) );
419  }
420  break;
421 
423  {
424  EDA_RECT bbox = m_parent->GetBoard()->ComputeBoundingBox( true );
425  xOrg = Iu2Millimeter( bbox.GetCenter().x );
426  yOrg = Iu2Millimeter( bbox.GetCenter().y );
428  cmdK2S.Append( wxString::Format( " --user-origin=\"%.6f x %.6f\"", xOrg, yOrg ) );
429  }
430  break;
431  }
432 
433  {
435  cmdK2S.Append( wxString::Format( " --min-distance=\"%.3f mm\"", tolerance ) );
436  }
437 
438  cmdK2S.Append( " -f -o " );
439  cmdK2S.Append( wxString::Format("\"%s\"", m_filePickerSTEP->GetPath() ) ); // input file path
440 
441  cmdK2S.Append( " " );
442  cmdK2S.Append( wxString::Format("\"%s\"", m_boardPath ) ); // output file path
443 
444  wxExecute( cmdK2S, wxEXEC_ASYNC | wxEXEC_SHOW_CONSOLE );
445 
446  #else
447 
448  KICAD2MCAD_PRMS params;
449  params.m_filename = m_boardPath;
450  params.m_outputFile = m_filePickerSTEP->GetPath();
451 
452  params.m_includeVirtual = !GetNoVirtOption();
453 
454  params.m_substModels = GetSubstOption();
455  params.m_minDistance = tolerance;
456  params.m_overwrite = true;
457 
458  switch( orgOpt )
459  {
461  break;
462 
464  params.m_useDrillOrigin = true;
465  break;
466 
468  params.m_useGridOrigin = true;
469  break;
470 
472  {
473  xOrg = GetXOrg();
474  yOrg = GetYOrg();
475 
476  if( GetOrgUnitsChoice() == 1 )
477  {
478  // selected reference unit is in inches, and STEP units are mm
479  xOrg *= 25.4;
480  yOrg *= 25.4;
481  }
482 
483  params.m_xOrigin = xOrg;
484  params.m_yOrigin = yOrg;
485  }
486  break;
487 
489  {
490  EDA_RECT bbox = m_parent->GetBoard()->ComputeBoundingBox( true );
491  xOrg = Iu2Millimeter( bbox.GetCenter().x );
492  yOrg = Iu2Millimeter( bbox.GetCenter().y );
493  params.m_xOrigin = xOrg;
494  params.m_yOrigin = yOrg;
495  }
496  break;
497  }
498 
499  KICAD2STEP converter( params );
500  converter.Run();
501  #endif
502 
503  aEvent.Skip(); // Close the dialog
504 }
void DoNotShowCheckbox(wxString file, int line)
Checks the 'do not show again' setting for the dialog.
Definition: confirm.cpp:55
DIALOG_EXPORT_STEP m_ExportStep
bool IsContentModified() const override
Get if the current board has been modified but not saved.
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:292
void onUpdateXPos(wxUpdateUIEvent &aEvent) override
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:40
Helper class to create more flexible dialogs, including 'do not show again' checkbox handling.
Definition: confirm.h:45
This file is part of the common library.
bool doAutoSave() override
Perform auto save when the board has been modified and not saved within the auto save interval.
void OnExportSTEP(wxCommandEvent &event)
Export the current BOARD to a STEP assembly.
wxRadioButton * m_rbDrillAndPlotOrigin
PCB_EDIT_FRAME * m_parent
bool WritePathList(const wxString &aDir, const wxString &aFilename, bool aResolvePaths)
Write the current path list to a config file.
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:106
const wxString & GetFileName() const
Definition: board.h:228
void SetValue(const wxString &aValue) override
Set a new value in evaluator buffer, and display it in the wxTextCtrl.
wxString GetLastPath(LAST_PATH_TYPE aType)
Get the last path for a particular type.
STEP_ORG_OPT GetOriginOption()
void onExportButton(wxCommandEvent &aEvent) override
wxFilePickerCtrl * m_filePickerSTEP
static LIB_SYMBOL * dummy()
Used to draw a dummy shape when a LIB_SYMBOL is not found in library.
Definition: sch_symbol.cpp:72
bool BuildBoardPolygonOutlines(BOARD *aBoard, SHAPE_POLY_SET &aOutlines, int aErrorMax, int aChainingEpsilon, OUTLINE_ERROR_HANDLER *aErrorHandler)
Extracts the board outlines and build a closed polygon from lines, arcs and circle items on edge cut ...
Represent a set of closed polygons.
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
FOOTPRINTS & Footprints()
Definition: board.h:233
Definition of file extensions used in Kicad.
void onUpdateUnits(wxUpdateUIEvent &aEvent) override
#define _(s)
wxStdDialogButtonSizer * m_sdbSizer
bool DoNotShowAgain() const
Definition: confirm.cpp:63
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
static wxString GetAutoSaveFilePrefix()
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
see class PGM_BASE
Provide an extensible class to resolve 3D model paths.
Class DIALOG_EXPORT_STEP_BASE.
Handle the component boundary box.
Definition: eda_rect.h:42
The main frame for Pcbnew.
PCBNEW_SETTINGS * GetPcbNewSettings() const
PCB_SCREEN * GetScreen() const override
Return a pointer to a BASE_SCREEN or one of its derivatives.
void onUpdateYPos(wxUpdateUIEvent &aEvent) override
EDA_RECT ComputeBoundingBox(bool aBoardEdgesOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition: board.cpp:1082
int ShowModal() override
Definition: confirm.cpp:99
BOARD * GetBoard() const
const wxPoint GetCenter() const
Definition: eda_rect.h:104
double DoubleValueFromString(EDA_UNITS aUnits, const wxString &aTextValue, EDA_DATA_TYPE aType)
Function DoubleValueFromString converts aTextValue to a double.
Definition: base_units.cpp:307
static constexpr int Millimeter2iu(double mm)
void SetLastPath(LAST_PATH_TYPE aType, const wxString &aLastPath)
Set the path of the last file successfully read.
DIALOG_EXPORT_STEP(PCB_EDIT_FRAME *aParent, const wxString &aBoardPath)
wxString AddFileExtListToFilter(const std::vector< std::string > &aExts)
Build the wildcard extension file dialog wildcard filter to add to the base message dialog.