KiCad PCB EDA Suite
panel_fp_properties_3d_model.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 (C) 2015 Dick Hollenbeck, [email protected]
6  * Copyright (C) 2008 Wayne Stambaugh <[email protected]>
7  * Copyright (C) 2004-2021 KiCad Developers, see AUTHORS.txt for contributors.
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, you may find one here:
21  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22  * or you may search the http://www.gnu.org website for the version 2 license,
23  * or you may write to the Free Software Foundation, Inc.,
24  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25  */
26 
28 
29 #include <confirm.h>
31 #include <board_design_settings.h>
32 #include <bitmaps.h>
35 #include <widgets/wx_grid.h>
36 #include <footprint.h>
37 #include <footprint_edit_frame.h>
40 #include "filename_resolver.h"
41 #include <pgm_base.h>
45 #include <kiway_holder.h>
46 #include <wx/defs.h>
47 
49 {
53 };
54 
56  PCB_BASE_EDIT_FRAME* aFrame, FOOTPRINT* aFootprint, DIALOG_SHIM* aDialogParent,
57  wxWindow* aParent, wxWindowID aId, const wxPoint& aPos, const wxSize& aSize, long aStyle,
58  const wxString& aName ) :
59  PANEL_FP_PROPERTIES_3D_MODEL_BASE( aParent, aId, aPos, aSize, aStyle, aName ),
60  m_parentDialog( aDialogParent ),
61  m_frame( aFrame ),
62  m_footprint( aFootprint ),
63  m_inSelect( false )
64 {
65  m_modelsGrid->SetDefaultRowSize( m_modelsGrid->GetDefaultRowSize() + 4 );
66 
67  GRID_TRICKS* trick = new GRID_TRICKS( m_modelsGrid );
68  trick->SetTooltipEnable( COL_PROBLEM );
69 
70  m_modelsGrid->PushEventHandler( trick );
71 
72  // Get the last 3D directory
73  PCBNEW_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings<PCBNEW_SETTINGS>();
74 
75  if( cfg->m_lastFootprint3dDir.IsEmpty() )
76  wxGetEnv( KICAD6_3DMODEL_DIR, &cfg->m_lastFootprint3dDir );
77 
78  // Icon showing warning/error information
79  wxGridCellAttr* attr = new wxGridCellAttr;
80  attr->SetReadOnly();
81  m_modelsGrid->SetColAttr( COL_PROBLEM, attr );
82 
83  // Filename
84  attr = new wxGridCellAttr;
86  wxT( "*.*" ), true, m_frame->Prj().GetProjectPath() ) );
87  m_modelsGrid->SetColAttr( COL_FILENAME, attr );
88 
89  // Show checkbox
90  attr = new wxGridCellAttr;
91  attr->SetRenderer( new wxGridCellBoolRenderer() );
92  attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
93  attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
94  m_modelsGrid->SetColAttr( COL_SHOWN, attr );
95  m_modelsGrid->SetWindowStyleFlag( m_modelsGrid->GetWindowStyle() & ~wxHSCROLL );
96 
97  m_frame->Prj().Get3DCacheManager()->GetResolver()->SetProgramBase( &Pgm() );
98 
100 
101  bLowerSizer3D->Add( m_previewPane, 1, wxEXPAND, 5 );
102 
103  // Configure button logos
107 }
108 
109 
111 {
112  // Delete the GRID_TRICKS.
113  m_modelsGrid->PopEventHandler( true );
114 
115  // free the memory used by all models, otherwise models which were
116  // browsed but not used would consume memory
117  m_frame->Prj().Get3DCacheManager()->FlushCache( false );
118 
119  delete m_previewPane;
120 }
121 
122 
124 {
126  return true;
127 }
128 
130 {
131  // Only commit changes in the editor, not the models
132  // The container dialog is responsible for moving the new models into
133  // the footprint inside a commit.
135  return false;
136 
137  return true;
138 }
139 
140 
142 {
143  wxString default_path;
144  wxGetEnv( KICAD6_3DMODEL_DIR, &default_path );
145 
146 #ifdef __WINDOWS__
147  default_path.Replace( wxT( "/" ), wxT( "\\" ) );
148 #endif
149 
150  m_shapes3D_list.clear();
152 
153  wxString origPath, alias, shortPath;
154  FILENAME_RESOLVER* res = m_frame->Prj().Get3DCacheManager()->GetResolver();
155 
156  for( const FP_3DMODEL& model : m_footprint->Models() )
157  {
158  m_shapes3D_list.push_back( model );
159  origPath = model.m_Filename;
160 
161  if( res && res->SplitAlias( origPath, alias, shortPath ) )
162  origPath = alias + wxT( ":" ) + shortPath;
163 
164  m_modelsGrid->AppendRows( 1 );
165  int row = m_modelsGrid->GetNumberRows() - 1;
166  m_modelsGrid->SetCellValue( row, COL_FILENAME, origPath );
167  m_modelsGrid->SetCellValue( row, COL_SHOWN, model.m_Show ? wxT( "1" ) : wxT( "0" ) );
168 
169  // Must be after the filename is set
170  updateValidateStatus( row );
171  }
172 
173  select3DModel( 0 );
174 
176  m_modelsGrid->SetColSize( COL_SHOWN, m_modelsGrid->GetVisibleWidth( COL_SHOWN, true, false, false ) );
177 
178  Layout();
179 }
180 
181 
183 {
184  m_inSelect = true;
185 
186  aModelIdx = std::max( 0, aModelIdx );
187  aModelIdx = std::min( aModelIdx, m_modelsGrid->GetNumberRows() - 1 );
188 
189  if( m_modelsGrid->GetNumberRows() )
190  {
191  m_modelsGrid->SelectRow( aModelIdx );
192  m_modelsGrid->SetGridCursor( aModelIdx, COL_FILENAME );
193  }
194 
195  m_previewPane->SetSelectedModel( aModelIdx );
196 
197  m_inSelect = false;
198 }
199 
200 
202 {
203  if( !m_inSelect )
204  select3DModel( aEvent.GetRow() );
205 }
206 
207 
209 {
210  if( aEvent.GetCol() == COL_FILENAME )
211  {
212  bool hasAlias = false;
213  FILENAME_RESOLVER* res = m_frame->Prj().Get3DCacheManager()->GetResolver();
214  wxString filename = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_FILENAME );
215 
216  // Perform cleanup and validation on the filename if it isn't empty
217  if( !filename.empty() )
218  {
219  filename.Replace( wxT( "\n" ), wxEmptyString );
220  filename.Replace( wxT( "\r" ), wxEmptyString );
221  filename.Replace( wxT( "\t" ), wxEmptyString );
222 
223  res->ValidateFileName( filename, hasAlias );
224 
225  // If the user has specified an alias in the name then prepend ':'
226  if( hasAlias )
227  filename.insert( 0, wxT( ":" ) );
228 
229 #ifdef __WINDOWS__
230  // In KiCad files, filenames and paths are stored using Unix notation
231  filename.Replace( wxT( "\\" ), wxT( "/" ) );
232 #endif
233 
234  // Update the grid with the modified filename
235  m_modelsGrid->SetCellValue( aEvent.GetRow(), COL_FILENAME, filename );
236  }
237 
238  // Save the filename in the 3D shapes table
239  m_shapes3D_list[ aEvent.GetRow() ].m_Filename = filename;
240 
241  // Update the validation status
242  updateValidateStatus( aEvent.GetRow() );
243  }
244  else if( aEvent.GetCol() == COL_SHOWN )
245  {
246  wxString showValue = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_SHOWN );
247 
248  m_shapes3D_list[ aEvent.GetRow() ].m_Show = ( showValue == wxT( "1" ) );
249  }
250 
252 }
253 
254 
256 {
258  return;
259 
260  int idx = m_modelsGrid->GetGridCursorRow();
261 
262  if( idx >= 0 && m_modelsGrid->GetNumberRows() && !m_shapes3D_list.empty() )
263  {
264  m_shapes3D_list.erase( m_shapes3D_list.begin() + idx );
265  m_modelsGrid->DeleteRows( idx );
266 
267  select3DModel( idx ); // will clamp idx within bounds
269  }
270 }
271 
272 
274 {
276  return;
277 
278  int selected = m_modelsGrid->GetGridCursorRow();
279 
280  PROJECT& prj = m_frame->Prj();
281  FP_3DMODEL model;
282 
283  wxString initialpath = prj.GetRString( PROJECT::VIEWER_3D_PATH );
284  wxString sidx = prj.GetRString( PROJECT::VIEWER_3D_FILTER_INDEX );
285  int filter = 0;
286 
287  // If the PROJECT::VIEWER_3D_PATH hasn't been set yet, use the KICAD6_3DMODEL_DIR environment
288  // variable and fall back to the project path if necessary.
289  if( initialpath.IsEmpty() )
290  {
291  if( !wxGetEnv( wxT( "KICAD6_3DMODEL_DIR" ), &initialpath ) || initialpath.IsEmpty() )
292  initialpath = prj.GetProjectPath();
293  }
294 
295  if( !sidx.empty() )
296  {
297  long tmp;
298  sidx.ToLong( &tmp );
299 
300  if( tmp > 0 && tmp <= INT_MAX )
301  filter = (int) tmp;
302  }
303 
304  if( !S3D::Select3DModel( this, m_frame->Prj().Get3DCacheManager(), initialpath, filter, &model )
305  || model.m_Filename.empty() )
306  {
307  select3DModel( selected );
308  updateValidateStatus( selected );
309  return;
310  }
311 
312  prj.SetRString( PROJECT::VIEWER_3D_PATH, initialpath );
313  sidx = wxString::Format( wxT( "%i" ), filter );
315  FILENAME_RESOLVER* res = m_frame->Prj().Get3DCacheManager()->GetResolver();
316  wxString alias;
317  wxString shortPath;
318  wxString filename = model.m_Filename;
319 
320  if( res && res->SplitAlias( filename, alias, shortPath ) )
321  filename = alias + wxT( ":" ) + shortPath;
322 
323 #ifdef __WINDOWS__
324  // In KiCad files, filenames and paths are stored using Unix notation
325  model.m_Filename.Replace( wxT( "\\" ), wxT( "/" ) );
326 #endif
327 
328  model.m_Show = true;
329  m_shapes3D_list.push_back( model );
330 
331  int idx = m_modelsGrid->GetNumberRows();
332  m_modelsGrid->AppendRows( 1 );
333  m_modelsGrid->SetCellValue( idx, COL_FILENAME, filename );
334  m_modelsGrid->SetCellValue( idx, COL_SHOWN, wxT( "1" ) );
335 
336  select3DModel( idx );
337  updateValidateStatus( idx );
338 
340 }
341 
342 
344 {
346  return;
347 
348  FP_3DMODEL model;
349 
350  model.m_Show = true;
351  m_shapes3D_list.push_back( model );
352 
353  int row = m_modelsGrid->GetNumberRows();
354  m_modelsGrid->AppendRows( 1 );
355  m_modelsGrid->SetCellValue( row, COL_SHOWN, wxT( "1" ) );
356  m_modelsGrid->SetCellValue( row, COL_PROBLEM, "" );
357 
358  select3DModel( row );
359 
360  m_modelsGrid->SetFocus();
361  m_modelsGrid->MakeCellVisible( row, COL_FILENAME );
362  m_modelsGrid->SetGridCursor( row, COL_FILENAME );
363 
364  m_modelsGrid->EnableCellEditControl( true );
365  m_modelsGrid->ShowCellEditControl();
366 
367  updateValidateStatus( row );
368 }
369 
370 
372 {
373  int icon = 0;
374  wxString errStr;
375 
376  switch( validateModelExists( m_modelsGrid->GetCellValue( aRow, COL_FILENAME) ) )
377  {
379  icon = 0;
380  errStr = "";
381  break;
382 
384  icon = wxICON_WARNING;
385  errStr = _( "No filename entered" );
386  break;
387 
389  icon = wxICON_ERROR;
390  errStr = _( "Illegal filename" );
391  break;
392 
394  icon = wxICON_ERROR;
395  errStr = _( "File not found" );
396  break;
397 
399  icon = wxICON_ERROR;
400  errStr = _( "Unable to open file" );
401  break;
402 
403  default:
404  icon = wxICON_ERROR;
405  errStr = _( "Unknown error" );
406  break;
407  }
408 
409  m_modelsGrid->SetCellValue( aRow, COL_PROBLEM, errStr );
410  m_modelsGrid->SetCellRenderer( aRow, COL_PROBLEM,
411  new GRID_CELL_STATUS_ICON_RENDERER( icon ) );
412 }
413 
414 
416 {
417  if( aFilename.empty() )
419 
420  bool hasAlias = false;
421  FILENAME_RESOLVER* resolv = m_frame->Prj().Get3DFilenameResolver();
422 
423  if( !resolv )
425 
426  if( !resolv->ValidateFileName( aFilename, hasAlias ) )
428 
429  wxString fullPath = resolv->ResolvePath( aFilename );
430 
431  if( fullPath.IsEmpty() )
433 
434  if( !wxFileName::IsFileReadable( fullPath ) )
436 
438 }
439 
440 
441 void PANEL_FP_PROPERTIES_3D_MODEL::Cfg3DPath( wxCommandEvent& event )
442 {
443  if( S3D::Configure3DPaths( this, m_frame->Prj().Get3DCacheManager()->GetResolver() ) )
445 }
446 
447 
449 {
450  // Account for scroll bars
451  int modelsWidth = aWidth - ( m_modelsGrid->GetSize().x - m_modelsGrid->GetClientSize().x );
452 
453  int width = modelsWidth - m_modelsGrid->GetColSize( COL_SHOWN )
454  - m_modelsGrid->GetColSize( COL_PROBLEM ) - 5;
455 
456  m_modelsGrid->SetColSize( COL_FILENAME, width );
457 }
458 
459 
460 void PANEL_FP_PROPERTIES_3D_MODEL::OnUpdateUI( wxUpdateUIEvent& event )
461 {
462  m_button3DShapeRemove->Enable( m_modelsGrid->GetNumberRows() > 0 );
463 }
void SetSelectedModel(int idx)
Set the currently selected index in the model list so that the scale/rotation/offset controls can be ...
void On3DModelSelected(wxGridEvent &) override
Container for project specific data.
Definition: project.h:62
std::list< FP_3DMODEL > & Models()
Definition: footprint.h:183
void OnUpdateUI(wxUpdateUIEvent &event) override
void UpdateDummyFootprint(bool aRelaodRequired=true)
Copy shapes from the current shape list which are flagged for preview to the copy of footprint that i...
This file is part of the common library.
void SetReadOnly(bool aReadOnly)
Definition: json_settings.h:84
bool SplitAlias(const wxString &aFileName, wxString &anAlias, wxString &aRelPath) const
Return true if the given name contains an alias and populates the string anAlias with the alias and a...
bool Select3DModel(wxWindow *aParent, S3D_CACHE *aCache, wxString &prevModelSelectDir, int &prevModelWildcard, FP_3DMODEL *aModel)
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition: grid_tricks.h:55
MODEL_VALIDATE_ERRORS validateModelExists(const wxString &aFilename)
std::vector< FP_3DMODEL > m_shapes3D_list
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition: dialog_shim.h:82
void OnAdd3DModel(wxCommandEvent &event) override
void On3DModelCellChanged(wxGridEvent &aEvent) override
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:106
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition: project.cpp:122
int GetVisibleWidth(int aCol, bool aHeader=true, bool aContents=false, bool aKeep=true)
Calculates the specified column based on the actual size of the text on screen.
Definition: wx_grid.cpp:282
bool ValidateFileName(const wxString &aFileName, bool &hasAlias) const
Returns true if the given path is a valid aliased relative path.
void OnAdd3DRow(wxCommandEvent &event) override
wxString m_lastFootprint3dDir
bool m_Show
Include model in rendering.
Definition: footprint.h:99
void OnRemove3DModel(wxCommandEvent &event) override
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
#define KICAD6_3DMODEL_DIR
A variable name whose value holds the path of 3D shape files.
#define _(s)
Editor for wxGrid cells that adds a file/folder browser to the grid input field.
bool CommitPendingChanges(bool aQuietMode=false)
Close any open cell edit controls.
Definition: wx_grid.cpp:226
wxBitmap KiBitmap(BITMAPS aBitmap, int aHeightTag)
Construct a wxBitmap from an image identifier Returns the image from the active theme if the image ha...
Definition: bitmap.cpp:105
void Cfg3DPath(wxCommandEvent &event) override
virtual void SetRString(RSTRING_T aStringId, const wxString &aString)
Store a "retained string", which is any session and project specific string identified in enum RSTRIN...
Definition: project.cpp:212
wxString ResolvePath(const wxString &aFileName)
Determines the full path of the given file name.
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
wxString m_Filename
The 3D shape filename in 3D library.
Definition: footprint.h:98
virtual const wxString & GetRString(RSTRING_T aStringId)
Return a "retained string", which is any session and project specific string identified in enum RSTRI...
Definition: project.cpp:227
bool Configure3DPaths(wxWindow *aParent, FILENAME_RESOLVER *aResolver)
see class PGM_BASE
Provide an extensible class to resolve 3D model paths.
Common, abstract interface for edit frames.
void ClearRows()
wxWidgets recently added an ASSERT which fires if the position is greater than or equal to the number...
Definition: wx_grid.h:109
Class PANEL_FP_PROPERTIES_3D_MODEL_BASE.
void SetTooltipEnable(int aCol, bool aEnable=true)
Enable the tooltip for a column.
Definition: grid_tricks.h:68
PANEL_FP_PROPERTIES_3D_MODEL(PCB_BASE_EDIT_FRAME *aFrame, FOOTPRINT *aFootprint, DIALOG_SHIM *aDialogParent, wxWindow *aParent, wxWindowID aId=wxID_ANY, const wxPoint &aPos=wxDefaultPosition, const wxSize &aSize=wxDefaultSize, long aStyle=wxTAB_TRAVERSAL, const wxString &aName=wxEmptyString)