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