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>
44 
45 // HTML Messages used more than one time:
46 #define MSG_NO_MORE_LAYER\
47  _( "<b>No more available free graphic layer</b> in Gerbview to load files" )
48 #define MSG_NOT_LOADED _( "\n<b>Not loaded:</b> <i>%s</i>" )
49 
50 void GERBVIEW_FRAME::OnGbrFileHistory( wxCommandEvent& event )
51 {
52  wxString fn;
53 
54  fn = GetFileFromHistory( event.GetId(), _( "Gerber files" ) );
55 
56  if( !fn.IsEmpty() )
57  {
58  Erase_Current_DrawLayer( false );
59  LoadGerberFiles( fn );
60  }
61 }
62 
63 
64 void GERBVIEW_FRAME::OnDrlFileHistory( wxCommandEvent& event )
65 {
66  wxString fn;
67 
68  fn = GetFileFromHistory( event.GetId(), _( "Drill files" ), &m_drillFileHistory );
69 
70  if( !fn.IsEmpty() )
71  {
72  Erase_Current_DrawLayer( false );
73  LoadExcellonFiles( fn );
74  }
75 }
76 
77 void GERBVIEW_FRAME::OnZipFileHistory( wxCommandEvent& event )
78 {
79  wxString filename;
80  filename = GetFileFromHistory( event.GetId(), _( "Zip files" ), &m_zipFileHistory );
81 
82  if( !filename.IsEmpty() )
83  {
84  Erase_Current_DrawLayer( false );
85  LoadZipArchiveFile( filename );
86  }
87 }
88 
89 
90 /* File commands. */
91 void GERBVIEW_FRAME::Files_io( wxCommandEvent& event )
92 {
93  int id = event.GetId();
94 
95  switch( id )
96  {
97  case wxID_FILE:
98  Erase_Current_DrawLayer( false );
99  LoadGerberFiles( wxEmptyString );
100  break;
101 
103  Clear_DrawLayers( false );
104  Zoom_Automatique( false );
105  m_canvas->Refresh();
106  ClearMsgPanel();
107  break;
108 
110  LoadExcellonFiles( wxEmptyString );
111  m_canvas->Refresh();
112  break;
113 
115  LoadZipArchiveFile( wxEmptyString );
116  m_canvas->Refresh();
117  break;
118 
119  default:
120  wxFAIL_MSG( wxT( "File_io: unexpected command id" ) );
121  break;
122  }
123 }
124 
125 
126 bool GERBVIEW_FRAME::LoadGerberFiles( const wxString& aFullFileName )
127 {
128  wxString filetypes;
129  wxArrayString filenamesList;
130  wxFileName filename = aFullFileName;
131  wxString currentPath;
132 
133  if( !filename.IsOk() )
134  {
135  /* Standard gerber filetypes
136  * (See http://en.wikipedia.org/wiki/Gerber_File)
137  * the .gbr (.pho in legacy files) extension is the default used in Pcbnew
138  * However there are a lot of other extensions used for gerber files
139  * Because the first letter is usually g, we accept g* as extension
140  * (Mainly internal copper layers do not have specific extension,
141  * and filenames are like *.g1, *.g2 *.gb1 ...).
142  * Now (2014) Ucamco (the company which manages the Gerber format) encourages
143  * use of .gbr only and the Gerber X2 file format.
144  */
145  filetypes = _( "Gerber files (.g* .lgr .pho)" );
146  filetypes << wxT("|");
147  filetypes += wxT("*.g*;*.G*;*.pho;*.PHO" );
148  filetypes << wxT("|");
149 
150  /* Special gerber filetypes */
151  filetypes += _( "Top layer (*.GTL)|*.GTL;*.gtl|" );
152  filetypes += _( "Bottom layer (*.GBL)|*.GBL;*.gbl|" );
153  filetypes += _( "Bottom solder resist (*.GBS)|*.GBS;*.gbs|" );
154  filetypes += _( "Top solder resist (*.GTS)|*.GTS;*.gts|" );
155  filetypes += _( "Bottom overlay (*.GBO)|*.GBO;*.gbo|" );
156  filetypes += _( "Top overlay (*.GTO)|*.GTO;*.gto|" );
157  filetypes += _( "Bottom paste (*.GBP)|*.GBP;*.gbp|" );
158  filetypes += _( "Top paste (*.GTP)|*.GTP;*.gtp|" );
159  filetypes += _( "Keep-out layer (*.GKO)|*.GKO;*.gko|" );
160  filetypes += _( "Mechanical layers (*.GMx)|*.GM1;*.gm1;*.GM2;*.gm2;*.GM3;*.gm3|" );
161  filetypes += _( "Top Pad Master (*.GPT)|*.GPT;*.gpt|" );
162  filetypes += _( "Bottom Pad Master (*.GPB)|*.GPB;*.gpb|" );
163 
164  // All filetypes
165  filetypes += AllFilesWildcard;
166 
167  // Use the current working directory if the file name path does not exist.
168  if( filename.DirExists() )
169  currentPath = filename.GetPath();
170  else
171  {
172  currentPath = m_mruPath;
173 
174  // On wxWidgets 3.1 (bug?) the path in wxFileDialog is ignored when
175  // finishing by the dir separator. Remove it if any:
176  if( currentPath.EndsWith( '\\' ) || currentPath.EndsWith( '/' ) )
177  currentPath.RemoveLast();
178  }
179 
180  wxFileDialog dlg( this, _( "Open Gerber File" ),
181  currentPath,
182  filename.GetFullName(),
183  filetypes,
184  wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE | wxFD_CHANGE_DIR );
185 
186  if( dlg.ShowModal() == wxID_CANCEL )
187  return false;
188 
189  dlg.GetPaths( filenamesList );
190 
191  // @todo Take a closer look at the CWD switching here. The current working directory
192  // gets changed by wxFileDialog because the wxFD_CHANGE_DIR flag is set. Is this the
193  // appropriate behavior? The current working directory is not returned to the previous
194  // value so this may be an issue elsewhere.
195  currentPath = wxGetCwd();
196  m_mruPath = currentPath;
197  }
198  else
199  {
200  filenamesList.Add( aFullFileName );
201  currentPath = filename.GetPath();
202  m_mruPath = currentPath;
203  }
204 
205  // Read gerber files: each file is loaded on a new GerbView layer
206  bool success = true;
207  int layer = getActiveLayer();
208 
209  // Manage errors when loading files
210  wxString msg;
211  WX_STRING_REPORTER reporter( &msg );
212 
213  for( unsigned ii = 0; ii < filenamesList.GetCount(); ii++ )
214  {
215  filename = filenamesList[ii];
216 
217  if( !filename.IsAbsolute() )
218  filename.SetPath( currentPath );
219 
220  m_lastFileName = filename.GetFullPath();
221 
222  setActiveLayer( layer, false );
223 
224  if( Read_GERBER_File( filename.GetFullPath() ) )
225  {
227 
228  layer = getNextAvailableLayer( layer );
229 
230  if( layer == NO_AVAILABLE_LAYERS && ii < filenamesList.GetCount()-1 )
231  {
232  success = false;
234 
235  // Report the name of not loaded files:
236  ii += 1;
237  while( ii < filenamesList.GetCount() )
238  {
239  filename = filenamesList[ii++];
240  wxString txt;
241  txt.Printf( MSG_NOT_LOADED,
242  GetChars( filename.GetFullName() ) );
243  reporter.Report( txt, REPORTER::RPT_ERROR );
244  }
245  break;
246  }
247 
248  setActiveLayer( layer, false );
249  }
250  }
251 
252  if( !success )
253  {
254  HTML_MESSAGE_BOX mbox( this, _( "Errors" ) );
255  mbox.ListSet( msg );
256  mbox.ShowModal();
257  }
258 
259  Zoom_Automatique( false );
260 
261  // Synchronize layers tools with actual active layer:
265  syncLayerBox();
266  return success;
267 }
268 
269 
270 bool GERBVIEW_FRAME::LoadExcellonFiles( const wxString& aFullFileName )
271 {
272  wxString filetypes;
273  wxArrayString filenamesList;
274  wxFileName filename = aFullFileName;
275  wxString currentPath;
276 
277  if( !filename.IsOk() )
278  {
279  filetypes = wxGetTranslation( DrillFileWildcard );
280  filetypes << wxT("|");
281  /* All filetypes */
282  filetypes += wxGetTranslation( AllFilesWildcard );
283 
284  /* Use the current working directory if the file name path does not exist. */
285  if( filename.DirExists() )
286  currentPath = filename.GetPath();
287  else
288  currentPath = m_mruPath;
289 
290  wxFileDialog dlg( this, _( "Open Drill File" ),
291  currentPath, filename.GetFullName(), filetypes,
292  wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE | wxFD_CHANGE_DIR );
293 
294  if( dlg.ShowModal() == wxID_CANCEL )
295  return false;
296 
297  dlg.GetPaths( filenamesList );
298  currentPath = wxGetCwd();
299  m_mruPath = currentPath;
300  }
301  else
302  {
303  filenamesList.Add( aFullFileName );
304  currentPath = filename.GetPath();
305  m_mruPath = currentPath;
306  }
307 
308  // Read Excellon drill files: each file is loaded on a new GerbView layer
309  bool success = true;
310  int layer = getActiveLayer();
311 
312  // Manage errors when loading files
313  wxString msg;
314  WX_STRING_REPORTER reporter( &msg );
315 
316  for( unsigned ii = 0; ii < filenamesList.GetCount(); ii++ )
317  {
318  filename = filenamesList[ii];
319 
320  if( !filename.IsAbsolute() )
321  filename.SetPath( currentPath );
322 
323  m_lastFileName = filename.GetFullPath();
324 
325  setActiveLayer( layer, false );
326 
327  if( Read_EXCELLON_File( filename.GetFullPath() ) )
328  {
329  // Update the list of recent drill files.
330  UpdateFileHistory( filename.GetFullPath(), &m_drillFileHistory );
331 
332  layer = getNextAvailableLayer( layer );
333 
334  if( layer == NO_AVAILABLE_LAYERS && ii < filenamesList.GetCount()-1 )
335  {
336  success = false;
338 
339  // Report the name of not loaded files:
340  ii += 1;
341  while( ii < filenamesList.GetCount() )
342  {
343  filename = filenamesList[ii++];
344  wxString txt;
345  txt.Printf( MSG_NOT_LOADED,
346  GetChars( filename.GetFullName() ) );
347  reporter.Report( txt, REPORTER::RPT_ERROR );
348  }
349  break;
350  }
351 
352  setActiveLayer( layer, false );
353  }
354  }
355 
356  if( !success )
357  {
358  HTML_MESSAGE_BOX mbox( this, _( "Errors" ) );
359  mbox.ListSet( msg );
360  mbox.ShowModal();
361  }
362 
363  Zoom_Automatique( false );
364 
365  // Synchronize layers tools with actual active layer:
369  syncLayerBox();
370 
371  return success;
372 }
373 
374 
375 bool GERBVIEW_FRAME::unarchiveFiles( const wxString& aFullFileName, REPORTER* aReporter )
376 {
377  wxString msg;
378 
379  // Extract the path of aFullFileName. We use it to store temporary files
380  wxFileName fn( aFullFileName );
381  wxString unzipDir = fn.GetPath();
382 
383  wxFFileInputStream zipFile( aFullFileName );
384 
385  if( !zipFile.IsOk() )
386  {
387  if( aReporter )
388  {
389  msg.Printf( _( "Zip file '%s' cannot be opened" ), GetChars( aFullFileName ) );
390  aReporter->Report( msg, REPORTER::RPT_ERROR );
391  }
392 
393  return false;
394  }
395 
396  // Update the list of recent zip files.
397  UpdateFileHistory( aFullFileName, &m_zipFileHistory );
398 
399  // The unzipped file in only a temporary file. Give it a filename
400  // which cannot conflict with an usual filename.
401  // TODO: make Read_GERBER_File() and Read_EXCELLON_File() able to
402  // accept a stream, and avoid using a temp file.
403  wxFileName temp_fn( "$tempfile.tmp" );
404  temp_fn.MakeAbsolute( unzipDir );
405  wxString unzipped_tempfile = temp_fn.GetFullPath();
406 
407 
408  bool success = true;
409  wxZipInputStream zipArchive( zipFile );
410  wxZipEntry* entry;
411  bool reported_no_more_layer = false;
412 
413  while( ( entry = zipArchive.GetNextEntry() ) )
414  {
415  wxString fname = entry->GetName();
416  wxFileName uzfn = fname;
417  wxString curr_ext = uzfn.GetExt().Lower();
418 
419  // The archive contains Gerber and/or Excellon drill files. Use the right loader.
420  // However it can contain a few other files (reports, pdf files...),
421  // which will be skipped.
422  // Gerber files ext is usually "gbr", but can be also an other value, starting by "g"
423  // old gerber files ext from kicad is .pho
424  // drill files do not have a well defined ext
425  // It is .drl in kicad, but .txt in Altium for instance
426  // Allows only .drl for drill files.
427  if( curr_ext[0] != 'g' && curr_ext != "pho" && curr_ext != "drl" )
428  {
429  if( aReporter )
430  {
431  msg.Printf( _( "Info: skip file <i>'%s'</i> (unknown type)\n" ),
432  GetChars( entry->GetName() ) );
433  aReporter->Report( msg, REPORTER::RPT_WARNING );
434  }
435 
436  continue;
437  }
438 
439  int layer = getActiveLayer();
440 
441  if( layer == NO_AVAILABLE_LAYERS )
442  {
443  success = false;
444 
445  if( aReporter )
446  {
447  if( !reported_no_more_layer )
449 
450  reported_no_more_layer = true;
451 
452  // Report the name of not loaded files:
453  msg.Printf( MSG_NOT_LOADED, GetChars( entry->GetName() ) );
454  aReporter->Report( msg, REPORTER::RPT_ERROR );
455  }
456 
457  delete entry;
458  continue;
459  }
460 
461  // Create the unzipped temporary file:
462  {
463  wxFFileOutputStream temporary_ofile( unzipped_tempfile );
464 
465  if( temporary_ofile.Ok() )
466  temporary_ofile.Write( zipArchive );
467  else
468  {
469  success = false;
470 
471  if( aReporter )
472  {
473  msg.Printf( _( "<b>Unable to create temporary file '%s'</b>\n"),
474  GetChars( unzipped_tempfile ) );
475  aReporter->Report( msg, REPORTER::RPT_ERROR );
476  }
477  }
478  }
479 
480  bool read_ok = true;
481 
482  if( curr_ext[0] == 'g' || curr_ext == "pho" )
483  {
484  // Read gerber files: each file is loaded on a new GerbView layer
485  read_ok = Read_GERBER_File( unzipped_tempfile );
486  }
487  else // if( curr_ext == "drl" )
488  {
489  read_ok = Read_EXCELLON_File( unzipped_tempfile );
490  }
491 
492  delete entry;
493 
494  // The unzipped file is only a temporary file, delete it.
495  wxRemoveFile( unzipped_tempfile );
496 
497  if( !read_ok )
498  {
499  success = false;
500 
501  if( aReporter )
502  {
503  msg.Printf( _("<b>unzipped file %s read error</b>\n"),
504  GetChars( unzipped_tempfile ) );
505  aReporter->Report( msg, REPORTER::RPT_ERROR );
506  }
507  }
508  else
509  {
510  GERBER_FILE_IMAGE* gerber_image = GetGbrImage( layer );
511  gerber_image->m_FileName = fname;
512 
513  layer = getNextAvailableLayer( layer );
514  setActiveLayer( layer, false );
515  }
516  }
517 
518  return success;
519 }
520 
521 
522 bool GERBVIEW_FRAME::LoadZipArchiveFile( const wxString& aFullFileName )
523 {
524 #define ZipFileExtension "zip"
525 #define ZipFileWildcard _( "Zip file (*.zip)|*.zip;.zip" )
526  wxFileName filename = aFullFileName;
527  wxString currentPath;
528 
529  if( !filename.IsOk() )
530  {
531  // Use the current working directory if the file name path does not exist.
532  if( filename.DirExists() )
533  currentPath = filename.GetPath();
534  else
535  currentPath = m_mruPath;
536 
537  wxFileDialog dlg( this,
538  _( "Open Zip File" ),
539  currentPath,
540  filename.GetFullName(),
542  wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_CHANGE_DIR );
543 
544  if( dlg.ShowModal() == wxID_CANCEL )
545  return false;
546 
547  filename = dlg.GetPath();
548  currentPath = wxGetCwd();
549  m_mruPath = currentPath;
550  }
551  else
552  {
553  currentPath = filename.GetPath();
554  m_mruPath = currentPath;
555  }
556 
557  wxString msg;
558  WX_STRING_REPORTER reporter( &msg );
559 
560  if( filename.IsOk() )
561  unarchiveFiles( filename.GetFullPath(), &reporter );
562 
563  Zoom_Automatique( false );
564 
565  // Synchronize layers tools with actual active layer:
569  syncLayerBox();
570 
571  if( !msg.IsEmpty() )
572  {
573  wxSafeYield(); // Allows slice of time to redraw the screen
574  // to refresh widgets, before displaying messages
575  HTML_MESSAGE_BOX mbox( this, _( "Messages" ) );
576  mbox.ListSet( msg );
577  mbox.ShowModal();
578  }
579 
580  return true;
581 }
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
Class GERBER_FILE_IMAGE holds the Image data and parameters for one gerber file and layer parameters ...
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...
GERBER_FILE_IMAGE * GetGbrImage(int aIdx) const
void ClearMsgPanel(void)
Clear all messages from the message panel.
Definition: draw_frame.cpp:745
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)