You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

653 lines
20 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2004-2019 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <wx/wfstream.h>
  25. #include <wx/zipstrm.h>
  26. #include <reporter.h>
  27. #include <html_messagebox.h>
  28. #include <gerbview_frame.h>
  29. #include <gerbview_id.h>
  30. #include <gerber_file_image.h>
  31. #include <gerber_file_image_list.h>
  32. #include <excellon_image.h>
  33. #include <gerbview_layer_widget.h>
  34. #include <wildcards_and_files_ext.h>
  35. #include <widgets/progress_reporter.h>
  36. // HTML Messages used more than one time:
  37. #define MSG_NO_MORE_LAYER _( "<b>No more available layers</b> in Gerbview to load files" )
  38. #define MSG_NOT_LOADED _( "\n<b>Not loaded:</b> <i>%s</i>" )
  39. void GERBVIEW_FRAME::OnGbrFileHistory( wxCommandEvent& event )
  40. {
  41. wxString fn;
  42. fn = GetFileFromHistory( event.GetId(), _( "Gerber files" ) );
  43. if( !fn.IsEmpty() )
  44. {
  45. Erase_Current_DrawLayer( false );
  46. LoadGerberFiles( fn );
  47. }
  48. }
  49. void GERBVIEW_FRAME::OnClearGbrFileHistory( wxCommandEvent& aEvent )
  50. {
  51. ClearFileHistory();
  52. }
  53. void GERBVIEW_FRAME::OnDrlFileHistory( wxCommandEvent& event )
  54. {
  55. wxString fn;
  56. fn = GetFileFromHistory( event.GetId(), _( "Drill files" ), &m_drillFileHistory );
  57. if( !fn.IsEmpty() )
  58. {
  59. Erase_Current_DrawLayer( false );
  60. LoadExcellonFiles( fn );
  61. }
  62. }
  63. void GERBVIEW_FRAME::OnClearDrlFileHistory( wxCommandEvent& aEvent )
  64. {
  65. ClearFileHistory( &m_drillFileHistory );
  66. }
  67. void GERBVIEW_FRAME::OnZipFileHistory( wxCommandEvent& event )
  68. {
  69. wxString filename;
  70. filename = GetFileFromHistory( event.GetId(), _( "Zip files" ), &m_zipFileHistory );
  71. if( !filename.IsEmpty() )
  72. {
  73. Erase_Current_DrawLayer( false );
  74. LoadZipArchiveFile( filename );
  75. }
  76. }
  77. void GERBVIEW_FRAME::OnClearZipFileHistory( wxCommandEvent& aEvent )
  78. {
  79. ClearFileHistory( &m_zipFileHistory );
  80. }
  81. void GERBVIEW_FRAME::OnJobFileHistory( wxCommandEvent& event )
  82. {
  83. wxString filename = GetFileFromHistory( event.GetId(), _( "Job files" ), &m_jobFileHistory );
  84. if( !filename.IsEmpty() )
  85. LoadGerberJobFile( filename );
  86. }
  87. void GERBVIEW_FRAME::OnClearJobFileHistory( wxCommandEvent& aEvent )
  88. {
  89. ClearFileHistory( &m_jobFileHistory );
  90. }
  91. bool GERBVIEW_FRAME::LoadGerberFiles( const wxString& aFullFileName )
  92. {
  93. static int lastGerberFileWildcard = 0;
  94. wxString filetypes;
  95. wxArrayString filenamesList;
  96. wxFileName filename = aFullFileName;
  97. wxString currentPath;
  98. if( !filename.IsOk() )
  99. {
  100. /* Standard gerber filetypes
  101. * (See http://en.wikipedia.org/wiki/Gerber_File)
  102. * The .gbr (.pho in legacy files) extension is the default used in Pcbnew; however
  103. * there are a lot of other extensions used for gerber files. Because the first letter
  104. * is usually g, we accept g* as extension.
  105. * (Mainly internal copper layers do not have specific extension, and filenames are like
  106. * *.g1, *.g2 *.gb1 ...)
  107. * Now (2014) Ucamco (the company which manages the Gerber format) encourages use of .gbr
  108. * only and the Gerber X2 file format.
  109. */
  110. filetypes = _( "Gerber files (.g* .lgr .pho)" );
  111. filetypes << wxT("|");
  112. filetypes += wxT("*.g*;*.G*;*.pho;*.PHO" );
  113. filetypes << wxT("|");
  114. /* Special gerber filetypes */
  115. filetypes += _( "Top layer (*.GTL)|*.GTL;*.gtl|" );
  116. filetypes += _( "Bottom layer (*.GBL)|*.GBL;*.gbl|" );
  117. filetypes += _( "Bottom solder resist (*.GBS)|*.GBS;*.gbs|" );
  118. filetypes += _( "Top solder resist (*.GTS)|*.GTS;*.gts|" );
  119. filetypes += _( "Bottom overlay (*.GBO)|*.GBO;*.gbo|" );
  120. filetypes += _( "Top overlay (*.GTO)|*.GTO;*.gto|" );
  121. filetypes += _( "Bottom paste (*.GBP)|*.GBP;*.gbp|" );
  122. filetypes += _( "Top paste (*.GTP)|*.GTP;*.gtp|" );
  123. filetypes += _( "Keep-out layer (*.GKO)|*.GKO;*.gko|" );
  124. filetypes += _( "Mechanical layers (*.GMx)|*.GM1;*.gm1;*.GM2;*.gm2;*.GM3;*.gm3|" );
  125. filetypes += _( "Top Pad Master (*.GPT)|*.GPT;*.gpt|" );
  126. filetypes += _( "Bottom Pad Master (*.GPB)|*.GPB;*.gpb|" );
  127. // All filetypes
  128. filetypes += AllFilesWildcard();
  129. // Use the current working directory if the file name path does not exist.
  130. if( filename.DirExists() )
  131. currentPath = filename.GetPath();
  132. else
  133. {
  134. currentPath = m_mruPath;
  135. // On wxWidgets 3.1 (bug?) the path in wxFileDialog is ignored when
  136. // finishing by the dir separator. Remove it if any:
  137. if( currentPath.EndsWith( '\\' ) || currentPath.EndsWith( '/' ) )
  138. currentPath.RemoveLast();
  139. }
  140. wxFileDialog dlg( this, _( "Open Gerber File(s)" ), currentPath, filename.GetFullName(),
  141. filetypes,
  142. wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE | wxFD_CHANGE_DIR );
  143. dlg.SetFilterIndex( lastGerberFileWildcard );
  144. if( dlg.ShowModal() == wxID_CANCEL )
  145. return false;
  146. lastGerberFileWildcard = dlg.GetFilterIndex();
  147. dlg.GetPaths( filenamesList );
  148. m_mruPath = currentPath = dlg.GetDirectory();
  149. }
  150. else
  151. {
  152. filenamesList.Add( aFullFileName );
  153. m_mruPath = currentPath = filename.GetPath();
  154. }
  155. Erase_Current_DrawLayer( false );
  156. // Set the busy cursor
  157. wxBusyCursor wait;
  158. return LoadListOfGerberAndDrillFiles( currentPath, filenamesList );
  159. }
  160. bool GERBVIEW_FRAME::LoadListOfGerberAndDrillFiles( const wxString& aPath,
  161. const wxArrayString& aFilenameList,
  162. const std::vector<int>* aFileType )
  163. {
  164. wxFileName filename;
  165. // Read gerber files: each file is loaded on a new GerbView layer
  166. bool success = true;
  167. int layer = GetActiveLayer();
  168. LSET visibility = GetVisibleLayers();
  169. // Manage errors when loading files
  170. wxString msg;
  171. WX_STRING_REPORTER reporter( &msg );
  172. // Create progress dialog (only used if more than 1 file to load
  173. std::unique_ptr<WX_PROGRESS_REPORTER> progress = nullptr;
  174. for( unsigned ii = 0; ii < aFilenameList.GetCount(); ii++ )
  175. {
  176. filename = aFilenameList[ii];
  177. if( !filename.IsAbsolute() )
  178. filename.SetPath( aPath );
  179. // Check for non existing files, to avoid creating broken or useless data
  180. // and report all in one error list:
  181. if( !filename.FileExists() )
  182. {
  183. wxString warning;
  184. warning << "<b>" << _( "File not found:" ) << "</b><br>"
  185. << filename.GetFullPath() << "<br>";
  186. reporter.Report( warning, RPT_SEVERITY_WARNING );
  187. success = false;
  188. continue;
  189. }
  190. m_lastFileName = filename.GetFullPath();
  191. if( !progress && ( aFilenameList.GetCount() > 1 ) )
  192. {
  193. progress = std::make_unique<WX_PROGRESS_REPORTER>( this,
  194. _( "Loading Gerber files..." ), 1, false );
  195. progress->SetMaxProgress( aFilenameList.GetCount() - 1 );
  196. progress->Report( wxString::Format( _("Loading %u/%zu %s" ), ii+1,
  197. aFilenameList.GetCount(), m_lastFileName ) );
  198. }
  199. else if( progress )
  200. {
  201. progress->Report( wxString::Format( _("Loading %u/%zu %s" ), ii+1,
  202. aFilenameList.GetCount(), m_lastFileName ) );
  203. progress->KeepRefreshing();
  204. }
  205. SetActiveLayer( layer, false );
  206. visibility[ layer ] = true;
  207. if( aFileType && (*aFileType)[ii] == 1 )
  208. {
  209. LoadExcellonFiles( filename.GetFullPath() );
  210. layer = GetActiveLayer(); // Loading NC drill file changes the active layer
  211. }
  212. else
  213. {
  214. if( filename.GetExt() == GerberJobFileExtension.c_str() )
  215. {
  216. //We cannot read a gerber job file as a gerber plot file: skip it
  217. wxString txt;
  218. txt.Printf(
  219. _( "<b>A gerber job file cannot be loaded as a plot file</b> <i>%s</i>" ),
  220. filename.GetFullName() );
  221. success = false;
  222. reporter.Report( txt, RPT_SEVERITY_ERROR );
  223. }
  224. else if( Read_GERBER_File( filename.GetFullPath() ) )
  225. {
  226. UpdateFileHistory( m_lastFileName );
  227. layer = getNextAvailableLayer( layer );
  228. if( layer == NO_AVAILABLE_LAYERS && ii < aFilenameList.GetCount()-1 )
  229. {
  230. success = false;
  231. reporter.Report( MSG_NO_MORE_LAYER, RPT_SEVERITY_ERROR );
  232. // Report the name of not loaded files:
  233. ii += 1;
  234. while( ii < aFilenameList.GetCount() )
  235. {
  236. filename = aFilenameList[ii++];
  237. wxString txt = wxString::Format( MSG_NOT_LOADED, filename.GetFullName() );
  238. reporter.Report( txt, RPT_SEVERITY_ERROR );
  239. }
  240. break;
  241. }
  242. SetActiveLayer( layer, false );
  243. }
  244. }
  245. if( progress )
  246. progress->AdvanceProgress();
  247. }
  248. if( !success )
  249. {
  250. wxSafeYield(); // Allows slice of time to redraw the screen
  251. // to refresh widgets, before displaying messages
  252. HTML_MESSAGE_BOX mbox( this, _( "Errors" ) );
  253. mbox.ListSet( msg );
  254. mbox.ShowModal();
  255. }
  256. SetVisibleLayers( visibility );
  257. Zoom_Automatique( false );
  258. // Synchronize layers tools with actual active layer:
  259. ReFillLayerWidget();
  260. // TODO: it would be nice if we could set the active layer to one of the
  261. // ones that was just loaded, but to maintain the previous user experience
  262. // we need to set it to a blank layer in case they load another file.
  263. // We can't start with the next available layer when loading files because
  264. // some users expect the behavior of overwriting the active layer on load.
  265. SetActiveLayer( getNextAvailableLayer( layer ), true );
  266. m_LayersManager->UpdateLayerIcons();
  267. syncLayerBox( true );
  268. GetCanvas()->Refresh();
  269. return success;
  270. }
  271. bool GERBVIEW_FRAME::LoadExcellonFiles( const wxString& aFullFileName )
  272. {
  273. wxString filetypes;
  274. wxArrayString filenamesList;
  275. wxFileName filename = aFullFileName;
  276. wxString currentPath;
  277. if( !filename.IsOk() )
  278. {
  279. filetypes = DrillFileWildcard();
  280. filetypes << wxT( "|" );
  281. /* All filetypes */
  282. filetypes += AllFilesWildcard();
  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. wxFileDialog dlg( this, _( "Open NC (Excellon) Drill File(s)" ),
  289. currentPath, filename.GetFullName(), filetypes,
  290. wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE | wxFD_CHANGE_DIR );
  291. if( dlg.ShowModal() == wxID_CANCEL )
  292. return false;
  293. dlg.GetPaths( filenamesList );
  294. currentPath = wxGetCwd();
  295. m_mruPath = currentPath;
  296. }
  297. else
  298. {
  299. filenamesList.Add( aFullFileName );
  300. currentPath = filename.GetPath();
  301. m_mruPath = currentPath;
  302. }
  303. // Read Excellon drill files: each file is loaded on a new GerbView layer
  304. bool success = true;
  305. int layer = GetActiveLayer();
  306. // Manage errors when loading files
  307. wxString msg;
  308. WX_STRING_REPORTER reporter( &msg );
  309. for( unsigned ii = 0; ii < filenamesList.GetCount(); ii++ )
  310. {
  311. filename = filenamesList[ii];
  312. if( !filename.IsAbsolute() )
  313. filename.SetPath( currentPath );
  314. m_lastFileName = filename.GetFullPath();
  315. SetActiveLayer( layer, false );
  316. if( Read_EXCELLON_File( filename.GetFullPath() ) )
  317. {
  318. // Update the list of recent drill files.
  319. UpdateFileHistory( filename.GetFullPath(), &m_drillFileHistory );
  320. layer = getNextAvailableLayer( layer );
  321. if( layer == NO_AVAILABLE_LAYERS && ii < filenamesList.GetCount()-1 )
  322. {
  323. success = false;
  324. reporter.Report( MSG_NO_MORE_LAYER, RPT_SEVERITY_ERROR );
  325. // Report the name of not loaded files:
  326. ii += 1;
  327. while( ii < filenamesList.GetCount() )
  328. {
  329. filename = filenamesList[ii++];
  330. wxString txt = wxString::Format( MSG_NOT_LOADED, filename.GetFullName() );
  331. reporter.Report( txt, RPT_SEVERITY_ERROR );
  332. }
  333. break;
  334. }
  335. SetActiveLayer( layer, false );
  336. }
  337. }
  338. if( !success )
  339. {
  340. HTML_MESSAGE_BOX mbox( this, _( "Errors" ) );
  341. mbox.ListSet( msg );
  342. mbox.ShowModal();
  343. }
  344. Zoom_Automatique( false );
  345. // Synchronize layers tools with actual active layer:
  346. ReFillLayerWidget();
  347. SetActiveLayer( GetActiveLayer() );
  348. m_LayersManager->UpdateLayerIcons();
  349. syncLayerBox();
  350. return success;
  351. }
  352. bool GERBVIEW_FRAME::unarchiveFiles( const wxString& aFullFileName, REPORTER* aReporter )
  353. {
  354. wxString msg;
  355. // Extract the path of aFullFileName. We use it to store temporary files
  356. wxFileName fn( aFullFileName );
  357. wxString unzipDir = fn.GetPath();
  358. wxFFileInputStream zipFile( aFullFileName );
  359. if( !zipFile.IsOk() )
  360. {
  361. if( aReporter )
  362. {
  363. msg.Printf( _( "Zip file \"%s\" cannot be opened" ), aFullFileName );
  364. aReporter->Report( msg, RPT_SEVERITY_ERROR );
  365. }
  366. return false;
  367. }
  368. // Update the list of recent zip files.
  369. UpdateFileHistory( aFullFileName, &m_zipFileHistory );
  370. // The unzipped file in only a temporary file. Give it a filename
  371. // which cannot conflict with an usual filename.
  372. // TODO: make Read_GERBER_File() and Read_EXCELLON_File() able to
  373. // accept a stream, and avoid using a temp file.
  374. wxFileName temp_fn( "$tempfile.tmp" );
  375. temp_fn.MakeAbsolute( unzipDir );
  376. wxString unzipped_tempfile = temp_fn.GetFullPath();
  377. bool success = true;
  378. wxZipInputStream zipArchive( zipFile );
  379. wxZipEntry* entry;
  380. bool reported_no_more_layer = false;
  381. while( ( entry = zipArchive.GetNextEntry() ) )
  382. {
  383. wxString fname = entry->GetName();
  384. wxFileName uzfn = fname;
  385. wxString curr_ext = uzfn.GetExt().Lower();
  386. // The archive contains Gerber and/or Excellon drill files. Use the right loader.
  387. // However it can contain a few other files (reports, pdf files...),
  388. // which will be skipped.
  389. // Gerber files ext is usually "gbr", but can be also another value, starting by "g"
  390. // old gerber files ext from kicad is .pho
  391. // drill files do not have a well defined ext
  392. // It is .drl in kicad, but .txt in Altium for instance
  393. // Allows only .drl for drill files.
  394. if( curr_ext[0] != 'g' && curr_ext != "pho" && curr_ext != "drl" )
  395. {
  396. if( aReporter )
  397. {
  398. msg.Printf( _( "Info: skip file \"%s\" (unknown type)\n" ), entry->GetName() );
  399. aReporter->Report( msg, RPT_SEVERITY_WARNING );
  400. }
  401. continue;
  402. }
  403. if( curr_ext == GerberJobFileExtension.c_str() )
  404. {
  405. //We cannot read a gerber job file as a gerber plot file: skip it
  406. if( aReporter )
  407. {
  408. msg.Printf( _( "Info: skip file \"%s\" (gerber job file)\n" ), entry->GetName() );
  409. aReporter->Report( msg, RPT_SEVERITY_WARNING );
  410. }
  411. continue;
  412. }
  413. int layer = GetActiveLayer();
  414. if( layer == NO_AVAILABLE_LAYERS )
  415. {
  416. success = false;
  417. if( aReporter )
  418. {
  419. if( !reported_no_more_layer )
  420. aReporter->Report( MSG_NO_MORE_LAYER, RPT_SEVERITY_ERROR );
  421. reported_no_more_layer = true;
  422. // Report the name of not loaded files:
  423. msg.Printf( MSG_NOT_LOADED, GetChars( entry->GetName() ) );
  424. aReporter->Report( msg, RPT_SEVERITY_ERROR );
  425. }
  426. delete entry;
  427. continue;
  428. }
  429. // Create the unzipped temporary file:
  430. {
  431. wxFFileOutputStream temporary_ofile( unzipped_tempfile );
  432. if( temporary_ofile.Ok() )
  433. temporary_ofile.Write( zipArchive );
  434. else
  435. {
  436. success = false;
  437. if( aReporter )
  438. {
  439. msg.Printf( _( "<b>Unable to create temporary file \"%s\"</b>\n"),
  440. unzipped_tempfile );
  441. aReporter->Report( msg, RPT_SEVERITY_ERROR );
  442. }
  443. }
  444. }
  445. bool read_ok = true;
  446. if( curr_ext[0] == 'g' || curr_ext == "pho" )
  447. {
  448. // Read gerber files: each file is loaded on a new GerbView layer
  449. read_ok = Read_GERBER_File( unzipped_tempfile );
  450. }
  451. else // if( curr_ext == "drl" )
  452. {
  453. read_ok = Read_EXCELLON_File( unzipped_tempfile );
  454. }
  455. delete entry;
  456. // The unzipped file is only a temporary file, delete it.
  457. wxRemoveFile( unzipped_tempfile );
  458. if( !read_ok )
  459. {
  460. success = false;
  461. if( aReporter )
  462. {
  463. msg.Printf( _("<b>unzipped file %s read error</b>\n"), unzipped_tempfile );
  464. aReporter->Report( msg, RPT_SEVERITY_ERROR );
  465. }
  466. }
  467. else
  468. {
  469. GERBER_FILE_IMAGE* gerber_image = GetGbrImage( layer );
  470. if( gerber_image )
  471. gerber_image->m_FileName = fname;
  472. layer = getNextAvailableLayer( layer );
  473. SetActiveLayer( layer, false );
  474. }
  475. }
  476. return success;
  477. }
  478. bool GERBVIEW_FRAME::LoadZipArchiveFile( const wxString& aFullFileName )
  479. {
  480. #define ZipFileExtension "zip"
  481. wxFileName filename = aFullFileName;
  482. wxString currentPath;
  483. if( !filename.IsOk() )
  484. {
  485. // Use the current working directory if the file name path does not exist.
  486. if( filename.DirExists() )
  487. currentPath = filename.GetPath();
  488. else
  489. currentPath = m_mruPath;
  490. wxFileDialog dlg( this, _( "Open Zip File" ), currentPath, filename.GetFullName(),
  491. ZipFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_CHANGE_DIR );
  492. if( dlg.ShowModal() == wxID_CANCEL )
  493. return false;
  494. filename = dlg.GetPath();
  495. currentPath = wxGetCwd();
  496. m_mruPath = currentPath;
  497. }
  498. else
  499. {
  500. currentPath = filename.GetPath();
  501. m_mruPath = currentPath;
  502. }
  503. wxString msg;
  504. WX_STRING_REPORTER reporter( &msg );
  505. if( filename.IsOk() )
  506. unarchiveFiles( filename.GetFullPath(), &reporter );
  507. Zoom_Automatique( false );
  508. // Synchronize layers tools with actual active layer:
  509. ReFillLayerWidget();
  510. SetActiveLayer( GetActiveLayer() );
  511. m_LayersManager->UpdateLayerIcons();
  512. syncLayerBox();
  513. if( !msg.IsEmpty() )
  514. {
  515. wxSafeYield(); // Allows slice of time to redraw the screen
  516. // to refresh widgets, before displaying messages
  517. HTML_MESSAGE_BOX mbox( this, _( "Messages" ) );
  518. mbox.ListSet( msg );
  519. mbox.ShowModal();
  520. }
  521. return true;
  522. }