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.

338 lines
11 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2012 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2022 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 <pcb_edit_frame.h>
  25. #include <pcbnew_settings.h>
  26. #include <wildcards_and_files_ext.h>
  27. #include <reporter.h>
  28. #include <board_design_settings.h>
  29. #include <confirm.h>
  30. #include <core/arraydim.h>
  31. #include <core/kicad_algo.h>
  32. #include <pcbplot.h>
  33. #include <locale_io.h>
  34. #include <board.h>
  35. #include <dialog_export_svg_base.h>
  36. #include <bitmaps.h>
  37. #include <widgets/unit_binder.h>
  38. #include <widgets/wx_html_report_panel.h>
  39. #include <plotters/plotters_pslike.h>
  40. #include <wx/dirdlg.h>
  41. #include <pgm_base.h>
  42. #include <pcb_plot_svg.h>
  43. class DIALOG_EXPORT_SVG : public DIALOG_EXPORT_SVG_BASE
  44. {
  45. public:
  46. DIALOG_EXPORT_SVG( PCB_EDIT_FRAME* aParent, BOARD* aBoard );
  47. ~DIALOG_EXPORT_SVG() override;
  48. private:
  49. BOARD* m_board;
  50. PCB_EDIT_FRAME* m_parent;
  51. LSET m_printMaskLayer;
  52. // the list of existing board layers in wxCheckListBox, with the
  53. // board layers id:
  54. std::pair<wxCheckListBox*, int> m_boxSelectLayer[PCB_LAYER_ID_COUNT];
  55. bool m_printBW;
  56. wxString m_outputDirectory;
  57. bool m_printMirror;
  58. bool m_oneFileOnly;
  59. void initDialog();
  60. void OnButtonPlot( wxCommandEvent& event ) override;
  61. void onPagePerLayerClicked( wxCommandEvent& event ) override;
  62. void OnOutputDirectoryBrowseClicked( wxCommandEvent& event ) override;
  63. void ExportSVGFile( bool aOnlyOneFile );
  64. LSET getCheckBoxSelectedLayers() const;
  65. };
  66. /*
  67. * DIALOG_EXPORT_SVG functions
  68. */
  69. DIALOG_EXPORT_SVG::DIALOG_EXPORT_SVG( PCB_EDIT_FRAME* aParent, BOARD* aBoard ) :
  70. DIALOG_EXPORT_SVG_BASE( aParent ),
  71. m_board( aBoard ),
  72. m_parent( aParent ),
  73. m_printBW( false ),
  74. m_printMirror( false ),
  75. m_oneFileOnly( false )
  76. {
  77. m_browseButton->SetBitmap( KiBitmap( BITMAPS::small_folder ) );
  78. m_messagesPanel->SetFileName( Prj().GetProjectPath() + wxT( "report.txt" ) );
  79. initDialog();
  80. SetupStandardButtons( { { wxID_OK, _( "Export" ) },
  81. { wxID_CANCEL, _( "Close" ) } } );
  82. finishDialogSettings();
  83. }
  84. DIALOG_EXPORT_SVG::~DIALOG_EXPORT_SVG()
  85. {
  86. m_printBW = m_ModeColorOption->GetSelection();
  87. m_oneFileOnly = !m_checkboxPagePerLayer->GetValue();
  88. m_outputDirectory = m_outputDirectoryName->GetValue();
  89. m_outputDirectory.Replace( wxT( "\\" ), wxT( "/" ) );
  90. auto cfg = m_parent->GetPcbNewSettings();
  91. cfg->m_ExportSvg.black_and_white = m_printBW;
  92. cfg->m_ExportSvg.mirror = m_printMirror;
  93. cfg->m_ExportSvg.one_file = m_oneFileOnly;
  94. cfg->m_ExportSvg.page_size = m_rbSvgPageSizeOpt->GetSelection();
  95. cfg->m_ExportSvg.output_dir = m_outputDirectory.ToStdString();
  96. if( m_checkboxPagePerLayer->GetValue() )
  97. {
  98. m_oneFileOnly = false;
  99. cfg->m_ExportSvg.plot_board_edges = m_checkboxEdgesOnAllPages->GetValue();
  100. }
  101. else
  102. {
  103. m_oneFileOnly = true;
  104. }
  105. cfg->m_ExportSvg.layers.clear();
  106. for( unsigned layer = 0; layer < arrayDim( m_boxSelectLayer ); ++layer )
  107. {
  108. if( !m_boxSelectLayer[layer].first )
  109. continue;
  110. if( m_boxSelectLayer[layer].first->IsChecked( m_boxSelectLayer[layer].second ) )
  111. cfg->m_ExportSvg.layers.push_back( layer );
  112. }
  113. }
  114. void DIALOG_EXPORT_SVG::initDialog()
  115. {
  116. PCBNEW_SETTINGS* cfg = m_parent->GetPcbNewSettings();
  117. m_printBW = cfg->m_ExportSvg.black_and_white;
  118. m_printMirror = cfg->m_ExportSvg.mirror;
  119. m_oneFileOnly = cfg->m_ExportSvg.one_file;
  120. m_outputDirectory = cfg->m_ExportSvg.output_dir;
  121. m_rbSvgPageSizeOpt->SetSelection( cfg->m_ExportSvg.page_size );
  122. m_checkboxPagePerLayer->SetValue( !m_oneFileOnly );
  123. wxCommandEvent dummy;
  124. onPagePerLayerClicked( dummy );
  125. m_outputDirectoryName->SetValue( m_outputDirectory );
  126. m_ModeColorOption->SetSelection( m_printBW ? 1 : 0 );
  127. m_printMirrorOpt->SetValue( m_printMirror );
  128. for( LSEQ seq = m_board->GetEnabledLayers().UIOrder(); seq; ++seq )
  129. {
  130. PCB_LAYER_ID layer = *seq;
  131. int checkIndex;
  132. if( IsCopperLayer( layer ) )
  133. {
  134. checkIndex = m_CopperLayersList->Append( m_board->GetLayerName( layer ) );
  135. m_boxSelectLayer[layer] = std::make_pair( m_CopperLayersList, checkIndex );
  136. }
  137. else
  138. {
  139. checkIndex = m_TechnicalLayersList->Append( m_board->GetLayerName( layer ) );
  140. m_boxSelectLayer[layer] = std::make_pair( m_TechnicalLayersList, checkIndex );
  141. }
  142. if( alg::contains( cfg->m_ExportSvg.layers, layer ) )
  143. m_boxSelectLayer[layer].first->Check( checkIndex, true );
  144. }
  145. }
  146. LSET DIALOG_EXPORT_SVG::getCheckBoxSelectedLayers() const
  147. {
  148. LSET ret;
  149. for( unsigned layer = 0; layer < arrayDim(m_boxSelectLayer); ++layer )
  150. {
  151. if( !m_boxSelectLayer[layer].first )
  152. continue;
  153. if( m_boxSelectLayer[layer].first->IsChecked( m_boxSelectLayer[layer].second ) )
  154. ret.set( layer );
  155. }
  156. return ret;
  157. }
  158. void DIALOG_EXPORT_SVG::OnOutputDirectoryBrowseClicked( wxCommandEvent& event )
  159. {
  160. // Build the absolute path of current output directory to preselect it in the file browser.
  161. wxString path = ExpandEnvVarSubstitutions( m_outputDirectoryName->GetValue(), &Prj() );
  162. path = Prj().AbsolutePath( path );
  163. wxDirDialog dirDialog( this, _( "Select Output Directory" ), path );
  164. if( dirDialog.ShowModal() == wxID_CANCEL )
  165. return;
  166. wxFileName dirName = wxFileName::DirName( dirDialog.GetPath() );
  167. wxMessageDialog dialog( this, _( "Use a relative path?" ), _( "Plot Output Directory" ),
  168. wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT );
  169. if( dialog.ShowModal() == wxID_YES )
  170. {
  171. wxString boardFilePath = Prj().AbsolutePath( m_board->GetFileName() );
  172. boardFilePath = wxPathOnly( boardFilePath );
  173. if( !dirName.MakeRelativeTo( boardFilePath ) )
  174. wxMessageBox( _( "Cannot make path relative (target volume different from board "
  175. "file volume)!" ),
  176. _( "Plot Output Directory" ), wxOK | wxICON_ERROR );
  177. }
  178. m_outputDirectoryName->SetValue( dirName.GetFullPath() );
  179. m_outputDirectory = m_outputDirectoryName->GetValue();
  180. }
  181. void DIALOG_EXPORT_SVG::onPagePerLayerClicked( wxCommandEvent& event )
  182. {
  183. PCBNEW_SETTINGS* cfg = m_parent->GetPcbNewSettings();
  184. if( m_checkboxPagePerLayer->GetValue() )
  185. {
  186. m_checkboxEdgesOnAllPages->Enable( true );
  187. m_checkboxEdgesOnAllPages->SetValue( cfg->m_ExportSvg.plot_board_edges );
  188. }
  189. else
  190. {
  191. m_checkboxEdgesOnAllPages->Enable( false );
  192. m_checkboxEdgesOnAllPages->SetValue( false );
  193. }
  194. }
  195. void DIALOG_EXPORT_SVG::ExportSVGFile( bool aOnlyOneFile )
  196. {
  197. m_outputDirectory = m_outputDirectoryName->GetValue();
  198. // Create output directory if it does not exist (also transform it in absolute form).
  199. // Bail if it fails.
  200. std::function<bool( wxString* )> textResolver =
  201. [&]( wxString* token ) -> bool
  202. {
  203. // Handles m_board->GetTitleBlock() *and* m_board->GetProject()
  204. return m_board->ResolveTextVar( token, 0 );
  205. };
  206. wxString path = m_outputDirectory;
  207. path = ExpandTextVars( path, &textResolver, nullptr, nullptr );
  208. path = ExpandEnvVarSubstitutions( path, nullptr );
  209. wxFileName outputDir = wxFileName::DirName( path );
  210. wxString boardFilename = m_board->GetFileName();
  211. REPORTER& reporter = m_messagesPanel->Reporter();
  212. if( !EnsureFileDirectoryExists( &outputDir, boardFilename, &reporter ) )
  213. {
  214. wxString msg = wxString::Format( _( "Could not write plot files to folder '%s'." ),
  215. outputDir.GetPath() );
  216. DisplayError( this, msg );
  217. return;
  218. }
  219. m_printMirror = m_printMirrorOpt->GetValue();
  220. m_printBW = m_ModeColorOption->GetSelection();
  221. LSET all_selected = getCheckBoxSelectedLayers();
  222. PCB_PLOT_SVG_OPTIONS svgPlotOptions;
  223. svgPlotOptions.m_blackAndWhite = m_printBW;
  224. svgPlotOptions.m_printMaskLayer = m_printMaskLayer;
  225. svgPlotOptions.m_pageSizeMode = m_rbSvgPageSizeOpt->GetSelection();
  226. svgPlotOptions.m_colorTheme = ""; // will use default
  227. svgPlotOptions.m_mirror = m_printMirror;
  228. for( LSEQ seq = all_selected.Seq(); seq; ++seq )
  229. {
  230. PCB_LAYER_ID layer = *seq;
  231. wxFileName fn( boardFilename );
  232. wxString suffix = aOnlyOneFile ? wxT( "brd" ) : m_board->GetStandardLayerName( layer );
  233. BuildPlotFileName( &fn, outputDir.GetPath(), suffix, SVGFileExtension );
  234. wxString svgPath = fn.GetFullPath();
  235. m_printMaskLayer = aOnlyOneFile ? all_selected : LSET( layer );
  236. if( m_checkboxEdgesOnAllPages->GetValue() )
  237. m_printMaskLayer.set( Edge_Cuts );
  238. svgPlotOptions.m_outputFile = svgPath;
  239. svgPlotOptions.m_printMaskLayer = m_printMaskLayer;
  240. if( PCB_PLOT_SVG::Plot(m_board, svgPlotOptions ) )
  241. {
  242. reporter.Report( wxString::Format( _( "Exported '%s'." ), svgPath ),
  243. RPT_SEVERITY_ACTION );
  244. }
  245. else // Error
  246. {
  247. reporter.Report( wxString::Format( _( "Failed to create file '%s'." ), svgPath ),
  248. RPT_SEVERITY_ERROR );
  249. }
  250. if( aOnlyOneFile )
  251. break;
  252. }
  253. }
  254. void DIALOG_EXPORT_SVG::OnButtonPlot( wxCommandEvent& event )
  255. {
  256. m_oneFileOnly = !m_checkboxPagePerLayer->GetValue();
  257. ExportSVGFile( m_oneFileOnly );
  258. }
  259. bool InvokeExportSVG( PCB_EDIT_FRAME* aCaller, BOARD* aBoard )
  260. {
  261. DIALOG_EXPORT_SVG dlg( aCaller, aBoard );
  262. dlg.ShowModal();
  263. return true;
  264. }