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