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