KiCad PCB EDA Suite
dialog_export_svg.cpp
Go to the documentation of this file.
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 
25 
26 #include <pcb_edit_frame.h>
27 #include <pcbnew_settings.h>
29 #include <reporter.h>
30 #include <board_design_settings.h>
31 #include <confirm.h>
32 #include <core/arraydim.h>
33 #include <core/kicad_algo.h>
34 #include <pcbplot.h>
35 #include <locale_io.h>
36 #include <board.h>
37 #include <dialog_export_svg_base.h>
38 #include <wx_html_report_panel.h>
39 #include <bitmaps.h>
40 #include <widgets/unit_binder.h>
42 #include <wx/dirdlg.h>
43 #include <pgm_base.h>
44 
46 {
47 public:
48  DIALOG_EXPORT_SVG( PCB_EDIT_FRAME* aParent, BOARD* aBoard );
49  ~DIALOG_EXPORT_SVG() override;
50 
51 private:
55  // the list of existing board layers in wxCheckListBox, with the
56  // board layers id:
57  std::pair<wxCheckListBox*, int> m_boxSelectLayer[PCB_LAYER_ID_COUNT];
58  bool m_printBW;
62 
63  void initDialog();
64 
65  void OnButtonPlot( wxCommandEvent& event ) override;
66 
67  void onPagePerLayerClicked( wxCommandEvent& event ) override;
68  void OnOutputDirectoryBrowseClicked( wxCommandEvent& event ) override;
69  void ExportSVGFile( bool aOnlyOneFile );
70 
71  bool CreateSVGFile( const wxString& FullFileName );
72 
74 };
75 
76 
77 /*
78  * DIALOG_EXPORT_SVG functions
79  */
81  DIALOG_EXPORT_SVG_BASE( aParent ),
82  m_board( aBoard ),
83  m_parent( aParent ),
84  m_printBW( false ),
85  m_printMirror( false ),
86  m_oneFileOnly( false )
87 {
89 
90  m_messagesPanel->SetFileName( Prj().GetProjectPath() + wxT( "report.txt" ) );
91 
92  initDialog();
93 
94  // We use a sdbSizer to get platform-dependent ordering of the action buttons, but
95  // that requires us to correct the button labels here.
96  m_sdbSizer1OK->SetLabel( _( "Export" ) );
97  m_sdbSizer1Cancel->SetLabel( _( "Close" ) );
98  m_sdbSizer1->Layout();
99 
101 }
102 
103 
105 {
106  m_printBW = m_ModeColorOption->GetSelection();
107  m_oneFileOnly = !m_checkboxPagePerLayer->GetValue();
109  m_outputDirectory.Replace( wxT( "\\" ), wxT( "/" ) );
110 
111  auto cfg = m_parent->GetPcbNewSettings();
112 
114  cfg->m_ExportSvg.mirror = m_printMirror;
115  cfg->m_ExportSvg.one_file = m_oneFileOnly;
116  cfg->m_ExportSvg.page_size = m_rbSvgPageSizeOpt->GetSelection();
117  cfg->m_ExportSvg.output_dir = m_outputDirectory.ToStdString();
118 
119  if( m_checkboxPagePerLayer->GetValue() )
120  {
121  m_oneFileOnly = false;
122  cfg->m_ExportSvg.plot_board_edges = m_checkboxEdgesOnAllPages->GetValue();
123  }
124  else
125  {
126  m_oneFileOnly = true;
127  }
128 
129  cfg->m_ExportSvg.layers.clear();
130 
131  for( unsigned layer = 0; layer < arrayDim( m_boxSelectLayer ); ++layer )
132  {
133  if( !m_boxSelectLayer[layer].first )
134  continue;
135 
136  if( m_boxSelectLayer[layer].first->IsChecked( m_boxSelectLayer[layer].second ) )
137  cfg->m_ExportSvg.layers.push_back( layer );
138  }
139 }
140 
141 
143 {
145 
150 
151  m_rbSvgPageSizeOpt->SetSelection( cfg->m_ExportSvg.page_size );
153 
154  wxCommandEvent dummy;
156 
158 
159  m_ModeColorOption->SetSelection( m_printBW ? 1 : 0 );
160  m_printMirrorOpt->SetValue( m_printMirror );
161 
162  for( LSEQ seq = m_board->GetEnabledLayers().UIOrder(); seq; ++seq )
163  {
164  PCB_LAYER_ID layer = *seq;
165  int checkIndex;
166 
167  if( IsCopperLayer( layer ) )
168  {
169  checkIndex = m_CopperLayersList->Append( m_board->GetLayerName( layer ) );
170  m_boxSelectLayer[layer] = std::make_pair( m_CopperLayersList, checkIndex );
171  }
172  else
173  {
174  checkIndex = m_TechnicalLayersList->Append( m_board->GetLayerName( layer ) );
175  m_boxSelectLayer[layer] = std::make_pair( m_TechnicalLayersList, checkIndex );
176  }
177 
178  if( alg::contains( cfg->m_ExportSvg.layers, layer ) )
179  m_boxSelectLayer[layer].first->Check( checkIndex, true );
180  }
181 }
182 
183 
185 {
186  LSET ret;
187 
188  for( unsigned layer = 0; layer < arrayDim(m_boxSelectLayer); ++layer )
189  {
190  if( !m_boxSelectLayer[layer].first )
191  continue;
192 
193  if( m_boxSelectLayer[layer].first->IsChecked( m_boxSelectLayer[layer].second ) )
194  ret.set( layer );
195  }
196 
197  return ret;
198 }
199 
200 
202 {
203  // Build the absolute path of current output directory to preselect it in the file browser.
204  wxString path = ExpandEnvVarSubstitutions( m_outputDirectoryName->GetValue(), &Prj() );
205  path = Prj().AbsolutePath( path );
206 
207  wxDirDialog dirDialog( this, _( "Select Output Directory" ), path );
208 
209  if( dirDialog.ShowModal() == wxID_CANCEL )
210  return;
211 
212  wxFileName dirName = wxFileName::DirName( dirDialog.GetPath() );
213 
214  wxMessageDialog dialog( this, _( "Use a relative path?" ), _( "Plot Output Directory" ),
215  wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT );
216 
217  if( dialog.ShowModal() == wxID_YES )
218  {
219  wxString boardFilePath = Prj().AbsolutePath( m_board->GetFileName() );
220 
221  boardFilePath = wxPathOnly( boardFilePath );
222 
223  if( !dirName.MakeRelativeTo( boardFilePath ) )
224  wxMessageBox( _( "Cannot make path relative (target volume different from board file volume)!" ),
225  _( "Plot Output Directory" ), wxOK | wxICON_ERROR );
226  }
227 
228  m_outputDirectoryName->SetValue( dirName.GetFullPath() );
230 }
231 
232 
233 void DIALOG_EXPORT_SVG::onPagePerLayerClicked( wxCommandEvent& event )
234 {
236 
237  if( m_checkboxPagePerLayer->GetValue() )
238  {
239  m_checkboxEdgesOnAllPages->Enable( true );
241  }
242  else
243  {
244  m_checkboxEdgesOnAllPages->Enable( false );
245  m_checkboxEdgesOnAllPages->SetValue( false );
246  }
247 }
248 
249 
250 void DIALOG_EXPORT_SVG::ExportSVGFile( bool aOnlyOneFile )
251 {
253 
254  // Create output directory if it does not exist (also transform it in absolute form).
255  // Bail if it fails.
256 
257  std::function<bool( wxString* )> textResolver =
258  [&]( wxString* token ) -> bool
259  {
260  // Handles m_board->GetTitleBlock() *and* m_board->GetProject()
261  return m_board->ResolveTextVar( token, 0 );
262  };
263 
264  wxString path = m_outputDirectory;
265  path = ExpandTextVars( path, &textResolver, nullptr, nullptr );
266  path = ExpandEnvVarSubstitutions( path, nullptr );
267 
268  wxFileName outputDir = wxFileName::DirName( path );
269  wxString boardFilename = m_board->GetFileName();
270 
271  REPORTER& reporter = m_messagesPanel->Reporter();
272 
273  if( !EnsureFileDirectoryExists( &outputDir, boardFilename, &reporter ) )
274  {
275  wxString msg = wxString::Format( _( "Could not write plot files to folder '%s'." ),
276  outputDir.GetPath() );
277  DisplayError( this, msg );
278  return;
279  }
280 
281  m_printMirror = m_printMirrorOpt->GetValue();
282  m_printBW = m_ModeColorOption->GetSelection();
283 
284  LSET all_selected = getCheckBoxSelectedLayers();
285 
286  for( LSEQ seq = all_selected.Seq(); seq; ++seq )
287  {
288  PCB_LAYER_ID layer = *seq;
289  wxFileName fn( boardFilename );
290  wxString suffix = aOnlyOneFile ? wxT( "brd" ) : m_board->GetStandardLayerName( layer );
291 
292  BuildPlotFileName( &fn, outputDir.GetPath(), suffix, SVGFileExtension );
293  wxString svgPath = fn.GetFullPath();
294 
295  m_printMaskLayer = aOnlyOneFile ? all_selected : LSET( layer );
296 
297  if( m_checkboxEdgesOnAllPages->GetValue() )
299 
300  if( CreateSVGFile( svgPath ) )
301  {
302  reporter.Report( wxString::Format( _( "Exported '%s'." ), svgPath ),
304  }
305  else // Error
306  {
307  reporter.Report( wxString::Format( _( "Failed to create file '%s'." ), svgPath ),
309  }
310 
311  if( aOnlyOneFile )
312  break;
313  }
314 }
315 
316 
317 // Actual SVG file export function.
318 bool DIALOG_EXPORT_SVG::CreateSVGFile( const wxString& aFullFileName )
319 {
320  PCB_PLOT_PARAMS plot_opts;
321 
322  plot_opts.SetPlotFrameRef( m_rbSvgPageSizeOpt->GetSelection() == 0 );
323 
324  // Adding drill marks, for copper layers
325  if( ( m_printMaskLayer & LSET::AllCuMask() ).any() )
327  else
329 
330  plot_opts.SetSkipPlotNPTH_Pads( false );
331 
332  plot_opts.SetMirror( m_printMirror );
333  plot_opts.SetFormat( PLOT_FORMAT::SVG );
334 
335  PAGE_INFO savedPageInfo = m_board->GetPageSettings();
336  wxPoint savedAuxOrigin = m_board->GetDesignSettings().GetAuxOrigin();
337 
338  if( m_rbSvgPageSizeOpt->GetSelection() == 2 ) // Page is board boundary size
339  {
341  PAGE_INFO currpageInfo = m_board->GetPageSettings();
342 
343  currpageInfo.SetWidthMils( bbox.GetWidth() / IU_PER_MILS );
344  currpageInfo.SetHeightMils( bbox.GetHeight() / IU_PER_MILS );
345  m_board->SetPageSettings( currpageInfo );
346  plot_opts.SetUseAuxOrigin( true );
347  wxPoint origin = bbox.GetOrigin();
349  }
350 
351  SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
353 
354  plot_opts.SetColorSettings( mgr.GetColorSettings( cfg->m_ColorTheme ) );
355 
356  LOCALE_IO toggle;
357 
358  SVG_PLOTTER* plotter = (SVG_PLOTTER*) StartPlotBoard( m_board, &plot_opts, UNDEFINED_LAYER,
359  aFullFileName, wxEmptyString );
360 
361  if( plotter )
362  {
363  plotter->SetColorMode( !m_printBW );
364 
365  for( LSEQ seq = m_printMaskLayer.SeqStackupBottom2Top(); seq; ++seq )
366  PlotOneBoardLayer( m_board, plotter, *seq, plot_opts );
367 
368  plotter->EndPlot();
369  }
370 
371  delete plotter;
372 
373  // reset to the values saved earlier
374  m_board->GetDesignSettings().SetAuxOrigin( savedAuxOrigin );
375  m_board->SetPageSettings( savedPageInfo );
376 
377  return true;
378 }
379 
380 
381 void DIALOG_EXPORT_SVG::OnButtonPlot( wxCommandEvent& event )
382 {
383  m_oneFileOnly = !m_checkboxPagePerLayer->GetValue();
385 }
386 
387 
388 bool InvokeExportSVG( PCB_EDIT_FRAME* aCaller, BOARD* aBoard )
389 {
390  DIALOG_EXPORT_SVG dlg( aCaller, aBoard );
391 
392  dlg.ShowModal();
393 
394  return true;
395 }
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:759
void DisplayError(wxWindow *aParent, const wxString &aText, int aDisplayTime)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:279
const PAGE_INFO & GetPageSettings() const
Definition: board.h:536
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition: board.cpp:362
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:40
void OnOutputDirectoryBrowseClicked(wxCommandEvent &event) override
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject)
Definition: common.cpp:57
void SetUseAuxOrigin(bool aAux)
wxCheckListBox * m_CopperLayersList
This file is part of the common library.
void PlotOneBoardLayer(BOARD *aBoard, PLOTTER *aPlotter, PCB_LAYER_ID aLayer, const PCB_PLOT_PARAMS &aPlotOpt)
Plot one copper or technical layer.
virtual void SetColorMode(bool aColorMode)
Plot in B/W or color.
Definition: plotter.h:152
void SetPageSettings(const PAGE_INFO &aPageSettings)
Definition: board.h:537
bool CreateSVGFile(const wxString &FullFileName)
PLOTTER * StartPlotBoard(BOARD *aBoard, const PCB_PLOT_PARAMS *aPlotOpts, int aLayer, const wxString &aFullFileName, const wxString &aSheetDesc)
Open a new plotfile using the options (and especially the format) specified in the options and prepar...
int GetWidth() const
Definition: eda_rect.h:118
void SetMirror(bool aFlag)
LSET GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition: board.cpp:467
LSEQ SeqStackupBottom2Top() const
Return the sequence that is typical for a bottom-to-top stack-up.
Definition: lset.cpp:461
void SetDrillMarksType(DrillMarksType aVal)
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:70
LSEQ Seq(const PCB_LAYER_ID *aWishListSequence, unsigned aCount) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition: lset.cpp:411
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:106
const wxString & GetFileName() const
Definition: board.h:229
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:590
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
virtual const wxString AbsolutePath(const wxString &aFileName) const
Fix up aFileName if it is relative to the project's directory to be an absolute path and filename.
Definition: project.cpp:269
Board plot function definition file.
void onPagePerLayerClicked(wxCommandEvent &event) override
static LIB_SYMBOL * dummy()
Used to draw a dummy shape when a LIB_SYMBOL is not found in library.
Definition: sch_symbol.cpp:72
virtual bool EndPlot() override
void SetPlotFrameRef(bool aFlag)
DIALOG_EXPORT_SVG m_ExportSvg
LSET is a set of PCB_LAYER_IDs.
Definition: layer_ids.h:516
wxCheckListBox * m_TechnicalLayersList
void SetAuxOrigin(const wxPoint &aOrigin)
DIALOG_EXPORT_SVG(PCB_EDIT_FRAME *aParent, BOARD *aBoard)
void OnButtonPlot(wxCommandEvent &event) override
void SetColorSettings(COLOR_SETTINGS *aSettings)
const wxPoint GetOrigin() const
Definition: eda_rect.h:110
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition: page_info.h:53
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
bool EnsureFileDirectoryExists(wxFileName *aTargetFullFileName, const wxString &aBaseFilename, REPORTER *aReporter)
Make aTargetFullFileName absolute and create the path of this file if it doesn't yet exist.
Definition: common.cpp:295
T * GetAppSettings(bool aLoadNow=true)
Returns a handle to the a given settings by type If the settings have already been loaded,...
void SetSkipPlotNPTH_Pads(bool aSkip)
Definition of file extensions used in Kicad.
#define _(s)
constexpr std::size_t arrayDim(T const (&)[N]) noexcept
Returns # of elements in an array.
Definition: arraydim.h:31
LSET getCheckBoxSelectedLayers() const
void SetFormat(PLOT_FORMAT aFormat)
wxBitmap KiBitmap(BITMAPS aBitmap, int aHeightTag)
Construct a wxBitmap from an image identifier Returns the image from the active theme if the image ha...
Definition: bitmap.cpp:105
Parameters and options when plotting/printing a board.
void SetHeightMils(int aHeightInMils)
Definition: page_info.cpp:257
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition: kicad_algo.h:99
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition: layer_ids.h:477
int GetHeight() const
Definition: eda_rect.h:119
bool IsCopperLayer(LAYER_NUM aLayerId)
Tests whether a layer is a copper layer.
Definition: layer_ids.h:808
Plotting engines similar to ps (PostScript, Gerber, svg)
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
void BuildPlotFileName(wxFileName *aFilename, const wxString &aOutputDir, const wxString &aSuffix, const wxString &aExtension)
Complete a plot filename.
Definition: pcbplot.cpp:372
Class DIALOG_EXPORT_SVG_BASE.
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
COLOR_SETTINGS * GetColorSettings(const wxString &aName="user")
Retrieves a color settings object that applications can read colors from.
wxStdDialogButtonSizer * m_sdbSizer1
see class PGM_BASE
const wxPoint & GetAuxOrigin()
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:191
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:65
WX_HTML_REPORT_PANEL * m_messagesPanel
void SetWidthMils(int aWidthInMils)
Definition: page_info.cpp:243
Handle the component boundary box.
Definition: eda_rect.h:42
The main frame for Pcbnew.
PCBNEW_SETTINGS * GetPcbNewSettings() const
const std::string SVGFileExtension
#define IU_PER_MILS
Definition: plotter.cpp:130
bool InvokeExportSVG(PCB_EDIT_FRAME *aCaller, BOARD *aBoard)
Function InvokeExportSVG shows the Export SVG dialog.
EDA_RECT ComputeBoundingBox(bool aBoardEdgesOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition: board.cpp:1085
PCB_EDIT_FRAME * m_parent
void ExportSVGFile(bool aOnlyOneFile)
void SetFileName(const wxString &aReportFileName)
bool ResolveTextVar(wxString *token, int aDepth) const
Definition: board.cpp:240
LSEQ UIOrder() const
Definition: lset.cpp:904
std::pair< wxCheckListBox *, int > m_boxSelectLayer[PCB_LAYER_ID_COUNT]
static wxString GetStandardLayerName(PCB_LAYER_ID aLayerId)
Return an "English Standard" name of a PCB layer when given aLayerNumber.
Definition: board.h:606
wxString m_ColorTheme
Active color theme name.
Definition: app_settings.h:184