mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-15 10:43:15 +02:00
Recommendation is to avoid using the year nomenclature as this information is already encoded in the git repo. Avoids needing to repeatly update. Also updates AUTHORS.txt from current repo with contributor names
786 lines
27 KiB
C++
786 lines
27 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2019 Alexander Shuklin, jasuramme@gmail.com
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
|
|
#include "dialog_board_statistics.h"
|
|
#include <kiplatform/ui.h>
|
|
#include <confirm.h>
|
|
#include <pad.h>
|
|
#include <macros.h>
|
|
#include <wildcards_and_files_ext.h>
|
|
#include <widgets/wx_grid.h>
|
|
#include <wx/filedlg.h>
|
|
|
|
#define COL_LABEL 0
|
|
#define COL_AMOUNT 1
|
|
|
|
// Defines for components view
|
|
#define ROW_LABEL 0
|
|
#define COL_FRONT_SIDE 1
|
|
#define COL_BOTTOM_SIDE 2
|
|
#define COL_TOTAL 3
|
|
|
|
// Defines for board view
|
|
#define ROW_BOARD_WIDTH 0
|
|
#define ROW_BOARD_HEIGHT 1
|
|
#define ROW_BOARD_AREA 2
|
|
|
|
|
|
/**
|
|
* The dialog last saved state.
|
|
*/
|
|
struct DIALOG_BOARD_STATISTICS_SAVED_STATE
|
|
{
|
|
DIALOG_BOARD_STATISTICS_SAVED_STATE() :
|
|
excludeNoPins( false ),
|
|
subtractHoles( false ),
|
|
saveReportInitialized(false)
|
|
{
|
|
}
|
|
|
|
// Flags to remember last checkboxes state
|
|
bool excludeNoPins;
|
|
bool subtractHoles;
|
|
|
|
// Variables to save last report file name and folder
|
|
bool saveReportInitialized; // true after the 3 next string are initialized
|
|
wxString saveReportFolder; // last report folder
|
|
wxString saveReportName; // last report filename
|
|
wxString m_project; // name of the project used to create the last report
|
|
// used to reinit last state after a project change
|
|
};
|
|
|
|
|
|
static DIALOG_BOARD_STATISTICS_SAVED_STATE s_savedDialogState;
|
|
|
|
DIALOG_BOARD_STATISTICS::DIALOG_BOARD_STATISTICS( PCB_EDIT_FRAME* aParentFrame ) :
|
|
DIALOG_BOARD_STATISTICS_BASE( aParentFrame ),
|
|
m_parentFrame(aParentFrame),
|
|
m_boardWidth( 0 ),
|
|
m_boardHeight( 0 ),
|
|
m_boardArea( 0.0 ),
|
|
m_hasOutline( false ),
|
|
m_startLayerColInitialSize( 1 ),
|
|
m_stopLayerColInitialSize( 1 )
|
|
{
|
|
m_gridDrills->Connect( wxEVT_GRID_COL_SORT,
|
|
wxGridEventHandler( DIALOG_BOARD_STATISTICS::drillGridSort ),
|
|
nullptr, this );
|
|
|
|
m_checkBoxExcludeComponentsNoPins->SetValue( s_savedDialogState.excludeNoPins );
|
|
m_checkBoxSubtractHoles->SetValue( s_savedDialogState.subtractHoles );
|
|
|
|
// Make labels for grids
|
|
wxFont headingFont = KIUI::GetStatusFont( this );
|
|
m_gridComponents->SetCellValue( ROW_LABEL, COL_FRONT_SIDE, _( "Front Side" ) );
|
|
m_gridComponents->SetCellFont( ROW_LABEL, COL_FRONT_SIDE, headingFont );
|
|
m_gridComponents->SetCellValue( ROW_LABEL, COL_BOTTOM_SIDE, _( "Back Side" ) );
|
|
m_gridComponents->SetCellFont( ROW_LABEL, COL_BOTTOM_SIDE, headingFont );
|
|
m_gridComponents->SetCellValue( ROW_LABEL, COL_TOTAL, _( "Total" ) );
|
|
m_gridComponents->SetCellFont( ROW_LABEL, COL_TOTAL, headingFont );
|
|
|
|
m_gridBoard->SetCellValue( 0, 0, _( "Width:" ) );
|
|
m_gridBoard->SetCellAlignment( 0, 0, wxALIGN_LEFT, wxALIGN_CENTRE );
|
|
m_gridBoard->SetCellValue( 1, 0, _( "Height:" ) );
|
|
m_gridBoard->SetCellAlignment( 1, 0, wxALIGN_LEFT, wxALIGN_CENTRE );
|
|
m_gridBoard->SetCellValue( 2, 0, _( "Area:" ) );
|
|
m_gridBoard->SetCellAlignment( 2, 0, wxALIGN_LEFT, wxALIGN_CENTRE );
|
|
|
|
wxGrid* grids[] = { m_gridComponents, m_gridPads, m_gridVias, m_gridBoard };
|
|
|
|
for( auto& grid : grids )
|
|
{
|
|
// Remove wxgrid's selection boxes
|
|
grid->SetCellHighlightPenWidth( 0 );
|
|
grid->SetColMinimalAcceptableWidth( 80 );
|
|
for( int i = 0; i < grid->GetNumberRows(); i++ )
|
|
grid->SetCellAlignment( i, COL_LABEL, wxALIGN_LEFT, wxALIGN_CENTRE );
|
|
}
|
|
|
|
wxFileName fn = m_parentFrame->GetBoard()->GetFileName();
|
|
|
|
if( !s_savedDialogState.saveReportInitialized
|
|
|| s_savedDialogState.m_project != Prj().GetProjectFullName() )
|
|
{
|
|
fn.SetName( fn.GetName() + wxT( "_report" ) );
|
|
fn.SetExt( wxT( "txt" ) );
|
|
s_savedDialogState.saveReportName = fn.GetFullName();
|
|
s_savedDialogState.saveReportFolder = wxPathOnly( Prj().GetProjectFullName() );
|
|
s_savedDialogState.m_project = Prj().GetProjectFullName();
|
|
s_savedDialogState.saveReportInitialized = true;
|
|
}
|
|
|
|
// The wxStdDialogButtonSizer wxID_CANCLE button is in fact a close button
|
|
// Nothing to cancel:
|
|
m_sdbControlSizerCancel->SetLabel( _( "Close" ) );
|
|
}
|
|
|
|
|
|
void DIALOG_BOARD_STATISTICS::refreshItemsTypes()
|
|
{
|
|
m_fpTypes.clear();
|
|
|
|
// If you need some more types to be shown, simply add them to the corresponding list
|
|
m_fpTypes.push_back( FP_LINE_ITEM( FP_THROUGH_HOLE, FP_THROUGH_HOLE, _( "THT:" ) ) );
|
|
m_fpTypes.push_back( FP_LINE_ITEM( FP_SMD, FP_SMD, _( "SMD:" ) ) );
|
|
m_fpTypes.push_back( FP_LINE_ITEM( FP_THROUGH_HOLE|FP_SMD, 0, _( "Unspecified:" ) ) );
|
|
|
|
m_padTypes.clear();
|
|
m_padTypes.push_back( LINE_ITEM<PAD_ATTRIB>( PAD_ATTRIB::PTH, _( "Through hole:" ) ) );
|
|
m_padTypes.push_back( LINE_ITEM<PAD_ATTRIB>( PAD_ATTRIB::SMD, _( "SMD:" ) ) );
|
|
m_padTypes.push_back( LINE_ITEM<PAD_ATTRIB>( PAD_ATTRIB::CONN, _( "Connector:" ) ) );
|
|
m_padTypes.push_back( LINE_ITEM<PAD_ATTRIB>( PAD_ATTRIB::NPTH, _( "NPTH:" ) ) );
|
|
|
|
m_viaTypes.clear();
|
|
m_viaTypes.push_back( LINE_ITEM<VIATYPE>( VIATYPE::THROUGH, _( "Through vias:" ) ) );
|
|
m_viaTypes.push_back( LINE_ITEM<VIATYPE>( VIATYPE::BLIND_BURIED, _( "Blind/buried:" ) ) );
|
|
m_viaTypes.push_back( LINE_ITEM<VIATYPE>( VIATYPE::MICROVIA, _( "Micro vias:" ) ) );
|
|
|
|
// If there not enough rows in grids, append some
|
|
int appendRows = m_fpTypes.size() + 2 - m_gridComponents->GetNumberRows();
|
|
|
|
if( appendRows > 0 )
|
|
m_gridComponents->AppendRows( appendRows );
|
|
|
|
appendRows = m_padTypes.size() + 1 - m_gridPads->GetNumberRows();
|
|
|
|
if( appendRows > 0 )
|
|
m_gridPads->AppendRows( appendRows );
|
|
|
|
appendRows = m_viaTypes.size() + 1 - m_gridVias->GetNumberRows();
|
|
|
|
if( appendRows )
|
|
m_gridVias->AppendRows( appendRows );
|
|
}
|
|
|
|
|
|
bool DIALOG_BOARD_STATISTICS::TransferDataToWindow()
|
|
{
|
|
refreshItemsTypes();
|
|
getDataFromPCB();
|
|
updateWidets();
|
|
|
|
Layout();
|
|
m_drillsPanel->Layout();
|
|
|
|
m_gridDrills->AutoSizeColumns();
|
|
m_startLayerColInitialSize = m_gridDrills->GetColSize( DRILL_LINE_ITEM::COL_START_LAYER );
|
|
m_stopLayerColInitialSize = m_gridDrills->GetColSize( DRILL_LINE_ITEM::COL_STOP_LAYER );
|
|
|
|
// Add space for the vertical scrollbar, so that it won't overlap with the cells.
|
|
m_gridDrills->SetMinSize( wxSize( m_gridDrills->GetEffectiveMinSize().x
|
|
+ wxSystemSettings::GetMetric( wxSYS_VSCROLL_X ),
|
|
60 ) );
|
|
|
|
adjustDrillGridColumns();
|
|
|
|
finishDialogSettings();
|
|
return true;
|
|
}
|
|
|
|
|
|
void DIALOG_BOARD_STATISTICS::getDataFromPCB()
|
|
{
|
|
BOARD* board = m_parentFrame->GetBoard();
|
|
m_drillTypes.clear();
|
|
m_gridDrills->ClearRows();
|
|
|
|
// Get footprints and pads count
|
|
for( FOOTPRINT* footprint : board->Footprints() )
|
|
{
|
|
// Do not proceed footprints with no pads if checkbox checked
|
|
if( m_checkBoxExcludeComponentsNoPins->GetValue() && ! footprint->Pads().size() )
|
|
continue;
|
|
|
|
// Go through components types list
|
|
for( FP_LINE_ITEM& line : m_fpTypes )
|
|
{
|
|
if( ( footprint->GetAttributes() & line.attribute_mask ) == line.attribute_value )
|
|
{
|
|
switch( footprint->GetSide() )
|
|
{
|
|
case F_Cu: line.frontSideQty++; break;
|
|
case B_Cu: line.backSideQty++; break;
|
|
default: /* unsided: user-layers only, etc. */ break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
for( PAD* pad : footprint->Pads() )
|
|
{
|
|
// Go through pads types list
|
|
for( LINE_ITEM<PAD_ATTRIB>& line : m_padTypes )
|
|
{
|
|
if( pad->GetAttribute() == line.attribute )
|
|
{
|
|
line.qty++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( pad->GetDrillSize().x > 0 && pad->GetDrillSize().y > 0 )
|
|
{
|
|
PCB_LAYER_ID top, bottom;
|
|
|
|
if( pad->GetLayerSet().CuStack().empty() )
|
|
{
|
|
// The pad is not on any copper layer
|
|
top = UNDEFINED_LAYER;
|
|
bottom = UNDEFINED_LAYER;
|
|
}
|
|
else
|
|
{
|
|
top = pad->GetLayerSet().CuStack().front();
|
|
bottom = pad->GetLayerSet().CuStack().back();
|
|
}
|
|
|
|
DRILL_LINE_ITEM drill( pad->GetDrillSize().x, pad->GetDrillSize().y,
|
|
pad->GetDrillShape(),
|
|
pad->GetAttribute() != PAD_ATTRIB::NPTH,
|
|
true, top, bottom );
|
|
|
|
auto it = m_drillTypes.begin();
|
|
|
|
for( ; it != m_drillTypes.end(); ++it )
|
|
{
|
|
if( *it == drill )
|
|
{
|
|
it->qty++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( it == m_drillTypes.end() )
|
|
{
|
|
drill.qty = 1;
|
|
m_drillTypes.push_back( drill );
|
|
m_gridDrills->InsertRows();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get via counts
|
|
for( PCB_TRACK* track : board->Tracks() )
|
|
{
|
|
if( track->Type() == PCB_VIA_T )
|
|
{
|
|
PCB_VIA* via = static_cast<PCB_VIA*>( track );
|
|
|
|
for( LINE_ITEM<VIATYPE>& line : m_viaTypes )
|
|
{
|
|
if( via->GetViaType() == line.attribute )
|
|
{
|
|
line.qty++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DRILL_LINE_ITEM drill( via->GetDrillValue(), via->GetDrillValue(),
|
|
PAD_DRILL_SHAPE::CIRCLE, true, false, via->TopLayer(),
|
|
via->BottomLayer() );
|
|
|
|
auto it = m_drillTypes.begin();
|
|
|
|
for( ; it != m_drillTypes.end(); ++it )
|
|
{
|
|
if( *it == drill )
|
|
{
|
|
it->qty++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( it == m_drillTypes.end() )
|
|
{
|
|
drill.qty = 1;
|
|
m_drillTypes.push_back( drill );
|
|
m_gridDrills->InsertRows();
|
|
}
|
|
}
|
|
}
|
|
|
|
sort( m_drillTypes.begin(), m_drillTypes.end(),
|
|
DRILL_LINE_ITEM::COMPARE( DRILL_LINE_ITEM::COL_COUNT, false ) );
|
|
|
|
SHAPE_POLY_SET polySet;
|
|
m_hasOutline = board->GetBoardPolygonOutlines( polySet );
|
|
|
|
if( m_hasOutline )
|
|
{
|
|
m_boardArea = 0.0;
|
|
|
|
for( int i = 0; i < polySet.OutlineCount(); i++ )
|
|
{
|
|
SHAPE_LINE_CHAIN& outline = polySet.Outline( i );
|
|
m_boardArea += outline.Area();
|
|
|
|
// If checkbox "subtract holes" is checked
|
|
if( m_checkBoxSubtractHoles->GetValue() )
|
|
{
|
|
for( int j = 0; j < polySet.HoleCount( i ); j++ )
|
|
m_boardArea -= polySet.Hole( i, j ).Area();
|
|
|
|
for( FOOTPRINT* fp : board->Footprints() )
|
|
{
|
|
for( PAD* pad : fp->Pads() )
|
|
{
|
|
if( !pad->HasHole() )
|
|
continue;
|
|
|
|
auto hole = pad->GetEffectiveHoleShape();
|
|
const SEG& seg = hole->GetSeg();
|
|
double width = hole->GetWidth();
|
|
double area = seg.Length() * width;
|
|
|
|
// Each end of the hole is a half-circle, so together, we have one
|
|
// full circle. The area of a circle is pi * r^2, so the area of the
|
|
// hole is pi * (d/2)^2 = pi * 1/4 * d^2.
|
|
area += M_PI * 0.25 * width * width;
|
|
m_boardArea -= area;
|
|
}
|
|
}
|
|
|
|
for( PCB_TRACK* track : board->Tracks() )
|
|
{
|
|
if( track->Type() == PCB_VIA_T )
|
|
{
|
|
PCB_VIA* via = static_cast<PCB_VIA*>( track );
|
|
double drill = via->GetDrillValue();
|
|
m_boardArea -= M_PI * 0.25 * drill * drill;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute the bounding box to get a rectangular size
|
|
// We use the polySet bounding box, not the board bounding box, because
|
|
// we do not want the thickness of graphic items defining the board outlines
|
|
// to be taken in account to calculate the physical board bbox
|
|
BOX2I bbox = polySet.BBox();
|
|
|
|
m_boardWidth = bbox.GetWidth();
|
|
m_boardHeight = bbox.GetHeight();
|
|
}
|
|
}
|
|
|
|
|
|
static wxString formatCount( int aCount )
|
|
{
|
|
return wxString::Format( wxT( "%i" ), aCount );
|
|
};
|
|
|
|
|
|
void DIALOG_BOARD_STATISTICS::updateWidets()
|
|
{
|
|
int totalPads = 0;
|
|
int row = 0;
|
|
|
|
for( const LINE_ITEM<PAD_ATTRIB>& line : m_padTypes )
|
|
{
|
|
m_gridPads->SetCellValue( row, COL_LABEL, line.title );
|
|
m_gridPads->SetCellValue( row, COL_AMOUNT, formatCount( line.qty ) );
|
|
totalPads += line.qty;
|
|
row++;
|
|
}
|
|
|
|
m_gridPads->SetCellValue( row, COL_LABEL, _( "Total:" ) );
|
|
m_gridPads->SetCellValue( row, COL_AMOUNT, formatCount( totalPads ) );
|
|
|
|
int totalVias = 0;
|
|
row = 0;
|
|
|
|
for( const LINE_ITEM<VIATYPE>& line : m_viaTypes )
|
|
{
|
|
m_gridVias->SetCellValue( row, COL_LABEL, line.title );
|
|
m_gridVias->SetCellValue( row, COL_AMOUNT, formatCount( line.qty ) );
|
|
totalVias += line.qty;
|
|
row++;
|
|
}
|
|
|
|
m_gridVias->SetCellValue( row, COL_LABEL, _( "Total:" ) );
|
|
m_gridVias->SetCellValue( row, COL_AMOUNT, formatCount( totalVias ) );
|
|
|
|
|
|
int totalFront = 0;
|
|
int totalBack = 0;
|
|
|
|
// We don't use row 0, as there labels are
|
|
row = 1;
|
|
|
|
for( const FP_LINE_ITEM& line : m_fpTypes )
|
|
{
|
|
m_gridComponents->SetCellValue( row, COL_LABEL, line.title );
|
|
m_gridComponents->SetCellValue( row, COL_FRONT_SIDE, formatCount( line.frontSideQty ) );
|
|
m_gridComponents->SetCellValue( row, COL_BOTTOM_SIDE, formatCount( line.backSideQty ) );
|
|
m_gridComponents->SetCellValue( row, 3, formatCount( line.frontSideQty + line.backSideQty ) );
|
|
totalFront += line.frontSideQty;
|
|
totalBack += line.backSideQty;
|
|
row++;
|
|
}
|
|
|
|
m_gridComponents->SetCellValue( row, COL_LABEL, _( "Total:" ) );
|
|
m_gridComponents->SetCellValue( row, COL_FRONT_SIDE, formatCount( totalFront ) );
|
|
m_gridComponents->SetCellValue( row, COL_BOTTOM_SIDE, formatCount( totalBack ) );
|
|
m_gridComponents->SetCellValue( row, COL_TOTAL, formatCount( totalFront + totalBack ) );
|
|
|
|
if( m_hasOutline )
|
|
{
|
|
m_gridBoard->SetCellValue( ROW_BOARD_WIDTH, COL_AMOUNT,
|
|
m_parentFrame->MessageTextFromValue( m_boardWidth ) );
|
|
m_gridBoard->SetCellValue( ROW_BOARD_HEIGHT, COL_AMOUNT,
|
|
m_parentFrame->MessageTextFromValue( m_boardHeight ) );
|
|
m_gridBoard->SetCellValue( ROW_BOARD_AREA, COL_AMOUNT,
|
|
m_parentFrame->MessageTextFromValue( m_boardArea, true,
|
|
EDA_DATA_TYPE::AREA ) );
|
|
}
|
|
else
|
|
{
|
|
m_gridBoard->SetCellValue( ROW_BOARD_WIDTH, COL_AMOUNT, _( "unknown" ) );
|
|
m_gridBoard->SetCellValue( ROW_BOARD_HEIGHT, COL_AMOUNT, _( "unknown" ) );
|
|
m_gridBoard->SetCellValue( ROW_BOARD_AREA, COL_AMOUNT, _( "unknown" ) );
|
|
}
|
|
|
|
updateDrillGrid();
|
|
|
|
m_gridComponents->AutoSize();
|
|
m_gridPads->AutoSize();
|
|
m_gridBoard->AutoSize();
|
|
m_gridVias->AutoSize();
|
|
|
|
adjustDrillGridColumns();
|
|
}
|
|
|
|
|
|
void DIALOG_BOARD_STATISTICS::updateDrillGrid()
|
|
{
|
|
BOARD* board = m_parentFrame->GetBoard();
|
|
int row = 0;
|
|
|
|
for( const DRILL_LINE_ITEM& line : m_drillTypes )
|
|
{
|
|
wxString shapeStr;
|
|
wxString startLayerStr;
|
|
wxString stopLayerStr;
|
|
|
|
switch( line.shape )
|
|
{
|
|
case PAD_DRILL_SHAPE::CIRCLE: shapeStr = _( "Round" ); break;
|
|
case PAD_DRILL_SHAPE::OBLONG: shapeStr = _( "Slot" ); break;
|
|
default: shapeStr = _( "???" ); break;
|
|
}
|
|
|
|
if( line.startLayer == UNDEFINED_LAYER )
|
|
startLayerStr = _( "N/A" );
|
|
else
|
|
startLayerStr = board->GetLayerName( line.startLayer );
|
|
|
|
if( line.stopLayer == UNDEFINED_LAYER )
|
|
stopLayerStr = _( "N/A" );
|
|
else
|
|
stopLayerStr = board->GetLayerName( line.stopLayer );
|
|
|
|
m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_COUNT, formatCount( line.qty ) );
|
|
m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_SHAPE, shapeStr );
|
|
m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_X_SIZE,
|
|
m_parentFrame->MessageTextFromValue( line.xSize ) );
|
|
m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_Y_SIZE,
|
|
m_parentFrame->MessageTextFromValue( line.ySize ) );
|
|
m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_PLATED,
|
|
line.isPlated ? _( "PTH" ) : _( "NPTH" ) );
|
|
m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_VIA_PAD,
|
|
line.isPad ? _( "Pad" ) : _( "Via" ) );
|
|
m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_START_LAYER, startLayerStr );
|
|
m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_STOP_LAYER, stopLayerStr );
|
|
|
|
row++;
|
|
}
|
|
}
|
|
|
|
|
|
void DIALOG_BOARD_STATISTICS::printGridToStringAsTable( wxGrid* aGrid, wxString& aStr,
|
|
bool aUseColLabels,
|
|
bool aUseFirstColAsLabel )
|
|
{
|
|
std::vector<int> widths( aGrid->GetNumberCols(), 0 );
|
|
int rowLabelsWidth = 0;
|
|
|
|
// Determine column widths.
|
|
|
|
if( aUseColLabels )
|
|
{
|
|
for( int col = 0; col < aGrid->GetNumberCols(); col++ )
|
|
widths[col] = aGrid->GetColLabelValue( col ).length();
|
|
}
|
|
|
|
for( int row = 0; row < aGrid->GetNumberRows(); row++ )
|
|
{
|
|
rowLabelsWidth = std::max<int>( rowLabelsWidth, aGrid->GetRowLabelValue( row ).length() );
|
|
|
|
for( int col = 0; col < aGrid->GetNumberCols(); col++ )
|
|
widths[col] = std::max<int>( widths[col], aGrid->GetCellValue( row, col ).length() );
|
|
}
|
|
|
|
// Print the cells.
|
|
|
|
wxString tmp;
|
|
|
|
// Print column labels.
|
|
|
|
aStr << wxT( "|" );
|
|
|
|
for( int col = 0; col < aGrid->GetNumberCols(); col++ )
|
|
{
|
|
if( aUseColLabels )
|
|
tmp.Printf( wxT( " %*s |" ), widths[col], aGrid->GetColLabelValue( col ) );
|
|
else
|
|
tmp.Printf( wxT( " %*s |" ), widths[col], aGrid->GetCellValue( 0, col ) );
|
|
|
|
aStr << tmp;
|
|
}
|
|
|
|
aStr << wxT( "\n" );
|
|
|
|
// Print column label horizontal separators.
|
|
|
|
aStr << wxT( "|" );
|
|
|
|
for( int col = 0; col < aGrid->GetNumberCols(); col++ )
|
|
{
|
|
aStr << wxT( "-" );
|
|
aStr.Append( '-', widths[col] );
|
|
aStr << wxT( "-|" );
|
|
}
|
|
|
|
aStr << wxT( "\n" );
|
|
|
|
// Print regular cells.
|
|
|
|
int firstRow = 0, firstCol = 0;
|
|
|
|
if( !aUseColLabels )
|
|
firstRow = 1;
|
|
|
|
if( aUseFirstColAsLabel )
|
|
firstCol = 1;
|
|
|
|
for( int row = firstRow; row < aGrid->GetNumberRows(); row++ )
|
|
{
|
|
if( aUseFirstColAsLabel )
|
|
tmp.Printf( wxT( "|%-*s |" ), widths[0], aGrid->GetCellValue( row, 0 ) );
|
|
else
|
|
tmp.Printf( wxT( "|" ) );
|
|
|
|
aStr << tmp;
|
|
|
|
for( int col = firstCol; col < aGrid->GetNumberCols(); col++ )
|
|
{
|
|
tmp.Printf( wxT( " %*s |" ), widths[col], aGrid->GetCellValue( row, col ) );
|
|
aStr << tmp;
|
|
}
|
|
|
|
aStr << wxT( "\n" );
|
|
}
|
|
}
|
|
|
|
|
|
void DIALOG_BOARD_STATISTICS::adjustDrillGridColumns()
|
|
{
|
|
wxGridUpdateLocker deferRepaintsTillLeavingScope( m_gridDrills );
|
|
|
|
m_gridDrills->EnsureColLabelsVisible();
|
|
|
|
double remainingWidth = KIPLATFORM::UI::GetUnobscuredSize( m_gridDrills ).x;
|
|
|
|
// Find the total current width
|
|
for( int i = 0; i < m_gridDrills->GetNumberCols(); i++ )
|
|
{
|
|
if( i != DRILL_LINE_ITEM::COL_START_LAYER && i != DRILL_LINE_ITEM::COL_STOP_LAYER )
|
|
remainingWidth -= m_gridDrills->GetColSize( i );
|
|
}
|
|
|
|
double scalingFactor = std::max( 1.0,
|
|
remainingWidth
|
|
/ ( m_startLayerColInitialSize + m_stopLayerColInitialSize ) );
|
|
int startLayerColWidth = static_cast<int>( m_startLayerColInitialSize * scalingFactor );
|
|
int stopLayerColWidth = static_cast<int>( m_stopLayerColInitialSize * scalingFactor );
|
|
|
|
m_gridDrills->SetColSize( DRILL_LINE_ITEM::COL_START_LAYER, startLayerColWidth );
|
|
m_gridDrills->SetColSize( DRILL_LINE_ITEM::COL_STOP_LAYER, stopLayerColWidth );
|
|
}
|
|
|
|
|
|
void DIALOG_BOARD_STATISTICS::checkboxClicked( wxCommandEvent& aEvent )
|
|
{
|
|
s_savedDialogState.excludeNoPins = m_checkBoxExcludeComponentsNoPins->GetValue();
|
|
s_savedDialogState.subtractHoles = m_checkBoxSubtractHoles->GetValue();
|
|
refreshItemsTypes();
|
|
getDataFromPCB();
|
|
updateWidets();
|
|
Layout();
|
|
m_drillsPanel->Layout();
|
|
}
|
|
|
|
|
|
void DIALOG_BOARD_STATISTICS::saveReportClicked( wxCommandEvent& aEvent )
|
|
{
|
|
FILE* outFile;
|
|
wxString msg;
|
|
wxString boardName;
|
|
|
|
wxFileName fn = m_parentFrame->GetBoard()->GetFileName();
|
|
boardName = fn.GetName();
|
|
wxFileDialog dlg( this, _( "Save Report File" ), s_savedDialogState.saveReportFolder,
|
|
s_savedDialogState.saveReportName, FILEEXT::TextFileWildcard(),
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
|
|
|
|
if( dlg.ShowModal() == wxID_CANCEL )
|
|
return;
|
|
|
|
s_savedDialogState.saveReportFolder = wxPathOnly( dlg.GetPath() );
|
|
s_savedDialogState.saveReportName = dlg.GetFilename();
|
|
|
|
outFile = wxFopen( dlg.GetPath(), wxT( "wt" ) );
|
|
|
|
if( outFile == nullptr )
|
|
{
|
|
msg.Printf( _( "Failed to create file '%s'." ), dlg.GetPath() );
|
|
DisplayErrorMessage( this, msg );
|
|
return;
|
|
}
|
|
|
|
msg << _( "PCB statistics report\n=====================" ) << wxT( "\n" );
|
|
msg << wxS( "- " ) << _( "Date" ) << wxS( ": " ) << wxDateTime::Now().Format() << wxT( "\n" );
|
|
msg << wxS( "- " ) << _( "Project" ) << wxS( ": " )<< Prj().GetProjectName() << wxT( "\n" );
|
|
msg << wxS( "- " ) << _( "Board name" ) << wxS( ": " )<< boardName << wxT( "\n" );
|
|
|
|
msg << wxT( "\n" );
|
|
msg << _( "Board" ) << wxT( "\n-----\n" );
|
|
|
|
if( m_hasOutline )
|
|
{
|
|
msg << wxS( "- " ) << _( "Width" ) << wxS( ": " )
|
|
<< m_parentFrame->MessageTextFromValue( m_boardWidth ) << wxT( "\n" );
|
|
msg << wxS( "- " ) << _( "Height" ) << wxS( ": " )
|
|
<< m_parentFrame->MessageTextFromValue( m_boardHeight ) << wxT( "\n" );
|
|
msg << wxS( "- " ) << _( "Area" ) + wxS( ": " )
|
|
<< m_parentFrame->MessageTextFromValue( m_boardArea, true, EDA_DATA_TYPE::AREA );
|
|
msg << wxT( "\n" );
|
|
}
|
|
else
|
|
{
|
|
msg << wxS( "- " ) << _( "Width" ) << wxS( ": " ) << _( "unknown" ) << wxT( "\n" );
|
|
msg << wxS( "- " ) << _( "Height" ) << wxS( ": " ) << _( "unknown" ) << wxT( "\n" );
|
|
msg << wxS( "- " ) << _( "Area" ) << wxS( ": " ) << _( "unknown" ) << wxT( "\n" );
|
|
}
|
|
|
|
msg << wxT( "\n" );
|
|
msg << _( "Pads" ) << wxT( "\n----\n" );
|
|
|
|
for( auto& type : m_padTypes )
|
|
msg << wxT( "- " ) << type.title << wxS( " " ) << type.qty << wxT( "\n" );
|
|
|
|
msg << wxT( "\n" );
|
|
msg << _( "Vias" ) << wxT( "\n----\n" );
|
|
|
|
for( auto& type : m_viaTypes )
|
|
msg << wxT( "- " ) << type.title << wxS( " " ) << type.qty << wxT( "\n" );
|
|
|
|
// We will save data about components in the table.
|
|
// We have to calculate column widths
|
|
std::vector<int> widths;
|
|
std::vector<wxString> labels{ wxT( "" ), _( "Front Side" ), _( "Back Side" ), _( "Total" ) };
|
|
wxString tmp;
|
|
|
|
widths.reserve( labels.size() );
|
|
|
|
for( const wxString& label : labels )
|
|
widths.push_back( label.size() );
|
|
|
|
int frontTotal = 0;
|
|
int backTotal = 0;
|
|
|
|
for( const FP_LINE_ITEM& line : m_fpTypes )
|
|
{
|
|
// Get maximum width for left label column
|
|
widths[0] = std::max<int>( line.title.size(), widths[0] );
|
|
frontTotal += line.frontSideQty;
|
|
backTotal += line.backSideQty;
|
|
}
|
|
|
|
// Get maximum width for other columns
|
|
tmp.Printf( wxT( "%i" ), frontTotal );
|
|
widths[1] = std::max<int>( tmp.size(), widths[1] );
|
|
tmp.Printf( wxT( "%i" ), backTotal );
|
|
widths[2] = std::max<int>( tmp.size(), widths[2] );
|
|
tmp.Printf( wxT( "%i" ), frontTotal + backTotal );
|
|
widths[3] = std::max<int>( tmp.size(), widths[3] );
|
|
|
|
//Write components amount to file
|
|
msg << wxT( "\n" );
|
|
msg << _( "Components" ) << wxT( "\n----------\n" );
|
|
msg << wxT( "\n" );
|
|
|
|
printGridToStringAsTable( m_gridComponents, msg, false, true );
|
|
|
|
msg << wxT( "\n" );
|
|
msg << _( "Drill holes" ) << wxT( "\n-----------\n" );
|
|
msg << wxT( "\n" );
|
|
|
|
printGridToStringAsTable( m_gridDrills, msg, true, false );
|
|
|
|
if( fprintf( outFile, "%s", TO_UTF8( msg ) ) < 0 )
|
|
{
|
|
msg.Printf( _( "Error writing file '%s'." ), dlg.GetPath() );
|
|
DisplayErrorMessage( this, msg );
|
|
}
|
|
|
|
fclose( outFile );
|
|
}
|
|
|
|
|
|
void DIALOG_BOARD_STATISTICS::drillGridSize( wxSizeEvent& aEvent )
|
|
{
|
|
aEvent.Skip();
|
|
adjustDrillGridColumns();
|
|
}
|
|
|
|
void DIALOG_BOARD_STATISTICS::drillGridSort( wxGridEvent& aEvent )
|
|
{
|
|
DRILL_LINE_ITEM::COL_ID colId = static_cast<DRILL_LINE_ITEM::COL_ID>( aEvent.GetCol() );
|
|
bool ascending = !( m_gridDrills->IsSortingBy( colId )
|
|
&& m_gridDrills->IsSortOrderAscending() );
|
|
|
|
sort( m_drillTypes.begin(), m_drillTypes.end(), DRILL_LINE_ITEM::COMPARE( colId, ascending ) );
|
|
|
|
updateDrillGrid();
|
|
}
|
|
|
|
|
|
DIALOG_BOARD_STATISTICS::~DIALOG_BOARD_STATISTICS()
|
|
{
|
|
}
|