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