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 
29 #include <pgm_base.h>
30 #include <board.h>
31 #include <confirm.h>
33 #include <footprint.h>
34 #include <kiface_i.h>
35 #include <locale_io.h>
36 #include <math/vector3.h>
37 #include <pcb_edit_frame.h>
38 #include <pcbnew_settings.h>
39 #include <project/project_file.h> // LAST_PATH_TYPE
40 #include <reporter.h>
41 #include <widgets/text_ctrl_eval.h>
42 #include <filename_resolver.h>
43 
45 {
46 public:
48  {
49  STEP_ORG_0, // absolute coordinates
50  STEP_ORG_PLOT_AXIS, // origin is plot/drill axis origin
51  STEP_ORG_GRID_AXIS, // origin is grid origin
52  STEP_ORG_BOARD_CENTER, // origin is board center
53  STEP_ORG_USER, // origin is entered by user
54  };
55 
56 private:
58  // The last preference for STEP Origin:
60  bool m_noVirtual; // remember last preference for No Virtual Component
61  int m_OrgUnits; // remember last units for User Origin
62  double m_XOrg; // remember last User Origin X value
63  double m_YOrg; // remember last User Origin Y value
64  wxString m_boardPath; // path to the exported board file
65 
66 protected:
67  void onUpdateUnits( wxUpdateUIEvent& aEvent ) override;
68  void onUpdateXPos( wxUpdateUIEvent& aEvent ) override;
69  void onUpdateYPos( wxUpdateUIEvent& aEvent ) override;
70  void onExportButton( wxCommandEvent& aEvent ) override;
71 
72  int GetOrgUnitsChoice() const
73  {
74  return m_STEP_OrgUnitChoice->GetSelection();
75  }
76 
77  double GetXOrg() const
78  {
80  }
81 
82  double GetYOrg()
83  {
85  }
86 
88 
90  {
91  return m_cbRemoveVirtual->GetValue();
92  }
93 
95  {
96  return m_cbSubstModels->GetValue();
97  }
98 
100  {
101  return m_cbOverwriteFile->GetValue();
102  }
103 
104 public:
105  DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString& aBoardPath );
107 };
108 
109 
110 DIALOG_EXPORT_STEP::DIALOG_EXPORT_STEP( PCB_EDIT_FRAME* aParent, const wxString& aBoardPath ) :
111  DIALOG_EXPORT_STEP_BASE( aParent )
112 {
113  m_parent = aParent;
114  m_boardPath = aBoardPath;
115  m_sdbSizerCancel->SetLabel( _( "Close" ) );
116  m_sdbSizerOK->SetLabel( _( "Export" ) );
117  m_sdbSizer->Layout();
118 
119  // Build default output file name
120  wxString path = m_parent->GetLastPath( LAST_PATH_STEP );
121 
122  if( path.IsEmpty() )
123  {
124  wxFileName brdFile = m_parent->GetBoard()->GetFileName();
125  brdFile.SetExt( "step" );
126  path = brdFile.GetFullPath();
127  }
128 
129  m_filePickerSTEP->SetPath( path );
130 
131  SetFocus();
132 
133  auto cfg = m_parent->GetPcbNewSettings();
134 
135  m_STEP_org_opt = static_cast<STEP_ORG_OPT>( cfg->m_ExportStep.origin_mode );
136 
137  switch( m_STEP_org_opt )
138  {
139  default: break;
140  case STEP_ORG_PLOT_AXIS: m_rbDrillAndPlotOrigin->SetValue( true ); break;
141  case STEP_ORG_GRID_AXIS: m_rbGridOrigin->SetValue( true ); break;
142  case STEP_ORG_USER: m_rbUserDefinedOrigin->SetValue( true ); break;
143  case STEP_ORG_BOARD_CENTER: m_rbBoardCenterOrigin->SetValue( true ); break;
144  }
145 
146  m_OrgUnits = cfg->m_ExportStep.origin_units;
147  m_XOrg = cfg->m_ExportStep.origin_x;
148  m_YOrg = cfg->m_ExportStep.origin_y;
149  m_noVirtual = cfg->m_ExportStep.no_virtual;
150 
151  m_cbRemoveVirtual->SetValue( m_noVirtual );
152  m_cbSubstModels->SetValue( cfg->m_ExportStep.replace_models );
153  m_cbOverwriteFile->SetValue( cfg->m_ExportStep.overwrite_file );
154 
155  m_STEP_OrgUnitChoice->SetSelection( m_OrgUnits );
156  wxString tmpStr;
157  tmpStr << m_XOrg;
158  m_STEP_Xorg->SetValue( tmpStr );
159  tmpStr = "";
160  tmpStr << m_YOrg;
161  m_STEP_Yorg->SetValue( tmpStr );
162 
163  wxString bad_scales;
164  size_t bad_count = 0;
165 
166  for( auto& fp : aParent->GetBoard()->Footprints() )
167  {
168  for( auto& model : fp->Models() )
169  {
170 
171  if( model.m_Scale.x != 1.0 ||
172  model.m_Scale.y != 1.0 ||
173  model.m_Scale.z != 1.0 )
174  {
175  bad_scales.Append( wxS("\n") );
176  bad_scales.Append( model.m_Filename );
177  bad_count++;
178  }
179  }
180 
181  if( bad_count >= 5 )
182  break;
183  }
184 
185  if( !bad_scales.empty()
186  && !Pgm().GetCommonSettings()->m_DoNotShowAgain.scaled_3d_models_warning )
187  {
188  wxString extendedMsg = _( "Non-unity scaled models:" ) + "\n" + bad_scales;
189 
190  KIDIALOG msgDlg( m_parent, _( "Scaled models detected. "
191  "Model scaling is not reliable for mechanical export." ),
192  _( "Model Scale Warning" ), wxOK | wxICON_WARNING );
193  msgDlg.SetExtendedMessage( extendedMsg );
194  msgDlg.DoNotShowCheckbox( __FILE__, __LINE__ );
195 
196  msgDlg.ShowModal();
197 
198  if( msgDlg.DoNotShowAgain() )
199  Pgm().GetCommonSettings()->m_DoNotShowAgain.scaled_3d_models_warning = true;
200  }
201  // Now all widgets have the size fixed, call FinishDialogSettings
203 }
204 
205 
207 {
208  GetOriginOption(); // Update m_STEP_org_opt member.
209 
210  auto cfg = m_parent->GetPcbNewSettings();
211 
212  cfg->m_ExportStep.origin_mode = static_cast<int>( m_STEP_org_opt );
213  cfg->m_ExportStep.origin_units = m_STEP_OrgUnitChoice->GetSelection();
214  cfg->m_ExportStep.replace_models = m_cbSubstModels->GetValue();
215  cfg->m_ExportStep.overwrite_file = m_cbOverwriteFile->GetValue();
216 
217  double val = 0.0;
218 
219  m_STEP_Xorg->GetValue().ToDouble( &val );
220  cfg->m_ExportStep.origin_x = val;
221 
222  m_STEP_Yorg->GetValue().ToDouble( &val );
223  cfg->m_ExportStep.origin_y = val;
224 
225  cfg->m_ExportStep.no_virtual = m_cbRemoveVirtual->GetValue();
226 }
227 
228 
230 {
232 
233  if( m_rbDrillAndPlotOrigin->GetValue() )
235  else if( m_rbGridOrigin->GetValue() )
237  else if( m_rbUserDefinedOrigin->GetValue() )
239  else if( m_rbBoardCenterOrigin->GetValue() )
241 
242  return m_STEP_org_opt;
243 }
244 
245 
246 void PCB_EDIT_FRAME::OnExportSTEP( wxCommandEvent& event )
247 {
248  wxFileName brdFile = GetBoard()->GetFileName();
249 
250  if( GetScreen()->IsContentModified() || brdFile.GetFullPath().empty() )
251  {
252  if( !doAutoSave() )
253  {
254  DisplayErrorMessage( this, _( "STEP export failed! "
255  "Please save the PCB and try again" ) );
256  return;
257  }
258 
259  // Use auto-saved board for export
260  brdFile.SetName( GetAutoSaveFilePrefix() + brdFile.GetName() );
261  }
262 
263  DIALOG_EXPORT_STEP dlg( this, brdFile.GetFullPath() );
264  dlg.ShowModal();
265 }
266 
267 
268 void DIALOG_EXPORT_STEP::onUpdateUnits( wxUpdateUIEvent& aEvent )
269 {
270  aEvent.Enable( m_rbUserDefinedOrigin->GetValue() );
271 }
272 
273 
274 void DIALOG_EXPORT_STEP::onUpdateXPos( wxUpdateUIEvent& aEvent )
275 {
276  aEvent.Enable( m_rbUserDefinedOrigin->GetValue() );
277 }
278 
279 
280 void DIALOG_EXPORT_STEP::onUpdateYPos( wxUpdateUIEvent& aEvent )
281 {
282  aEvent.Enable( m_rbUserDefinedOrigin->GetValue() );
283 }
284 
285 
286 void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent )
287 {
289 
290  double tolerance = 0.01; // default value in mm
291 
292  switch( m_tolerance->GetSelection() )
293  {
294  case 0: // small
295  tolerance = 0.001;
296  break;
297 
298  default:
299  case 1: break; // Normal
300 
301  case 2: // large
302  tolerance = 0.1;
303  break;
304  }
305 
306  SHAPE_POLY_SET outline;
307  wxString msg;
308 
309  // Check if the board outline is continuous
310  // max dist from one endPt to next startPt to build a closed shape:
311  int chainingEpsilon = Millimeter2iu( tolerance );
312  // Arc to segment approx error (not critical here: we do not use the outline shape):
313  int maxError = Millimeter2iu( 0.005 );
314  bool success = BuildBoardPolygonOutlines( m_parent->GetBoard(), outline, maxError,
315  chainingEpsilon, nullptr );
316  if( !success )
317  {
319  _( "Board outline is missing or not closed using %.3f mm tolerance.\n"
320  "Run DRC for a full analysis." ), tolerance ) );
321  return;
322  }
323 
324  wxFileName fn = m_filePickerSTEP->GetFileName();
325 
326  if( fn.FileExists() && !GetOverwriteFile() )
327  {
328  msg.Printf( _( "File '%s' already exists. Do you want overwrite this file?" ),
329  fn.GetFullPath() );
330 
331  if( wxMessageBox( msg, _( "STEP Export" ), wxYES_NO | wxICON_QUESTION, this ) == wxNO )
332  return;
333  }
334 
335  FILENAME_RESOLVER* fnResolver = m_parent->Prj().Get3DFilenameResolver();
336 
337  fnResolver->WritePathList( wxStandardPaths::Get().GetTempDir(), "ExportPaths.cfg", true );
338 
340  double xOrg = 0.0;
341  double yOrg = 0.0;
342 
343  wxFileName appK2S( wxStandardPaths::Get().GetExecutablePath() );
344 
345 #ifdef __WXMAC__
346  // On macOS, we have standalone applications inside the main bundle, so we handle that here:
347  if( appK2S.GetPath().Find( "/Contents/Applications/pcbnew.app/Contents/MacOS" ) != wxNOT_FOUND )
348  {
349  appK2S.AppendDir( ".." );
350  appK2S.AppendDir( ".." );
351  appK2S.AppendDir( ".." );
352  appK2S.AppendDir( ".." );
353  appK2S.AppendDir( "MacOS" );
354  }
355 #endif
356 
357  appK2S.SetName( "kicad2step" );
358 
359  wxString cmdK2S = "\"";
360  cmdK2S.Append( appK2S.GetFullPath() );
361  cmdK2S.Append( "\"" );
362 
363  if( GetNoVirtOption() )
364  cmdK2S.Append( " --no-virtual" );
365 
366  if( GetSubstOption() )
367  cmdK2S.Append( " --subst-models" );
368 
369  switch( orgOpt )
370  {
372  break;
373 
375  cmdK2S.Append( " --drill-origin" );
376  break;
377 
379  cmdK2S.Append( " --grid-origin" );
380  break;
381 
383  {
384  xOrg = GetXOrg();
385  yOrg = GetYOrg();
386 
387  if( GetOrgUnitsChoice() == 1 )
388  {
389  // selected reference unit is in inches, and STEP units are mm
390  xOrg *= 25.4;
391  yOrg *= 25.4;
392  }
393 
395  cmdK2S.Append( wxString::Format( " --user-origin=\"%.6f x %.6f\"", xOrg, yOrg ) );
396  }
397  break;
398 
400  {
401  EDA_RECT bbox = m_parent->GetBoard()->ComputeBoundingBox( true );
402  xOrg = Iu2Millimeter( bbox.GetCenter().x );
403  yOrg = Iu2Millimeter( bbox.GetCenter().y );
405  cmdK2S.Append( wxString::Format( " --user-origin=\"%.6f x %.6f\"", xOrg, yOrg ) );
406  }
407  break;
408  }
409 
410  {
412  cmdK2S.Append( wxString::Format( " --min-distance=\"%.3f mm\"", tolerance ) );
413  }
414 
415  cmdK2S.Append( " -f -o " );
416  cmdK2S.Append( wxString::Format("\"%s\"", m_filePickerSTEP->GetPath() ) ); // input file path
417 
418  cmdK2S.Append( " " );
419  cmdK2S.Append( wxString::Format("\"%s\"", m_boardPath ) ); // output file path
420 
421  wxExecute( cmdK2S, wxEXEC_ASYNC | wxEXEC_SHOW_CONSOLE );
422 
423  aEvent.Skip(); // Close the dialog
424 }
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:260
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
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:106
const wxString & GetFileName() const
Definition: board.h:228
bool WritePathList(const wxString &aDir, const wxString &aFilename, bool aWriteFullList)
Write the current path list to a config file.
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:71
Represent a set of closed polygons.
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
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 ...
FOOTPRINTS & Footprints()
Definition: board.h:233
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:1104
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)