KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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, [email protected]
5 * Copyright The 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
27#include <board_statistics.h>
28
29#include <wx/filedlg.h>
30
31#include <kiplatform/ui.h>
32#include <confirm.h>
33#include <pad.h>
34#include <macros.h>
35#include <string_utils.h>
37#include <widgets/wx_grid.h>
39#include <algorithm>
40
41
42#define COL_LABEL 0
43#define COL_AMOUNT 1
44
45// Defines for components view
46#define ROW_LABEL 0
47#define COL_FRONT_SIDE 1
48#define COL_BOTTOM_SIDE 2
49#define COL_TOTAL 3
50
51// Defines for board view
52#define ROW_BOARD_DIMS 0
53#define ROW_BOARD_AREA 1
54#define ROW_FRONT_COPPER_AREA 2
55#define ROW_BACK_COPPER_AREA 3
56#define ROW_MIN_CLEARANCE 4
57#define ROW_MIN_TRACK_WIDTH 5
58#define ROW_MIN_DRILL_DIAMETER 6
59#define ROW_BOARD_THICKNESS 7
60
61
66{
74
75 // Flags to remember last checkboxes state
79
80 // Variables to save last report file name and folder
81 bool saveReportInitialized; // true after the 3 next string are initialized
82 wxString saveReportFolder; // last report folder
83 wxString saveReportName; // last report filename
84 wxString m_project; // name of the project used to create the last report
85 // used to reinit last state after a project change
86};
87
88
90
92 DIALOG_BOARD_STATISTICS_BASE( aParentFrame ),
93 m_frame( aParentFrame ),
96{
97 m_gridDrills->Connect( wxEVT_GRID_COL_SORT,
98 wxGridEventHandler( DIALOG_BOARD_STATISTICS::drillGridSort ),
99 nullptr, this );
100
101 m_checkBoxExcludeComponentsNoPins->SetValue( s_savedDialogState.excludeNoPins );
102 m_checkBoxSubtractHoles->SetValue( s_savedDialogState.subtractHoles );
103 m_checkBoxSubtractHolesFromCopper->SetValue( s_savedDialogState.subtractHolesFromCopper );
104
105 wxFont labelFont = KIUI::GetSmallInfoFont( this );
106 m_componentsLabel->SetFont( labelFont );
107 m_boardLabel->SetFont( labelFont );
108 m_padsLabel->SetFont( labelFont );
109 m_viasLabel->SetFont( labelFont );
110
111 // Make labels for grids
112 m_gridComponents->SetCellValue( ROW_LABEL, COL_FRONT_SIDE, _( "Front Side" ) );
113 m_gridComponents->SetCellValue( ROW_LABEL, COL_BOTTOM_SIDE, _( "Back Side" ) );
114 m_gridComponents->SetCellValue( ROW_LABEL, COL_TOTAL, _( "Total" ) );
115
116 wxFont headingFont = KIUI::GetStatusFont( this );
117
118 for( int col = COL_AMOUNT; col < m_gridComponents->GetNumberCols(); col++ )
119 m_gridComponents->SetCellFont( ROW_LABEL, col, headingFont );
120
121 m_gridBoard->SetCellValue( ROW_BOARD_DIMS, COL_LABEL, _( "Dimensions:" ) );
122 m_gridBoard->SetCellValue( ROW_BOARD_AREA, COL_LABEL, _( "Area:" ) );
123 m_gridBoard->SetCellValue( ROW_FRONT_COPPER_AREA, COL_LABEL, _( "Front copper area:" ) );
124 m_gridBoard->SetCellValue( ROW_BACK_COPPER_AREA, COL_LABEL, _( "Back copper area:" ) );
125 m_gridBoard->SetCellValue( ROW_MIN_CLEARANCE, COL_LABEL, _( "Min track clearance:" ) );
126 m_gridBoard->SetCellValue( ROW_MIN_TRACK_WIDTH, COL_LABEL, _( "Min track width:" ) );
127 m_gridBoard->SetCellValue( ROW_MIN_DRILL_DIAMETER,COL_LABEL, _( "Min drill diameter:" ) );
128 m_gridBoard->SetCellValue( ROW_BOARD_THICKNESS, COL_LABEL, _( "Board stackup thickness:" ) );
129
131 {
132 // Remove wxgrid's selection boxes
133 grid->SetCellHighlightPenWidth( 0 );
134 grid->SetColMinimalAcceptableWidth( 80 );
135
136 for( int row = 0; row < grid->GetNumberRows(); row++ )
137 {
138 grid->SetCellAlignment( row, COL_LABEL, wxALIGN_LEFT, wxALIGN_CENTRE );
139
140 for( int col = COL_AMOUNT; col < grid->GetNumberCols(); col++ )
141 grid->SetCellAlignment( row, col, wxALIGN_RIGHT, wxALIGN_CENTER );
142 }
143 }
144
145 wxFileName fn = m_frame->GetBoard()->GetFileName();
146
147 if( !s_savedDialogState.saveReportInitialized
148 || s_savedDialogState.m_project != Prj().GetProjectFullName() )
149 {
150 fn.SetName( fn.GetName() + wxT( "_report" ) );
151 fn.SetExt( wxT( "txt" ) );
152 s_savedDialogState.saveReportName = fn.GetFullName();
153 s_savedDialogState.saveReportFolder = wxPathOnly( Prj().GetProjectFullName() );
155 s_savedDialogState.saveReportInitialized = true;
156 }
157
158 // The wxStdDialogButtonSizer wxID_CANCLE button is in fact a close button
159 // Nothing to cancel:
160 m_sdbControlSizerCancel->SetLabel( _( "Close" ) );
161}
162
163
165{
167
168 int appendRows = static_cast<int>( m_statsData.footprintEntries.size() ) + 2 - m_gridComponents->GetNumberRows();
169
170 if( appendRows > 0 )
171 m_gridComponents->AppendRows( appendRows );
172
173 appendRows = static_cast<int>( m_statsData.padEntries.size() ) + 1
174 + static_cast<int>( m_statsData.padPropertyEntries.size() ) - m_gridPads->GetNumberRows();
175
176 if( appendRows > 0 )
177 m_gridPads->AppendRows( appendRows );
178
179 appendRows = static_cast<int>( m_statsData.viaEntries.size() ) + 1 - m_gridVias->GetNumberRows();
180
181 if( appendRows > 0 )
182 m_gridVias->AppendRows( appendRows );
183}
184
185
187{
191
192 Layout();
193 m_drillsPanel->Layout();
194
195 m_gridDrills->AutoSizeColumns();
198
199 // Add space for the vertical scrollbar, so that it won't overlap with the cells.
200 m_gridDrills->SetMinSize( wxSize( m_gridDrills->GetEffectiveMinSize().x
201 + wxSystemSettings::GetMetric( wxSYS_VSCROLL_X ),
202 60 ) );
203
205
207 return true;
208}
209
210
212{
217
218 ComputeBoardStatistics( m_frame->GetBoard(), options, m_statsData );
219
220 m_gridDrills->ClearRows();
221
222 if( !m_statsData.drillEntries.empty() )
223 m_gridDrills->AppendRows( static_cast<int>( m_statsData.drillEntries.size() ) );
224}
225
226
227static wxString formatCount( int aCount )
228{
229 return wxString::Format( wxT( "%i" ), aCount );
230};
231
232
234{
235 int totalPads = 0;
236 int row = 0;
237
238 for( const BOARD_STATISTICS_INFO_ENTRY<PAD_ATTRIB>& line : m_statsData.padEntries )
239 {
240 m_gridPads->SetCellValue( row, COL_LABEL, line.title );
241 m_gridPads->SetCellValue( row, COL_AMOUNT, formatCount( line.quantity ) );
242 totalPads += line.quantity;
243 row++;
244 }
245
246 m_gridPads->SetCellValue( row, COL_LABEL, _( "Total:" ) );
247 m_gridPads->SetCellValue( row, COL_AMOUNT, formatCount( totalPads ) );
248 row++;
249
250 for( const BOARD_STATISTICS_INFO_ENTRY<PAD_PROP>& line : m_statsData.padPropertyEntries )
251 {
252 m_gridPads->SetCellValue( row, COL_LABEL, line.title );
253 m_gridPads->SetCellValue( row, COL_AMOUNT, formatCount( line.quantity ) );
254 row++;
255 }
256
257 int totalVias = 0;
258 row = 0;
259
260 for( const BOARD_STATISTICS_INFO_ENTRY<VIATYPE>& line : m_statsData.viaEntries )
261 {
262 m_gridVias->SetCellValue( row, COL_LABEL, line.title );
263 m_gridVias->SetCellValue( row, COL_AMOUNT, formatCount( line.quantity ) );
264 totalVias += line.quantity;
265 row++;
266 }
267
268 m_gridVias->SetCellValue( row, COL_LABEL, _( "Total:" ) );
269 m_gridVias->SetCellValue( row, COL_AMOUNT, formatCount( totalVias ) );
270
271
272 int totalFront = 0;
273 int totalBack = 0;
274
275 // We don't use row 0, as there labels are
276 row = 1;
277
278 for( const BOARD_STATISTICS_FP_ENTRY& line : m_statsData.footprintEntries )
279 {
280 m_gridComponents->SetCellValue( row, COL_LABEL, line.title );
281 m_gridComponents->SetCellValue( row, COL_FRONT_SIDE, formatCount( line.frontCount ) );
282 m_gridComponents->SetCellValue( row, COL_BOTTOM_SIDE, formatCount( line.backCount ) );
283 m_gridComponents->SetCellValue( row, 3, formatCount( line.frontCount + line.backCount ) );
284 totalFront += line.frontCount;
285 totalBack += line.backCount;
286 row++;
287 }
288
289 m_gridComponents->SetCellValue( row, COL_LABEL, _( "Total:" ) );
290 m_gridComponents->SetCellValue( row, COL_FRONT_SIDE, formatCount( totalFront ) );
291 m_gridComponents->SetCellValue( row, COL_BOTTOM_SIDE, formatCount( totalBack ) );
292 m_gridComponents->SetCellValue( row, COL_TOTAL, formatCount( totalFront + totalBack ) );
293
294 if( m_statsData.hasOutline )
295 {
296 m_gridBoard->SetCellValue( ROW_BOARD_DIMS, COL_AMOUNT,
297 wxString::Format( wxT( "%s x %s" ),
298 m_frame->MessageTextFromValue( m_statsData.boardWidth, false ),
299 m_frame->MessageTextFromValue( m_statsData.boardHeight, true ) ) );
300 m_gridBoard->SetCellValue( ROW_BOARD_AREA, COL_AMOUNT,
301 m_frame->MessageTextFromValue( m_statsData.boardArea, true, EDA_DATA_TYPE::AREA ) );
302 }
303 else
304 {
305 m_gridBoard->SetCellValue( ROW_BOARD_DIMS, COL_AMOUNT, _( "unknown" ) );
306 m_gridBoard->SetCellValue( ROW_BOARD_AREA, COL_AMOUNT, _( "unknown" ) );
307 }
308
309 m_gridBoard->SetCellValue(
311 m_frame->MessageTextFromValue( m_statsData.frontCopperArea, true, EDA_DATA_TYPE::AREA ) );
313 m_frame->MessageTextFromValue( m_statsData.backCopperArea, true, EDA_DATA_TYPE::AREA ) );
314
315 m_gridBoard->SetCellValue(
317 m_frame->MessageTextFromValue( m_statsData.minClearanceTrackToTrack, true, EDA_DATA_TYPE::DISTANCE ) );
318
319 m_gridBoard->SetCellValue(
321 m_frame->MessageTextFromValue( m_statsData.minTrackWidth, true, EDA_DATA_TYPE::DISTANCE ) );
322
323 m_gridBoard->SetCellValue(
325 m_frame->MessageTextFromValue( m_statsData.boardThickness, true, EDA_DATA_TYPE::DISTANCE ) );
326
328
329 m_gridBoard->SetCellValue(
331 m_frame->MessageTextFromValue( m_statsData.minDrillSize, true, EDA_DATA_TYPE::DISTANCE ) );
332
333 m_gridComponents->AutoSize();
334 m_gridPads->AutoSize();
335 m_gridBoard->AutoSize();
336 m_gridVias->AutoSize();
337
339}
340
341
343{
344 BOARD* board = m_frame->GetBoard();
345 int row = 0;
346
347 for( const DRILL_LINE_ITEM& line : m_statsData.drillEntries )
348 {
349 wxString shapeStr;
350 wxString startLayerStr;
351 wxString stopLayerStr;
352
353 switch( line.shape )
354 {
355 case PAD_DRILL_SHAPE::CIRCLE: shapeStr = _( "Round" ); break;
356 case PAD_DRILL_SHAPE::OBLONG: shapeStr = _( "Slot" ); break;
357 default: shapeStr = _( "???" ); break;
358 }
359
360 if( line.startLayer == UNDEFINED_LAYER )
361 startLayerStr = _( "N/A" );
362 else
363 startLayerStr = board->GetLayerName( line.startLayer );
364
365 if( line.stopLayer == UNDEFINED_LAYER )
366 stopLayerStr = _( "N/A" );
367 else
368 stopLayerStr = board->GetLayerName( line.stopLayer );
369
370 m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_COUNT, formatCount( line.m_Qty ) );
371 m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_SHAPE, shapeStr );
372 m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_X_SIZE, m_frame->MessageTextFromValue( line.xSize ) );
373 m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_Y_SIZE, m_frame->MessageTextFromValue( line.ySize ) );
374 m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_PLATED, line.isPlated ? _( "PTH" ) : _( "NPTH" ) );
375 m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_VIA_PAD, line.isPad ? _( "Pad" ) : _( "Via" ) );
376 m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_START_LAYER, startLayerStr );
377 m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_STOP_LAYER, stopLayerStr );
378
379 row++;
380 }
381}
382
383
385{
386 wxGridUpdateLocker deferRepaintsTillLeavingScope( m_gridDrills );
387
388 m_gridDrills->EnsureColLabelsVisible();
389
390 double remainingWidth = KIPLATFORM::UI::GetUnobscuredSize( m_gridDrills ).x;
391
392 // Find the total current width
393 for( int i = 0; i < m_gridDrills->GetNumberCols(); i++ )
394 {
396 remainingWidth -= m_gridDrills->GetColSize( i );
397 }
398
399 double scalingFactor = std::max( 1.0,
401 int startLayerColWidth = static_cast<int>( m_startLayerColInitialSize * scalingFactor );
402 int stopLayerColWidth = static_cast<int>( m_stopLayerColInitialSize * scalingFactor );
403
404 m_gridDrills->SetColSize( DRILL_LINE_ITEM::COL_START_LAYER, startLayerColWidth );
405 m_gridDrills->SetColSize( DRILL_LINE_ITEM::COL_STOP_LAYER, stopLayerColWidth );
406}
407
408
409void DIALOG_BOARD_STATISTICS::checkboxClicked( wxCommandEvent& aEvent )
410{
411 s_savedDialogState.excludeNoPins = m_checkBoxExcludeComponentsNoPins->GetValue();
412 s_savedDialogState.subtractHoles = m_checkBoxSubtractHoles->GetValue();
413 s_savedDialogState.subtractHolesFromCopper = m_checkBoxSubtractHolesFromCopper->GetValue();
417 Layout();
418 m_drillsPanel->Layout();
419}
420
421
422void DIALOG_BOARD_STATISTICS::saveReportClicked( wxCommandEvent& aEvent )
423{
424 FILE* outFile;
425 wxString msg;
426 wxString boardName;
427
428 wxFileName fn = m_frame->GetBoard()->GetFileName();
429 boardName = fn.GetName();
430 wxFileDialog dlg( this, _( "Save Report File" ), s_savedDialogState.saveReportFolder,
432 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
433
434 if( dlg.ShowModal() == wxID_CANCEL )
435 return;
436
437 s_savedDialogState.saveReportFolder = wxPathOnly( dlg.GetPath() );
438 s_savedDialogState.saveReportName = dlg.GetFilename();
439
440 outFile = wxFopen( dlg.GetPath(), wxT( "wt" ) );
441
442 if( outFile == nullptr )
443 {
444 msg.Printf( _( "Failed to create file '%s'." ), dlg.GetPath() );
445 DisplayErrorMessage( this, msg );
446 return;
447 }
448
449 const UNITS_PROVIDER& unitsProvider = *m_frame;
450 wxString report = FormatBoardStatisticsReport( m_statsData, m_frame->GetBoard(), unitsProvider,
451 Prj().GetProjectName(), boardName );
452
453 if( fprintf( outFile, "%s", TO_UTF8( report ) ) < 0 )
454 {
455 msg.Printf( _( "Error writing file '%s'." ), dlg.GetPath() );
456 DisplayErrorMessage( this, msg );
457 }
458
459 fclose( outFile );
460}
461
462
463void DIALOG_BOARD_STATISTICS::drillGridSize( wxSizeEvent& aEvent )
464{
465 aEvent.Skip();
467}
468
469void DIALOG_BOARD_STATISTICS::drillGridSort( wxGridEvent& aEvent )
470{
471 DRILL_LINE_ITEM::COL_ID colId = static_cast<DRILL_LINE_ITEM::COL_ID>( aEvent.GetCol() );
472 bool ascending = !( m_gridDrills->IsSortingBy( colId )
473 && m_gridDrills->IsSortOrderAscending() );
474
475 sort( m_statsData.drillEntries.begin(), m_statsData.drillEntries.end(),
476 DRILL_LINE_ITEM::COMPARE( colId, ascending ) );
477
479}
480
481
void ComputeBoardStatistics(BOARD *aBoard, const BOARD_STATISTICS_OPTIONS &aOptions, BOARD_STATISTICS_DATA &aData)
static wxString formatCount(int aCount)
wxString FormatBoardStatisticsReport(const BOARD_STATISTICS_DATA &aData, BOARD *aBoard, const UNITS_PROVIDER &aUnitsProvider, const wxString &aProjectName, const wxString &aBoardName)
void InitializeBoardStatisticsData(BOARD_STATISTICS_DATA &aData)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:695
DIALOG_BOARD_STATISTICS_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Board Statistics"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
void checkboxClicked(wxCommandEvent &aEvent) override
Save board statistics to a file.
int m_startLayerColInitialSize
Width of the start layer column as calculated by the wxWidgets autosizing algorithm.
void updateWidgets()
Update drills grid.
void saveReportClicked(wxCommandEvent &aEvent) override
~DIALOG_BOARD_STATISTICS()
Get data from the PCB board and print it to dialog.
int m_stopLayerColInitialSize
Width of the stop layer column.
DIALOG_BOARD_STATISTICS(PCB_EDIT_FRAME *aParentFrame)
void drillGridSort(wxGridEvent &aEvent)
void refreshItemsTypes()
< Function to fill up all items types to be shown in the dialog.
void drillGridSize(wxSizeEvent &aEvent) override
BOARD_STATISTICS_DATA m_statsData
void getDataFromPCB()
Apply data to dialog widgets.
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
The main frame for Pcbnew.
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
Definition project.cpp:159
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:202
This file is part of the common library.
static DIALOG_BOARD_STATISTICS_SAVED_STATE s_savedDialogState
#define ROW_BOARD_AREA
#define ROW_BOARD_THICKNESS
#define COL_AMOUNT
#define ROW_LABEL
#define COL_BOTTOM_SIDE
#define ROW_BACK_COPPER_AREA
#define ROW_MIN_TRACK_WIDTH
#define COL_LABEL
#define COL_TOTAL
#define ROW_MIN_DRILL_DIAMETER
#define ROW_FRONT_COPPER_AREA
#define ROW_BOARD_DIMS
static wxString formatCount(int aCount)
#define COL_FRONT_SIDE
#define ROW_MIN_CLEARANCE
#define _(s)
static wxString TextFileWildcard()
@ UNDEFINED_LAYER
Definition layer_ids.h:61
This file contains miscellaneous commonly used macros and functions.
wxSize GetUnobscuredSize(const wxWindow *aWindow)
Tries to determine the size of the viewport of a scrollable widget (wxDataViewCtrl,...
Definition wxgtk/ui.cpp:264
KICOMMON_API wxFont GetStatusFont(wxWindow *aWindow)
KICOMMON_API wxFont GetSmallInfoFont(wxWindow *aWindow)
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
int quantity
wxString title
PCB_LAYER_ID stopLayer
PAD_DRILL_SHAPE shape
PCB_LAYER_ID startLayer
Definition of file extensions used in Kicad.