KiCad PCB EDA Suite
Loading...
Searching...
No Matches
board_statistics_report.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21#include "build_version.h"
22
23#include <board.h>
26#include <footprint.h>
27#include <pad.h>
28#include <pcb_track.h>
32#include <i18n_utility.h>
33#include <json_common.h>
34#include <string_utils.h>
35
36#include <algorithm>
37#include <cmath>
38#include <limits>
39#include <memory>
40#include <wx/datetime.h>
41
43 hasOutline( false ),
44 boardWidth( 0 ),
45 boardHeight( 0 ),
46 boardArea( 0.0 ),
47 frontCopperArea( 0.0 ),
48 backCopperArea( 0.0 ),
53 minClearanceTrackToTrack( std::numeric_limits<int>::max() ),
54 minTrackWidth( std::numeric_limits<int>::max() ),
55 minDrillSize( std::numeric_limits<int>::max() ),
56 boardThickness( 0 ),
58 padEntries(),
60 viaEntries(),
62{
63}
64
65
67{
68 hasOutline = false;
69 boardWidth = 0;
70 boardHeight = 0;
71 boardArea = 0.0;
72 frontCopperArea = 0.0;
73 backCopperArea = 0.0;
76 minClearanceTrackToTrack = std::numeric_limits<int>::max();
77 minTrackWidth = std::numeric_limits<int>::max();
78 minDrillSize = std::numeric_limits<int>::max();
80
82 {
83 fp.frontCount = 0;
84 fp.backCount = 0;
85 }
86
88 pad.quantity = 0;
89
91 prop.quantity = 0;
92
94 via.quantity = 0;
95
96 drillEntries.clear();
97}
98
99
101{
102 aData.footprintEntries.clear();
103 aData.padEntries.clear();
104 aData.padPropertyEntries.clear();
105 aData.viaEntries.clear();
106 aData.drillEntries.clear();
107
108 aData.footprintEntries.push_back(
110 aData.footprintEntries.push_back( BOARD_STATISTICS_FP_ENTRY( FP_SMD, FP_SMD, _( "SMD:" ), "smd" ) );
111 aData.footprintEntries.push_back(
112 BOARD_STATISTICS_FP_ENTRY( FP_THROUGH_HOLE | FP_SMD, 0, _( "Unspecified:" ), "unspecified" ) );
113
114 aData.padEntries.push_back(
115 BOARD_STATISTICS_INFO_ENTRY<PAD_ATTRIB>( PAD_ATTRIB::PTH, _( "Through hole:" ), "through_hole" ) );
116 aData.padEntries.push_back( BOARD_STATISTICS_INFO_ENTRY<PAD_ATTRIB>( PAD_ATTRIB::SMD, _( "SMD:" ), "smd" ) );
117 aData.padEntries.push_back(
118 BOARD_STATISTICS_INFO_ENTRY<PAD_ATTRIB>( PAD_ATTRIB::CONN, _( "Connector:" ), "connector" ) );
119 aData.padEntries.push_back( BOARD_STATISTICS_INFO_ENTRY<PAD_ATTRIB>( PAD_ATTRIB::NPTH, _( "NPTH:" ), "npth" ) );
120
121 aData.padPropertyEntries.push_back(
122 BOARD_STATISTICS_INFO_ENTRY<PAD_PROP>( PAD_PROP::CASTELLATED, _( "Castellated:" ), "castellated" ) );
123 aData.padPropertyEntries.push_back(
124 BOARD_STATISTICS_INFO_ENTRY<PAD_PROP>( PAD_PROP::PRESSFIT, _( "Press-fit:" ), "press_fit" ) );
125
126 aData.viaEntries.push_back(
127 BOARD_STATISTICS_INFO_ENTRY<VIATYPE>( VIATYPE::THROUGH, _( "Through vias:" ), "through" ) );
128 aData.viaEntries.push_back( BOARD_STATISTICS_INFO_ENTRY<VIATYPE>( VIATYPE::BLIND, _( "Blind vias:" ), "blind" ) );
129 aData.viaEntries.push_back(
130 BOARD_STATISTICS_INFO_ENTRY<VIATYPE>( VIATYPE::BURIED, _( "Buried vias:" ), "buried" ) );
131 aData.viaEntries.push_back(
132 BOARD_STATISTICS_INFO_ENTRY<VIATYPE>( VIATYPE::MICROVIA, _( "Micro vias:" ), "micro" ) );
133
134 aData.ResetCounts();
135}
136
137
138static void updatePadCounts( FOOTPRINT* aFootprint, BOARD_STATISTICS_DATA& aData )
139{
140 for( PAD* pad : aFootprint->Pads() )
141 {
143 {
144 if( pad->GetAttribute() == padEntry.attribute )
145 {
146 padEntry.quantity++;
147 break;
148 }
149 }
150
152 {
153 if( pad->GetProperty() == propEntry.attribute )
154 {
155 propEntry.quantity++;
156 break;
157 }
158 }
159 }
160}
161
162
164{
165 aData.ResetCounts();
166
167 if( !aBoard )
168 return;
169
170 static const std::vector<KICAD_T> trackTypes = { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T };
171
172 for( FOOTPRINT* footprint : aBoard->Footprints() )
173 {
174 if( aOptions.excludeFootprintsWithoutPads && footprint->Pads().empty() )
175 continue;
176
177 for( BOARD_STATISTICS_FP_ENTRY& entry : aData.footprintEntries )
178 {
179 if( ( footprint->GetAttributes() & entry.attributeMask ) == entry.attributeValue )
180 {
181 switch( footprint->GetSide() )
182 {
183 case F_Cu: entry.frontCount++; break;
184 case B_Cu: entry.backCount++; break;
185 default: break;
186 }
187
188 break;
189 }
190 }
191
192 updatePadCounts( footprint, aData );
193 }
194
195 for( PCB_TRACK* track : aBoard->Tracks() )
196 {
197 if( track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T )
198 aData.minTrackWidth = std::min( aData.minTrackWidth, track->GetWidth() );
199
200 if( !track->IsType( trackTypes ) )
201 continue;
202
203 for( PCB_TRACK* otherTrack : aBoard->Tracks() )
204 {
205 if( track->GetNetCode() == otherTrack->GetNetCode() )
206 continue;
207
208 if( !otherTrack->IsType( trackTypes ) )
209 continue;
210
211 LSET commonLayers = track->GetLayerSet() & otherTrack->GetLayerSet() & LSET::AllCuMask();
212
213 for( PCB_LAYER_ID layer : commonLayers.CuStack() )
214 {
215 int actual = 0;
216 auto trackShapeA = track->GetEffectiveShape( layer );
217 auto trackShapeB = otherTrack->GetEffectiveShape( layer );
218 bool collide = trackShapeA->Collide( trackShapeB.get(), aData.minClearanceTrackToTrack, &actual );
219
220 if( collide )
222 }
223 }
224
225 if( track->Type() == PCB_VIA_T )
226 {
227 PCB_VIA* via = static_cast<PCB_VIA*>( track );
228
230 {
231 if( via->GetViaType() == entry.attribute )
232 {
233 entry.quantity++;
234 break;
235 }
236 }
237 }
238 }
239
240 {
241 std::vector<DRILL_LINE_ITEM> drills;
242 CollectDrillLineItems( aBoard, drills );
243
244 aData.drillEntries = std::move( drills );
245 }
246
247 std::sort( aData.drillEntries.begin(), aData.drillEntries.end(),
249
250 aData.minDrillSize = std::numeric_limits<int>::max();
251
252 for( const DRILL_LINE_ITEM& drill : aData.drillEntries )
253 {
254 if( drill.shape == PAD_DRILL_SHAPE::CIRCLE )
255 aData.minDrillSize = std::min( aData.minDrillSize, drill.xSize );
256 }
257
258 SHAPE_POLY_SET polySet;
259 aData.hasOutline = aBoard->GetBoardPolygonOutlines( polySet, false );
260
261 if( aData.hasOutline )
262 {
263 aData.boardArea = 0.0;
264
265 for( int i = 0; i < polySet.OutlineCount(); ++i )
266 {
267 SHAPE_LINE_CHAIN& outline = polySet.Outline( i );
268 aData.boardArea += outline.Area();
269
270 if( aOptions.subtractHolesFromBoardArea )
271 {
272 for( int j = 0; j < polySet.HoleCount( i ); ++j )
273 aData.boardArea -= polySet.Hole( i, j ).Area();
274 }
275 }
276
277 if( aOptions.subtractHolesFromBoardArea )
278 {
279 for( FOOTPRINT* footprint : aBoard->Footprints() )
280 {
281 for( PAD* pad : footprint->Pads() )
282 {
283 if( !pad->HasHole() )
284 continue;
285
286 std::shared_ptr<SHAPE_SEGMENT> hole = pad->GetEffectiveHoleShape();
287
288 if( !hole )
289 continue;
290
291 const SEG& seg = hole->GetSeg();
292 double width = hole->GetWidth();
293 double area = seg.Length() * width;
294
295 area += M_PI * 0.25 * width * width;
296 aData.boardArea -= area;
297 }
298 }
299
300 for( PCB_TRACK* track : aBoard->Tracks() )
301 {
302 if( track->Type() == PCB_VIA_T )
303 {
304 PCB_VIA* via = static_cast<PCB_VIA*>( track );
305 double drill = via->GetDrillValue();
306 aData.boardArea -= M_PI * 0.25 * drill * drill;
307 }
308 }
309 }
310
311 BOX2I bbox = polySet.BBox();
312
313 aData.boardWidth = static_cast<int>( bbox.GetWidth() );
314 aData.boardHeight = static_cast<int>( bbox.GetHeight() );
315
316 }
317
318 // Now determine the courtyard areas which reflects how much space is occupied by components
319 // This will always assume all components are populated as its intended for
320 // layout purposes and arguing with people saying theres not enough space.
321 SHAPE_POLY_SET frontShapesForArea;
322 SHAPE_POLY_SET backShapesForArea;
323
324 std::shared_ptr<NET_SETTINGS>& netSettings = aBoard->GetDesignSettings().m_NetSettings;
325 int minPadClearanceOuter = netSettings->GetDefaultNetclass()->GetClearance();
326
327 for( FOOTPRINT* fp : aBoard->Footprints() )
328 {
329 const SHAPE_POLY_SET& frontA = fp->GetCourtyard( F_CrtYd );
330 const SHAPE_POLY_SET& backA = fp->GetCourtyard( B_CrtYd );
331
332 if( frontA.OutlineCount() != 0 )
333 frontShapesForArea.Append( frontA );
334
335 if( backA.OutlineCount() != 0 )
336 backShapesForArea.Append( backA );
337
338 // PTH/NPTH holes in footprints can be outside the main courtyard and also
339 // consume space on the other side of the board but without a courtyard
340 for( PAD* pad : fp->Pads() )
341 {
342 if( !pad->HasHole() )
343 continue;
344
345 if( pad->GetAttribute() != PAD_ATTRIB::NPTH )
346 {
347 pad->TransformShapeToPolygon( frontShapesForArea, F_Cu,
348 std::min( minPadClearanceOuter, pad->GetOwnClearance( F_Cu ) ),
350 pad->TransformShapeToPolygon( backShapesForArea, B_Cu,
351 std::min( minPadClearanceOuter, pad->GetOwnClearance( B_Cu ) ),
353 }
354 else
355 {
356 pad->TransformHoleToPolygon( frontShapesForArea, 0, ARC_LOW_DEF, ERROR_INSIDE );
357 pad->TransformHoleToPolygon( backShapesForArea, 0, ARC_LOW_DEF, ERROR_INSIDE );
358 }
359 }
360 }
361
362 // deal with overlapping courtyards (if people are ignoring DRC or something)
363 // and such through simplify
364 frontShapesForArea.Simplify();
365 backShapesForArea.Simplify();
366
367 aData.frontFootprintCourtyardArea = frontShapesForArea.Area();
368 aData.backFootprintCourtyardArea = backShapesForArea.Area();
369
370 if( aData.hasOutline )
371 {
374 }
375
376 SHAPE_POLY_SET frontCopper;
377 SHAPE_POLY_SET backCopper;
378 SHAPE_POLY_SET frontHoles;
379 SHAPE_POLY_SET backHoles;
380
381 aBoard->RunOnChildren(
382 [&]( BOARD_ITEM* child )
383 {
384 if( child->Type() == PCB_FOOTPRINT_T
385 || child->Type() == PCB_GROUP_T
386 || child->Type() == PCB_GENERATOR_T )
387 {
388 return;
389 }
390
391 if( child->IsOnLayer( F_Cu ) )
392 child->TransformShapeToPolySet( frontCopper, F_Cu, 0, ARC_LOW_DEF, ERROR_INSIDE );
393
394 if( child->IsOnLayer( B_Cu ) )
395 child->TransformShapeToPolySet( backCopper, B_Cu, 0, ARC_LOW_DEF, ERROR_INSIDE );
396
397 if( child->Type() == PCB_PAD_T )
398 {
399 PAD* pad = static_cast<PAD*>( child );
400
401 if( pad->HasHole() )
402 {
403 pad->TransformHoleToPolygon( frontHoles, 0, ARC_LOW_DEF, ERROR_OUTSIDE );
404 pad->TransformHoleToPolygon( backHoles, 0, ARC_LOW_DEF, ERROR_OUTSIDE );
405 }
406 }
407 else if( child->Type() == PCB_VIA_T )
408 {
409 PCB_VIA* via = static_cast<PCB_VIA*>( child );
410 VECTOR2I center = via->GetPosition();
411 int radius = via->GetDrillValue() / 2;
412
413 if( via->IsOnLayer( F_Cu ) )
415
416 if( via->IsOnLayer( B_Cu ) )
418 }
419 },
421
422 frontCopper.Simplify();
423 backCopper.Simplify();
424
425 if( aOptions.subtractHolesFromCopperAreas )
426 {
427 frontCopper.BooleanSubtract( frontHoles );
428 backCopper.BooleanSubtract( backHoles );
429 }
430
431 aData.frontCopperArea = frontCopper.Area();
432 aData.backCopperArea = backCopper.Area();
433
435}
436
437
438static wxString formatCount( int aCount )
439{
440 return wxString::Format( wxT( "%i" ), aCount );
441}
442
443
444static void appendTable( const std::vector<std::vector<wxString>>& aRows, bool aUseFirstColAsLabel, wxString& aOut )
445{
446 if( aRows.empty() )
447 return;
448
449 size_t columnCount = 0;
450
451 for( const std::vector<wxString>& row : aRows )
452 {
453 if( row.size() > columnCount )
454 columnCount = row.size();
455 }
456
457 if( columnCount == 0 )
458 return;
459
460 std::vector<int> widths( columnCount, 0 );
461
462 for( const std::vector<wxString>& row : aRows )
463 {
464 for( size_t col = 0; col < columnCount; ++col )
465 {
466 if( col >= row.size() )
467 continue;
468
469 int cellWidth = static_cast<int>( row[col].length() );
470
471 if( cellWidth > widths[col] )
472 widths[col] = cellWidth;
473 }
474 }
475
476 auto appendDataRow =
477 [&]( const std::vector<wxString>& row, bool treatFirstAsLabel )
478 {
479 if( treatFirstAsLabel && aUseFirstColAsLabel )
480 {
481 wxString formatted;
482 wxString firstColumn;
483
484 if( !row.empty() )
485 firstColumn = row[0];
486
487 formatted.Printf( wxS( "|%-*s |" ), widths[0], firstColumn );
488 aOut << formatted;
489
490 for( size_t col = 1; col < columnCount; ++col )
491 {
492 wxString value;
493 if( col < row.size() )
494 value = row[col];
495
496 formatted.Printf( wxS( " %*s |" ), widths[col], value );
497 aOut << formatted;
498 }
499 }
500 else
501 {
502 aOut << wxS( "|" );
503
504 for( size_t col = 0; col < columnCount; ++col )
505 {
506 wxString value;
507 if( col < row.size() )
508 value = row[col];
509
510 wxString formatted;
511 formatted.Printf( wxS( " %*s |" ), widths[col], value );
512 aOut << formatted;
513 }
514 }
515
516 aOut << wxS( "\n" );
517 };
518
519 appendDataRow( aRows.front(), false );
520
521 aOut << wxS( "|" );
522
523 for( size_t col = 0; col < columnCount; ++col )
524 {
525 int dashCount = widths[col] + 2;
526
527 if( dashCount < 3 )
528 dashCount = 3;
529
530 wxString dashes;
531
532 for( int i = 0; i < dashCount; ++i )
533 dashes << wxS( "-" );
534
535 aOut << dashes << wxS( "|" );
536 }
537
538 aOut << wxS( "\n" );
539
540 for( size_t rowIdx = 1; rowIdx < aRows.size(); ++rowIdx )
541 appendDataRow( aRows[rowIdx], true );
542}
543
544
545wxString FormatBoardStatisticsMinimum( int aValue, const UNITS_PROVIDER& aUnitsProvider )
546{
547 if( aValue == std::numeric_limits<int>::max() )
548 return _( "unknown" );
549
550 return aUnitsProvider.MessageTextFromValue( aValue, true, EDA_DATA_TYPE::DISTANCE );
551}
552
553
555 const UNITS_PROVIDER& aUnitsProvider, const wxString& aProjectName,
556 const wxString& aBoardName )
557{
558 wxString report;
559
560 report << _( "PCB statistics report\n=====================" ) << wxS( "\n" );
561 report << wxS( "- " ) << _( "Date" ) << wxS( ": " ) << wxDateTime::Now().Format() << wxS( "\n" );
562 report << wxS( "- " ) << _( "Project" ) << wxS( ": " ) << aProjectName << wxS( "\n" );
563 report << wxS( "- " ) << _( "Board name" ) << wxS( ": " ) << aBoardName << wxS( "\n\n" );
564
565 report << _( "Board" ) << wxS( "\n-----\n" );
566
567 if( aData.hasOutline )
568 {
569 report << wxS( "- " ) << _( "Width" ) << wxS( ": " ) << aUnitsProvider.MessageTextFromValue( aData.boardWidth )
570 << wxS( "\n" );
571 report << wxS( "- " ) << _( "Height" ) << wxS( ": " )
572 << aUnitsProvider.MessageTextFromValue( aData.boardHeight ) << wxS( "\n" );
573 report << wxS( "- " ) << _( "Area" ) << wxS( ": " )
574 << aUnitsProvider.MessageTextFromValue( aData.boardArea, true, EDA_DATA_TYPE::AREA ) << wxS( "\n" );
575 }
576 else
577 {
578 report << wxS( "- " ) << _( "Dimensions" ) << wxS( ": " ) << _( "unknown" ) << wxS( "\n" );
579 report << wxS( "- " ) << _( "Area" ) << wxS( ": " ) << _( "unknown" ) << wxS( "\n" );
580 }
581
582 report << wxS( "- " ) << _( "Front copper area" ) << wxS( ": " )
583 << aUnitsProvider.MessageTextFromValue( aData.frontCopperArea, true, EDA_DATA_TYPE::AREA ) << wxS( "\n" );
584 report << wxS( "- " ) << _( "Back copper area" ) << wxS( ": " )
585 << aUnitsProvider.MessageTextFromValue( aData.backCopperArea, true, EDA_DATA_TYPE::AREA ) << wxS( "\n" );
586
587 report << wxS( "- " ) << _( "Min track clearance" ) << wxS( ": " )
588 << FormatBoardStatisticsMinimum( aData.minClearanceTrackToTrack, aUnitsProvider ) << wxS( "\n" );
589
590 report << wxS( "- " ) << _( "Min track width" ) << wxS( ": " )
591 << FormatBoardStatisticsMinimum( aData.minTrackWidth, aUnitsProvider ) << wxS( "\n" );
592
593 report << wxS( "- " ) << _( "Min drill diameter" ) << wxS( ": " )
594 << FormatBoardStatisticsMinimum( aData.minDrillSize, aUnitsProvider ) << wxS( "\n" );
595
596 report << wxS( "- " ) << _( "Board stackup thickness" ) << wxS( ": " )
597 << aUnitsProvider.MessageTextFromValue( aData.boardThickness, true, EDA_DATA_TYPE::DISTANCE )
598 << wxS( "\n\n" );
599
600 report << wxS( "- " ) << _( "Front footprint area" ) << wxS( ": " )
601 << aUnitsProvider.MessageTextFromValue( aData.frontFootprintCourtyardArea, true, EDA_DATA_TYPE::AREA ) << wxS( "\n" );
602 report << wxS( "- " ) << _( "Back footprint area" ) << wxS( ": " )
603 << aUnitsProvider.MessageTextFromValue( aData.backFootprintCourtyardArea, true, EDA_DATA_TYPE::AREA ) << wxS( "\n" );
604
605 report << wxS( "- " ) << _( "Front component density" ) << wxS( ": " );
606
607 if( aData.hasOutline )
608 report << wxString::Format( "%.2f %%", aData.frontFootprintDensity );
609 else
610 report << _( "unknown" );
611
612 report << wxS( "\n" );
613
614 report << wxS( "- " ) << _( "Back component density" ) << wxS( ": " );
615
616 if( aData.hasOutline )
617 report << wxString::Format( "%.2f %%", aData.backFootprintDensity );
618 else
619 report << _( "unknown" );
620
621 report << wxS( "\n" );
622
623 report << _( "Pads" ) << wxS( "\n----\n" );
624
625 for( const BOARD_STATISTICS_INFO_ENTRY<PAD_ATTRIB>& padEntry : aData.padEntries )
626 report << wxS( "- " ) << padEntry.title << wxS( " " ) << padEntry.quantity << wxS( "\n" );
627
628 for( const BOARD_STATISTICS_INFO_ENTRY<PAD_PROP>& propEntry : aData.padPropertyEntries )
629 report << wxS( "- " ) << propEntry.title << wxS( " " ) << propEntry.quantity << wxS( "\n" );
630
631 report << wxS( "\n" );
632 report << _( "Vias" ) << wxS( "\n----\n" );
633
634 for( const BOARD_STATISTICS_INFO_ENTRY<VIATYPE>& viaEntry : aData.viaEntries )
635 report << wxS( "- " ) << viaEntry.title << wxS( " " ) << viaEntry.quantity << wxS( "\n" );
636
637 report << wxS( "\n" );
638 report << _( "Components" ) << wxS( "\n----------\n\n" );
639
640 std::vector<std::vector<wxString>> componentRows;
641 std::vector<wxString> header;
642 header.push_back( wxString() );
643 header.push_back( _( "Front Side" ) );
644 header.push_back( _( "Back Side" ) );
645 header.push_back( _( "Total" ) );
646 componentRows.push_back( std::move( header ) );
647
648 int frontTotal = 0;
649 int backTotal = 0;
650
651 for( const BOARD_STATISTICS_FP_ENTRY& fpEntry : aData.footprintEntries )
652 {
653 std::vector<wxString> row;
654 row.push_back( fpEntry.title );
655 row.push_back( formatCount( fpEntry.frontCount ) );
656 row.push_back( formatCount( fpEntry.backCount ) );
657 row.push_back( formatCount( fpEntry.frontCount + fpEntry.backCount ) );
658 componentRows.push_back( std::move( row ) );
659
660 frontTotal += fpEntry.frontCount;
661 backTotal += fpEntry.backCount;
662 }
663
664 std::vector<wxString> totalRow;
665 totalRow.push_back( _( "Total:" ) );
666 totalRow.push_back( formatCount( frontTotal ) );
667 totalRow.push_back( formatCount( backTotal ) );
668 totalRow.push_back( formatCount( frontTotal + backTotal ) );
669 componentRows.push_back( std::move( totalRow ) );
670
671 appendTable( componentRows, true, report );
672
673 report << wxS( "\n" );
674 report << _( "Drill holes" ) << wxS( "\n-----------\n\n" );
675
676 std::vector<std::vector<wxString>> drillRows;
677 std::vector<wxString> drillHeader;
678 drillHeader.push_back( _( "Count" ) );
679 drillHeader.push_back( _( "Shape" ) );
680 drillHeader.push_back( _( "X Size" ) );
681 drillHeader.push_back( _( "Y Size" ) );
682 drillHeader.push_back( _( "Plated" ) );
683 drillHeader.push_back( _( "Via/Pad" ) );
684 drillHeader.push_back( _( "Start Layer" ) );
685 drillHeader.push_back( _( "Stop Layer" ) );
686 drillRows.push_back( std::move( drillHeader ) );
687
688 for( const DRILL_LINE_ITEM& drill : aData.drillEntries )
689 {
690 wxString shapeStr;
691
692 switch( drill.shape )
693 {
694 case PAD_DRILL_SHAPE::CIRCLE: shapeStr = _( "Round" ); break;
695 case PAD_DRILL_SHAPE::OBLONG: shapeStr = _( "Slot" ); break;
696 default: shapeStr = _( "???" ); break;
697 }
698
699 wxString platedStr = drill.isPlated ? _( "PTH" ) : _( "NPTH" );
700 wxString itemStr = drill.isPad ? _( "Pad" ) : _( "Via" );
701
702 wxString startLayerStr;
703 wxString stopLayerStr;
704
705 if( drill.startLayer == UNDEFINED_LAYER )
706 startLayerStr = _( "N/A" );
707 else if( aBoard )
708 startLayerStr = aBoard->GetLayerName( drill.startLayer );
709 else
710 startLayerStr = _( "N/A" );
711
712 if( drill.stopLayer == UNDEFINED_LAYER )
713 stopLayerStr = _( "N/A" );
714 else if( aBoard )
715 stopLayerStr = aBoard->GetLayerName( drill.stopLayer );
716 else
717 stopLayerStr = _( "N/A" );
718
719 std::vector<wxString> row;
720 row.push_back( formatCount( drill.m_Qty ) );
721 row.push_back( shapeStr );
722 row.push_back( aUnitsProvider.MessageTextFromValue( drill.xSize ) );
723 row.push_back( aUnitsProvider.MessageTextFromValue( drill.ySize ) );
724 row.push_back( platedStr );
725 row.push_back( itemStr );
726 row.push_back( startLayerStr );
727 row.push_back( stopLayerStr );
728 drillRows.push_back( std::move( row ) );
729 }
730
731 appendTable( drillRows, false, report );
732
733 return report;
734}
735
736
738 const UNITS_PROVIDER& aUnitsProvider, const wxString& aProjectName,
739 const wxString& aBoardName )
740{
741 const auto formatMinimum = [&aUnitsProvider]( int aValue ) -> nlohmann::json
742 {
743 if( aValue == std::numeric_limits<int>::max() )
744 return nlohmann::json();
745
746 return aUnitsProvider.MessageTextFromValue( aValue );
747 };
748
749 nlohmann::ordered_json root;
750
751 nlohmann::ordered_json metadata;
752 metadata["date"] = GetISO8601CurrentDateTime();
753 metadata["generator"] = "KiCad " + GetBuildVersion();
754 metadata["project"] = aProjectName;
755 metadata["board_name"] = aBoardName;
756 root["metadata"] = metadata;
757
758 nlohmann::ordered_json board;
759 board["has_outline"] = aData.hasOutline;
760
761 if( aData.hasOutline )
762 {
763 board["width"] = aUnitsProvider.MessageTextFromValue( aData.boardWidth );
764 board["height"] = aUnitsProvider.MessageTextFromValue( aData.boardHeight );
765 board["area"] = aUnitsProvider.MessageTextFromValue( aData.boardArea, true, EDA_DATA_TYPE::AREA );
766 board["front_component_density"] = wxString::Format( "%.2f", aData.frontFootprintDensity );
767 board["back_component_density"] = wxString::Format( "%.2f", aData.backFootprintDensity );
768 }
769 else
770 {
771 board["width"] = nlohmann::json();
772 board["height"] = nlohmann::json();
773 board["area"] = nlohmann::json();
774 board["front_component_density"] = nlohmann::json();
775 board["back_component_density"] = nlohmann::json();
776 }
777
778 board["front_copper_area"] = aUnitsProvider.MessageTextFromValue( aData.frontCopperArea, true,
780 board["back_copper_area"] = aUnitsProvider.MessageTextFromValue( aData.backCopperArea, true,
782 board["min_track_clearance"] = formatMinimum( aData.minClearanceTrackToTrack );
783 board["min_track_width"] = formatMinimum( aData.minTrackWidth );
784 board["min_drill_diameter"] = formatMinimum( aData.minDrillSize );
785 board["board_thickness"] = aUnitsProvider.MessageTextFromValue( aData.boardThickness );
786 board["front_footprint_area"] = aUnitsProvider.MessageTextFromValue( aData.frontFootprintCourtyardArea, true,
788 board["back_footprint_area"] = aUnitsProvider.MessageTextFromValue( aData.backFootprintCourtyardArea, true,
790
791 if( aData.hasOutline )
792 {
793 board["front_footprint_density"] = wxString::Format( "%.2f", aData.frontFootprintDensity );
794 board["back_footprint_density"] = wxString::Format( "%.2f", aData.backFootprintDensity );
795 }
796 else
797 {
798 board["front_footprint_density"] = nlohmann::json();
799 board["back_footprint_density"] = nlohmann::json();
800 }
801
802 root["board"] = board;
803
804 nlohmann::ordered_json pads = nlohmann::ordered_json::object();
805
806 for( const BOARD_STATISTICS_INFO_ENTRY<PAD_ATTRIB>& padEntry : aData.padEntries )
807 pads[padEntry.jsonKey] = padEntry.quantity;
808
809 for( const BOARD_STATISTICS_INFO_ENTRY<PAD_PROP>& propEntry : aData.padPropertyEntries )
810 pads[propEntry.jsonKey] = propEntry.quantity;
811
812 root["pads"] = pads;
813
814 nlohmann::ordered_json vias = nlohmann::ordered_json::object();
815
816 for( const BOARD_STATISTICS_INFO_ENTRY<VIATYPE>& viaEntry : aData.viaEntries )
817 vias[viaEntry.jsonKey] = viaEntry.quantity;
818
819 root["vias"] = vias;
820
821 int frontTotal = 0;
822 int backTotal = 0;
823 nlohmann::ordered_json components = nlohmann::ordered_json::object();
824
825 for( const BOARD_STATISTICS_FP_ENTRY& fpEntry : aData.footprintEntries )
826 {
827 nlohmann::ordered_json component;
828 component["front"] = fpEntry.frontCount;
829 component["back"] = fpEntry.backCount;
830 component["total"] = fpEntry.frontCount + fpEntry.backCount;
831 components[fpEntry.jsonKey] = component;
832
833 frontTotal += fpEntry.frontCount;
834 backTotal += fpEntry.backCount;
835 }
836
837 nlohmann::ordered_json totals;
838 totals["front"] = frontTotal;
839 totals["back"] = backTotal;
840 totals["total"] = frontTotal + backTotal;
841 components["total"] = totals;
842
843 root["components"] = components;
844
845 nlohmann::ordered_json drillHoles = nlohmann::ordered_json::array();
846
847 for( const DRILL_LINE_ITEM& drill : aData.drillEntries )
848 {
849 nlohmann::ordered_json drillJson;
850
851 wxString shapeStr;
852
853 switch( drill.shape )
854 {
855 case PAD_DRILL_SHAPE::CIRCLE: shapeStr = _( "Round" ); break;
856 case PAD_DRILL_SHAPE::OBLONG: shapeStr = _( "Slot" ); break;
857 default: shapeStr = _( "???" ); break;
858 }
859
860 wxString sourceStr = drill.isPad ? _( "Pad" ) : _( "Via" );
861 wxString startLayerStr = _( "N/A" );
862 wxString stopLayerStr = _( "N/A" );
863
864 if( aBoard && drill.startLayer != UNDEFINED_LAYER )
865 startLayerStr = aBoard->GetLayerName( drill.startLayer );
866
867 if( aBoard && drill.stopLayer != UNDEFINED_LAYER )
868 stopLayerStr = aBoard->GetLayerName( drill.stopLayer );
869
870 drillJson["count"] = drill.m_Qty;
871 drillJson["shape"] = shapeStr;
872 drillJson["x_size"] = aUnitsProvider.MessageTextFromValue( drill.xSize );
873 drillJson["y_size"] = aUnitsProvider.MessageTextFromValue( drill.ySize );
874 drillJson["plated"] = drill.isPlated;
875 drillJson["source"] = sourceStr;
876 drillJson["start_layer"] = startLayerStr;
877 drillJson["stop_layer"] = stopLayerStr;
878 drillHoles.push_back( std::move( drillJson ) );
879 }
880
881 root["drill_holes"] = drillHoles;
882
883 std::string jsonText = root.dump( 2 );
884 return wxString::FromUTF8( jsonText.c_str() );
885}
@ ERROR_OUTSIDE
@ ERROR_INSIDE
constexpr int ARC_LOW_DEF
Definition base_units.h:143
void CollectDrillLineItems(BOARD *board, std::vector< DRILL_LINE_ITEM > &out)
static void updatePadCounts(FOOTPRINT *aFootprint, BOARD_STATISTICS_DATA &aData)
wxString FormatBoardStatisticsJson(const BOARD_STATISTICS_DATA &aData, BOARD *aBoard, const UNITS_PROVIDER &aUnitsProvider, const wxString &aProjectName, const wxString &aBoardName)
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)
wxString FormatBoardStatisticsMinimum(int aValue, const UNITS_PROVIDER &aUnitsProvider)
Format a minimum distance, or "unknown" when no qualifying measurement was found.
void InitializeBoardStatisticsData(BOARD_STATISTICS_DATA &aData)
static void appendTable(const std::vector< std::vector< wxString > > &aRows, bool aUseFirstColAsLabel, wxString &aOut)
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
wxString GetBuildVersion()
Get the full KiCad version string.
std::shared_ptr< NET_SETTINGS > m_NetSettings
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual bool IsOnLayer(PCB_LAYER_ID aLayer) const
Test to see if this object is on the given layer.
Definition board_item.h:409
virtual void TransformShapeToPolySet(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, KIGFX::RENDER_SETTINGS *aRenderSettings=nullptr) const
Convert the item shape to a polyset.
Definition board_item.h:546
int BuildBoardThicknessFromStackup() const
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
BOARD_STACKUP GetStackupOrDefault() const
Definition board.cpp:3660
const FOOTPRINTS & Footprints() const
Definition board.h:464
const TRACKS & Tracks() const
Definition board.h:462
bool GetBoardPolygonOutlines(SHAPE_POLY_SET &aOutlines, bool aInferOutlineIfNecessary, OUTLINE_ERROR_HANDLER *aErrorHandler=nullptr, bool aAllowUseArcsInPolygons=false, bool aIncludeNPTHAsOutlines=false)
Extract the board outlines and build a closed polygon from lines, arcs and circle items on edge cut l...
Definition board.cpp:3805
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:943
void RunOnChildren(const std::function< void(BOARD_ITEM *)> &aFunction, RECURSE_MODE aMode) const override
Invoke a function on all children.
Definition board.cpp:848
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1301
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr size_type GetHeight() const
Definition box2.h:212
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
std::deque< PAD * > & Pads()
Definition footprint.h:405
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
LSEQ CuStack() const
Return a sequence of copper layers in starting from the front/top and extending to the back/bottom.
Definition lset.cpp:259
Definition pad.h:61
Definition seg.h:38
int Length() const
Return the length (this).
Definition seg.h:340
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
double Area(bool aAbsolute=true) const
Return the area of this chain.
Represent a set of closed polygons.
double Area()
Return the area of this poly set.
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
SHAPE_LINE_CHAIN & Hole(int aOutline, int aHole)
Return the reference to aHole-th hole in the aIndex-th outline.
int OutlineCount() const
Return the number of outlines in the set.
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
wxString MessageTextFromValue(double aValue, bool aAddUnitLabel=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
A lower-precision version of StringFromValue().
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
#define _(s)
@ RECURSE
Definition eda_item.h:51
static const std::vector< KICAD_T > trackTypes
@ FP_SMD
Definition footprint.h:87
@ FP_THROUGH_HOLE
Definition footprint.h:86
Some functions to handle hotkeys in KiCad.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ B_Cu
Definition layer_ids.h:61
@ B_CrtYd
Definition layer_ids.h:111
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ F_Cu
Definition layer_ids.h:60
STL namespace.
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:98
@ PTH
Plated through hole pad.
Definition padstack.h:97
@ CONN
Like smd, does not appear on the solder paste layer (default) Note: also has a special attribute in G...
Definition padstack.h:99
@ PRESSFIT
a PTH with a hole diameter with tight tolerances for press fit pin
Definition padstack.h:122
@ CASTELLATED
a pad with a castellated through hole
Definition padstack.h:120
bool collide(T aObject, U aAnotherObject, int aLayer, int aMinDistance)
Used by SHAPE_INDEX to implement Query().
wxString GetISO8601CurrentDateTime()
std::vector< BOARD_STATISTICS_INFO_ENTRY< PAD_ATTRIB > > padEntries
std::vector< BOARD_STATISTICS_FP_ENTRY > footprintEntries
std::vector< BOARD_STATISTICS_INFO_ENTRY< VIATYPE > > viaEntries
std::vector< BOARD_STATISTICS_INFO_ENTRY< PAD_PROP > > padPropertyEntries
std::vector< DRILL_LINE_ITEM > drillEntries
int attributeMask
int attributeValue
int backCount
int frontCount
std::string jsonKey
wxString title
std::string jsonKey
int quantity
wxString title
T attribute
PCB_LAYER_ID stopLayer
PAD_DRILL_SHAPE shape
PCB_LAYER_ID startLayer
VECTOR2I center
int radius
int actual
#define M_PI
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:83
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:103
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708