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