You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

752 lines
25 KiB

  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. #include "dialog_board_statistics.h"
  25. #include "base_units.h"
  26. #include <macros.h>
  27. #include <wildcards_and_files_ext.h>
  28. #include <wx/filedlg.h>
  29. #define COL_LABEL 0
  30. #define COL_AMOUNT 1
  31. // Defines for components view
  32. #define ROW_LABEL 0
  33. #define COL_FRONT_SIDE 1
  34. #define COL_BOTTOM_SIDE 2
  35. #define COL_TOTAL 3
  36. // Defines for board view
  37. #define ROW_BOARD_WIDTH 0
  38. #define ROW_BOARD_HEIGHT 1
  39. #define ROW_BOARD_AREA 2
  40. /**
  41. * Struct containing the dialog last saved state
  42. */
  43. struct DIALOG_BOARD_STATISTICS_SAVED_STATE
  44. {
  45. DIALOG_BOARD_STATISTICS_SAVED_STATE() :
  46. excludeNoPins( false ),
  47. subtractHoles( false ),
  48. saveReportInitialized(false)
  49. {
  50. }
  51. // Flags to remember last checkboxes state
  52. bool excludeNoPins;
  53. bool subtractHoles;
  54. // Variables to save last report file name and folder
  55. bool saveReportInitialized; // true after the 3 next string are initialized
  56. wxString saveReportFolder; // last report folder
  57. wxString saveReportName; // last report filename
  58. wxString m_project; // name of the project used to create the last report
  59. // used to reinit last state after a project change
  60. };
  61. static DIALOG_BOARD_STATISTICS_SAVED_STATE s_savedDialogState;
  62. DIALOG_BOARD_STATISTICS::DIALOG_BOARD_STATISTICS( PCB_EDIT_FRAME* aParentFrame ) :
  63. DIALOG_BOARD_STATISTICS_BASE( aParentFrame ),
  64. m_boardWidth( 0 ),
  65. m_boardHeight( 0 ),
  66. m_boardArea( 0.0 ),
  67. m_hasOutline( false )
  68. {
  69. m_parentFrame = aParentFrame;
  70. m_gridDrills->UseNativeColHeader();
  71. m_gridDrills->Connect( wxEVT_GRID_COL_SORT, wxGridEventHandler( DIALOG_BOARD_STATISTICS::drillGridSort ), NULL, this );
  72. m_checkBoxExcludeComponentsNoPins->SetValue( s_savedDialogState.excludeNoPins );
  73. m_checkBoxSubtractHoles->SetValue( s_savedDialogState.subtractHoles );
  74. // Make labels for grids
  75. wxFont headingFont = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT );
  76. headingFont.SetSymbolicSize( wxFONTSIZE_SMALL );
  77. m_gridComponents->SetCellValue( ROW_LABEL, COL_FRONT_SIDE, _( "Front Side" ) );
  78. m_gridComponents->SetCellFont( ROW_LABEL, COL_FRONT_SIDE, headingFont );
  79. m_gridComponents->SetCellValue( ROW_LABEL, COL_BOTTOM_SIDE, _( "Back Side" ) );
  80. m_gridComponents->SetCellFont( ROW_LABEL, COL_BOTTOM_SIDE, headingFont );
  81. m_gridComponents->SetCellValue( ROW_LABEL, COL_TOTAL, _( "Total" ) );
  82. m_gridComponents->SetCellFont( ROW_LABEL, COL_TOTAL, headingFont );
  83. m_gridBoard->SetCellValue( 0, 0, _( "Width:" ) );
  84. m_gridBoard->SetCellAlignment( 0, 0, wxALIGN_LEFT, wxALIGN_CENTRE );
  85. m_gridBoard->SetCellValue( 1, 0, _( "Height:" ) );
  86. m_gridBoard->SetCellAlignment( 1, 0, wxALIGN_LEFT, wxALIGN_CENTRE );
  87. m_gridBoard->SetCellValue( 2, 0, _( "Area:" ) );
  88. m_gridBoard->SetCellAlignment( 2, 0, wxALIGN_LEFT, wxALIGN_CENTRE );
  89. wxGrid* grids[] = { m_gridComponents, m_gridPads, m_gridVias, m_gridBoard };
  90. for( auto& grid : grids )
  91. {
  92. // Remove wxgrid's selection boxes
  93. grid->SetCellHighlightPenWidth( 0 );
  94. grid->SetColMinimalAcceptableWidth( 80 );
  95. for( int i = 0; i < grid->GetNumberRows(); i++ )
  96. grid->SetCellAlignment( i, COL_LABEL, wxALIGN_LEFT, wxALIGN_CENTRE );
  97. }
  98. wxFileName fn = m_parentFrame->GetBoard()->GetFileName();
  99. if( !s_savedDialogState.saveReportInitialized
  100. || s_savedDialogState.m_project != Prj().GetProjectFullName() )
  101. {
  102. fn.SetName( fn.GetName() + "_report" );
  103. fn.SetExt( "txt" );
  104. s_savedDialogState.saveReportName = fn.GetFullName();
  105. s_savedDialogState.saveReportFolder = wxPathOnly( Prj().GetProjectFullName() );
  106. s_savedDialogState.m_project = Prj().GetProjectFullName();
  107. s_savedDialogState.saveReportInitialized = true;
  108. }
  109. // The wxStdDialogButtonSizer wxID_CANCLE button is in fact a close button
  110. // Nothing to cancel:
  111. m_sdbControlSizerCancel->SetLabel( _( "Close" ) );
  112. }
  113. void DIALOG_BOARD_STATISTICS::refreshItemsTypes()
  114. {
  115. m_componentsTypes.clear();
  116. // If you need some more types to be shown, simply add them to the
  117. // corresponding list
  118. m_componentsTypes.push_back( componentsType_t( FP_THROUGH_HOLE, _( "THT:" ) ) );
  119. m_componentsTypes.push_back( componentsType_t( FP_SMD, _( "SMD:" ) ) );
  120. m_padsTypes.clear();
  121. m_padsTypes.push_back( padsType_t( PAD_ATTRIB::PTH, _( "Through hole:" ) ) );
  122. m_padsTypes.push_back( padsType_t( PAD_ATTRIB::SMD, _( "SMD:" ) ) );
  123. m_padsTypes.push_back( padsType_t( PAD_ATTRIB::CONN, _( "Connector:" ) ) );
  124. m_padsTypes.push_back( padsType_t( PAD_ATTRIB::NPTH, _( "NPTH:" ) ) );
  125. m_viasTypes.clear();
  126. m_viasTypes.push_back( viasType_t( VIATYPE::THROUGH, _( "Through vias:" ) ) );
  127. m_viasTypes.push_back( viasType_t( VIATYPE::BLIND_BURIED, _( "Blind/buried:" ) ) );
  128. m_viasTypes.push_back( viasType_t( VIATYPE::MICROVIA, _( "Micro vias:" ) ) );
  129. // If there not enough rows in grids, append some
  130. int appendRows = m_componentsTypes.size() + 2 - m_gridComponents->GetNumberRows();
  131. if( appendRows > 0 )
  132. m_gridComponents->AppendRows( appendRows );
  133. appendRows = m_padsTypes.size() + 1 - m_gridPads->GetNumberRows();
  134. if( appendRows > 0 )
  135. m_gridPads->AppendRows( appendRows );
  136. appendRows = m_viasTypes.size() + 1 - m_gridVias->GetNumberRows();
  137. if( appendRows )
  138. m_gridVias->AppendRows( appendRows );
  139. }
  140. bool DIALOG_BOARD_STATISTICS::TransferDataToWindow()
  141. {
  142. refreshItemsTypes();
  143. getDataFromPCB();
  144. updateWidets();
  145. Layout();
  146. drillsPanel->Layout();
  147. finishDialogSettings();
  148. return true;
  149. }
  150. void DIALOG_BOARD_STATISTICS::getDataFromPCB()
  151. {
  152. BOARD* board = m_parentFrame->GetBoard();
  153. // Get footprints and pads count
  154. for( FOOTPRINT* footprint : board->Footprints() )
  155. {
  156. // Do not proceed footprints with no pads if checkbox checked
  157. if( m_checkBoxExcludeComponentsNoPins->GetValue() && ! footprint->Pads().size() )
  158. continue;
  159. // Go through components types list
  160. for( auto& type : m_componentsTypes )
  161. {
  162. if(( footprint->GetAttributes() & type.attribute ) > 0 )
  163. {
  164. if( footprint->IsFlipped() )
  165. type.backSideQty++;
  166. else
  167. type.frontSideQty++;
  168. break;
  169. }
  170. }
  171. for( PAD* pad : footprint->Pads() )
  172. {
  173. // Go through pads types list
  174. for( auto& type : m_padsTypes )
  175. {
  176. if( pad->GetAttribute() == type.attribute )
  177. {
  178. type.qty++;
  179. break;
  180. }
  181. }
  182. if( pad->GetDrillSize().x > 0 && pad->GetDrillSize().y > 0 )
  183. {
  184. PCB_LAYER_ID top, bottom;
  185. if( pad->GetLayerSet().CuStack().empty() )
  186. {
  187. // The pad is not on any copper layer
  188. top = UNDEFINED_LAYER;
  189. bottom = UNDEFINED_LAYER;
  190. }
  191. else
  192. {
  193. top = pad->GetLayerSet().CuStack().front();
  194. bottom = pad->GetLayerSet().CuStack().back();
  195. }
  196. drillType_t drill( pad->GetDrillSize().x, pad->GetDrillSize().y,
  197. pad->GetDrillShape(), pad->GetAttribute() != PAD_ATTRIB::NPTH,
  198. true, top, bottom );
  199. auto it = m_drillTypes.begin();
  200. for( ; it != m_drillTypes.end(); ++it )
  201. {
  202. if( *it == drill )
  203. {
  204. it->qty++;
  205. break;
  206. }
  207. }
  208. if( it == m_drillTypes.end() )
  209. {
  210. drill.qty = 1;
  211. m_drillTypes.push_back( drill );
  212. m_gridDrills->InsertRows();
  213. }
  214. }
  215. }
  216. }
  217. // Get via counts
  218. for( TRACK* track : board->Tracks() )
  219. {
  220. if( VIA* via = dyn_cast<VIA*>( track ) )
  221. {
  222. for( auto& type : m_viasTypes )
  223. {
  224. if( via->GetViaType() == type.attribute )
  225. {
  226. type.qty++;
  227. break;
  228. }
  229. }
  230. drillType_t drill( via->GetDrillValue(), via->GetDrillValue(), PAD_DRILL_SHAPE_CIRCLE,
  231. true, false, via->TopLayer(), via->BottomLayer() );
  232. auto it = m_drillTypes.begin();
  233. for( ; it != m_drillTypes.end(); ++it )
  234. {
  235. if( *it == drill )
  236. {
  237. it->qty++;
  238. break;
  239. }
  240. }
  241. if( it == m_drillTypes.end() )
  242. {
  243. drill.qty = 1;
  244. m_drillTypes.push_back( drill );
  245. m_gridDrills->InsertRows();
  246. }
  247. }
  248. }
  249. sort( m_drillTypes.begin(), m_drillTypes.end(),
  250. drillType_t::COMPARE( drillType_t::COL_COUNT, false ) );
  251. bool boundingBoxCreated = false; //flag if bounding box initialized
  252. BOX2I bbox;
  253. SHAPE_POLY_SET polySet;
  254. m_hasOutline = board->GetBoardPolygonOutlines( polySet );
  255. // If board has no Edge Cuts lines, board->GetBoardPolygonOutlines will
  256. // return small rectangle, so we double check that
  257. bool edgeCutsExists = false;
  258. for( BOARD_ITEM* drawing : board->Drawings() )
  259. {
  260. if( drawing->GetLayer() == Edge_Cuts )
  261. {
  262. edgeCutsExists = true;
  263. break;
  264. }
  265. }
  266. if( !edgeCutsExists )
  267. m_hasOutline = false;
  268. if( m_hasOutline )
  269. {
  270. m_boardArea = 0.0;
  271. for( int i = 0; i < polySet.OutlineCount(); i++ )
  272. {
  273. SHAPE_LINE_CHAIN& outline = polySet.Outline( i );
  274. m_boardArea += std::fabs( outline.Area() );
  275. // If checkbox "subtract holes" is checked
  276. if( m_checkBoxSubtractHoles->GetValue() )
  277. {
  278. for( int j = 0; j < polySet.HoleCount( i ); j++ )
  279. m_boardArea -= std::fabs( polySet.Hole( i, j ).Area() );
  280. }
  281. if( boundingBoxCreated )
  282. {
  283. bbox.Merge( outline.BBox() );
  284. }
  285. else
  286. {
  287. bbox = outline.BBox();
  288. boundingBoxCreated = true;
  289. }
  290. }
  291. m_boardWidth = bbox.GetWidth();
  292. m_boardHeight = bbox.GetHeight();
  293. }
  294. }
  295. void DIALOG_BOARD_STATISTICS::updateWidets()
  296. {
  297. int totalPads = 0;
  298. int currentRow = 0;
  299. for( const auto& type : m_padsTypes )
  300. {
  301. m_gridPads->SetCellValue( currentRow, COL_LABEL, type.title );
  302. m_gridPads->SetCellValue(
  303. currentRow, COL_AMOUNT, wxString::Format( wxT( "%i " ), type.qty ) );
  304. totalPads += type.qty;
  305. currentRow++;
  306. }
  307. m_gridPads->SetCellValue( currentRow, COL_LABEL, _( "Total:" ) );
  308. m_gridPads->SetCellValue( currentRow, COL_AMOUNT, wxString::Format( "%i ", totalPads ) );
  309. int totalVias = 0;
  310. currentRow = 0;
  311. for( const auto& type : m_viasTypes )
  312. {
  313. m_gridVias->SetCellValue( currentRow, COL_LABEL, type.title );
  314. m_gridVias->SetCellValue(
  315. currentRow, COL_AMOUNT, wxString::Format( "%i ", type.qty ) );
  316. totalVias += type.qty;
  317. currentRow++;
  318. }
  319. m_gridVias->SetCellValue( currentRow, COL_LABEL, _( "Total:" ) );
  320. m_gridVias->SetCellValue( currentRow, COL_AMOUNT, wxString::Format( "%i ", totalVias ) );
  321. int totalFront = 0;
  322. int totalBack = 0;
  323. // We don't use row 0, as there labels are
  324. currentRow = 1;
  325. for( const auto& type : m_componentsTypes )
  326. {
  327. m_gridComponents->SetCellValue( currentRow, COL_LABEL, type.title );
  328. m_gridComponents->SetCellValue(
  329. currentRow, COL_FRONT_SIDE, wxString::Format( "%i ", type.frontSideQty ) );
  330. m_gridComponents->SetCellValue(
  331. currentRow, COL_BOTTOM_SIDE, wxString::Format( "%i ", type.backSideQty ) );
  332. m_gridComponents->SetCellValue( currentRow, 3,
  333. wxString::Format( wxT( "%i " ), type.frontSideQty + type.backSideQty ) );
  334. totalFront += type.frontSideQty;
  335. totalBack += type.backSideQty;
  336. currentRow++;
  337. }
  338. m_gridComponents->SetCellValue( currentRow, COL_LABEL, _( "Total:" ) );
  339. m_gridComponents->SetCellValue( currentRow, COL_FRONT_SIDE,
  340. wxString::Format( "%i ", totalFront ) );
  341. m_gridComponents->SetCellValue( currentRow, COL_BOTTOM_SIDE,
  342. wxString::Format( "%i ", totalBack ) );
  343. m_gridComponents->SetCellValue( currentRow, COL_TOTAL,
  344. wxString::Format( "%i ", totalFront + totalBack ) );
  345. if( m_hasOutline )
  346. {
  347. m_gridBoard->SetCellValue( ROW_BOARD_WIDTH, COL_AMOUNT,
  348. MessageTextFromValue( GetUserUnits(), m_boardWidth ) + " " );
  349. m_gridBoard->SetCellValue( ROW_BOARD_HEIGHT, COL_AMOUNT,
  350. MessageTextFromValue( GetUserUnits(), m_boardHeight ) + " " );
  351. m_gridBoard->SetCellValue( ROW_BOARD_AREA, COL_AMOUNT,
  352. MessageTextFromValue( GetUserUnits(), m_boardArea, true, EDA_DATA_TYPE::AREA ) );
  353. }
  354. else
  355. {
  356. m_gridBoard->SetCellValue( ROW_BOARD_WIDTH, COL_AMOUNT, _( "unknown" ) );
  357. m_gridBoard->SetCellValue( ROW_BOARD_HEIGHT, COL_AMOUNT, _( "unknown" ) );
  358. m_gridBoard->SetCellValue( ROW_BOARD_AREA, COL_AMOUNT, _( "unknown" ) );
  359. }
  360. updateDrillGrid();
  361. m_gridComponents->AutoSize();
  362. m_gridPads->AutoSize();
  363. m_gridBoard->AutoSize();
  364. m_gridVias->AutoSize();
  365. m_gridDrills->AutoSize();
  366. adjustDrillGridColumns();
  367. }
  368. void DIALOG_BOARD_STATISTICS::updateDrillGrid()
  369. {
  370. BOARD* board = m_parentFrame->GetBoard();
  371. int currentRow = 0;
  372. for( const auto& type : m_drillTypes )
  373. {
  374. wxString shapeStr;
  375. wxString startLayerStr;
  376. wxString stopLayerStr;
  377. switch( type.shape )
  378. {
  379. case PAD_DRILL_SHAPE_CIRCLE:
  380. shapeStr = _( "Round" );
  381. break;
  382. case PAD_DRILL_SHAPE_OBLONG:
  383. shapeStr = _( "Slot" );
  384. break;
  385. default:
  386. shapeStr = _( "???" );
  387. break;
  388. }
  389. if( type.startLayer == UNDEFINED_LAYER )
  390. startLayerStr = _( "N/A" );
  391. else
  392. startLayerStr = board->GetLayerName( type.startLayer );
  393. if( type.stopLayer == UNDEFINED_LAYER )
  394. stopLayerStr = _( "N/A" );
  395. else
  396. stopLayerStr = board->GetLayerName( type.stopLayer );
  397. m_gridDrills->SetCellValue(
  398. currentRow, drillType_t::COL_COUNT, wxString::Format( "%i", type.qty ) );
  399. m_gridDrills->SetCellValue( currentRow, drillType_t::COL_SHAPE, shapeStr );
  400. m_gridDrills->SetCellValue( currentRow, drillType_t::COL_X_SIZE,
  401. MessageTextFromValue( GetUserUnits(), type.xSize ) );
  402. m_gridDrills->SetCellValue( currentRow, drillType_t::COL_Y_SIZE,
  403. MessageTextFromValue( GetUserUnits(), type.ySize ) );
  404. m_gridDrills->SetCellValue(
  405. currentRow, drillType_t::COL_PLATED, type.isPlated ? _( "PTH" ) : _( "NPTH" ) );
  406. m_gridDrills->SetCellValue(
  407. currentRow, drillType_t::COL_VIA_PAD, type.isPad ? _( "Pad" ) : _( "Via" ) );
  408. m_gridDrills->SetCellValue( currentRow, drillType_t::COL_START_LAYER, startLayerStr );
  409. m_gridDrills->SetCellValue( currentRow, drillType_t::COL_STOP_LAYER, stopLayerStr );
  410. currentRow++;
  411. }
  412. }
  413. void DIALOG_BOARD_STATISTICS::printGridToStringAsTable( wxGrid* aGrid, wxString& aStr,
  414. bool aUseRowLabels, bool aUseColLabels, bool aUseFirstColAsLabel )
  415. {
  416. std::vector<int> widths( aGrid->GetNumberCols(), 0 );
  417. int rowLabelsWidth = 0;
  418. // Determine column widths.
  419. if( aUseColLabels )
  420. {
  421. for( int col = 0; col < aGrid->GetNumberCols(); col++ )
  422. widths[col] = aGrid->GetColLabelValue( col ).length();
  423. }
  424. for( int row = 0; row < aGrid->GetNumberRows(); row++ )
  425. {
  426. rowLabelsWidth = std::max<int>( rowLabelsWidth, aGrid->GetRowLabelValue( row ).length() );
  427. for( int col = 0; col < aGrid->GetNumberCols(); col++ )
  428. widths[col] = std::max<int>( widths[col], aGrid->GetCellValue( row, col ).length() );
  429. }
  430. // Print the cells.
  431. wxString tmp;
  432. // Print column labels.
  433. aStr << "|";
  434. if( aUseRowLabels )
  435. {
  436. aStr.Append( ' ', rowLabelsWidth );
  437. aStr << " |";
  438. }
  439. for( int col = 0; col < aGrid->GetNumberCols(); col++ )
  440. {
  441. if( aUseColLabels )
  442. tmp.Printf( " %*s |", widths[col], aGrid->GetColLabelValue( col ) );
  443. else
  444. tmp.Printf( " %*s |", widths[col], aGrid->GetCellValue( 0, col ) );
  445. aStr << tmp;
  446. }
  447. aStr << "\n";
  448. // Print column label horizontal separators.
  449. aStr << "|";
  450. if( aUseRowLabels )
  451. {
  452. aStr.Append( '-', rowLabelsWidth );
  453. aStr << "-|";
  454. }
  455. for( int col = 0; col < aGrid->GetNumberCols(); col++ )
  456. {
  457. aStr << "-";
  458. aStr.Append( '-', widths[col] );
  459. aStr << "-|";
  460. }
  461. aStr << "\n";
  462. // Print regular cells.
  463. int firstRow = 0, firstCol = 0;
  464. if( !aUseColLabels )
  465. firstRow = 1;
  466. if( !aUseRowLabels && aUseFirstColAsLabel )
  467. firstCol = 1;
  468. for( int row = firstRow; row < aGrid->GetNumberRows(); row++ )
  469. {
  470. if( aUseRowLabels )
  471. tmp.Printf( "|%-*s |", rowLabelsWidth, aGrid->GetRowLabelValue( row ) );
  472. else if( aUseFirstColAsLabel )
  473. tmp.Printf( "|%-*s |", widths[0], aGrid->GetCellValue( row, 0 ) );
  474. else
  475. tmp.Printf( "|" );
  476. aStr << tmp;
  477. for( int col = firstCol; col < aGrid->GetNumberCols(); col++ )
  478. {
  479. tmp.Printf( " %*s |", widths[col], aGrid->GetCellValue( row, col ) );
  480. aStr << tmp;
  481. }
  482. aStr << "\n";
  483. }
  484. }
  485. void DIALOG_BOARD_STATISTICS::adjustDrillGridColumns()
  486. {
  487. int newTotalWidth = m_gridDrills->GetClientSize().GetWidth();
  488. int curTotalWidth = 0;
  489. // Find the total current width
  490. for( int i = 0; i < m_gridDrills->GetNumberCols(); i++ )
  491. {
  492. if( i != drillType_t::COL_START_LAYER && i != drillType_t::COL_STOP_LAYER )
  493. curTotalWidth += m_gridDrills->GetColSize( i );
  494. }
  495. // Resize the last two columns to fill all available space
  496. int remainingWidth = newTotalWidth - curTotalWidth;
  497. m_gridDrills->SetColSize( drillType_t::COL_START_LAYER, remainingWidth / 2 );
  498. m_gridDrills->SetColSize( drillType_t::COL_STOP_LAYER, remainingWidth - remainingWidth / 2 );
  499. m_gridDrills->Refresh();
  500. }
  501. // If any checkbox clicked, we have to refresh dialog data
  502. void DIALOG_BOARD_STATISTICS::checkboxClicked( wxCommandEvent& aEvent )
  503. {
  504. s_savedDialogState.excludeNoPins = m_checkBoxExcludeComponentsNoPins->GetValue();
  505. s_savedDialogState.subtractHoles = m_checkBoxSubtractHoles->GetValue();
  506. refreshItemsTypes();
  507. getDataFromPCB();
  508. updateWidets();
  509. Layout();
  510. drillsPanel->Layout();
  511. }
  512. void DIALOG_BOARD_STATISTICS::saveReportClicked( wxCommandEvent& aEvent )
  513. {
  514. FILE* outFile;
  515. wxString msg;
  516. wxString boardName;
  517. wxFileName fn = m_parentFrame->GetBoard()->GetFileName();
  518. boardName = fn.GetName();
  519. wxFileDialog saveFileDialog( this, _( "Save Report File" ),
  520. s_savedDialogState.saveReportFolder,
  521. s_savedDialogState.saveReportName,
  522. TextFileWildcard(),
  523. wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
  524. if( saveFileDialog.ShowModal() == wxID_CANCEL )
  525. return;
  526. s_savedDialogState.saveReportFolder = wxPathOnly( saveFileDialog.GetPath() );
  527. s_savedDialogState.saveReportName = saveFileDialog.GetFilename();
  528. outFile = wxFopen( saveFileDialog.GetPath(), "wt" );
  529. if( outFile == NULL )
  530. {
  531. msg.Printf( _( "Unable to create file \"%s\"" ), saveFileDialog.GetPath() );
  532. DisplayErrorMessage( this, msg );
  533. return;
  534. }
  535. msg << _( "PCB statistics report\n=====================" ) << "\n";
  536. msg << wxS( "- " ) << _( "Date" ) << wxS( ": " ) << wxDateTime::Now().Format() << "\n";
  537. msg << wxS( "- " ) << _( "Project" ) << wxS( ": " )<< Prj().GetProjectName() << "\n";
  538. msg << wxS( "- " ) << _( "Board name" ) << wxS( ": " )<< boardName << "\n";
  539. msg << "\n";
  540. msg << _( "Board" ) << "\n-----\n";
  541. if( m_hasOutline )
  542. {
  543. msg << wxS( "- " ) << _( "Width" ) << wxS( ": " ) << MessageTextFromValue( GetUserUnits(), m_boardWidth ) << "\n";
  544. msg << wxS( "- " ) << _( "Height" ) << wxS( ": " )<< MessageTextFromValue( GetUserUnits(), m_boardHeight ) << "\n";
  545. msg << wxS( "- " ) << _( "Area" ) + wxS( ": " ) << MessageTextFromValue( GetUserUnits(), m_boardArea, true, EDA_DATA_TYPE::AREA );
  546. msg << "\n";
  547. }
  548. else
  549. {
  550. msg << wxS( "- " ) << _( "Width" ) << wxS( ": " ) << _( "unknown" ) << "\n";
  551. msg << wxS( "- " ) << _( "Height" ) << wxS( ": " ) << _( "unknown" ) << "\n";
  552. msg << wxS( "- " ) << _( "Area" ) << wxS( ": " ) << _( "unknown" ) << "\n";
  553. }
  554. msg << "\n";
  555. msg << _( "Pads" ) << "\n----\n";
  556. for( auto& type : m_padsTypes )
  557. msg << "- " << type.title << " " << type.qty << "\n";
  558. msg << "\n";
  559. msg << _( "Vias" ) << "\n----\n";
  560. for( auto& type : m_viasTypes )
  561. msg << "- " << type.title << " " << type.qty << "\n";
  562. // We will save data about components in the table.
  563. // We have to calculate column widths
  564. std::vector<int> widths;
  565. std::vector<wxString> labels{ "", _( "Front Side" ), _( "Back Side" ), _( "Total" ) };
  566. wxString tmp;
  567. widths.reserve( labels.size() );
  568. for( const auto& label : labels )
  569. widths.push_back( label.size() );
  570. int frontTotal = 0;
  571. int backTotal = 0;
  572. for( const auto& type : m_componentsTypes )
  573. {
  574. // Get maximum width for left label column
  575. widths[0] = std::max<int>( type.title.size(), widths[0] );
  576. frontTotal += type.frontSideQty;
  577. backTotal += type.backSideQty;
  578. }
  579. // Get maximum width for other columns
  580. tmp.Printf( "%i", frontTotal );
  581. widths[1] = std::max<int>( tmp.size(), widths[1] );
  582. tmp.Printf( "%i", backTotal );
  583. widths[2] = std::max<int>( tmp.size(), widths[2] );
  584. tmp.Printf( "%i", frontTotal + backTotal );
  585. widths[3] = std::max<int>( tmp.size(), widths[3] );
  586. //Write components amount to file
  587. msg << "\n";
  588. msg << _( "Components" ) << "\n----------\n";
  589. msg << "\n";
  590. printGridToStringAsTable( m_gridComponents, msg, false, false, true );
  591. msg << "\n";
  592. msg << _( "Drill holes" ) << "\n-----------\n";
  593. msg << "\n";
  594. printGridToStringAsTable( m_gridDrills, msg, false, true, false );
  595. if( fprintf( outFile, "%s", TO_UTF8( msg ) ) < 0 )
  596. {
  597. msg.Printf( _( "Error writing to file \"%s\"" ), saveFileDialog.GetPath() );
  598. DisplayErrorMessage( this, msg );
  599. }
  600. fclose( outFile );
  601. }
  602. void DIALOG_BOARD_STATISTICS::drillGridSize( wxSizeEvent& aEvent )
  603. {
  604. aEvent.Skip();
  605. adjustDrillGridColumns();
  606. }
  607. void DIALOG_BOARD_STATISTICS::drillGridSort( wxGridEvent& aEvent )
  608. {
  609. drillType_t::COL_ID colId = static_cast<drillType_t::COL_ID>( aEvent.GetCol() );
  610. bool ascending =
  611. !( m_gridDrills->IsSortingBy( colId ) && m_gridDrills->IsSortOrderAscending() );
  612. sort( m_drillTypes.begin(), m_drillTypes.end(), drillType_t::COMPARE( colId, ascending ) );
  613. updateDrillGrid();
  614. }
  615. DIALOG_BOARD_STATISTICS::~DIALOG_BOARD_STATISTICS()
  616. {
  617. }