KiCad PCB EDA Suite
wizard_3DShape_Libs_downloader.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) 2015 CERN
5  * Code derived from "wizard_add_fplib.cpp" ( author Maciej Suminski <maciej.suminski@cern.ch> )
6  * Copyright (C) 2014-2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
7  * Copyright (C) 1992-2015 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 
35 #include <wx/wx.h>
36 #include <wx/uri.h>
37 #include <wx/dir.h>
38 #include <wx/progdlg.h>
39 
40 #include <pgm_base.h>
41 #include <project.h>
43 #include <confirm.h>
45 #include <bitmaps.h>
47 
48 #include <../github/github_getliblist.h>
49 
50 // a key to store the default Kicad Github 3D libs URL
51 #define KICAD_3DLIBS_URL_KEY wxT( "kicad_3Dlib_url" )
52 #define KICAD_3DLIBS_LAST_DOWNLOAD_DIR wxT( "kicad_3Dlib_last_download_dir" )
53 
54 #define DEFAULT_GITHUB_3DSHAPES_LIBS_URL \
55  "https://github.com/KiCad/kicad-packages3d"
56 // wxT( "https://github.com/KiCad/kicad-library/tree/master/modules/packages3d" )
57 
58 void Invoke3DShapeLibsDownloaderWizard( wxWindow* aCaller )
59 {
60  WIZARD_3DSHAPE_LIBS_DOWNLOADER wizard( aCaller );
61  wizard.RunWizard( wizard.GetFirstPage() );
62 }
63 
64 
67 {
68  m_welcomeDlg = m_pages[0];
70  m_reviewDlg = m_pages[2];
71 
72  // Initialize default download dir (local target folder of 3D shapes libs)
73  wxString default_path;
74  wxGetEnv( KISYS3DMOD, &default_path );
75 
76  auto cfg = Pgm().GetCommonSettings();
77 
78  setDownloadDir( cfg->m_3DLibsDownloadPath.empty() ? default_path : cfg->m_3DLibsDownloadPath );
79 
80  // Restore the Github 3D shapes libs url
81  wxString githubUrl = cfg->m_3DLibsUrl;
82 
83  if( githubUrl.IsEmpty() )
85 
86  SetGithubURL( githubUrl );
87 
88 
89  // Give the minimal size to the dialog, which allows displaying any page
90  wxSize minsize;
91 
92  for( unsigned ii = 0; ii < m_pages.size(); ii++ )
93  {
94  wxSize size = m_pages[ii]->GetSizer()->CalcMin();
95  minsize.x = std::max( minsize.x, size.x );
96  minsize.y = std::max( minsize.y, size.y );
97  }
98 
99  SetMinSize( minsize );
100  SetPageSize( minsize );
101  GetSizer()->SetSizeHints( this );
102  Center();
103 
106 
107  // When starting m_textCtrlGithubURL has the focus, and the text is selected,
108  // and not fully visible.
109  // Forcing deselection does not work, at least on W7 with wxWidgets 3.0.2
110  // So (and also because m_textCtrlGithubURL and m_downloadDir are rarely modified
111  // the focus is given to another widget.
112  m_hyperlinkGithubKicad->SetFocus();
113 
114  Connect( wxEVT_RADIOBUTTON, wxCommandEventHandler( WIZARD_3DSHAPE_LIBS_DOWNLOADER::OnSourceCheck ), NULL, this );
115  Connect( wxEVT_CHECKLISTBOX, wxCommandEventHandler( WIZARD_3DSHAPE_LIBS_DOWNLOADER::OnCheckGithubList ), NULL, this );
116 }
117 
118 
120 {
121  auto cfg = Pgm().GetCommonSettings();
122 
123  cfg->m_3DLibsUrl = GetGithubURL().ToStdString();
124  cfg->m_3DLibsDownloadPath = getDownloadDir().ToStdString();
125 }
126 
127 
128 
130 {
131  SetBitmap( KiBitmap( wizard_add_fplib_icon_xpm ) );
132  enableNext( true );
133 
134  if( GetCurrentPage() == m_githubListDlg )
135  setupGithubList();
136  else if( GetCurrentPage() == m_reviewDlg )
137  setupReview();
138 }
139 
140 
142 {
143  wxArrayInt dummy;
144 
145  enableNext( m_checkList3Dlibnames->GetCheckedItems( dummy ) > 0 );
146 }
147 
148 
149 void WIZARD_3DSHAPE_LIBS_DOWNLOADER::OnSourceCheck( wxCommandEvent& aEvent )
150 {
153 }
154 
156 {
157  // Adjust the width of the column 1 of m_gridLibReview (library names) to the
158  // max available width.
159  int gridwidth = m_gridLibReview->GetClientSize().x;
160  gridwidth -= m_gridLibReview->GetColSize( 0 ) + m_gridLibReview->GetColLabelSize();
161 
162  if( gridwidth < 200 )
163  gridwidth = 200;
164 
165  m_gridLibReview->SetColSize( 1, gridwidth );
166 
167  event.Skip();
168 }
169 
170 
172 {
173  // Prepare the last page of the wizard.
174 
175  m_LocalFolderInfo->SetLabel( getDownloadDir() );
176 
177  wxArrayInt checkedIndices;
178  m_checkList3Dlibnames->GetCheckedItems( checkedIndices );
179 
180  m_libraries.Clear();
181 
182  // populate m_libraries with the name of libraries, without the github path:
183  for( unsigned int ii = 0; ii < checkedIndices.GetCount(); ++ii )
184  {
185  m_libraries.Add( m_checkList3Dlibnames->GetString( checkedIndices[ii] ).AfterLast( '/' ) );
186  }
187 
188  // Adjust number of rows in m_gridLibReview:
189  int delta = m_libraries.GetCount() - m_gridLibReview->GetNumberRows();
190 
191  if( delta < 0 )
192  m_gridLibReview->DeleteRows( -delta );
193  else if( delta > 0 )
194  m_gridLibReview->AppendRows( delta );
195 
196  // For user info, verify the existence of these libs in local folder
197  wxArrayString liblist;
198  wxFileName fn;
199  fn.AssignDir( getDownloadDir() );
200 
201  for( unsigned int ii = 0; ii < m_libraries.GetCount(); ++ii )
202  {
203  fn.SetName( m_libraries[ii] );
204 
205  wxDir dirs;
206  bool isNew = ! dirs.Exists( fn.GetFullPath() );
207  wxString info = isNew ? _( "New" ) : _( "Update" );
208 
209  liblist.Add( info + wxT(" ") + m_libraries[ii] );
210 
211  m_gridLibReview->SetCellValue( ii, 0, info );
212  m_gridLibReview->SetCellValue( ii, 1, m_libraries[ii] );
213  }
214 
215  m_gridLibReview->AutoSizeColumn( 0 );
216 }
217 
218 
220 {
221  for( unsigned int i = 0; i < m_checkList3Dlibnames->GetCount(); ++i )
222  m_checkList3Dlibnames->Check( i, true );
223 
224  // The list might be empty, e.g. in case of download error
225  wxArrayInt dummy;
226  enableNext( m_checkList3Dlibnames->GetCheckedItems( dummy ) > 0 );
227 }
228 
229 
231 {
232  for( unsigned int i = 0; i < m_checkList3Dlibnames->GetCount(); ++i )
233  m_checkList3Dlibnames->Check( i, false );
234 
235  enableNext( false );
236 }
237 
238 
240 {
241  wxString searchPhrase = m_searchCtrl3Dlibs->GetValue().Lower();
242 
243  // Store the current selection
244  wxArrayInt checkedIndices;
245  m_checkList3Dlibnames->GetCheckedItems( checkedIndices );
246  wxArrayString checkedStrings;
247 
248  for( unsigned int i = 0; i < checkedIndices.GetCount(); ++i )
249  checkedStrings.Add( m_checkList3Dlibnames->GetString( checkedIndices[i] ).AfterLast( '/' ) );
250 
251  m_checkList3Dlibnames->Clear();
252 
253  // Rebuild the list, putting the matching entries on the top
254  int matching = 0; // number of entries matching the search phrase
255  for( unsigned int i = 0; i < m_githubLibs.GetCount(); ++i )
256  {
257  const wxString& lib = m_githubLibs[i].AfterLast( '/' );
258  bool wasChecked = ( checkedStrings.Index( lib ) != wxNOT_FOUND );
259  int insertedIdx = -1;
260 
261  if( !searchPhrase.IsEmpty() && lib.Lower().BeforeLast( '.' ).Contains( searchPhrase ) )
262  {
263  insertedIdx = m_checkList3Dlibnames->Insert( lib, matching++ );
264  m_checkList3Dlibnames->SetSelection( insertedIdx );
265  }
266  else
267  insertedIdx = m_checkList3Dlibnames->Append( lib );
268 
269  if( wasChecked )
270  m_checkList3Dlibnames->Check( insertedIdx );
271  }
272 
273  if( !m_checkList3Dlibnames->IsEmpty() )
274  m_checkList3Dlibnames->EnsureVisible( 0 );
275 }
276 
277 
279 {
280  // we download a localy copy of the libraries
281  wxString error;
282 
283  if( !downloadGithubLibsFromList( m_libraries, &error ) )
284  {
285  DisplayError( this, error );
286  }
287 }
288 
289 
291 {
292  wxString path = getDownloadDir();
293 
294  path = wxDirSelector( _("Choose a folder to save the downloaded libraries" ),
295  path, 0, wxDefaultPosition, this );
296 
297  if( !path.IsEmpty() && wxDirExists( path ) )
298  {
299  setDownloadDir( path );
301  }
302 }
303 
304 
306 {
307  wxString default_path;
308  wxGetEnv( KISYS3DMOD, &default_path );
309 
310  if( !default_path.IsEmpty() && wxDirExists( default_path ) )
311  {
312  setDownloadDir( default_path );
314  }
315  else
316  wxMessageBox( _( "KISYS3DMOD path not defined , or not existing" ) );
317 }
318 
319 
321 {
323 }
324 
325 
327 {
328  wxBeginBusyCursor();
329 
330  // Be sure there is no trailing '/' at the end of the repo name
331  wxString git_url = m_textCtrlGithubURL->GetValue();
332 
333  if( git_url.EndsWith( wxT( "/" ) ) )
334  {
335  git_url.RemoveLast();
336  m_textCtrlGithubURL->SetValue( git_url );
337  }
338 
339  GITHUB_GETLIBLIST getter( git_url );
341 
342  wxEndBusyCursor();
343 }
344 
345 
346 // Download the .3Dshapes libraries folders found in aUrlList and store them on disk
347 // in a master folder
349  wxString* aErrorMessage )
350 {
351  // Display a progress bar to show the download state
352  // The title is updated for each downloaded library.
353  // the state will be updated by downloadOneLib() for each file.
354  // for OSX do not enable wPD_APP_MODAL, keep wxPD_AUTO_HIDE
355  wxProgressDialog pdlg( _( "Downloading 3D libraries" ), wxEmptyString,
356  aUrlList.GetCount(), this,
357 #ifndef __WXMAC__
358  wxPD_APP_MODAL |
359 #endif
360  wxPD_CAN_ABORT | wxPD_AUTO_HIDE );
361 
362  // Built the full server name string:
363  wxURI repo( GetGithubURL() );
364  wxString server = repo.GetScheme() + "://" + repo.GetServer();
365 
366  // Download libs:
367  for( size_t ii = 0; ii < aUrlList.GetCount(); ii++ )
368  {
369  wxString& libsrc_name = aUrlList[ii];
370 
371  // Recover the full URL lib from short name:
372  // (note: m_githubLibs stores the URL relative to the server name)
373  wxString url;
374 
375  for( unsigned jj = 0; jj < m_githubLibs.GetCount(); jj++ )
376  {
377  if( m_githubLibs[jj].EndsWith( libsrc_name ) )
378  {
379  url = server + m_githubLibs[jj];
380  break;
381  }
382  }
383 
384  wxFileName fn( libsrc_name );
385  // Set our local path
386  fn.SetPath( getDownloadDir() );
387  wxString libdst_name = fn.GetFullPath();
388 
389  // Display the name of the library to download in the wxProgressDialog
390  pdlg.SetTitle( wxString::Format( wxT("%s [%lu/%lu]" ),
391  libsrc_name.AfterLast( '/' ).GetData(),
392  ii + 1, aUrlList.GetCount() ) );
393 
394  if( !wxDirExists( libdst_name ) )
395  wxMkdir( libdst_name );
396 
397  if( !downloadOneLib( url, libdst_name, &pdlg, aErrorMessage ) )
398  return false;
399  }
400 
401  return true;
402 }
403 
404 
406  const wxString& aLocalLibName, wxProgressDialog* aIndicator,
407  wxString* aErrorMessage )
408 {
409  wxArrayString fileslist;
410 
411  bool success;
412 
413  // Get the list of candidate files: with ext .wrl .stp .step .STEP .STP or .wings
414  do
415  {
416  GITHUB_GETLIBLIST getter( aLibURL );
417  success = getter.Get3DshapesLibsList( &fileslist, filter3dshapesfiles );
418  } while( 0 );
419 
420  if( !success )
421  return false;
422 
423  // Load each file in list:
424  wxURI repo( aLibURL );
425 
426  wxString server = repo.GetServer();
427 
428  // Github gives the current url of files inside .3dshapes folders like:
429  // "https://github.com/KiCad/kicad-library/blob/master/modules/packages3d/Capacitors_SMD.3dshapes/C_0402.wrl"
430  // which displays a html page showing the file in html form.
431  //
432  // the URL of the corresponding raw file is
433  // "https://github.com/KiCad/kicad-library/raw/master/modules/packages3d/Capacitors_SMD.3dshapes/C_0402.wrl"
434  //
435  // However Github redirects this current url to raw.githubusercontent.com/fullfilename
436  // when trying to download raw files.
437  // "https://github.com/KiCad/kicad-library/raw/master/modules/packages3d/Capacitors_SMD.3dshapes/C_0402.wrl"
438  // would be redirected to:
439  // "https://raw.githubusercontent.com/KiCad/kicad-library/master/modules/packages3d/Capacitors_SMD.3dshapes/C_0402.wrl"
440  // So use raw.githubusercontent.com instead of github.com
441  // (and removes the "/raw" in path) speed up the downloads (x2 faster).
442  //
443  // wxURI has no way to change the server name, so we need to use tricks to make the URL.
444  //
445  // Comment this next line to use the github.com URL
446 #define FORCE_GITHUB_RAW_URL
447 
448 #ifdef FORCE_GITHUB_RAW_URL
449  if( server.Cmp( wxT( "github.com" ) ) == 0 )
450  server = wxT( "raw.githubusercontent.com" );
451 #endif
452 
453  wxString full_url_base = repo.GetScheme() + wxT( "://" ) + server;
454  wxString target_full_url;
455 
456  for( unsigned ii = 0; ii < fileslist.GetCount(); ii++ )
457  {
458  target_full_url = full_url_base + fileslist[ii];
459 
460 #ifdef FORCE_GITHUB_RAW_URL
461  // Remove "blob/" in URL string to build the URL on "raw.githubusercontent.com"
462  // server from "github.com" URL string:
463  target_full_url.Replace( wxT( "blob/" ), wxT( "" ) );
464 #else
465  // Replace "blob" by "raw" in URL to access the raw file itself, not the html page
466  // on "github.com" server
467  target_full_url.Replace( wxT( "blob" ), wxT( "raw" ) );
468 #endif
469  aIndicator->SetRange( fileslist.GetCount() );
470  bool abort = !aIndicator->Update( ii, target_full_url.AfterLast( '/' ) );
471 
472  if( abort )
473  {
474  if( aErrorMessage )
475  *aErrorMessage << _( "Aborted by user" );
476  return false;
477  }
478 
479  // Download the current file.
480  // Get3DshapesLibsList actually downloads and stores the target_full_url content.
481  GITHUB_GETLIBLIST getter( target_full_url );
482  success = getter.Get3DshapesLibsList( NULL, NULL );
483 
484  if( !success )
485  break;
486 
487  wxFileName fn;
488  fn.AssignDir( aLocalLibName );
489  fn.SetFullName( fileslist[ii].AfterLast( '/' ) );
490 
491  // The entire downloaded file is stored in getter buffer
492  const std::string& buffer = getter.GetBuffer();
493 
494  // Write is "as this". It can be a binary file.
495  wxFile file(fn.GetFullPath(), wxFile::write);
496  file.Write( &buffer[0], buffer.size() );
497  }
498 
499  return success;
500 }
501 
502 
504 {
505  // Enable 'Next' only if there is at least one library selected
506  wxArrayInt checkedIndices;
507  m_checkList3Dlibnames->GetCheckedItems( checkedIndices );
508  enableNext( checkedIndices.GetCount() > 0 );
509 
510  // Update only if the text has changed or the list is empty
511  if( m_githubLibs.GetCount() == 0 || m_textCtrlGithubURL->IsModified() )
512  {
513  m_githubLibs.Clear();
515 
516  // Populate the list
517  m_checkList3Dlibnames->Clear();
518  for( unsigned int i = 0; i < m_githubLibs.GetCount(); ++i )
519  {
520  const wxString& lib = m_githubLibs[i].AfterLast( '/' );
521  m_checkList3Dlibnames->Append( lib );
522  }
523 
524  m_textCtrlGithubURL->SetModified( 0 );
525  }
526 
527  if( !m_checkList3Dlibnames->IsEmpty() )
528  m_checkList3Dlibnames->EnsureVisible( 0 );
529 
530  // Clear the search box
531  m_searchCtrl3Dlibs->Clear();
532 
533  // Clear the review list so it will be reloaded
534  m_libraries.clear();
535 }
536 
537 
539 {
540  bool valid = wxFileName::IsDirWritable( getDownloadDir() );
541 
542  // Shows or not the warning text if the target 3d folder does not exist, or is not
543  // writable.
544  m_invalidDirWarningText->Show( !valid );
545  m_bitmapDirWarn->Show( !valid );
546 
547  // If the dialog starts with m_invalidDirWarningText and m_bitmapDirWarn not shown
548  // the size and position of the sizer containing these widgets can be incorrect,
549  // until a wxSizeEvent is fired, and the widgets are not shown, or truncated,
550  // at least on Windows. So fire a dummy wxSizeEvent if the size looks bad
551  if( m_invalidDirWarningText->IsShown() && m_invalidDirWarningText->GetSize().x < 2 )
552  {
553  wxSizeEvent event( GetSize() );
554  wxPostEvent( this, event );
555  }
556 
557  // Allow to go further only if there is a valid target directory selected
558  enableNext( valid );
559 }
560 
561 // Called when the local folder name is edited.
563 {
565 }
566 
567 
569 {
570  m_welcomeDlg->SetNext( m_githubListDlg );
571  m_githubListDlg->SetPrev( m_welcomeDlg );
572  m_githubListDlg->SetNext( m_reviewDlg );
573  m_reviewDlg->SetPrev( m_githubListDlg );
574 }
575 
void DisplayError(wxWindow *aParent, const wxString &aText, int aDisplayTime)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:239
wxArrayString m_libraries
Libraries names selected in the wizard
void OnWizardFinished(wxWizardEvent &aEvent) override
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:104
bool downloadGithubLibsFromList(wxArrayString &aUrlList, wxString *aErrorMessage)
Saves a list of Github libraries locally.
This file is part of the common library.
void OnUnselectAll3Dlibs(wxCommandEvent &aEvent) override
void OnChangeSearch(wxCommandEvent &aEvent) override
Called when the content of m_searchCtrl3Dlibs has changed.
void Invoke3DShapeLibsDownloaderWizard(wxWindow *aCaller)
Function Invoke3DShapeLibsDownloaderWizard Runs the downloader wizard for easy 3D shape libraries dow...
void OnBrowseButtonClick(wxCommandEvent &aEvent) override
#define KISYS3DMOD
A variable name whose value holds the path of 3D shape files.
Definition: eda_3d_viewer.h:45
wxWizardPage * GetFirstPage() const
Function GetFirstPage Returns the welcoming page for the wizard.
wxArrayString m_githubLibs
Cache for the downloaded Github library list
wxString getDownloadDir()
Gets the current target for downloaded libraries
void OnCheckGithubList(wxCommandEvent &aEvent)
void updateGithubControls()
Enables Github widgets depending on the selected options.
void enableNext(bool aEnable)
Enables/disable 'Next' button
wxBitmap KiBitmap(BITMAP_DEF aBitmap)
Construct a wxBitmap from a memory record, held in a BITMAP_DEF.
Definition: bitmap.cpp:80
void getLibsListGithub(wxArrayString &aList)
Downloads the list of Github libraries
bool Get3DshapesLibsList(wxArrayString *aList, bool(*aFilter)(const wxString &aData))
Fills aList by the URL of libraries found on the github repo.
#define NULL
const BITMAP_OPAQUE wizard_add_fplib_icon_xpm[1]
void OnSelectAll3Dlibs(wxCommandEvent &aEvent) override
static bool filter3dshapesfiles(const wxString &aData)
void OnGridLibReviewSize(wxSizeEvent &event) override
bool downloadOneLib(const wxString &aLibURL, const wxString &aLocalLibName, wxProgressDialog *aIndicator, wxString *aErrorMessage)
Saves a Github library aLibURL locally in aLocalLibName.
Class WIZARD_3DSHAPE_LIBS_DOWNLOADER_BASE.
void OnLocalFolderChange(wxCommandEvent &event) override
void SetGithubURL(const wxString &aUrl)
Function SetGithubURL Sets the current Github repository URL used by the wizard.
void OnPageChanged(wxWizardEvent &aEvent) override
static bool filter3dshapeslibraries(const wxString &aData)
see class PGM_BASE
Declaration of the eda_3d_viewer class.
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:205
static LIB_PART * dummy()
Used to draw a dummy shape when a LIB_PART is not found in library.
#define _(s)
Definition: 3d_actions.cpp:33
wxString GetGithubURL() const
Function GetGithubURL Returns the current Github repository URL set in the wizard.
void setDownloadDir(const wxString &aDir)
Sets the target directory for libraries downloaded from Github
void OnDefault3DPathButtonClick(wxCommandEvent &event) override
#define DEFAULT_GITHUB_3DSHAPES_LIBS_URL
std::string & GetBuffer()
GITHUB_GETLIBLIST implements a portion of pcbnew's PLUGIN interface to provide read only access to a ...