/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 Alexander Shuklin, jasuramme@gmail.com * Copyright (C) 2019 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 #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 /** * Struct containing 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_boardWidth( 0 ), m_boardHeight( 0 ), m_boardArea( 0.0 ), m_hasOutline( false ) { m_parentFrame = aParentFrame; m_checkBoxExcludeComponentsNoPins->SetValue( s_savedDialogState.excludeNoPins ); m_checkBoxSubtractHoles->SetValue( s_savedDialogState.subtractHoles ); // Make labels for grids wxFont headingFont = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT ); headingFont.SetSymbolicSize( wxFONTSIZE_SMALL ); 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() + "_report" ); fn.SetExt( "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_componentsTypes.clear(); // If you need some more types to be shown, simply add them to the // corresponding list m_componentsTypes.push_back( componentsType_t( MOD_DEFAULT, _( "THT:" ) ) ); m_componentsTypes.push_back( componentsType_t( MOD_CMS, _( "SMD:" ) ) ); m_componentsTypes.push_back( componentsType_t( MOD_VIRTUAL, _( "Virtual:" ) ) ); m_padsTypes.clear(); m_padsTypes.push_back( padsType_t( PAD_ATTRIB_STANDARD, _( "Through hole:" ) ) ); m_padsTypes.push_back( padsType_t( PAD_ATTRIB_SMD, _( "SMD:" ) ) ); m_padsTypes.push_back( padsType_t( PAD_ATTRIB_CONN, _( "Connector:" ) ) ); m_padsTypes.push_back( padsType_t( PAD_ATTRIB_HOLE_NOT_PLATED, _( "NPTH:" ) ) ); m_viasTypes.clear(); m_viasTypes.push_back( viasType_t( VIA_THROUGH, _( "Through vias:" ) ) ); m_viasTypes.push_back( viasType_t( VIA_BLIND_BURIED, _( "Blind/buried:" ) ) ); m_viasTypes.push_back( viasType_t( VIA_MICROVIA, _( "Micro vias:" ) ) ); // If there not enough rows in grids, append some int appendRows = m_componentsTypes.size() + 2 - m_gridComponents->GetNumberRows(); if( appendRows > 0 ) m_gridComponents->AppendRows( appendRows ); appendRows = m_padsTypes.size() + 1 - m_gridPads->GetNumberRows(); if( appendRows > 0 ) m_gridPads->AppendRows( appendRows ); appendRows = m_viasTypes.size() + 1 - m_gridVias->GetNumberRows(); if( appendRows ) m_gridVias->AppendRows( appendRows ); } bool DIALOG_BOARD_STATISTICS::TransferDataToWindow() { refreshItemsTypes(); getDataFromPCB(); updateWidets(); Layout(); FinishDialogSettings(); return true; } void DIALOG_BOARD_STATISTICS::getDataFromPCB() { auto board = m_parentFrame->GetBoard(); // Get modules and pads count for( MODULE* module : board->Modules() ) { auto& pads = module->Pads(); // Do not proceed modules with no pads if checkbox checked if( m_checkBoxExcludeComponentsNoPins->GetValue() && !pads.size() ) continue; // Go through components types list for( auto& type : m_componentsTypes ) { if( module->GetAttributes() == type.attribute ) { if( module->IsFlipped() ) type.backSideQty++; else type.frontSideQty++; break; } } for( auto& pad : pads ) { // Go through pads types list for( auto& type : m_padsTypes ) { if( pad->GetAttribute() == type.attribute ) { type.qty++; break; } } } } // Get vias count for( auto& track : board->Tracks() ) { if( auto via = dyn_cast( track ) ) { for( auto& type : m_viasTypes ) { if( via->GetViaType() == type.attribute ) { type.qty++; break; } } } } bool boundingBoxCreated = false; //flag if bounding box initialized BOX2I bbox; SHAPE_POLY_SET polySet; m_hasOutline = board->GetBoardPolygonOutlines( polySet ); // If board has no Edge Cuts lines, board->GetBoardPolygonOutlines will // return small rectangle, so we double check that bool edgeCutsExists = false; for( auto& drawing : board->Drawings() ) { if( drawing->GetLayer() == Edge_Cuts ) { edgeCutsExists = true; break; } } if( !edgeCutsExists ) m_hasOutline = false; if( m_hasOutline ) { m_boardArea = 0.0; for( int i = 0; i < polySet.OutlineCount(); i++ ) { SHAPE_LINE_CHAIN& outline = polySet.Outline( i ); m_boardArea += std::fabs( outline.Area() ); // If checkbox "subtract holes" is checked if( m_checkBoxSubtractHoles->GetValue() ) { for( int j = 0; j < polySet.HoleCount( i ); j++ ) m_boardArea -= std::fabs( polySet.Hole( i, j ).Area() ); } if( boundingBoxCreated ) { bbox.Merge( outline.BBox() ); } else { bbox = outline.BBox(); boundingBoxCreated = true; } } if( GetUserUnits() == INCHES ) m_boardArea /= ( IU_PER_MILS * IU_PER_MILS * 1000000 ); else m_boardArea /= ( IU_PER_MM * IU_PER_MM ); m_boardWidth = bbox.GetWidth(); m_boardHeight = bbox.GetHeight(); } } void DIALOG_BOARD_STATISTICS::updateWidets() { int totalPads = 0; int currentRow = 0; for( auto& type : m_padsTypes ) { m_gridPads->SetCellValue( currentRow, COL_LABEL, type.title ); m_gridPads->SetCellValue( currentRow, COL_AMOUNT, wxString::Format( wxT( "%i " ), type.qty ) ); totalPads += type.qty; currentRow++; } m_gridPads->SetCellValue( currentRow, COL_LABEL, _( "Total:" ) ); m_gridPads->SetCellValue( currentRow, COL_AMOUNT, wxString::Format( "%i ", totalPads ) ); int totalVias = 0; currentRow = 0; for( auto& type : m_viasTypes ) { m_gridVias->SetCellValue( currentRow, COL_LABEL, type.title ); m_gridVias->SetCellValue( currentRow, COL_AMOUNT, wxString::Format( "%i ", type.qty ) ); totalVias += type.qty; currentRow++; } m_gridVias->SetCellValue( currentRow, COL_LABEL, _( "Total:" ) ); m_gridVias->SetCellValue( currentRow, COL_AMOUNT, wxString::Format( "%i ", totalVias ) ); int totalFront = 0; int totalBack = 0; // We don't use row 0, as there labels are currentRow = 1; for( auto& type : m_componentsTypes ) { m_gridComponents->SetCellValue( currentRow, COL_LABEL, type.title ); m_gridComponents->SetCellValue( currentRow, COL_FRONT_SIDE, wxString::Format( "%i ", type.frontSideQty ) ); m_gridComponents->SetCellValue( currentRow, COL_BOTTOM_SIDE, wxString::Format( "%i ", type.backSideQty ) ); m_gridComponents->SetCellValue( currentRow, 3, wxString::Format( wxT( "%i " ), type.frontSideQty + type.backSideQty ) ); totalFront += type.frontSideQty; totalBack += type.backSideQty; currentRow++; } m_gridComponents->SetCellValue( currentRow, COL_LABEL, _( "Total:" ) ); m_gridComponents->SetCellValue( currentRow, COL_FRONT_SIDE, wxString::Format( "%i ", totalFront ) ); m_gridComponents->SetCellValue( currentRow, COL_BOTTOM_SIDE, wxString::Format( "%i ", totalBack ) ); m_gridComponents->SetCellValue( currentRow, COL_TOTAL, wxString::Format( "%i ", totalFront + totalBack ) ); if( m_hasOutline ) { m_gridBoard->SetCellValue( ROW_BOARD_WIDTH, COL_AMOUNT, MessageTextFromValue( GetUserUnits(), m_boardWidth ) + " " ); m_gridBoard->SetCellValue( ROW_BOARD_HEIGHT, COL_AMOUNT, MessageTextFromValue( GetUserUnits(), m_boardHeight ) + " " ); m_gridBoard->SetCellValue( ROW_BOARD_AREA, COL_AMOUNT, wxString::Format( wxT( "%.3f %s²" ), m_boardArea, GetAbbreviatedUnitsLabel( GetUserUnits() ) ) ); } 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" ) ); } m_gridComponents->AutoSize(); m_gridPads->AutoSize(); m_gridBoard->AutoSize(); m_gridVias->AutoSize(); } // If any checkbox clicked, we have to refresh dialog data void DIALOG_BOARD_STATISTICS::checkboxClicked( wxCommandEvent& event ) { s_savedDialogState.excludeNoPins = m_checkBoxExcludeComponentsNoPins->GetValue(); s_savedDialogState.subtractHoles = m_checkBoxSubtractHoles->GetValue(); refreshItemsTypes(); getDataFromPCB(); updateWidets(); Layout(); Fit(); } void DIALOG_BOARD_STATISTICS::saveReportClicked( wxCommandEvent& event ) { FILE* outFile; wxString msg; wxString boardName; wxFileName fn = m_parentFrame->GetBoard()->GetFileName(); boardName = fn.GetName(); wxFileDialog saveFileDialog( this, _( "Save Report File" ), s_savedDialogState.saveReportFolder, s_savedDialogState.saveReportName, TextFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); if( saveFileDialog.ShowModal() == wxID_CANCEL ) return; s_savedDialogState.saveReportFolder = wxPathOnly( saveFileDialog.GetPath() ); s_savedDialogState.saveReportName = saveFileDialog.GetFilename(); outFile = wxFopen( saveFileDialog.GetPath(), "wt" ); if( outFile == NULL ) { msg.Printf( _( "Unable to create file \"%s\"" ), saveFileDialog.GetPath() ); DisplayErrorMessage( this, msg ); return; } msg << _( "PCB statistics report" ) << "\n"; msg << _( "Date: " ) << wxDateTime::Now().Format() << "\n"; msg << _( "Project: " ) << Prj().GetProjectName() << "\n"; msg << _( "Board name: " ) << boardName << "\n"; msg << "\n"; msg << "Board\n"; if( m_hasOutline ) { msg << _( "Width: " ) << MessageTextFromValue( GetUserUnits(), m_boardWidth ) << "\n"; msg << _( "Height: " ) << MessageTextFromValue( GetUserUnits(), m_boardHeight ) << "\n"; msg << _( "Area: " ) << wxString::Format( wxT( "%.3f %s²" ), m_boardArea, GetAbbreviatedUnitsLabel( GetUserUnits() ) ) << "\n"; } else { msg << _( "Width: " ) << _( "unknown" ) << "\n"; msg << _( "Height: " ) << _( "unknown" ) << "\n"; msg << _( "Area: " ) << _( "unknown" ) << "\n"; } msg << "\n"; msg << "Pads\n"; for( auto& type : m_padsTypes ) msg << type.title << " " << type.qty << "\n"; msg << "\n"; msg << "Vias\n"; for( auto& type : m_viasTypes ) msg << type.title << " " << type.qty << "\n"; // We will save data about components in the table. // We have to calculate column widths int colsWidth[4]; wxString columns[4] = { "", _( "Front Side" ), _( "Back Side" ), _( "Total" ) }; wxString tmp; for( int i = 0; i < 4; i++ ) colsWidth[i] = columns[i].size(); int frontTotal = 0; int backTotal = 0; for( auto& type : m_componentsTypes ) { // Get maximum width for left label column colsWidth[0] = std::max( type.title.size(), colsWidth[0] ); frontTotal += type.frontSideQty; backTotal += type.backSideQty; } // Get maximum width for other columns tmp.Printf( "%d", frontTotal ); colsWidth[1] = std::max( tmp.size(), colsWidth[1] ); tmp.Printf( "%d", backTotal ); colsWidth[2] = std::max( tmp.size(), colsWidth[2] ); tmp.Printf( "%d", frontTotal + backTotal ); colsWidth[3] = std::max( tmp.size(), colsWidth[3] ); //Write components amount to file msg << "\n"; msg << _( "Components" ) << "\n"; tmp.Printf( "%-*s | %*s | %*s | %*s |\n", colsWidth[0], columns[0], colsWidth[1], columns[1], colsWidth[2], columns[2], colsWidth[3], columns[3] ); msg += tmp; for( auto& type : m_componentsTypes ) { tmp.Printf( "%-*s | %*d | %*d | %*d |\n", colsWidth[0], type.title, colsWidth[1], type.frontSideQty, colsWidth[2], type.backSideQty, colsWidth[3], type.backSideQty + type.frontSideQty ); msg += tmp; } tmp.Printf( "%-*s | %*d | %*d | %*d |\n", colsWidth[0], _( "Total:" ), colsWidth[1], frontTotal, colsWidth[2], backTotal, colsWidth[3], frontTotal + backTotal ); msg += tmp; if( fprintf( outFile, "%s", TO_UTF8( msg ) ) < 0 ) { msg.Printf( _( "Error writing to file \"%s\"" ), saveFileDialog.GetPath() ); DisplayErrorMessage( this, msg ); } fclose( outFile ); } DIALOG_BOARD_STATISTICS::~DIALOG_BOARD_STATISTICS() { }