KiCad PCB EDA Suite
dialog_board_statistics.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) 2019 Alexander Shuklin, jasuramme@gmail.com
5  * Copyright (C) 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 
25 
28 
29 #define COL_LABEL 0
30 #define COL_AMOUNT 1
31 
32 // Defines for components view
33 #define ROW_LABEL 0
34 #define COL_FRONT_SIDE 1
35 #define COL_BOTTOM_SIDE 2
36 #define COL_TOTAL 3
37 
38 // Defines for board view
39 #define ROW_BOARD_WIDTH 0
40 #define ROW_BOARD_HEIGHT 1
41 #define ROW_BOARD_AREA 2
42 
47 {
49  : excludeNoPins( false ),
50  subtractHoles( false ),
52  {
53  }
54 
55  // Flags to remember last checkboxes state
58 
59  // Variables to save last report file name and folder
60  bool saveReportInitialized; // true after the 3 next string are initialized
61  wxString saveReportFolder; // last report folder
62  wxString saveReportName; // last report filename
63  wxString m_project; // name of the project used to create the last report
64  // used to reinit last state after a project change
65 };
66 
68 
70  : DIALOG_BOARD_STATISTICS_BASE( aParentFrame )
71 {
72  m_parentFrame = aParentFrame;
73 
76 
77  // Make labels for grids
78  wxFont headingFont = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT );
79  headingFont.SetSymbolicSize( wxFONTSIZE_SMALL );
80  m_gridComponents->SetCellValue( ROW_LABEL, COL_FRONT_SIDE, _( "Front Side" ) );
81  m_gridComponents->SetCellFont( ROW_LABEL, COL_FRONT_SIDE, headingFont );
82  m_gridComponents->SetCellValue( ROW_LABEL, COL_BOTTOM_SIDE, _( "Back Side" ) );
83  m_gridComponents->SetCellFont( ROW_LABEL, COL_BOTTOM_SIDE, headingFont );
84  m_gridComponents->SetCellValue( ROW_LABEL, COL_TOTAL, _( "Total" ) );
85  m_gridComponents->SetCellFont( ROW_LABEL, COL_TOTAL, headingFont );
86 
87  m_gridBoard->SetCellValue( 0, 0, _( "Width:" ) );
88  m_gridBoard->SetCellAlignment( 0, 0, wxALIGN_LEFT, wxALIGN_CENTRE );
89  m_gridBoard->SetCellValue( 1, 0, _( "Height:" ) );
90  m_gridBoard->SetCellAlignment( 1, 0, wxALIGN_LEFT, wxALIGN_CENTRE );
91  m_gridBoard->SetCellValue( 2, 0, _( "Area:" ) );
92  m_gridBoard->SetCellAlignment( 2, 0, wxALIGN_LEFT, wxALIGN_CENTRE );
93 
94 
95  wxGrid* grids[] = { m_gridComponents, m_gridPads, m_gridVias, m_gridBoard };
96  for( auto& grid : grids )
97  {
98  // Remove wxgrid's selection boxes
99  grid->SetCellHighlightPenWidth( 0 );
100  grid->SetColMinimalAcceptableWidth( 80 );
101  for( int i = 0; i < grid->GetNumberRows(); i++ )
102  grid->SetCellAlignment( i, COL_LABEL, wxALIGN_LEFT, wxALIGN_CENTRE );
103  }
104 
105  wxFileName fn = m_parentFrame->GetBoard()->GetFileName();
106 
108  s_savedDialogState.m_project != Prj().GetProjectFullName()
109  )
110  {
111  fn.SetName( fn.GetName() + "_report" );
112  fn.SetExt( "txt" );
113  s_savedDialogState.saveReportName = fn.GetFullName();
114  s_savedDialogState.saveReportFolder = wxPathOnly( Prj().GetProjectFullName() );
117  }
118 
119  // The wxStdDialogButtonSizer wxID_CANCLE button is in fact a close button
120  // Nothing to cancel:
121  m_sdbControlSizerCancel->SetLabel( _( "Close" ) );
122 }
123 
125 {
126  m_componentsTypes.clear();
127 
128  // If you need some more types to be shown, simply add them to the
129  // corresponding list
130  m_componentsTypes.push_back( componentsType_t( MOD_DEFAULT, _( "THT:" ) ) );
131  m_componentsTypes.push_back( componentsType_t( MOD_CMS, _( "SMD:" ) ) );
132  m_componentsTypes.push_back( componentsType_t( MOD_VIRTUAL, _( "Virtual:" ) ) );
133 
134  m_padsTypes.clear();
135  m_padsTypes.push_back( padsType_t( PAD_ATTRIB_STANDARD, _( "Through hole:" ) ) );
136  m_padsTypes.push_back( padsType_t( PAD_ATTRIB_SMD, _( "SMD:" ) ) );
137  m_padsTypes.push_back( padsType_t( PAD_ATTRIB_CONN, _( "Connector:" ) ) );
138  m_padsTypes.push_back( padsType_t( PAD_ATTRIB_HOLE_NOT_PLATED, _( "NPTH:" ) ) );
139 
140  m_viasTypes.clear();
141  m_viasTypes.push_back( viasType_t( VIA_THROUGH, _( "Through vias:" ) ) );
142  m_viasTypes.push_back( viasType_t( VIA_BLIND_BURIED, _( "Blind/buried:" ) ) );
143  m_viasTypes.push_back( viasType_t( VIA_MICROVIA, _( "Micro vias:" ) ) );
144 
145  // If there not enough rows in grids, append some
146  int appendRows = m_componentsTypes.size() + 2 - m_gridComponents->GetNumberRows();
147 
148  if( appendRows > 0 )
149  m_gridComponents->AppendRows( appendRows );
150 
151  appendRows = m_padsTypes.size() + 1 - m_gridPads->GetNumberRows();
152 
153  if( appendRows > 0 )
154  m_gridPads->AppendRows( appendRows );
155 
156  appendRows = m_viasTypes.size() + 1 - m_gridVias->GetNumberRows();
157  if( appendRows )
158  m_gridVias->AppendRows( appendRows );
159 }
160 
162 {
164  getDataFromPCB();
165  updateWidets();
166  Layout();
168  return true;
169 }
170 
172 {
173  auto board = m_parentFrame->GetBoard();
174 
175  // Get modules and pads count
176  for( MODULE* module : board->Modules() )
177  {
178  auto& pads = module->Pads();
179 
180  // Do not proceed modules with no pads if checkbox checked
181  if( m_checkBoxExcludeComponentsNoPins->GetValue() && !pads.size() )
182  continue;
183 
184  // Go through components types list
185  for( auto& type : m_componentsTypes )
186  {
187  if( module->GetAttributes() == type.attribute )
188  {
189  if( module->IsFlipped() )
190  type.backSideQty++;
191  else
192  type.frontSideQty++;
193  break;
194  }
195  }
196 
197  for( auto& pad : pads )
198  {
199  // Go through pads types list
200  for( auto& type : m_padsTypes )
201  {
202  if( pad->GetAttribute() == type.attribute )
203  {
204  type.qty++;
205  break;
206  }
207  }
208  }
209  }
210 
211  // Get vias count
212  VIA* via;
213  for( auto& track : board->Tracks() )
214  {
215  if( track->Type() == PCB_VIA_T )
216  {
217  via = dynamic_cast<VIA*>( track );
218 
219  // We have to check if cast was successful
220  if( via )
221  {
222  for( auto& type : m_viasTypes )
223  {
224  if( via->GetViaType() == type.attribute )
225  {
226  type.qty++;
227  break;
228  }
229  }
230  }
231  }
232  }
233 
234  bool boundingBoxCreated = false; //flag if bounding box initialized
235  BOX2I bbox;
236  SHAPE_POLY_SET polySet;
237  m_hasOutline = board->GetBoardPolygonOutlines( polySet );
238 
239  // If board has no Edge Cuts lines, board->GetBoardPolygonOutlines will
240  // return small rectangle, so we double check that
241  bool edgeCutsExists = false;
242  for( auto& drawing : board->Drawings() )
243  {
244  if( drawing->GetLayer() == Edge_Cuts )
245  {
246  edgeCutsExists = true;
247  break;
248  }
249  }
250 
251  if( !edgeCutsExists )
252  m_hasOutline = false;
253 
254  if( m_hasOutline )
255  {
256  m_boardArea = 0;
257  int outlinesCount = polySet.OutlineCount();
258  for( int i = 0; i < outlinesCount; i++ )
259  {
260  auto& outline = polySet.Outline( i );
261  m_boardArea += abs( outline.Area() );
262 
263  // If checkbox "subtract holes" is checked
264  if( m_checkBoxSubtractHoles->GetValue() )
265  {
266  int holesCount = polySet.HoleCount( i );
267  for( int j = 0; j < holesCount; j++ )
268  {
269  m_boardArea -= abs( polySet.Hole( i, j ).Area() );
270  }
271  }
272 
273  if( GetUserUnits() == INCHES )
274  m_boardArea /= ( IU_PER_MILS * IU_PER_MILS * 1000000 );
275  else
276  m_boardArea /= ( IU_PER_MM * IU_PER_MM );
277 
278  if( boundingBoxCreated )
279  {
280  bbox.Merge( outline.BBox() );
281  }
282  else
283  {
284  bbox = outline.BBox();
285  boundingBoxCreated = true;
286  }
287  }
288  m_boardWidth = bbox.GetWidth();
289  m_boardHeight = bbox.GetHeight();
290  }
291 }
292 
294 {
295  int totalPads = 0;
296  int currentRow = 0;
297  for( auto& type : m_padsTypes )
298  {
299  m_gridPads->SetCellValue( currentRow, COL_LABEL, type.title );
300  m_gridPads->SetCellValue(
301  currentRow, COL_AMOUNT, wxString::Format( wxT( "%i " ), type.qty ) );
302  totalPads += type.qty;
303  currentRow++;
304  }
305  m_gridPads->SetCellValue( currentRow, COL_LABEL, _( "Total:" ) );
306  m_gridPads->SetCellValue( currentRow, COL_AMOUNT, wxString::Format( "%i ", totalPads ) );
307 
308  int totalVias = 0;
309  currentRow = 0;
310  for( auto& type : m_viasTypes )
311  {
312  m_gridVias->SetCellValue( currentRow, COL_LABEL, type.title );
313  m_gridVias->SetCellValue(
314  currentRow, COL_AMOUNT, wxString::Format( "%i ", type.qty ) );
315  totalVias += type.qty;
316  currentRow++;
317  }
318  m_gridVias->SetCellValue( currentRow, COL_LABEL, _( "Total:" ) );
319  m_gridVias->SetCellValue( currentRow, COL_AMOUNT, wxString::Format( "%i ", totalVias ) );
320 
321 
322  int totalFront = 0;
323  int totalBack = 0;
324 
325  // We don't use row 0, as there labels are
326  currentRow = 1;
327  for( auto& type : m_componentsTypes )
328  {
329  m_gridComponents->SetCellValue( currentRow, COL_LABEL, type.title );
330  m_gridComponents->SetCellValue(
331  currentRow, COL_FRONT_SIDE, wxString::Format( "%i ", type.frontSideQty ) );
332  m_gridComponents->SetCellValue(
333  currentRow, COL_BOTTOM_SIDE, wxString::Format( "%i ", type.backSideQty ) );
334  m_gridComponents->SetCellValue( currentRow, 3,
335  wxString::Format( wxT( "%i " ), type.frontSideQty + type.backSideQty ) );
336  totalFront += type.frontSideQty;
337  totalBack += type.backSideQty;
338  currentRow++;
339  }
340  m_gridComponents->SetCellValue( currentRow, COL_LABEL, _( "Total:" ) );
341  m_gridComponents->SetCellValue( currentRow, COL_FRONT_SIDE,
342  wxString::Format( "%i ", totalFront ) );
343  m_gridComponents->SetCellValue( currentRow, COL_BOTTOM_SIDE,
344  wxString::Format( "%i ", totalBack ) );
345  m_gridComponents->SetCellValue(
346  currentRow, COL_TOTAL, wxString::Format( "%i ", totalFront + totalBack ) );
347 
348  if( m_hasOutline )
349  {
350  m_gridBoard->SetCellValue( ROW_BOARD_WIDTH, COL_AMOUNT,
352  m_gridBoard->SetCellValue( ROW_BOARD_HEIGHT, COL_AMOUNT,
354  m_gridBoard->SetCellValue( ROW_BOARD_AREA, COL_AMOUNT,
355  wxString::Format( wxT( "%.3f %s²" ), m_boardArea,
356  GetAbbreviatedUnitsLabel( GetUserUnits(), false ) ) );
357  }
358  else
359  {
360  m_gridBoard->SetCellValue( ROW_BOARD_WIDTH, COL_AMOUNT, _( "unknown" ) );
361  m_gridBoard->SetCellValue( ROW_BOARD_HEIGHT, COL_AMOUNT, _( "unknown" ) );
362  m_gridBoard->SetCellValue( ROW_BOARD_AREA, COL_AMOUNT, _( "unknown" ) );
363  }
364 
365  m_gridComponents->AutoSize();
366  m_gridPads->AutoSize();
367  m_gridBoard->AutoSize();
368  m_gridVias->AutoSize();
369 }
370 
371 // If any checkbox clicked, we have to refresh dialog data
372 void DIALOG_BOARD_STATISTICS::checkboxClicked( wxCommandEvent& event )
373 {
377  getDataFromPCB();
378  updateWidets();
379  Layout();
380  Fit();
381 }
382 
383 void DIALOG_BOARD_STATISTICS::saveReportClicked( wxCommandEvent& event )
384 {
385  FILE* outFile;
386  wxString msg;
387  wxString boardName;
388 
389  wxFileName fn = m_parentFrame->GetBoard()->GetFileName();
390  boardName = fn.GetName();
391  wxFileDialog saveFileDialog( this, _( "Save Report File" ), s_savedDialogState.saveReportFolder,
393  wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
394 
395  if( saveFileDialog.ShowModal() == wxID_CANCEL )
396  return;
397 
398  s_savedDialogState.saveReportFolder = wxPathOnly( saveFileDialog.GetPath() );
399  s_savedDialogState.saveReportName = saveFileDialog.GetFilename();
400 
401  outFile = wxFopen( saveFileDialog.GetPath(), "wt" );
402 
403  if( outFile == NULL )
404  {
405  msg.Printf( _( "Unable to create file \"%s\"" ), saveFileDialog.GetPath() );
406  DisplayErrorMessage( this, msg );
407  return;
408  }
409  msg << _( "PCB statistics report" ) << "\n";
410  msg << _( "Date: " ) << wxDateTime::Now().Format() << "\n";
411  msg << _( "Project: " ) << Prj().GetProjectName() << "\n";
412  msg << _( "Board name: " ) << boardName << "\n";
413 
414  msg << "\n";
415  msg << "Board\n";
416 
417  if( m_hasOutline )
418  {
419  msg << _( "Width: " )
421  msg << _( "Height: " )
423  msg << _( "Area: " )
424  << wxString::Format( wxT( "%.3f %s²" ), m_boardArea,
426  << "\n";
427  }
428  else
429  {
430  msg << _( "Width: " ) << _( "unknown" ) << "\n";
431  msg << _( "Height: " ) << _( "unknown" ) << "\n";
432  msg << _( "Area: " ) << _( "unknown" ) << "\n";
433  }
434 
435  msg << "\n";
436  msg << "Pads\n";
437  for( auto& type : m_padsTypes )
438  msg << type.title << " " << type.qty << "\n";
439 
440  msg << "\n";
441  msg << "Vias\n";
442  for( auto& type : m_viasTypes )
443  msg << type.title << " " << type.qty << "\n";
444 
445  // We will save data about components in the table.
446  // We have to calculate column widths
447  size_t colsWidth[4];
448  wxString columns[4] = { "", _( "Front Side" ), _( "Back Side" ), _( "Total" ) };
449  wxString tmp;
450  for( int i = 0; i < 4; i++ )
451  {
452  colsWidth[i] = columns[i].size();
453  }
454  int frontTotal = 0;
455  int backTotal = 0;
456  for( auto& type : m_componentsTypes )
457  {
458  // Get maximum width for left label column
459  colsWidth[0] = std::max( type.title.size(), colsWidth[0] );
460  frontTotal += type.frontSideQty;
461  backTotal += type.backSideQty;
462  }
463 
464  // Get maximum width for other columns
465  tmp.Printf( "%d", frontTotal );
466  colsWidth[1] = std::max( tmp.size(), colsWidth[1] );
467  tmp.Printf( "%d", backTotal );
468  colsWidth[2] = std::max( tmp.size(), colsWidth[2] );
469  tmp.Printf( "%d", frontTotal + backTotal );
470  colsWidth[3] = std::max( tmp.size(), colsWidth[3] );
471 
472  //Write components amount to file
473  msg << "\n";
474  msg << _( "Components" ) << "\n";
475  tmp.Printf( "%-*s | %*s | %*s | %*s |\n",
476  colsWidth[0], columns[0], colsWidth[1], columns[1],
477  colsWidth[2], columns[2], colsWidth[3], columns[3] );
478  msg += tmp;
479  for( auto& type : m_componentsTypes )
480  {
481  tmp.Printf( "%-*s | %*d | %*d | %*d |\n", colsWidth[0], type.title, colsWidth[1],
482  type.frontSideQty, colsWidth[2], type.backSideQty, colsWidth[3],
483  type.backSideQty + type.frontSideQty );
484  msg += tmp;
485  }
486  tmp.Printf( "%-*s | %*d | %*d | %*d |\n", colsWidth[0], _( "Total:" ), colsWidth[1], frontTotal,
487  colsWidth[2], backTotal, colsWidth[3], frontTotal + backTotal );
488  msg += tmp;
489 
490  int success = fprintf( outFile, "%s", TO_UTF8( msg ) );
491  if( success < 0 )
492  {
493  msg.Printf( _( "Error writing to file \"%s\"" ), saveFileDialog.GetPath() );
494  DisplayErrorMessage( this, msg );
495  }
496  fclose( outFile );
497 }
498 
500 {
501 }
bool m_hasOutline
Shows if board outline properly defined
typeContainer_t< VIATYPE_T > viasType_t
int OutlineCount() const
Returns the number of outlines in the set
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:258
like PAD_STANDARD, but not plated mechanical use only, no connection allowed
Definition: pad_shapes.h:66
void refreshItemsTypes()
Function to fill up all items types to be shown in the dialog.
bool TransferDataToWindow() override
Get data from the PCB board and print it to dialog
#define COL_AMOUNT
void FinishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
Smd pad, appears on the solder paste layer (default)
Definition: pad_shapes.h:62
void checkboxClicked(wxCommandEvent &event) override
Set for modules listed in the automatic insertion list (usually SMD footprints)
Definition: class_module.h:76
viasTypeList_t m_viasTypes
Holds all vias types to be shown in the dialog
#define ROW_BOARD_AREA
SHAPE_LINE_CHAIN & Hole(int aOutline, int aHole)
Returns the reference to aHole-th hole in the aIndex-th outline
void getDataFromPCB()
Gets data from board
Struct holds information about component type (such as SMD, THT, Virtual and so on),...
const wxString & GetFileName() const
Definition: class_board.h:225
#define abs(a)
Definition: auxiliary.h:84
#define ROW_BOARD_HEIGHT
#define COL_TOTAL
wxString MessageTextFromValue(EDA_UNITS_T aUnits, int aValue, bool aUseMils)
Definition: base_units.cpp:125
#define TO_UTF8(wxstring)
Macro TO_UTF8 converts a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: macros.h:48
#define COL_BOTTOM_SIDE
Class SHAPE_POLY_SET.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Returns the reference to aIndex-th outline in the set
#define ROW_LABEL
PROJECT & Prj() const
Function Prj returns a reference to the PROJECT "associated with" this KIWAY.
coord_type GetWidth() const
Definition: box2.h:195
#define COL_FRONT_SIDE
Definition: common.h:155
Definition of file extensions used in Kicad.
VIATYPE_T GetViaType() const
Definition: class_track.h:346
void saveReportClicked(wxCommandEvent &event) override
Save board statistics to a file
DIALOG_BOARD_STATISTICS(PCB_EDIT_FRAME *aParentFrame)
typeContainer_t< PAD_ATTR_T > padsType_t
VTBL_ENTRY const wxString GetProjectFullName() const
Function GetProjectFullName returns the full path and name of the project.
Definition: project.cpp:96
BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Function Merge modifies the position and size of the rectangle in order to contain aRect.
Definition: box2.h:384
Like smd, does not appear on the solder paste layer (default) note also has a special attribute in Ge...
Definition: pad_shapes.h:63
#define _(s)
int HoleCount(int aOutline) const
Returns the number of holes in a given outline
double Area() const
#define ROW_BOARD_WIDTH
default
Definition: class_module.h:75
void updateWidets()
Applies data to dialog widgets
Class DIALOG_BOARD_STATISTICS_BASE.
EDA_UNITS_T GetUserUnits() const
Definition: dialog_shim.h:135
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:205
EDA_UNITS_T GetUserUnits() const
Return the user units currently in use.
#define max(a, b)
Definition: auxiliary.h:86
Virtual component: when created by copper shapes on board (Like edge card connectors,...
Definition: class_module.h:78
size_t i
Definition: json11.cpp:597
Class PCB_EDIT_FRAME is the main frame for Pcbnew.
#define IU_PER_MILS
Definition: plotter.cpp:134
coord_type GetHeight() const
Definition: box2.h:196
wxString TextFileWildcard()
padsTypeList_t m_padsTypes
Holds all pads types to be shown in the dialog
VTBL_ENTRY const wxString GetProjectName() const
Function GetProjectName returns the short name of the project.
Definition: project.cpp:108
class VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:96
BOARD * GetBoard() const
#define COL_LABEL
static DIALOG_BOARD_STATISTICS_SAVED_STATE s_savedDialogState
componentsTypeList_t m_componentsTypes
Holds all components types to be shown in the dialog
Struct containing the dialog last saved state.
wxString GetAbbreviatedUnitsLabel(EDA_UNITS_T aUnit, bool aUseMils)
Get the units string for a given units type.
Definition: base_units.cpp:437