KiCad PCB EDA Suite
gerbview/files.cpp
Go to the documentation of this file.
1 
5 /*
6  * This program source code file is part of KiCad, a free EDA CAD application.
7  *
8  * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
9  * Copyright (C) 2004-2017 KiCad Developers, see AUTHORS.txt for contributors.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, you may find one here:
23  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
24  * or you may search the http://www.gnu.org website for the version 2 license,
25  * or you may write to the Free Software Foundation, Inc.,
26  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
27  */
28 
29 #include <fctsys.h>
30 #include <wx/fs_zip.h>
31 #include <wx/wfstream.h>
32 #include <wx/zipstrm.h>
33 
34 #include <common.h>
35 #include <class_drawpanel.h>
36 #include <reporter.h>
37 #include <html_messagebox.h>
38 
39 #include <gerbview_frame.h>
40 #include <gerbview_id.h>
43 
44 // HTML Messages used more than one time:
45 #define MSG_NO_MORE_LAYER\
46  _( "<b>No more available free graphic layer</b> in Gerbview to load files" )
47 #define MSG_NOT_LOADED _( "\n<b>Not loaded:</b> <i>%s</i>" )
48 
49 void GERBVIEW_FRAME::OnGbrFileHistory( wxCommandEvent& event )
50 {
51  wxString fn;
52 
53  fn = GetFileFromHistory( event.GetId(), _( "Gerber files" ) );
54 
55  if( !fn.IsEmpty() )
56  {
57  Erase_Current_DrawLayer( false );
58  LoadGerberFiles( fn );
59  }
60 }
61 
62 
63 void GERBVIEW_FRAME::OnDrlFileHistory( wxCommandEvent& event )
64 {
65  wxString fn;
66 
67  fn = GetFileFromHistory( event.GetId(), _( "Drill files" ), &m_drillFileHistory );
68 
69  if( !fn.IsEmpty() )
70  {
71  Erase_Current_DrawLayer( false );
72  LoadExcellonFiles( fn );
73  }
74 }
75 
76 void GERBVIEW_FRAME::OnZipFileHistory( wxCommandEvent& event )
77 {
78  wxString filename;
79  filename = GetFileFromHistory( event.GetId(), _( "Zip files" ), &m_zipFileHistory );
80 
81  if( !filename.IsEmpty() )
82  {
83  Erase_Current_DrawLayer( false );
84  LoadZipArchiveFile( filename );
85  }
86 }
87 
88 
89 /* File commands. */
90 void GERBVIEW_FRAME::Files_io( wxCommandEvent& event )
91 {
92  int id = event.GetId();
93 
94  switch( id )
95  {
96  case wxID_FILE:
97  Erase_Current_DrawLayer( false );
98  LoadGerberFiles( wxEmptyString );
99  break;
100 
102  Clear_DrawLayers( false );
103  Zoom_Automatique( false );
104  m_canvas->Refresh();
105  ClearMsgPanel();
106  break;
107 
109  LoadExcellonFiles( wxEmptyString );
110  m_canvas->Refresh();
111  break;
112 
114  LoadZipArchiveFile( wxEmptyString );
115  m_canvas->Refresh();
116  break;
117 
118  default:
119  wxFAIL_MSG( wxT( "File_io: unexpected command id" ) );
120  break;
121  }
122 }
123 
124 
125 bool GERBVIEW_FRAME::LoadGerberFiles( const wxString& aFullFileName )
126 {
127  wxString filetypes;
128  wxArrayString filenamesList;
129  wxFileName filename = aFullFileName;
130  wxString currentPath;
131 
132  if( !filename.IsOk() )
133  {
134  /* Standard gerber filetypes
135  * (See http://en.wikipedia.org/wiki/Gerber_File)
136  * the .gbr (.pho in legacy files) extension is the default used in Pcbnew
137  * However there are a lot of other extensions used for gerber files
138  * Because the first letter is usually g, we accept g* as extension
139  * (Mainly internal copper layers do not have specific extension,
140  * and filenames are like *.g1, *.g2 *.gb1 ...).
141  * Now (2014) Ucamco (the company which manages the Gerber format) encourages
142  * use of .gbr only and the Gerber X2 file format.
143  */
144  filetypes = _( "Gerber files (.g* .lgr .pho)" );
145  filetypes << wxT("|");
146  filetypes += wxT("*.g*;*.G*;*.pho;*.PHO" );
147  filetypes << wxT("|");
148 
149  /* Special gerber filetypes */
150  filetypes += _( "Top layer (*.GTL)|*.GTL;*.gtl|" );
151  filetypes += _( "Bottom layer (*.GBL)|*.GBL;*.gbl|" );
152  filetypes += _( "Bottom solder resist (*.GBS)|*.GBS;*.gbs|" );
153  filetypes += _( "Top solder resist (*.GTS)|*.GTS;*.gts|" );
154  filetypes += _( "Bottom overlay (*.GBO)|*.GBO;*.gbo|" );
155  filetypes += _( "Top overlay (*.GTO)|*.GTO;*.gto|" );
156  filetypes += _( "Bottom paste (*.GBP)|*.GBP;*.gbp|" );
157  filetypes += _( "Top paste (*.GTP)|*.GTP;*.gtp|" );
158  filetypes += _( "Keep-out layer (*.GKO)|*.GKO;*.gko|" );
159  filetypes += _( "Mechanical layers (*.GMx)|*.GM1;*.gm1;*.GM2;*.gm2;*.GM3;*.gm3|" );
160  filetypes += _( "Top Pad Master (*.GPT)|*.GPT;*.gpt|" );
161  filetypes += _( "Bottom Pad Master (*.GPB)|*.GPB;*.gpb|" );
162 
163  // All filetypes
164  filetypes += AllFilesWildcard;
165 
166  // Use the current working directory if the file name path does not exist.
167  if( filename.DirExists() )
168  currentPath = filename.GetPath();
169  else
170  {
171  currentPath = m_mruPath;
172 
173  // On wxWidgets 3.1 (bug?) the path in wxFileDialog is ignored when
174  // finishing by the dir separator. Remove it if any:
175  if( currentPath.EndsWith( '\\' ) || currentPath.EndsWith( '/' ) )
176  currentPath.RemoveLast();
177  }
178 
179  wxFileDialog dlg( this, _( "Open Gerber File" ),
180  currentPath,
181  filename.GetFullName(),
182  filetypes,
183  wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE | wxFD_CHANGE_DIR );
184 
185  if( dlg.ShowModal() == wxID_CANCEL )
186  return false;
187 
188  dlg.GetPaths( filenamesList );
189 
190  // @todo Take a closer look at the CWD switching here. The current working directory
191  // gets changed by wxFileDialog because the wxFD_CHANGE_DIR flag is set. Is this the
192  // appropriate behavior? The current working directory is not returned to the previous
193  // value so this may be an issue elsewhere.
194  currentPath = wxGetCwd();
195  m_mruPath = currentPath;
196  }
197  else
198  {
199  filenamesList.Add( aFullFileName );
200  currentPath = filename.GetPath();
201  m_mruPath = currentPath;
202  }
203 
204  // Read gerber files: each file is loaded on a new GerbView layer
205  bool success = true;
206  int layer = getActiveLayer();
207 
208  // Manage errors when loading files
209  wxString msg;
210  WX_STRING_REPORTER reporter( &msg );
211 
212  for( unsigned ii = 0; ii < filenamesList.GetCount(); ii++ )
213  {
214  filename = filenamesList[ii];
215 
216  if( !filename.IsAbsolute() )
217  filename.SetPath( currentPath );
218 
219  m_lastFileName = filename.GetFullPath();
220 
221  setActiveLayer( layer, false );
222 
223  if( Read_GERBER_File( filename.GetFullPath() ) )
224  {
226 
227  layer = getNextAvailableLayer( layer );
228 
229  if( layer == NO_AVAILABLE_LAYERS && ii < filenamesList.GetCount()-1 )
230  {
231  success = false;
233 
234  // Report the name of not loaded files:
235  ii += 1;
236  while( ii < filenamesList.GetCount() )
237  {
238  filename = filenamesList[ii++];
239  wxString txt;
240  txt.Printf( MSG_NOT_LOADED,
241  GetChars( filename.GetFullName() ) );
242  reporter.Report( txt, REPORTER::RPT_ERROR );
243  }
244  break;
245  }
246 
247  setActiveLayer( layer, false );
248  }
249  }
250 
251  if( !success )
252  {
253  HTML_MESSAGE_BOX mbox( this, _( "Errors" ) );
254  mbox.ListSet( msg );
255  mbox.ShowModal();
256  }
257 
258  Zoom_Automatique( false );
259 
260  // Synchronize layers tools with actual active layer:
264  syncLayerBox();
265  return success;
266 }
267 
268 
269 bool GERBVIEW_FRAME::LoadExcellonFiles( const wxString& aFullFileName )
270 {
271  wxString filetypes;
272  wxArrayString filenamesList;
273  wxFileName filename = aFullFileName;
274  wxString currentPath;
275 
276  if( !filename.IsOk() )
277  {
278  filetypes = wxGetTranslation( DrillFileWildcard );
279  filetypes << wxT("|");
280  /* All filetypes */
281  filetypes += wxGetTranslation( AllFilesWildcard );
282 
283  /* Use the current working directory if the file name path does not exist. */
284  if( filename.DirExists() )
285  currentPath = filename.GetPath();
286  else
287  currentPath = m_mruPath;
288 
289  wxFileDialog dlg( this, _( "Open Drill File" ),
290  currentPath, filename.GetFullName(), filetypes,
291  wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE | wxFD_CHANGE_DIR );
292 
293  if( dlg.ShowModal() == wxID_CANCEL )
294  return false;
295 
296  dlg.GetPaths( filenamesList );
297  currentPath = wxGetCwd();
298  m_mruPath = currentPath;
299  }
300  else
301  {
302  filenamesList.Add( aFullFileName );
303  currentPath = filename.GetPath();
304  m_mruPath = currentPath;
305  }
306 
307  // Read Excellon drill files: each file is loaded on a new GerbView layer
308  bool success = true;
309  int layer = getActiveLayer();
310 
311  // Manage errors when loading files
312  wxString msg;
313  WX_STRING_REPORTER reporter( &msg );
314 
315  for( unsigned ii = 0; ii < filenamesList.GetCount(); ii++ )
316  {
317  filename = filenamesList[ii];
318 
319  if( !filename.IsAbsolute() )
320  filename.SetPath( currentPath );
321 
322  m_lastFileName = filename.GetFullPath();
323 
324  setActiveLayer( layer, false );
325 
326  if( Read_EXCELLON_File( filename.GetFullPath() ) )
327  {
328  // Update the list of recent drill files.
329  UpdateFileHistory( filename.GetFullPath(), &m_drillFileHistory );
330 
331  layer = getNextAvailableLayer( layer );
332 
333  if( layer == NO_AVAILABLE_LAYERS && ii < filenamesList.GetCount()-1 )
334  {
335  success = false;
337 
338  // Report the name of not loaded files:
339  ii += 1;
340  while( ii < filenamesList.GetCount() )
341  {
342  filename = filenamesList[ii++];
343  wxString txt;
344  txt.Printf( MSG_NOT_LOADED,
345  GetChars( filename.GetFullName() ) );
346  reporter.Report( txt, REPORTER::RPT_ERROR );
347  }
348  break;
349  }
350 
351  setActiveLayer( layer, false );
352  }
353  }
354 
355  if( !success )
356  {
357  HTML_MESSAGE_BOX mbox( this, _( "Errors" ) );
358  mbox.ListSet( msg );
359  mbox.ShowModal();
360  }
361 
362  Zoom_Automatique( false );
363 
364  // Synchronize layers tools with actual active layer:
368  syncLayerBox();
369 
370  return success;
371 }
372 
373 
374 bool GERBVIEW_FRAME::unarchiveFiles( const wxString& aFullFileName, REPORTER* aReporter )
375 {
376  wxFileSystem zipfilesys;
377 
378  zipfilesys.AddHandler( new wxZipFSHandler );
379  zipfilesys.ChangePathTo( aFullFileName + wxT( "#zip:" ), true );
380 
381  wxString localfilename = zipfilesys.FindFirst( wxT( "*.*" ) );
382  wxFileName fn( aFullFileName );
383  wxString unzipDir = fn.GetPath();
384 
385  // Update the list of recent zip files.
386  UpdateFileHistory( aFullFileName, &m_zipFileHistory );
387 
388  bool success = true;
389  wxString msg;
390 
391  while( !localfilename.IsEmpty() )
392  {
393  wxFSFile* zipfile = zipfilesys.OpenFile( localfilename );
394 
395  if( !zipfile )
396  {
397  if( aReporter )
398  {
399  msg.Printf( _( "Zip file '%s' cannot be read" ), GetChars( aFullFileName ) );
400  aReporter->Report( msg, REPORTER::RPT_ERROR );
401  }
402  success = false;
403  break;
404  }
405 
406  // In order to load a file in this archive, this file is unzipped and
407  // a temporary file is created in the same folder as the archive.
408  // This file will be deleted after being loaded in the viewer.
409  // One other (and better) way is to load it from the memory image, but currently
410  // Read_GERBER_File and Read_EXCELLON_File expects a file.
411  wxFileName uzfn = localfilename.AfterLast( ':' );
412  uzfn.MakeAbsolute( unzipDir );
413 
414  // The unzipped file in only a temporary file. Give it a filename
415  // which cannot conflict with an usual gerber or drill file
416  // by adding a '$' to its ext.
417  wxString unzipfilename = uzfn.GetFullPath() + "$";
418 
419  wxInputStream* stream = zipfile->GetStream();
420  wxFFileOutputStream* temporary_ofile = new wxFFileOutputStream( unzipfilename );
421 
422  if( temporary_ofile->Ok() )
423  temporary_ofile->Write( *stream );
424  else
425  {
426  success = false;
427  }
428 
429  // Close streams:
430  delete temporary_ofile;
431  delete zipfile;
432 
433  // The archive contains Gerber and/or Excellon drill files. Use the right loader.
434  // However it can contain a few other files (reports, pdf files...),
435  // which will be skipped.
436  // Gerber files ext is usually "gbr", but can be also an other value, starting by "g"
437  // old gerber files ext from kicad is .pho
438  // drill files do not have a well defined ext
439  // It is .drl in kicad, but .txt in Altium for instance
440  // Allows only .drl for drill files.
441  int layer = getActiveLayer();
442  setActiveLayer( layer, false );
443  bool read_ok = true;
444  bool skip_file = false;
445 
446  wxString curr_ext = uzfn.GetExt().Lower();
447 
448  if( curr_ext[0] == 'g' || curr_ext == "pho" )
449  {
450  // Read gerber files: each file is loaded on a new GerbView layer
451  read_ok = Read_GERBER_File( unzipfilename );
452  }
453  else if( curr_ext == "drl" )
454  {
455  read_ok = Read_EXCELLON_File( unzipfilename );
456  }
457  else // if the ext is not "pho", "g*" or "drl",
458  {
459  // the type is unknown for Gerbview. Skip it
460  skip_file = true;
461  success = false;
462 
463  if( aReporter )
464  {
465  msg.Printf( _( "Info: skip <i>%s</i> (unknown type)\n" ),
466  GetChars( localfilename.AfterLast( ':' ) ) );
467  aReporter->Report( msg, REPORTER::RPT_WARNING );
468  }
469  }
470 
471  // The unzipped file is only a temporary file, delete it.
472  wxRemoveFile( unzipfilename );
473 
474  if( !read_ok && !skip_file)
475  {
476  success = false;
477 
478  if( aReporter )
479  {
480  msg.Printf( _("<b>file %s read error</b>\n"), GetChars( unzipfilename ) );
481  aReporter->Report( msg, REPORTER::RPT_ERROR );
482  }
483  }
484 
485  // Prepare the loading of the next file in archive, if exists
486  localfilename = zipfilesys.FindNext();
487 
488  if( !skip_file )
489  {
490  if( read_ok )
491  {
492  layer = getNextAvailableLayer( layer );
493 
494  if( layer == NO_AVAILABLE_LAYERS && !localfilename.IsEmpty() )
495  {
496  success = false;
497 
498  if( aReporter )
499  {
501 
502  // Report the name of not loaded files:
503  while( !localfilename.IsEmpty() )
504  {
505  msg.Printf( MSG_NOT_LOADED,
506  GetChars( localfilename.AfterLast( ':' ) ) );
507  aReporter->Report( msg, REPORTER::RPT_ERROR );
508  localfilename = zipfilesys.FindNext();
509  }
510  }
511  break;
512  }
513  }
514 
515  setActiveLayer( layer, false );
516  }
517  }
518 
519  return success;
520 }
521 
522 
523 bool GERBVIEW_FRAME::LoadZipArchiveFile( const wxString& aFullFileName )
524 {
525 #define ZipFileExtension "zip"
526 #define ZipFileWildcard _( "Zip file (*.zip)|*.zip" )
527  wxFileName filename = aFullFileName;
528  wxString currentPath;
529 
530  if( !filename.IsOk() )
531  {
532  // Use the current working directory if the file name path does not exist.
533  if( filename.DirExists() )
534  currentPath = filename.GetPath();
535  else
536  currentPath = m_mruPath;
537 
538  wxFileDialog dlg( this,
539  _( "Open Zip File" ),
540  currentPath,
541  filename.GetFullName(),
543  wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_CHANGE_DIR );
544 
545  if( dlg.ShowModal() == wxID_CANCEL )
546  return false;
547 
548  filename = dlg.GetPath();
549  currentPath = wxGetCwd();
550  m_mruPath = currentPath;
551  }
552  else
553  {
554  currentPath = filename.GetPath();
555  m_mruPath = currentPath;
556  }
557 
558  wxString msg;
559  WX_STRING_REPORTER reporter( &msg );
560 
561  if( filename.IsOk() )
562  unarchiveFiles( filename.GetFullPath(), &reporter );
563 
564  Zoom_Automatique( false );
565 
566  // Synchronize layers tools with actual active layer:
570  syncLayerBox();
571 
572  if( !msg.IsEmpty() )
573  {
574  wxSafeYield(); // Allows slice of time to redraw the screen
575  // to refresh widgets, before displaying messages
576  HTML_MESSAGE_BOX mbox( this, _( "Messages" ) );
577  mbox.ListSet( msg );
578  mbox.ShowModal();
579  }
580 
581  return true;
582 }
wxString m_lastFileName
The last filename chosen to be proposed to the user.
bool Clear_DrawLayers(bool query)
wxString m_mruPath
Most recently used path.
Definition: wxstruct.h:155
void Files_io(wxCommandEvent &event)
virtual void Refresh(bool eraseBackground=true, const wxRect *rect=NULL) override
Definition: draw_panel.cpp:326
void syncLayerBox(bool aRebuildLayerBox=false)
Function syncLayerBox updates the currently "selected" layer within m_SelLayerBox The currently activ...
const wxString AllFilesWildcard
GERBER_LAYER_WIDGET * m_LayersManager
bool Read_GERBER_File(const wxString &GERBER_FullFileName)
Definition: readgerb.cpp:39
void OnGbrFileHistory(wxCommandEvent &event)
Function OnGbrFileHistory deletes the current data and loads a Gerber file selected from history list...
wxFileHistory m_drillFileHistory
Class REPORTER is a pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:61
void Erase_Current_DrawLayer(bool query)
void UpdateFileHistory(const wxString &FullFileName, wxFileHistory *aFileHistory=NULL)
Function UpdateFileHistory Updates the list of recently opened files.
Definition: basicframe.cpp:388
int getNextAvailableLayer(int aLayer=0) const
Function getNextAvailableLayer finds the next empty layer starting at aLayer and returns it to the ca...
const wxString DrillFileWildcard
#define MSG_NOT_LOADED
void Zoom_Automatique(bool aWarpPointer)
Function Zoom_Automatique redraws the screen with best zoom level and the best centering that shows a...
Definition: zoom.cpp:77
#define ZipFileWildcard
REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_UNDEFINED) override
Function Report is a pure virtual function to override in the derived object.
Definition: reporter.cpp:48
bool unarchiveFiles(const wxString &aFullFileName, REPORTER *aReporter=nullptr)
Extracts gerber and drill files from the zip archive, and load them.
#define NO_AVAILABLE_LAYERS
bool LoadGerberFiles(const wxString &aFileName)
function LoadGerberFiles Load a photoplot (Gerber) file or many files.
void ListSet(const wxString &aList)
Function ListSet Add a list of items.
bool LoadZipArchiveFile(const wxString &aFileName)
function LoadZipArchiveFileLoadZipArchiveFile Load a zipped archive file.
Subclass of DIALOG_DISPLAY_HTML_TEXT_BASE, which is generated by wxFormBuilder.
void UpdateLayerIcons()
Function UpdateLayerIcons Update all layer manager icons (layers only) Useful when loading a file or ...
The common library.
Class HTML_MESSAGE_BOX.
#define MSG_NO_MORE_LAYER
void setActiveLayer(int aLayer, bool doLayerWidgetUpdate=true)
Function setActiveLayer will change the currently active layer to aLayer and also update the GERBER_L...
Class WX_STRING_REPORTER is a wrapper for reporting to a wxString object.
Definition: reporter.h:114
wxFileHistory m_zipFileHistory
EDA_DRAW_PANEL * m_canvas
The area to draw on.
Definition: draw_frame.h:92
static const wxChar * GetChars(const wxString &s)
Function GetChars returns a wxChar* to the actual wxChar* data within a wxString, and is helpful for ...
Definition: macros.h:92
wxString GetFileFromHistory(int cmdId, const wxString &type, wxFileHistory *aFileHistory=NULL)
Function GetFileFromHistory fetches the file name from the file history list.
Definition: basicframe.cpp:400
int getActiveLayer()
Function getActiveLayer returns the active layer.
The common library.
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_UNDEFINED)=0
Function Report is a pure virtual function to override in the derived object.
void OnDrlFileHistory(wxCommandEvent &event)
Function OnDrlFileHistory deletes the current data and load a drill file in Excellon format selected ...
bool LoadExcellonFiles(const wxString &aFileName)
function LoadExcellonFiles Load a drill (EXCELLON) file or many files.
void OnZipFileHistory(wxCommandEvent &event)
Function OnZipFileHistory deletes the current data and load a zip archive file selected from the hist...
void ClearMsgPanel(void)
Clear all messages from the message panel.
Definition: draw_frame.cpp:746
void ReFillLayerWidget()
Function ReFillLayerWidget changes out all the layers in m_Layers and may be called upon loading new ...
bool Read_EXCELLON_File(const wxString &aFullFileName)